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
/**
* Implements hook_action_info().
* Registers custom VBO actions as Drupal actions.
*/
function views_bulk_operations_action_info() {
$actions = array();
Bojan Živanović
committed
$files = views_bulk_operations_load_action_includes();
foreach ($files as $filename) {
$action_info_fn = 'views_bulk_operations_'. str_replace('.', '_', basename($filename, '.inc')).'_info';
$action_info = call_user_func($action_info_fn);
if (is_array($action_info)) {
$actions += $action_info;
Bojan Živanović
committed
/**
* Loads the VBO actions placed in their own include files (under actions/).
*
* @return
* An array of containing filenames of the included actions.
*/
function views_bulk_operations_load_action_includes() {
static $loaded = FALSE;
// The list of VBO actions is fairly static, so it's hardcoded for better
// performance (hitting the filesystem with file_scan_directory(), and then
// caching the result has its cost).
$path = drupal_get_path('module', 'views_bulk_operations') . '/actions/';
$files = array(
Bojan Živanović
committed
'archive.action.inc',
Bojan Živanović
committed
'argument_selector.action.inc',
'delete.action.inc',
'script.action.inc',
'user_roles.action.inc',
);
if (!$loaded) {
foreach ($files as $file) {
include_once $path . $file;
}
$loaded = TRUE;
}
return $files;
}
Bojan Živanović
committed
*/
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, 'enable_select_all_pages' => TRUE),
Bojan Živanović
committed
),
Karim Ratib
committed
'views_bulk_operations_confirmation' => array(
'variables' => array('rows' => NULL, 'vbo' => NULL),
Karim Ratib
committed
),
Bojan Živanović
committed
$files = views_bulk_operations_load_action_includes();
foreach ($files as $filename) {
$action_theme_fn = 'views_bulk_operations_'. str_replace('.', '_', basename($filename, '.inc')).'_theme';
Karim Ratib
committed
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']]) && $_GET['q'] != 'views/ajax') {
Bojan Živanović
committed
unset($_SESSION['vbo_values']);
Karim Ratib
committed
}
Bojan Živanović
committed
}
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
/**
* Implements hook_ctools_plugin_type().
*/
function views_bulk_operations_ctools_plugin_type() {
return array(
'operation_types' => array(
'classes' => array(
'handler',
),
),
);
}
/**
* Implements hook_ctools_plugin_directory().
*/
function views_bulk_operations_ctools_plugin_directory($module, $plugin) {
if ($module == 'views_bulk_operations') {
return 'plugins/' . $plugin;
}
}
/**
* Fetch metadata for a specific operation type plugin.
*
* @param $operation_type
* Name of the plugin.
*
* @return
* An array with information about the requested operation type plugin.
*/
function views_bulk_operations_get_operation_type($operation_type) {
ctools_include('plugins');
return ctools_get_plugins('views_bulk_operations', 'operation_types', $operation_type);
}
/**
* Fetch metadata for all operation type plugins.
*
* @return
* An array of arrays with information about all available operation types.
*/
function views_bulk_operations_get_operation_types() {
ctools_include('plugins');
return ctools_get_plugins('views_bulk_operations', 'operation_types');
}
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,
Bojan Živanović
committed
'#attributes' => array('class' => array('vbo-table-select-all')),
Bojan Živanović
committed
);
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';
// Allow VBO to work when embedded using views_embed_view(), or in a block.
if (empty($vbo->view->override_path)) {
if (!empty($vbo->view->preview) || $vbo->view->display_handler instanceof views_plugin_display_block) {
$vbo->view->override_path = $_GET['q'];
}
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
Bojan Živanović
committed
* (for table style plugins with grouping disabled), or above the view results
* (for non-table style plugins), providing a choice between selecting items
* on the current page, and on all pages.
Bojan Živanović
committed
*
* 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'];
$enable_select_all_pages = $variables['enable_select_all_pages'];
Bojan Živanović
committed
$form = array();
Bojan Živanović
committed
if ($view->style_plugin instanceof views_plugin_style_table && empty($view->style_plugin->options['grouping'])) {
if (!$enable_select_all_pages) {
Bojan Živanović
committed
return '';
}
Bojan Živanović
committed
$wrapper_class = 'vbo-table-select-all-markup';
$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
$all_pages = t('Selected <strong>!row_count</strong> in this view.', array('!row_count' => $all_pages_count));
$form['select_all_pages'] = array(
'#type' => 'button',
Bojan Živanović
committed
'#attributes' => array('class' => array('vbo-table-select-all-pages')),
Bojan Živanović
committed
'#value' => t('Select all !row_count in this view.', array('!row_count' => $all_pages_count)),
Bojan Živanović
committed
'#prefix' => '<span class="vbo-table-this-page">' . $this_page . ' ',
Bojan Živanović
committed
'#suffix' => '</span>',
);
$form['select_this_page'] = array(
'#type' => 'button',
Bojan Živanović
committed
'#attributes' => array('class' => array('vbo-table-select-this-page')),
Bojan Živanović
committed
'#value' => t('Select only !row_count in this page.', array('!row_count' => $this_page_count)),
Bojan Živanović
committed
'#prefix' => '<span class="vbo-table-all-pages" style="display: none">' . $all_pages . ' ',
Bojan Živanović
committed
'#suffix' => '</span>',
);
}
else {
Bojan Živanović
committed
$wrapper_class = 'vbo-select-all-markup';
Bojan Živanović
committed
$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 ($enable_select_all_pages) {
Bojan Živanović
committed
$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="' . $wrapper_class . '">';
Bojan Živanović
committed
$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,
$selected_operations = array_keys($vbo->get_selected_operations());
$operation = $vbo->get_operation($selected_operations[0]);
if ($operation->configurable()) {
Bojan Živanović
committed
$dummy_selection = array();
$dummy_selection[$row_index] = $result->{$vbo->field_alias};
Bojan Živanović
committed
$context = array(
'settings' => $operation->getAdminOption('settings', array()),
'selection' => $dummy_selection,
);
$form += $operation->form($form, $form_state, $context);
Bojan Živanović
committed
}
$form_state['operation'] = $operation;
Bojan Živanović
committed
$form['actions']['submit'] = array(
'#type' => 'submit',
'#value' => $operation->label(),
Bojan Živanović
committed
);
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 {
Bojan Živanović
committed
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']) {
$enable_select_all_pages = FALSE;
// If the view is paginated, and "select all items on all pages" is
// enabled, tell that to the theme function.
if (count($vbo->view->result) != $vbo->view->total_rows && $vbo->options['vbo']['enable_select_all_pages']) {
$enable_select_all_pages = TRUE;
}
Bojan Živanović
committed
$form['select_all_markup'] = array(
'#type' => 'markup',
'#markup' => theme('views_bulk_operations_select_all', array('view' => $vbo->view, 'enable_select_all_pages' => $enable_select_all_pages)),
Bojan Živanović
committed
);
}
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 = $form_state['operation'];
Bojan Živanović
committed
drupal_set_title(t('Set parameters for %operation', array('%operation' => $operation->label())), PASS_THROUGH);
$context = array(
Bojan Živanović
committed
'settings' => $operation->getAdminOption('settings', array()),
Bojan Živanović
committed
'selection' => $form_state['selection'],
);
$form += $operation->form($form, $form_state, $context);
Bojan Živanović
committed
$query = drupal_get_query_parameters($_GET, array('q'));
$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
);
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 = $form_state['operation'];
$rows = $form_state['selection'];
Bojan Živanović
committed
$query = drupal_get_query_parameters($_GET, array('q'));
Bojan Živanović
committed
$form = confirm_form($form,
Bojan Živanović
committed
t('Are you sure you want to perform %operation on the selected items?', array('%operation' => $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;
}
* Theme function to show the confirmation page before executing the operation.
*/
function theme_views_bulk_operations_confirmation($variables) {
$vbo = $variables['vbo'];
$entity_type = $vbo->get_entity_type();
$rows = $variables['rows'];
$result_count = count($vbo->view->result);
$row_count = count($rows);
$items = array();
// All rows on all pages have been selected.
if ($result_count < $row_count) {
$entity_ids = array_slice(array_values($rows), 0, $result_count);
$entities = _views_bulk_operations_entity_load($entity_type, $entity_ids, $vbo->revision);
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));
}
else {
$entities = _views_bulk_operations_entity_load($entity_type, array_values($rows), $vbo->revision);
foreach ($entities as $entity) {
$items[] = check_plain(_views_bulk_operations_entity_label($entity_type, $entity));
}
}
Bojan Živanović
committed
$count = format_plural(count($entities), 'item', 'items');
$output = theme('item_list', array('items' => $items, 'title' => t('You selected the following <strong>!count</strong>:', array('!count' => $count))));
Bojan Živanović
committed
/**
* Goes through the submitted values, and returns
* an array of selected rows, in the form of
* $row_index => $entity_id.
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])) {
$selection = array_filter($form_state['values'][$field_name]);
Bojan Živanović
committed
}
else {
$selection = array($form_state['values'][$field_name]);
Bojan Živanović
committed
}
Bojan Živanović
committed
}
return $selection;
Bojan Živanović
committed
}
/**
* Helper function to adjust the selected set of rows.
*/
function _views_bulk_operations_adjust_selection(&$selection, $select_all, $vbo) {
if ($select_all) {
// Adjust selection to select all rows across pages.
$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);
Bojan Živanović
committed
$view->display_handler->set_option('pager', array('type' => 'none', 'options' => array()));
$view->build();
// 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]);
}
}
$view->execute($vbo->view->current_display);
$results = array();
foreach ($view->result as $row_index => $result) {
$results[$row_index] = $result->{$vbo->field_alias};
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
switch ($form_state['step']) {
case 'views_form_views_form':
if ($form_state['values']['single_operation']) {
$operation = &$form_state['operation'];
if ($operation->configurable()) {
$operation->formValidate($form, $form_state);
Bojan Živanović
committed
}
Bojan Živanović
committed
else {
if (!empty($form_state['triggering_element']['#hash'])) {
$form_state['values']['operation'] = $form_state['triggering_element']['#hash'];
Bojan Živanović
committed
}
Bojan Živanović
committed
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
}
Bojan Živanović
committed
case 'views_bulk_operations_config_form':
$operation = &$form_state['operation'];
$operation->formValidate($form, $form_state);
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
switch ($form_state['step']) {
case 'views_form_views_form':
$form_state['selection'] = _views_bulk_operations_get_selection($vbo, $form_state);
_views_bulk_operations_adjust_selection($form_state['selection'], $form_state['values']['select_all'], $vbo);
Bojan Živanović
committed
// Remember form element values.
$_SESSION['vbo_values'][$_GET['q']] = array(
'rows' => array(
'selection' => $form_state['selection'],
'select_all' => $form_state['values']['select_all'],
),
);
if (!$form_state['values']['single_operation']) {
$_SESSION['vbo_values'][$_GET['q']]['operation'] = $form_state['values']['operation'];
}
Bojan Živanović
committed
if ($form_state['values']['single_operation']) {
$operation = &$form_state['operation'];
if ($operation->configurable()) {
$operation->formSubmit($form, $form_state);
Bojan Živanović
committed
}
else {
$operation->formOptions = $form_state['values'];
}
if ($operation->getAdminOption('skip_confirmation')) {
Bojan Živanović
committed
break; // Go directly to execution
}
$form_state['step'] = 'views_bulk_operations_confirm_form';
Bojan Živanović
committed
else {
$form_state['operation'] = $operation = $vbo->get_operation($form_state['values']['operation']);
if (!$operation->configurable() && $operation->getAdminOption('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';
Bojan Živanović
committed
case 'views_bulk_operations_config_form':
$form_state['step'] = 'views_bulk_operations_confirm_form';
$operation = &$form_state['operation'];
$operation->formSubmit($form, $form_state);
if ($operation->getAdminOption('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 operation.
_views_bulk_operations_execute($vbo, $form_state['selection'], $form_state['operation']);
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
/**
* Entry point for executing the chosen operation upon selected rows.
* Routes to the right process function (based on the chosen execution method).
* There are three different ways of executing the operation:
* - Directly.
* Simple, fast, fragile. Tells PHP to ignore the execution time limit,
* loads all selected entities, runs the operation. 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 operations, because they need all selected entities,
* and Batch API / Drupal Queue are all about segmenting the entity loading
* and operation 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.
*
* @param $vbo
* The VBO field, containing a reference to the view in $vbo->view.
* @param $selection
* An array in the form of $row_index => $entity_id.
* @param $operation
* The operation object.
* @param $force_direct
* Whether to force the direct method (when ran through drush, for example).
Karim Ratib
committed
*/
function _views_bulk_operations_execute($vbo, $selection, $operation, $force_direct = FALSE) {
Bojan Živanović
committed
global $user;
// Options that affect execution.
$options = array(
'revision' => $vbo->revision,
'display_result' => $vbo->options['vbo']['display_result'],
'entity_load_capacity' => $vbo->options['vbo']['entity_load_capacity'],
);
// An operation that needs aggregated results can only be executed directly.
if ($operation->aggregate()) {
$force_direct = TRUE;
}
// Create an array of rows with the data needed by the process functions.
$rows = array();
foreach ($selection as $row_index => $entity_id) {
$rows[$row_index] = array(
'entity_id' => $entity_id,
'views_row' => array(),
);
// Some operations require full selected rows.
// @todo Make this work when all rows on all pages are selected?
if ($operation->needsRows() && isset($vbo->view->result[$row_index])) {
$rows[$row_index]['views_row'] = $vbo->view->result[$row_index];
}
}
if (!$force_direct && $operation->getAdminOption('use_queue')) {
Bojan Živanović
committed
$entity_type = $vbo->get_entity_type();
Bojan Živanović
committed
$current = 1;
foreach ($rows as $row_index => $row) {
Bojan Živanović
committed
// Some operations rely on knowing their position in the execution set
// (because of specific things that need to be done at the beginning
// or the end of the set).
$context = array(
'progress' => array(
'current' => $current,
'total' => count($rows),
),
);
Bojan Živanović
committed
$job = array(
'description' => t('Perform %operation on @type !entity_id.', array(
'%operation' => $operation->label(),
Bojan Živanović
committed
'@type' => $entity_type,
'!entity_id' => $row['entity_id'],
Bojan Živanović
committed
)),
Bojan Živanović
committed
'arguments' => array($row_index, $row, $operation, $context, $options, $user->uid),
);
Bojan Živanović
committed
$queue = DrupalQueue::get('views_bulk_operations');
$queue->createItem($job);
Bojan Živanović
committed
$current++;
if ($options['display_result']) {
drupal_set_message(t('Enqueued the selected operation (%operation).', array(
'%operation' => $operation->label(),
)));
elseif (!$force_direct && $options['entity_load_capacity'] < count($rows)) {
$batch = array(
'operations' => array(
Bojan Živanović
committed
array('_views_bulk_operations_batch_process', array($rows, $operation, $options)),
),
'finished' => '_views_bulk_operations_execute_finished',
'progress_message' => '',
Bojan Živanović
committed
'title' => t('Performing %operation on the selected items...', array('%operation' => $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, $options, $context);
_views_bulk_operations_execute_finished(TRUE, $context['results'], array(), 0, $options + array('operation' => $operation));
}
/**
*/
Bojan Živanović
committed
function _views_bulk_operations_queue_process($data) {
Bojan Živanović
committed
list($row_index, $row, $operation, $operation_context, $options, $uid) = $data['arguments'];
$entity_type = $operation->entityType;
$entities = _views_bulk_operations_entity_load($entity_type, array($row['entity_id']), $options['revision']);
// 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 %operation on @type %title due to insufficient permissions.', array(
'%operation' => $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 operation if needed.
if ($operation->needsRows()) {
$operation_context['rows'] = array($row_index => $row['views_row']);
}
_views_bulk_operations_operation_do($operation, $entity, $operation_context, $account);
Bojan Živanović
committed
// @todo Provide a way to spam less here.
if ($options['display_result']) {
watchdog('views bulk operations', 'Performed %operation on @type %title.', array(
'%operation' => $operation->label(),
Bojan Živanović
committed
'@type' => $entity_type,
'%title' => _views_bulk_operations_entity_label($entity_type, $entity),
), WATCHDOG_INFO);
Bojan Živanović
committed
function _views_bulk_operations_batch_process($rows, $operation, $options, &$context) {
Bojan Živanović
committed
// It is still possible to hit the time limit.
set_time_limit(0);
if (!isset($context['sandbox']['progress'])) {
$context['sandbox']['progress'] = 0;
$context['sandbox']['max'] = count($rows);
$context['results']['time'] = microtime(TRUE);
}
// Process the rows in groups.
$remaining = $context['sandbox']['max'] - $context['sandbox']['progress'];
$count = min($options['entity_load_capacity'], $remaining);
$row_group = array_slice($rows, $context['sandbox']['progress'], $count, TRUE);
$entity_type = $operation->entityType;
$entity_ids = array();
foreach ($row_group as $row_id => $row) {
$entity_ids[] = $row['entity_id'];
}
$entities = _views_bulk_operations_entity_load($entity_type, $entity_ids, $options['revision']);
foreach ($row_group as $row_id => $row) {
$entity_id = $row['entity_id'];
// A matching entity couldn't be loaded. Adjust the count and move on.
if (!isset($entities[$entity_id])) {
$context['sandbox']['progress']++;
unset($row_group[$row_id]);
continue;
}
$entity = $entities[$entity_id];
if (!_views_bulk_operations_entity_access($operation, $entity_type, $entity)) {
$context['results']['log'][] = t('Skipped %operation on @type %title due to insufficient permissions.', array(
'%operation' => $operation->label(),
'@type' => $entity_type,
'%title' => _views_bulk_operations_entity_label($entity_type, $entity),
));
$context['sandbox']['progress']++;
continue;
}
Bojan Živanović
committed
// Some operations rely on knowing their position in the execution set
// (because of specific things that need to be done at the beginning
// or the end of the set).
$operation_context = array(
'progress' => array(
'current' => $context['sandbox']['progress'] + 1,
'total' => $context['sandbox']['max'],
),
);
// Pass the selected row to the operation if needed.
if ($operation->needsRows()) {
$operation_context['rows'] = array($row_index => $row['views_row']);
}
_views_bulk_operations_operation_do($operation, $entity, $operation_context);
$context['sandbox']['progress']++;
unset($row_group[$row_id]);
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 {
Bojan Živanović
committed
// All done. Save data for the finish callback.
$context['results']['rows'] = $context['sandbox']['progress'];
Bojan Živanović
committed
$context['results']['options'] = $options;
Bojan Živanović
committed
if ($options['display_result']) {
$context['results']['log'][] = t('Performed %operation on @items.', array(
'%operation' => $operation->label(),
'@items' => format_plural($context['results']['rows'], '1 item', '@count items'),
));
}
}
function _views_bulk_operations_direct_process($operation, $rows, $options, &$context) {
$entity_type = $operation->entityType;
$entity_ids = array();
foreach ($rows as $row_index => $row) {
$entity_ids[] = $row['entity_id'];
}
$entities = _views_bulk_operations_entity_load($entity_type, $entity_ids, $options['revision']);
foreach ($entities as $id => $entity) {
if (!_views_bulk_operations_entity_access($operation, $entity_type, $entity)) {
$context['results']['log'][] = t('Skipped %operation on @type %title due to insufficient permissions.', array(
'%operation' => $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 operation if needed.
$operation_context = array();
if ($operation->needsRows()) {
$operation_context['rows'] = array();
foreach ($rows as $row_index => $row) {
$operation_context['rows'][$row_index] = $row['views_row'];
}
}
_views_bulk_operations_operation_do($operation, $entities, $operation_context);
Bojan Živanović
committed
$context['results']['log'][] = t('Performed aggregate %operation on @items.', array(
'%operation' => $operation->label(),
Bojan Živanović
committed
'@items' => format_plural(count($entities), '1 item', '@count items'),
));
$context['results']['rows'] += count($entities);
}
else {
foreach ($rows as $row_index => $row) {
$entity_id = $row['entity_id'];
// A matching entity couldn't be loaded.
if (!isset($entities[$entity_id])) {
continue;
}
$entity = $entities[$entity_id];
Bojan Živanović
committed
// Some operations rely on knowing their position in the execution set
// (because of specific things that need to be done at the beginning
// or the end of the set).
$operation_context = array(
'progress' => array(
'current' => $context['results']['rows'] + 1,
'total' => count($rows),
),
);
// Pass the selected rows to the operation if needed.
if ($operation->needsRows()) {
$operation_context['rows'] = array($row_index => $row['views_row']);
}
_views_bulk_operations_operation_do($operation, $entity, $operation_context);
$context['results']['rows'] += 1;
}
Bojan Živanović
committed
$context['results']['log'][] = t('Performed %operation on @items.', array(
'%operation' => $operation->label(),
'@items' => format_plural(count($entities), '1 item', '@count items'),
));
/**
* Executes the operation. Called from the process functions.
*/
function _views_bulk_operations_operation_do($operation, $entity, $context, $account = NULL) {
global $user;
// If no account was provided, fallback to the current user.
if (!$account) {
$account = $user;
}
if (!$operation->access($account)) {
watchdog('actions permissions', 'An attempt by user %user to run operation %operation was blocked due to insufficient permissions.',
array('%operation' => $operation->label(), '%user' => format_username($account)), WATCHDOG_ALERT);
drupal_access_denied();
drupal_exit();
}
$operation->execute($entity, $context);
}