$vid), WATCHDOG_ERROR); return; } // Find the view display that has the VBO style. $found = FALSE; foreach (array_keys($view->display) as $display) { $display_options = &$view->display[$display]->display_options; if (isset($display_options['style_plugin']) && $display_options['style_plugin'] == 'bulk') { $view->set_display($display); $found = TRUE; break; } } if (!$found) { watchdog('views bulk operations', 'Could not find a VBO display in view %vid.', array('%vid' => $vid), WATCHDOG_ERROR); return; } // Execute the view. $view->set_exposed_input($view_exposed_input); $view->set_arguments($view_arguments); $view->set_items_per_page(0); $view->execute(); $plugin = $view->style_plugin; if (empty($view->result)) { watchdog('views_bulk_operations', 'No results for view %vid.', array('%vid' => $vid), WATCHDOG_WARNING); return; } // Find the selected operation. $operations = $plugin->get_selected_operations(); if (!isset($operations[$operation_callback])) { watchdog('views bulk operations', 'Could not find operation %operation in view %vid.', array('%operation' => $operation_callback, '%vid' => $vid), WATCHDOG_ERROR); return; } $operation = $plugin->get_operation_info($operation_callback); // Execute the operation on the view results. $objects = array(); foreach ($view->result as $num => $result) { $objects[$num + 1] = $result; } $execution_type = $plugin->options['execution_type']; if ($execution_type == VBO_EXECUTION_BATCH) { $execution_type = VBO_EXECUTION_DIRECT; // we don't yet support Batch API here } _views_bulk_operations_execute( $view, $objects, $operation, $operation_arguments, array('execution_type' => $execution_type, 'display_result' => $plugin->options['display_result'], 'settings' => $plugin->get_operation_settings($operation)) ); } /** * API function to add actions to a VBO. */ function views_bulk_operations_add_actions($vid, $actions) { $view = views_get_view($vid); if (!is_object($view)) { watchdog('views bulk operations', 'Could not find view %vid.', array('%vid' => $vid), WATCHDOG_ERROR); return; } // Find the view display that has the VBO style. $found = FALSE; foreach (array_keys($view->display) as $display) { $display_options = &$view->display[$display]->display_options; if (isset($display_options['style_plugin']) && $display_options['style_plugin'] == 'bulk') { $found = TRUE; break; } } if (!$found) { watchdog('views bulk operations', 'Could not find a VBO display in view %vid.', array('%vid' => $vid), WATCHDOG_ERROR); return; } // Iterate on the desired actions. $selected_operations = $display_options['style_options']['selected_operations']; $ignored = $added = array(); if (!empty($actions)) foreach ($actions as $action) { $modified = FALSE; if (is_numeric($action)) { // aid $action_object = db_fetch_object(db_query("SELECT * FROM {actions} WHERE aid = %d", $action)); if (is_object($action_object)) { $parameters = unserialize($action_object->parameters); $key = $action_object->callback . (empty($parameters) ? '' : ':'. md5($action_object->parameters)); if (isset($selected_operations[$key])) { // available for this view $display_options['style_options']['selected_operations'][$key] = $key; $modified = TRUE; } } } else { // callback or title if (isset($selected_operations[$action])) { // callback and available for this view $display_options['style_options']['selected_operations'][$action] = $action; $modified = TRUE; } else { // try the title $action_object = db_fetch_object(db_query("SELECT * FROM {actions} WHERE UPPER(description) = '%s'", strtoupper(db_escape_string($action)))); if (is_object($action_object)) { $parameters = unserialize($action_object->parameters); $key = $action_object->callback . (empty($parameters) ? '' : ':'. md5($action_object->parameters)); if (isset($selected_operations[$key])) { // available for this view $display_options['style_options']['selected_operations'][$key] = $key; $modified = TRUE; } } } } if ($modified) { $added[] = $action; } else { $ignored[] = $action; } } // Save the view if anything was changed. if (!empty($added)) { $view->save(); views_object_cache_clear('view', $vid); if (empty($ignored)) { watchdog( 'views bulk operations', 'View %vid was successfully modified. The following actions were added: %added.', array('%vid' => $vid, '%added' => implode(', ', $added)), WATCHDOG_INFO ); } else { watchdog( 'views bulk operations', 'View %vid was modified. The following actions were added: %added. The following actions were ignored: %ignored.', array('%vid' => $vid, '%added' => implode(', ', $added), '%ignored' => implode(', ', $ignored)), WATCHDOG_WARNING ); } } else { watchdog( 'views bulk operations', 'View %vid was NOT modified. The following actions were ignored: %ignored.', array('%vid' => $vid, '%ignored' => implode(', ', $ignored)), WATCHDOG_ERROR ); } } // Define the steps in the multistep form that executes operations. define('VIEWS_BULK_OPS_STEP_VIEW', 1); define('VIEWS_BULK_OPS_STEP_CONFIG', 2); define('VIEWS_BULK_OPS_STEP_CONFIRM', 3); define('VIEWS_BULK_OPS_STEP_SINGLE', 4); // Types of bulk execution. define('VBO_EXECUTION_DIRECT', 1); define('VBO_EXECUTION_BATCH', 2); define('VBO_EXECUTION_QUEUE', 3); // Types of aggregate actions. define('VBO_AGGREGATE_FORCED', 1); define('VBO_AGGREGATE_FORBIDDEN', 0); define('VBO_AGGREGATE_OPTIONAL', 2); // 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); /** * Implementation of hook_views_api(). */ function views_bulk_operations_views_api() { return array( 'api' => 2.0, ); } /** * Implementation of hook_elements(). */ function views_bulk_operations_elements() { $type['views_node_selector'] = array( '#input' => TRUE, '#view' => NULL, '#process' => array('views_node_selector_process'), ); return $type; } /** * Process function for views_node_selector element. * * @see views_bulk_operations_elements() */ function views_node_selector_process($element, $edit) { $element['#tree'] = TRUE; if (!isset($element['#value'])) { $element['#value'] = array('selection' => array(), 'select_all' => FALSE); } $view = $element['#view']; $options = array(); foreach ($view->result as $num => $object) { $options[md5(serialize($object))] = ''; } $element['selection']['#options'] = $options; $element['selection']['#value'] = $element['#value']['select_all'] ? $options : $element['#value']['selection']; $element['selection']['#attributes'] = array('class' => 'vbo-select'); $element['selection'] = expand_checkboxes($element['selection']); $element['select_all'] = array('#type' => 'hidden', '#default_value' => $element['#value']['select_all']); return $element; } /** * Implementation of hook_theme(). */ function views_bulk_operations_theme() { $themes = array( 'views_node_selector' => array( 'arguments' => array('element' => NULL), ), 'views_bulk_operations_confirmation' => array( 'arguments' => array('objects' => NULL, 'view' => NULL), ), 'views_bulk_operations_table' => array( 'arguments' => array('header' => array(), 'rows' => array(), 'attributes' => array(), 'title' => NULL, 'select_all_rows' => array(), 'view' => NULL), 'template' => 'views-bulk-operations-table', ), ); foreach (_views_bulk_operations_load_actions() as $file) { $action_theme_fn = 'views_bulk_operations_'. $file .'_action_theme'; if (function_exists($action_theme_fn)) { $themes += call_user_func($action_theme_fn); } } return $themes; } /** * Template preprocessor for theme function 'views_bulk_operations_table'. */ function template_preprocess_views_bulk_operations_table(&$vars, $hook) { $view = $vars['view']; $options = $view->style_plugin->options; $count = 0; foreach ($vars['rows'] as $num => $row) { $vars['row_classes'][$num][] = ($count++ % 2 == 0) ? 'odd rowclick' : 'even rowclick'; } $vars['row_classes'][0][] = 'views-row-first'; $vars['row_classes'][count($vars['row_classes']) - 1][] = 'views-row-last'; $vars['class'] = 'views-bulk-operations-table'; if (!empty($options['sticky'])) { drupal_add_js('misc/tableheader.js'); $vars['class'] .= " sticky-enabled"; } $vars['class'] .= ' cols-'. count($vars['rows']); } /** * Theme function for views_node_selector element. */ function theme_views_node_selector($element) { require_once(drupal_get_path('module', 'views') . '/theme/theme.inc'); $output = ''; $view = $element['#view']; $sets = $element['#sets']; $vars = array( 'view' => $view, ); // Give each group its own headers row. foreach ($sets as $title => $records) { $headers = array(); // template_preprocess_views_view_table() expects the raw data in 'rows'. $vars['rows'] = $records; // Render the view as table. Use the hook from from views/theme/theme.inc // and allow overrides using the same algorithm as the theme system will // do calling the theme() function. $hook = 'views_view_table'; $hooks = theme_get_registry(); if (!isset($hooks[$hook])) { return ''; } $args = array(&$vars, $hook); foreach ($hooks[$hook]['preprocess functions'] as $func) { if (function_exists($func)) { call_user_func_array($func, $args); } } // Add checkboxes to the header and the rows. $hide_select_all = @$view->display['default']->display_options['style_options']['hide_select_all']; if (!$hide_select_all) { $headers['select'] = theme('table_select_header_cell'); } else { $headers['select'] = ''; } foreach ($vars['header'] as $field => $label) { $headers[$field] = $label; } $rows = array(); foreach ($records as $num => $object) { $row['select'] = theme('checkbox', $element['selection'][md5(serialize($object))]); foreach ($vars['rows'][$num] as $field => $content) { $row[$field] = $content; } $rows[] = $row; } // Add the first row as option to select all records across all pages. $select_all_rows = array(); $items_per_page = method_exists($view, 'get_items_per_page') ? $view->get_items_per_page() : (isset($view->pager) ? $view->pager['items_per_page'] : 0); if ($items_per_page && $view->total_rows > $items_per_page) { $group = count($sets) > 1 ? t('set') : t('page'); $select_all_rows = array( 'data' => ''. t('All !objects rows in this !group are selected.', array('!objects' => count($records), '!group' => $group)) .' ' .' ', 'class' => 'view-field view-field-select-all', 'colspan' => count($headers) + 1, ); } $output .= theme('views_bulk_operations_table', $headers, $rows, array('class' => $vars['class']), $title, $select_all_rows, $view); $output .= theme('hidden', $element['select_all']); } return theme('form_element', $element, $output); } /** * Implementation of hook_init(). */ function views_bulk_operations_init() { // Reset selection if we're not in the view anymore. if (!in_array($_GET['q'], array('views-bulk-operations/js/select')) && isset($_SESSION['vbo_values']) && !isset($_SESSION['vbo_values'][url($_GET['q'])])) { unset($_SESSION['vbo_values']); } // Automatically include the action files. // TODO: Why do we need that? _views_bulk_operations_load_actions(); } /** * Form implementation for main VBO multistep form. */ function views_bulk_operations_form($form_state, $plugin) { $url = url($_GET['q']); // Force browser to reload the page if Back is hit. if (preg_match('/msie/i', $_SERVER['HTTP_USER_AGENT'])) { drupal_set_header("Cache-Control: no-cache"); // works for IE6+ } else { drupal_set_header("Cache-Control: no-store"); // works for Firefox and other browsers } // Add the JS files. drupal_add_js('misc/tableselect.js'); drupal_add_js(drupal_get_path('module', 'views_bulk_operations') . '/views_bulk_operations.js'); drupal_add_js(drupal_get_path('module', 'views_bulk_operations') . '/json2.js'); drupal_add_css(drupal_get_path('module', 'views_bulk_operations') . '/views_bulk_operations.css', 'module'); static $once = FALSE; if (!$once) { // We can be called here twice. drupal_add_js(array('vbo' => array('url' => $url)), 'setting'); $once = TRUE; } // If there's a session variable on this view, pre-load the old values. if (isset($_SESSION['vbo_values'][$url])) { $objects = $_SESSION['vbo_values'][$url]['objects']; $default_objects['selection'] = array_filter($objects['selection']); $default_objects['select_all'] = $objects['select_all']; $default_operation = $_SESSION['vbo_values'][$url]['operation']; } else { $default_objects = array('selection' => NULL, 'select_all' => FALSE); $default_operation = NULL; } if (!isset($form_state['storage']['step'])) { if (count($plugin->get_selected_operations()) == 1 && $plugin->options['merge_single_action']) { $step = VIEWS_BULK_OPS_STEP_SINGLE; } else { $step = VIEWS_BULK_OPS_STEP_VIEW; } $form['exposed_input'] = array( '#type' => 'value', '#value' => $plugin->view->get_exposed_input(), ); $form['arguments'] = array( '#type' => 'value', '#value' => $plugin->view->args, ); // If empty view, render the empty text. if (!$plugin->view->result) { $form['empty'] = array('#value' => $plugin->view->display_handler->render_empty()); return $form; } } else { $plugin->strip_view(); switch ($form_state['storage']['step']) { case VIEWS_BULK_OPS_STEP_VIEW: $operation = $plugin->get_operation_info($form_state['storage'][VIEWS_BULK_OPS_STEP_VIEW]['operation']); if ($operation['configurable']) { $step = VIEWS_BULK_OPS_STEP_CONFIG; } else { $step = VIEWS_BULK_OPS_STEP_CONFIRM; } break; case VIEWS_BULK_OPS_STEP_SINGLE: case VIEWS_BULK_OPS_STEP_CONFIG: $step = VIEWS_BULK_OPS_STEP_CONFIRM; break; } } $form['step'] = array( '#type' => 'value', '#value' => $step ); $form['#plugin'] = $plugin; switch ($step) { case VIEWS_BULK_OPS_STEP_VIEW: $form['select'] = array( '#type' => 'fieldset', '#title' => t('Bulk operations'), '#prefix' => '
return array(\'argument1\' => \'value1\', ...);
and they should correspond to the values returned by the action\'s form submit function.
The variables &$object
and $context
are available to this script.
'),
'#default_value' => @$context['operation_arguments'],
);
}
else {
$form['operation_arguments']['wrapper']['operation_form'] = array(
'#type' => 'markup',
'#value' => t('This operation is not configurable.'),
);
$form['operation_arguments']['wrapper']['operation_arguments'] = array('#type' => 'value', '#value' => '');
}
$form['view_exposed_input'] = array(
'#type' => 'textarea',
'#title' => t('View exposed input'),
'#description' => t('Enter PHP script that will assemble the view exposed input (if the view accepts exposed input).
These inputs should be of the form: return array(\'input1\' => \'value1\', ...);
and they should correspond to the query values used on the view URL when exposed filters are applied.
The variables &$object
and $context
are available to this script.
'),
'#default_value' => @$context['view_exposed_input'],
);
$form['view_arguments'] = array(
'#type' => 'textarea',
'#title' => t('View arguments'),
'#description' => t('Enter PHP script that will assemble the view arguments (if the view accepts arguments).
These arguments should be of the form: return array(\'value1\', ...);
and they should correspond to the arguments defined in the view.
The variables &$object
and $context
are available to this script.
'),
'#default_value' => @$context['view_arguments'],
);
return $form;
}
/**
* Generic AHAH callback to manipulate a form.
*/
function views_bulk_operations_form_ahah($callback) {
$form_state = array('submitted' => FALSE);
$form_build_id = $_POST['form_build_id'];
// Add the new element to the stored form. Without adding the element to the
// form, Drupal is not aware of this new elements existence and will not
// process it. We retreive the cached form, add the element, and resave.
$form = form_get_cache($form_build_id, $form_state);
// Invoke the callback that will populate the form.
$render =& $callback($form, array('values' => $_POST));
form_set_cache($form_build_id, $form, $form_state);
$form += array(
'#post' => $_POST,
'#programmed' => FALSE,
);
// Rebuild the form.
$form = form_builder($_POST['form_id'], $form, $form_state);
// Render the new output.
$output_html = drupal_render($render);
$output_js = drupal_get_js();
print drupal_to_js(array('data' => theme('status_messages') . $output_html . $output_js, 'status' => TRUE));
exit();
}
/**
* Form callback to update an action form when a new action is selected in views_bulk_operations_action form.
*/
function& views_bulk_operations_action_form_operation(&$form, $form_state) {
// TODO: Replace this with autoloading of style plugin and view definitions to use $form['#plugin'].
$view = views_get_view($form_state['values']['view_vid']);
foreach (array_keys($view->display) as $display) {
$display_options = &$view->display[$display]->display_options;
if (isset($display_options['style_plugin']) && $display_options['style_plugin'] == 'bulk') {
$plugin = views_get_plugin('style', $display_options['style_plugin']);
$plugin->init($view, $view->display[$display], $display_options['style_options']);
break;
}
}
$form['#operation'] = $plugin->get_operation_info($form_state['values']['operation_key']);
if ($form['#operation']['configurable']) {
$form['operation_arguments']['wrapper'] = array(
'#type' => 'markup',
'#value' => '',
'#prefix' => 'return array(\'argument1\' => \'value1\', ...);
and they should correspond to the values returned by the action\'s form submit function.
The variables &$object
and $context
are available to this script.
'),
);
}
else {
$form['operation_arguments']['wrapper']['operation_form'] = array(
'#type' => 'markup',
'#value' => t('This operation is not configurable.'),
);
$form['operation_arguments']['wrapper']['operation_arguments'] = array('#type' => 'value', '#value' => '');
}
return $form['operation_arguments']['wrapper'];
}
/**
* Form validate function for views_bulk_operations_action action.
*/
function views_bulk_operations_action_validate($form, $form_state) {
if (empty($form_state['values']['view_vid'])) {
form_set_error('view_vid', t('You must choose a view to be executed.'));
}
if (empty($form_state['values']['operation_key'])) {
form_set_error('operation_callback', t('You must choose an operation to be executed.'));
}
if ($form['#operation']) {
module_invoke_all('action_info'); // some validate functions are created dynamically...
_views_bulk_operations_action_validate($form['#operation'], $form, $form_state);
}
}
/**
* Form submit function for views_bulk_operations_action action.
*/
function views_bulk_operations_action_submit($form, $form_state) {
$submit = array(
'view_vid' => $form_state['values']['view_vid'],
'operation_key' => $form_state['values']['operation_key'],
'operation_arguments' => $form_state['values']['operation_arguments'],
'view_exposed_input' => $form_state['values']['view_exposed_input'],
'view_arguments' => $form_state['values']['view_arguments'],
);
if ($form['#operation'] && function_exists($form['#operation']['callback'] . '_submit')) {
module_invoke_all('action_info'); // some submit functions are created dynamically...
$submit = array_merge($submit, _views_bulk_operations_action_submit($form['#operation'], $form, $form_state));
}
return $submit;
}
/**
* Execution function for views_bulk_operations_action action.
*/
function views_bulk_operations_action(&$object, $context) {
$view_exposed_input = array();
if (!empty($context['view_exposed_input'])) {
$view_exposed_input = eval($context['view_exposed_input']);
}
$view_arguments = array();
if (!empty($context['view_arguments'])) {
$view_arguments = eval($context['view_arguments']);
}
if (!empty($context['operation_arguments'])) {
$operation_arguments = eval($context['operation_arguments']);
}
else {
$operation_arguments = $context;
foreach (array('operation_key', 'operation_arguments', 'views_vid', 'view_exposed_input', 'view_arguments') as $key) {
unset($operation_arguments[$key]);
}
}
views_bulk_operations_execute($context['view_vid'], $context['operation_key'], $operation_arguments, $view_exposed_input, $view_arguments);
}
/**
* Helper function to execute the chosen action upon selected objects.
*/
function _views_bulk_operations_execute($view, $objects, $operation, $operation_arguments, $options) {
// Get the object info we're dealing with.
$object_info = _views_bulk_operations_object_info_for_view($view);
if (!$object_info) return;
// Add action arguments.
$params = array();
if ($operation['configurable'] && is_array($operation_arguments)) {
$params += $operation_arguments;
}
// Add static callback arguments. Note that in the case of actions, static arguments
// are picked up from the database in actions_do().
if (isset($operation['callback arguments'])) {
$params += $operation['callback arguments'];
}
// Add this view as parameter.
$params['view'] = array(
'vid' => !empty($view->vid) ? $view->vid : $view->name,
'exposed_input' => $view->get_exposed_input(),
'arguments' => $view->args,
);
// Add static settings to the params.
if (!empty($options['settings'])) {
$params['settings'] = $options['settings'];
}
// Add object info to the params.
$params['object_info'] = $object_info;
if (version_compare(VERSION, '6.10', '<')) {
// Hack to force actions_do() to process any number of invocations.
// Check http://drupal.org/node/290282 to understand more.
// This was fixed as of D6.10: http://cvs.drupal.org/viewvc.py/drupal/drupal/includes/actions.inc?view=log&pathrev=DRUPAL-6-10
variable_set('actions_max_stack', 10000000);
}
if ($operation['aggregate'] != VBO_AGGREGATE_FORCED && $options['execution_type'] == VBO_EXECUTION_BATCH) {
$operations = array();
foreach ($objects as $num => $row) {
$oid = $row->{$view->base_field};
$operations[] = array('_views_bulk_operations_batch_process', array($oid, $row));
}
// 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'] = $params;
$_SESSION['vbo_options']['object_info'] = $object_info;
$batch = array(
'operations' => $operations,
'finished' => '_views_bulk_operations_batch_finished',
'title' => t('Performing %action on selected rows...', array('%action' => $operation['label'])),
);
batch_set($batch);
}
else if ($operation['aggregate'] != VBO_AGGREGATE_FORCED && module_exists('job_queue') && $options['execution_type'] == VBO_EXECUTION_QUEUE) {
global $user;
foreach ($objects as $row) {
$oid = $row->{$view->base_field};
job_queue_add('_views_bulk_operations_queue_process',
t('Perform %action on @type %oid.', array(
'%action' => $operation['label'],
'@type' => t($object_info['type']),
'%oid' => $oid
)),
array($oid, $row, $operation, $params, $user->uid, $options['display_result'], $object_info)
);
$oids[] = $oid;
}
if ($options['display_result']) {
drupal_set_message(t('Enqueued %action on @types %oid. Check the queued jobs page.', array(
'%action' => $operation['label'],
'@types' => format_plural(count($objects), $object_info['type'], $object_info['type'] . 's'),
'%oid' => implode(', ', $oids),
'@queue' => url('admin/reports/job_queue')
)));
}
}
else /*if ($options['execution_type'] == VBO_EXECUTION_DIRECT)*/ {
@set_time_limit(0);
$context['results']['rows'] = 0;
$context['results']['time'] = microtime(TRUE);
_views_bulk_operations_direct_process($view, $operation, $objects, $params, $object_info, $context);
_views_bulk_operations_direct_finished(TRUE, $context['results'], array(), $options['display_result']);
}
}
/**
* Helper function to handle Job queue operations.
*/
function _views_bulk_operations_queue_process($oid, $row, $operation, $params, $uid, $display_result, $object_info) {
module_load_include('inc', 'node', 'node.admin');
$object = call_user_func($object_info['load'], $oid);
if (!$object) {
watchdog('views bulk operations', 'Skipped %action on @type id %oid.', array(
'%action' => $operation['label'],
'@type' => t($operation['type']),
'%oid' => $oid,
), WATCHDOG_ALERT);
return;
}
$account = user_load(array('uid' => $uid));
if (!_views_bulk_operations_object_permission($operation, $object, $object_info, $account)) {
watchdog('views bulk operations', 'Skipped %action on @type %title due to insufficient permissions.', array(
'%action' => $operation['label'],
'@type' => t($object_info['type']),
'%title' => $object->{$object_info['title']},
), WATCHDOG_ALERT);
return;
}
_views_bulk_operations_action_do($operation, $oid, $object, $row, $params, $object_info, $account);
if ($display_result) {
watchdog('views bulk operations', 'Performed %action on @type %title.', array(
'%action' => $operation['label'],
'@type' => t($object_info['type']),
'%title' => $object->{$object_info['title']},
), WATCHDOG_INFO);
}
}
/**
* Helper function to handle Batch API operations.
*/
function _views_bulk_operations_batch_process($oid, $row, &$context) {
module_load_include('inc', 'node', 'node.admin');
$operation = $_SESSION['vbo_options']['operation'];
$params = $_SESSION['vbo_options']['params'];
$object_info = $_SESSION['vbo_options']['object_info'];
if (!isset($context['results']['time'])) {
$context['results']['time'] = microtime(TRUE);
}
$object = call_user_func($object_info['load'], $oid);
if (!$object) {
$context['results']['log'][] = t('Skipped %action on @type id %oid.', array(
'%action' => $operation['label'],
'@type' => t($operation['type']),
'%oid' => $oid,
));
continue;
}
if (!_views_bulk_operations_object_permission($operation, $object, $object_info)) {
$context['results']['log'][] = t('Skipped %action on @type %title due to insufficient permissions.', array(
'%action' => $operation['label'],
'@type' => t($object_info['type']),
'%title' => $object->{$object_info['title']},
));
return;
}
_views_bulk_operations_action_do($operation, $oid, $object, $row, $params, $object_info);
$context['results']['log'][] = t('Performed %action on @type %title.', array(
'%action' => $operation['label'],
'@type' => t($object_info['type']),
'%title' => $object->{$object_info['title']},
));
if (isset($context['results']['rows'])) {
$context['results']['rows'] += 1;
}
else {
$context['results']['rows'] = 1;
}
}
/**
* Helper function to cleanup Batch API operations.
*/
function _views_bulk_operations_batch_finished($success, $results, $operations, $display_result = NULL) {
if ($success) {
if ($results['rows'] > 0) {
$message = t('!results rows processed in about !time ms:', array('!results' => $results['rows'], '!time' => round((microtime(TRUE) - $results['time']) * 1000)));
}
else {
$message = t('No rows were processed:');
}
$message .= "\n". theme('item_list', $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 (version_compare(VERSION, '6.10', '<')) {
// See http://cvs.drupal.org/viewvc.py/drupal/drupal/includes/actions.inc?view=log&pathrev=DRUPAL-6-10
variable_set('actions_max_stack', 35);
}
if ($display_result || @$_SESSION['vbo_options']['display_result']) {
drupal_set_message($message);
}
unset($_SESSION['vbo_options']); // unset the options which were used for just one invocation
}
/**
* Helper function for direct execution operations.
*/
function _views_bulk_operations_direct_process($view, $operation, $objects, $params, $object_info, &$context) {
if ($operation['aggregate'] != VBO_AGGREGATE_FORBIDDEN) {
foreach ($objects as $num => $row) {
$oid = $row->{$view->base_field};
if (isset($object_info['access'])) {
$object = call_user_func($object_info['load'], $oid);
if (!$object) {
unset($objects[$num]);
$context['results']['log'][] = t('Skipped %action on @type id %oid.', array(
'%action' => $operation['label'],
'@type' => t($operation['type']),
'%oid' => $oid,
));
continue;
}
if (!_views_bulk_operations_object_permission($operation, $object, $object_info)) {
unset($objects[$num]);
$context['results']['log'][] = t('Skipped %action on @type %title due to insufficient permissions.', array(
'%action' => $operation['label'],
'@type' => t($object_info['type']),
'%title' => $object->{$object_info['title']},
));
continue;
}
}
$oids[] = $oid;
}
if (!empty($objects)) {
_views_bulk_operations_action_aggregate_do($operation, $oids, $objects, $params, $object_info);
$context['results']['log'][] = t('Performed aggregate %action on @types %oids.', array(
'%action' => $operation['label'],
'@types' => format_plural(count($objects), $object_info['type'], $object_info['type'] . 's'),
'%oids' => implode(',', $oids),
));
$context['results']['rows'] += count($objects);
}
}
else foreach ($objects as $num => $row) {
$oid = $row->{$view->base_field};
$object = call_user_func($object_info['load'], $oid);
if (!$object) {
$context['results']['log'][] = t('Skipped %action on @type id %oid.', array(
'%action' => $operation['label'],
'@type' => t($operation['type']),
'%oid' => $oid,
));
continue;
}
if (!_views_bulk_operations_object_permission($operation, $object, $object_info)) {
$context['results']['log'][] = t('Skipped %action on @type %title due to insufficient permissions.', array(
'%action' => $operation['label'],
'@type' => t($object_info['type']),
'%title' => $object->{$object_info['title']},
));
continue;
}
_views_bulk_operations_action_do($operation, $oid, $object, $row, $params, $object_info);
$context['results']['log'][] = t('Performed %action on @type %title.', array(
'%action' => $operation['label'],
'@type' => t($object_info['type']),
'%title' => $object->{$object_info['title']},
));
$context['results']['rows'] += 1;
}
}
/**
* Helper function to cleanup direct execution operations.
*/
function _views_bulk_operations_direct_finished($success, $results, $operations, $display_result) {
_views_bulk_operations_batch_finished($success, $results, $operations, $display_result);
}
/**
* Helper function to execute one operation.
*/
function _views_bulk_operations_action_do($operation, $oid, $object, $row, $params, $object_info, $account = NULL) {
_views_bulk_operations_action_permission($operation, $account);
// Add the object to the context.
if (isset($object_info['context'])) {
$params[$object_info['context']] = $object;
}
else {
$params[$object_info['type']] = $object;
}
// If the operation type is different from the view type, normalize the context first.
$actual_object = $object;
if ($object_info['type'] != $operation['type']) {
if (isset($object_info['normalize']) && function_exists($object_info['normalize'])) {
$actual_object = call_user_func($object_info['normalize'], $operation['type'], $object);
}
$params['hook'] = $object_info['hook'];
}
if (is_null($actual_object)) { // Normalize function can return NULL: we don't want that
$actual_object = $object;
}
$params['row'] = $row; // Expose the original view row to the action
if ($operation['source'] == 'action') {
actions_do($operation['callback'], $actual_object, $params);
if ($operation['type'] == 'node' && ($operation['access op'] & VBO_ACCESS_OP_UPDATE)) { // Save nodes explicitly if needed
$node_options = variable_get('node_options_'. $actual_object->type, array('status', 'promote'));
if (in_array('revision', $node_options) && !isset($actual_object->revision)) {
$actual_object->revision = TRUE;
$actual_object->log = '';
}
node_save($actual_object);
}
}
else { // source == 'operation'
$args = array_merge(array(array($oid)), $params);
call_user_func_array($operation['callback'], $args);
}
}
/**
* Helper function to execute an aggregate operation.
*/
function _views_bulk_operations_action_aggregate_do($operation, $oids, $objects, $params, $object_info) {
_views_bulk_operations_action_permission($operation);
$params[$operation['type']] = $objects;
if ($operation['source'] == 'action') {
actions_do($operation['callback'], $oids, $params);
}
else {
$args = array_merge(array($oids), $params);
call_user_func_array($operation['callback'], $args);
}
}
/**
* Helper function to verify access permission to execute operation.
*/
function _views_bulk_operations_action_permission($operation, $account = NULL) {
if (module_exists('actions_permissions')) {
$perm = actions_permissions_get_perm($operation['perm label'], $operation['callback']);
if (!user_access($perm, $account)) {
global $user;
watchdog('actions permissions', 'An attempt by user %user to !perm was blocked due to insufficient permissions.',
array('!perm' => $perm, '%user' => isset($account) ? $account->name : $user->name), WATCHDOG_ALERT);
drupal_access_denied();
exit();
}
}
// Check against additional permissions.
if (!empty($operation['permissions'])) foreach ($operation['permissions'] as $perm) {
if (!user_access($perm, $account)) {
global $user;
watchdog('actions permissions', 'An attempt by user %user to !perm was blocked due to insufficient permissions.',
array('!perm' => $perm, '%user' => isset($account) ? $account->name : $user->name), WATCHDOG_ALERT);
drupal_access_denied();
exit();
}
}
}
/**
* Helper function to verify access permission to operate on object.
*/
function _views_bulk_operations_object_permission($operation, $object, $object_info, $account = NULL) {
// Check against object access permissions.
if (!isset($object_info['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 op'] & $bit) {
if (!call_user_func($object_info['access'], $op, $object, $account)) {
return FALSE;
}
}
}
return TRUE;
}
/**
* Helper function to let the configurable action provide its configuration form.
*/
function _views_bulk_operations_action_form($action, $view, $selection, $settings, $context = array()) {
$action_form = $action['callback'] . '_form';
$context = array_merge($context, array('view' => $view, 'selection' => $selection, 'settings' => $settings, 'object_info' => _views_bulk_operations_object_info_for_view($view)));
if (isset($action['callback arguments'])) {
$context = array_merge($context, $action['callback arguments']);
}
$form = call_user_func($action_form, $context);
return is_array($form) ? $form : array();
}
/**
* Helper function to let the configurable action validate the form if it provides a validator.
*/
function _views_bulk_operations_action_validate($action, $form, $form_values) {
$action_validate = $action['callback'] . '_validate';
if (function_exists($action_validate)) {
call_user_func($action_validate, $form, $form_values);
}
}
/**
* Helper function to let the configurable action process the configuration form.
*/
function _views_bulk_operations_action_submit($action, $form, &$form_state) {
$action_submit = $action['callback'] . '_submit';
return call_user_func($action_submit, $form, $form_state);
}
/**
* Helper function to return all object info.
*/
function _views_bulk_operations_get_object_info($reset = FALSE) {
static $object_info = array();
if ($reset || empty($object_info)) {
$object_info = module_invoke_all('views_bulk_operations_object_info');
}
drupal_alter('views_bulk_operations_object_info', $object_info);
return $object_info;
}
/**
* Helper function to return object info for a given view.
*/
function _views_bulk_operations_object_info_for_view($view) {
foreach (_views_bulk_operations_get_object_info() as $object_info) {
if ($object_info['base_table'] == $view->base_table) {
return $object_info;
}
}
watchdog('views bulk operations', 'Could not find object info for view table @table.', array('@table' => $view->base_table), WATCHDOG_ERROR);
return NULL;
}
/**
* Helper to include all action files.
*/
function _views_bulk_operations_load_actions() {
$files = array(
'archive',
'argument_selector',
'delete',
'fields',
'profile',
'ruleset',
'script',
'taxonomy',
'user_roles',
);
foreach ($files as $file) {
module_load_include('inc', 'views_bulk_operations', "$file.action");
}
return $files;
}
/**
* Helper function to adjust the selected set of nodes based on different situations.
*/
function _views_bulk_operations_adjust_selection(&$selection, $select_all, $exposed_input, $arguments, $plugin) {
$url = url($_GET['q']);
// Adjust selection to select all nodes across pages.
$view = views_get_view($plugin->view->vid ? $plugin->view->vid : $plugin->view->name);
$view->set_items_per_page(0);
$view->set_exposed_input($exposed_input);
$view->set_arguments($arguments);
// HACK for date_api_filter_handler: set $_GET with the exposed_input.
$_GET += $exposed_input;
$view->execute($plugin->view->current_display);
if ($select_all) {
$selection = array();
foreach ($view->result as $num => $result) {
$selection[md5(serialize($result))] = $result;
}
}
else {
// Adjust selection to filter out previous selections.
$results = array();
foreach ($view->result as $num => $result) {
$md5 = md5(serialize($result));
if (!empty($selection[$md5]) || !empty($_SESSION['vbo_values'][$url]['objects']['selection'][$md5])) {
$results[$md5] = $result;
}
}
$selection = $results;
}
// Adjust sticky selection accordingly.
$_SESSION['vbo_values'][$url]['objects'] = array('selection' => $selection, 'select_all' => $select_all);
}
/**
* Helper callback for array_walk().
*/
function _views_bulk_operations_get_oid($row, $base_field) {
return $row->$base_field;
}