source: sipes/modules_contrib/views_bulk_operations/views_bulk_operations.module @ 1e95969

stableversion-3.0
Last change on this file since 1e95969 was 177a560, checked in by José Gregorio Puentes <jpuentes@…>, 8 años ago

se agrego el directorio de modulos contribuidos de drupal

  • Propiedad mode establecida a 100755
File size: 77.8 KB
Línea 
1<?php
2
3/**
4 * @file
5 * Allows operations to be performed on items selected in a view.
6 *
7 * Table of contents:
8 * - API functions
9 * - Drupal hooks and callbacks
10 * - Helper functions
11 */
12
13/**
14 * API function to programmatically invoke a VBO.
15 */
16function views_bulk_operations_execute($vid, $operation_callback, $operation_arguments = array(), $view_exposed_input = array(), $view_arguments = array(), $respect_limit = FALSE) {
17  $view = views_get_view($vid);
18  if (!is_object($view)) {
19    watchdog('vbo', 'Could not find view %vid.', array('%vid' => $vid), WATCHDOG_ERROR);
20    return;
21  }
22  $vd = new views_bulk_operations_destructor($view); // this will take care of calling $view->destroy() on exit.
23
24  // Find the view display that has the VBO style.
25  $found = FALSE;
26  foreach (array_keys($view->display) as $display) {
27    $display_options = &$view->display[$display]->display_options;
28    if (isset($display_options['style_plugin']) && $display_options['style_plugin'] == 'bulk') {
29      $view->set_display($display);
30      $found = TRUE;
31      break;
32    }
33  }
34  if (!$found) {
35    watchdog('vbo', 'Could not find a VBO display in view %vid.', array('%vid' => $vid), WATCHDOG_ERROR);
36    return;
37  }
38
39  // Execute the view.
40  $view->set_exposed_input($view_exposed_input);
41  $view->set_arguments($view_arguments);
42  $view->set_items_per_page($respect_limit ? $display_options['items_per_page'] : 0);
43  $view->execute();
44  if (empty($view->result)) {
45    watchdog('vbo', 'No results for view %vid.', array('%vid' => $vid), WATCHDOG_WARNING);
46    return;
47  }
48
49  // Find the selected operation.
50  $plugin = $view->style_plugin;
51  $operations = $plugin->get_selected_operations();
52  if (!isset($operations[$operation_callback])) {
53    watchdog('vbo', 'Could not find operation %operation in view %vid.', array('%operation' => $operation_callback, '%vid' => $vid), WATCHDOG_ERROR);
54    return;
55  }
56  $operation = $plugin->get_operation_info($operation_callback);
57
58  // Execute the operation on the view results.
59  $execution_type = $plugin->options['execution_type'];
60  if ($execution_type == VBO_EXECUTION_BATCH) {
61    $execution_type = VBO_EXECUTION_DIRECT; // we don't yet support Batch API here
62  }
63  _views_bulk_operations_execute(
64    $view,
65    $view->result,
66    $operation,
67    $operation_arguments,
68    array(
69      'execution_type' => $execution_type,
70      'display_result' => $plugin->options['display_result'],
71      'max_performance' => $plugin->options['max_performance'],
72      'settings' => $operation['options']['settings'],
73    )
74  );
75}
76
77/**
78 * API function to add actions to a VBO.
79 */
80function views_bulk_operations_add_actions($vid, $actions) {
81  $view = views_get_view($vid);
82  if (!is_object($view)) {
83    watchdog('vbo', 'Could not find view %vid.', array('%vid' => $vid), WATCHDOG_ERROR);
84    return;
85  }
86
87  // Find the view display that has the VBO style.
88  $found = FALSE;
89  foreach (array_keys($view->display) as $display) {
90    $display_options = &$view->display[$display]->display_options;
91    if (isset($display_options['style_plugin']) && $display_options['style_plugin'] == 'bulk') {
92      $found = TRUE;
93      break;
94    }
95  }
96  if (!$found) {
97    watchdog('vbo', 'Could not find a VBO display in view %vid.', array('%vid' => $vid), WATCHDOG_ERROR);
98    return;
99  }
100
101  // Iterate on the desired actions.
102  $operations = $display_options['style_options']['operations'];
103  $ignored = $added = array();
104  if (!empty($actions)) foreach ($actions as $action) {
105    $modified = FALSE;
106    if (is_numeric($action)) { // aid
107      $action_object = db_fetch_object(db_query("SELECT * FROM {actions} WHERE aid = %d", $action));
108      if (is_object($action_object)) {
109        $parameters = unserialize($action_object->parameters);
110        $key = $action_object->callback . (empty($parameters) ? '' : '-'. md5($action_object->parameters));
111        if (isset($operations[$key])) { // available for this view
112          $display_options['style_options']['operations'][$key]['selected'] = TRUE;
113          $modified = TRUE;
114        }
115      }
116    }
117    else { // callback or title
118      if (isset($operations[$action])) { // callback and available for this view
119        $display_options['style_options']['operations'][$action]['selected'] = TRUE;
120        $modified = TRUE;
121      }
122      else { // try the title
123        $action_object = db_fetch_object(db_query("SELECT * FROM {actions} WHERE description LIKE '%s'", db_escape_string($action)));
124        if (is_object($action_object)) {
125          $parameters = unserialize($action_object->parameters);
126          $key = $action_object->callback . (empty($parameters) ? '' : '-'. md5($action_object->parameters));
127          if (isset($operations[$key])) { // available for this view
128            $display_options['style_options']['operations'][$key]['selected'] = TRUE;
129            $modified = TRUE;
130          }
131        }
132      }
133    }
134    if ($modified) {
135      $added[] = $action;
136    } else {
137      $ignored[] = $action;
138    }
139  }
140
141  // Save the view if anything was changed.
142  if (!empty($added)) {
143    $view->save();
144    views_object_cache_clear('view', $vid);
145
146    if (empty($ignored)) {
147      watchdog('vbo', 'View %vid was successfully modified. The following actions were added: %added.', array(
148        '%vid' => $vid,
149        '%added' => implode(', ', $added)
150      ), WATCHDOG_INFO);
151    } else {
152      watchdog('vbo', 'View %vid was modified. The following actions were added: %added. The following actions were ignored: %ignored.', array(
153        '%vid' => $vid,
154        '%added' => implode(', ', $added),
155        '%ignored' => implode(', ', $ignored)
156      ), WATCHDOG_WARNING);
157    }
158  }
159  else {
160    watchdog('vbo', 'View %vid was NOT modified. The following actions were ignored: %ignored.', array(
161      '%vid' => $vid,
162      '%ignored' => implode(', ', $ignored)
163    ), WATCHDOG_ERROR);
164  }
165}
166
167// Define the steps in the multistep form that executes operations.
168define('VBO_STEP_VIEW',    1);
169define('VBO_STEP_CONFIG',  2);
170define('VBO_STEP_CONFIRM', 3);
171define('VBO_STEP_SINGLE',  4);
172
173// Types of bulk execution.
174define('VBO_EXECUTION_DIRECT',    1);
175define('VBO_EXECUTION_BATCH',     2);
176define('VBO_EXECUTION_QUEUE',     3);
177
178// Types of aggregate actions.
179define('VBO_AGGREGATE_FORCED',    1);
180define('VBO_AGGREGATE_FORBIDDEN', 0);
181define('VBO_AGGREGATE_OPTIONAL',  2);
182
183// Access operations.
184define('VBO_ACCESS_OP_VIEW',      0x01);
185define('VBO_ACCESS_OP_UPDATE',    0x02);
186define('VBO_ACCESS_OP_CREATE',    0x04);
187define('VBO_ACCESS_OP_DELETE',    0x08);
188
189/**
190 * Implementation of hook_cron_queue_info().
191 */
192function views_bulk_operations_cron_queue_info() {
193  return array(
194    'views_bulk_operations' => array(
195      'worker callback' => '_views_bulk_operations_execute_queue',
196      'time' => 30,
197    ),
198  );
199}
200
201/**
202 * Implementation of hook_views_api().
203 */
204function views_bulk_operations_views_api() {
205  return array(
206    'api' => 2.0,
207  );
208}
209
210/**
211 * Implementation of hook_elements().
212 */
213function views_bulk_operations_elements() {
214  $type['views_node_selector'] = array(
215    '#input' => TRUE,
216    '#view' => NULL,
217    '#process' => array('views_node_selector_process'),
218  );
219  return $type;
220}
221
222/**
223 * Process function for views_node_selector element.
224 *
225 * @see views_bulk_operations_elements()
226 */
227function views_node_selector_process($element, $edit) {
228  $view = $element['#view'];
229  $view_id = _views_bulk_operations_view_id($view);
230  $view_name = $view->name;
231
232  // Gather options.
233  $result = $view->style_plugin->options['preserve_selection'] ? $_SESSION['vbo_values'][$view_name][$view_id]['result'] : $view->style_plugin->result;
234  $options = array();
235  foreach ($result as $k => $v) {
236    $options[$k] = '';
237  }
238
239  // Fix default value.
240  $element['#default_value'] += array('selection' => array(), 'selectall' => FALSE);
241  $element['#default_value']['selection'] =
242    $element['#default_value']['selectall'] ?
243    array_diff_key($options, array_filter($element['#default_value']['selection'], '_views_bulk_operations_filter_invert')) :
244    array_intersect_key($options, array_filter($element['#default_value']['selection']));
245
246  // Create selection FAPI elements.
247  $element['#tree'] = TRUE;
248  $element['selection'] = array(
249    '#options' => $options,
250    '#value' => $element['#default_value']['selection'],
251    '#attributes' => array('class' => 'select'),
252  );
253  $element['selection'] = expand_checkboxes($element['selection']);
254  $element['selectall'] = array(
255    '#type' => 'hidden',
256    '#default_value' => $element['#default_value']['selectall']
257  );
258  return $element;
259}
260
261/**
262 * Implementation of hook_theme().
263 */
264function views_bulk_operations_theme() {
265  $themes = array(
266    'views_node_selector' => array(
267      'arguments' => array('element' => NULL),
268    ),
269    'views_bulk_operations_confirmation' => array(
270      'arguments' => array('objects' => NULL, 'invert' => FALSE, 'view' => NULL),
271    ),
272    'views_bulk_operations_select_all' => array(
273      'arguments' => array('colspan' => 0, 'selection' => 0, 'view' => NULL),
274    ),
275    'views_bulk_operations_table' => array(
276      'arguments' => array('header' => array(), 'rows' => array(), 'attributes' => array(), 'title' => NULL, 'view' => NULL),
277      'pattern' => 'views_bulk_operations_table__',
278    ),
279  );
280
281  // Load action theme files.
282  foreach (_views_bulk_operations_load_actions() as $file) {
283    $action_theme_fn = 'views_bulk_operations_'. $file .'_action_theme';
284    if (function_exists($action_theme_fn)) {
285      $themes += call_user_func($action_theme_fn);
286    }
287  }
288
289  return $themes;
290}
291
292/**
293 * Theme function for 'views_bulk_operations_table'.
294 */
295function theme_views_bulk_operations_table($header, $rows, $attributes, $title, $view) {
296  return theme('table', $header, $rows, $attributes, $title);
297}
298
299/**
300 * Template preprocessor for theme function 'views_bulk_operations_table'.
301 */
302function template_preprocess_views_bulk_operations_table(&$vars, $hook) {
303  $view = $vars['view'];
304
305  $options = $view->style_plugin->options;
306  $handler = $view->style_plugin;
307
308  $fields = &$view->field;
309  $columns = $handler->sanitize_columns($options['columns'], $fields);
310
311  $active = !empty($handler->active) ? $handler->active : '';
312
313  foreach ($columns as $field => $column) {
314    $vars['fields'][$field] = views_css_safe($field);
315    if ($active == $field) {
316      $vars['fields'][$field] .= ' active';
317    }
318  }
319
320  $count = 0;
321  foreach ($vars['rows'] as $r => &$row) {
322    $vars['row_classes'][$r][] = ($count++ % 2 == 0) ? 'odd' : 'even';
323    $cells = $row;
324    if (isset($row['class'])) {
325      $vars['row_classes'][$r][] = $row['class'];
326    }
327    if (isset($row['data'])) {
328      $cells = $row['data'];
329    }
330    foreach ($cells as $c => &$cell) {
331      if (is_array($cell) && isset($cell['data'])) {
332        $cell = $cell['data'];
333      }
334    }
335    $row = $cells;
336  }
337
338  $vars['row_classes'][0][] = 'views-row-first';
339  $vars['row_classes'][count($vars['row_classes']) - 1][] = 'views-row-last';
340
341  $vars['class'] = 'views-bulk-operations-table';
342  if ($view->style_plugin->options['sticky']) {
343    drupal_add_js('misc/tableheader.js');
344    $vars['class'] .= ' sticky-enabled';
345  }
346  $vars['class'] .= ' cols-'. count($vars['rows']);
347  $vars['class'] .= ' views-table';
348}
349
350/**
351 * Theme function for 'views_node_selector'.
352 */
353function theme_views_node_selector($element) {
354  module_load_include('inc', 'views', 'theme/theme');
355
356  $output = '';
357  $view = $element['#view'];
358  $sets = $element['#sets'];
359  $vars = array(
360    'view' => $view,
361  );
362  // Give each group its own headers row.
363  foreach ($sets as $title => $records) {
364    $headers = array();
365
366    // template_preprocess_views_view_table() expects the raw data in 'rows'.
367    $vars['rows'] = $records;
368
369    // Render the view as table. Use the hook from from views/theme/theme.inc
370    // and allow overrides using the same algorithm as the theme system will
371    // do calling the theme() function.
372    $hook = 'views_view_table';
373    $hooks = theme_get_registry();
374    if (!isset($hooks[$hook])) {
375      return '';
376    }
377    $args = array(&$vars, $hook);
378    foreach ($hooks[$hook]['preprocess functions'] as $func) {
379      if (function_exists($func)) {
380        call_user_func_array($func, $args);
381      }
382    }
383
384    // Add checkboxes to the header and the rows.
385    $rows = array();
386    if (empty($view->style_plugin->options['hide_selector'])) {
387      $headers[] = array('class' => 'vbo-select-all');
388
389      // Add extra status row if needed.
390      $items_per_page = method_exists($view, 'get_items_per_page') ? $view->get_items_per_page() : (isset($view->pager) ? $view->pager['items_per_page'] : 0);
391      if ($items_per_page && $view->total_rows > $items_per_page) {
392        $row = theme('views_bulk_operations_select_all', count($vars['header']) + 1, _views_bulk_operations_get_selection_count($view->style_plugin, $element['#default_value']), $view);
393        $rows[] = $row;
394      }
395    }
396    else {
397      $headers[] = array('class' => 'no-select-all');
398    }
399    foreach ($vars['header'] as $field => $label) {
400      $headers[] = array('data' => $label, 'class' => "views-field views-field-{$vars['fields'][$field]}");
401    }
402    foreach ($records as $num => $object) {
403      $vars['row_classes'][$num][] = 'rowclick';
404      $row = array('class' => implode(' ', $vars['row_classes'][$num]), 'data' => array());
405      $row['data'][] =  theme('checkbox', $element['selection'][_views_bulk_operations_hash_object($object, $view)]);
406      foreach ($vars['rows'][$num] as $field => $content) {
407        // Support field classes in Views 3, but provide a fallback for Views 2.
408        if (views_api_version() == 2) {
409          $row['data'][] = array('data' => $content, 'class' => 'views-field views-field-' . $vars['fields'][$field]);
410        }
411        else {
412          $row['data'][] = array('data' => $content, 'class' => $vars['field_classes'][$field][$num]);
413        }
414      }
415      $rows[] = $row;
416    }
417
418    $theme_functions = views_theme_functions('views_bulk_operations_table', $view, $view->display[$view->current_display]);
419    $output .= theme($theme_functions, $headers, $rows, array('class' => $vars['class']), $title, $view);
420    $output .= theme('hidden', $element['selectall']);
421  }
422  return theme('form_element', $element, $output);
423}
424
425/**
426 * Theme function for 'views_bulk_operations_select_all'.
427 */
428function theme_views_bulk_operations_select_all($colspan, $selection, $view) {
429  $clear_selection = t('Clear selection');
430  $select_label = t('Select all items:');
431  $this_page = t('on this page only');
432  $all_pages = t('across all pages');
433  $this_page_checked = $selection['selectall'] ? '' : ' checked="checked"';
434  $all_pages_checked = $selection['selectall'] ? ' checked="checked"' : '';
435  $selection_count = t('<span class="selected">@selected</span> items selected.', array('@selected' => $selection['selected']));
436  $output = <<<EOF
437$selection_count
438<span class="select">
439$select_label
440<input type="radio" name="select-all" id="select-this-page" value="0"$this_page_checked /><label for="select-this-page">$this_page</label>
441<input type="radio" name="select-all" id="select-all-pages" value="1"$all_pages_checked /><label for="select-all-pages">$all_pages</label>
442</span>
443<input type="button" id="clear-selection" value="$clear_selection" />
444EOF;
445  return array(array('data' => $output, 'class' => 'views-field views-field-select-all', 'colspan' => $colspan));
446}
447
448/**
449 * Form implementation for main VBO multistep form.
450 */
451function views_bulk_operations_form($form_state, $form_id, $plugin) {
452  // Erase the form parameters from $_REQUEST for a clean pager.
453  if (!empty($form_state['post'])) {
454    $_REQUEST = array_diff($_REQUEST, $form_state['post']);
455  }
456
457  // Force browser to reload the page if Back is hit.
458  if (!empty($_SERVER['HTTP_USER_AGENT']) && preg_match('/msie/i', $_SERVER['HTTP_USER_AGENT'])) {
459    drupal_set_header("Cache-Control: no-cache"); // works for IE6+
460  }
461  else {
462    drupal_set_header("Cache-Control: no-store"); // works for Firefox and other browsers
463  }
464
465  // Which step is this?
466  if (empty($form_state['storage']['step'])) {
467
468    // If empty view, render the empty text.
469    if (empty($plugin->view->result)) {
470      $form['empty'] = array('#value' => $plugin->view->display_handler->render_empty());
471      return $form;
472    }
473
474    // If there's a session variable on this view, pre-load the old values.
475    $view_id = _views_bulk_operations_view_id($plugin->view);
476    $view_name = $plugin->view->name;
477    if (isset($_SESSION['vbo_values'][$view_name][$view_id]) && $plugin->options['preserve_selection']) {
478      // Avoid PHP warnings.
479      $_SESSION['vbo_values'][$view_name][$view_id] += array(
480        'selection' => array(),
481        'selectall' => FALSE,
482        'operation' => NULL,
483      );
484
485      $default_objects = array(
486        'selection' => $_SESSION['vbo_values'][$view_name][$view_id]['selection'],
487        'selectall' => $_SESSION['vbo_values'][$view_name][$view_id]['selectall'],
488      );
489      $default_operation = $_SESSION['vbo_values'][$view_name][$view_id]['operation'];
490    }
491    else {
492      $default_objects = array('selection' => array(), 'selectall' => FALSE);
493      $default_operation = NULL;
494    }
495
496    if (count($plugin->get_selected_operations()) == 1 && $plugin->options['merge_single_action']) {
497      $step = VBO_STEP_SINGLE;
498    }
499    else {
500      $step = VBO_STEP_VIEW;
501    }
502  }
503  else {
504    _views_bulk_operations_strip_view($plugin->view);
505    switch ($form_state['storage']['step']) {
506      case VBO_STEP_VIEW:
507        $operation = $form_state['storage']['operation'];
508        if ($operation['configurable']) {
509          $step = VBO_STEP_CONFIG;
510        }
511        else {
512          $step = VBO_STEP_CONFIRM;
513        }
514        break;
515      case VBO_STEP_SINGLE:
516      case VBO_STEP_CONFIG:
517        $step = VBO_STEP_CONFIRM;
518        break;
519      }
520  }
521  $form['step'] = array(
522    '#type' => 'value',
523    '#value' => $step
524  );
525  $form['#plugin'] = $plugin;
526
527  switch ($step) {
528    case VBO_STEP_VIEW:
529      $form['select'] = array(
530        '#type' => 'fieldset',
531        '#title' => t('Bulk operations'),
532        '#prefix' => '<div id="views-bulk-operations-select">',
533        '#suffix' => '</div>',
534      );
535      $form['objects'] = array(
536        '#type' => 'views_node_selector',
537        '#view' => $plugin->view,
538        '#sets' => $plugin->sets,
539        '#default_value' => $default_objects,
540        '#prefix' => '<div class="views-node-selector">',
541        '#suffix' => '</div>',
542      );
543      if ($plugin->options['display_type'] == 0) {
544        // Create dropdown and submit button.
545        $form['select']['operation'] = array(
546          '#type' => 'select',
547          '#options' => array(0 => t('- Choose an operation -')) + $plugin->get_selected_operations(),
548          '#default_value' => $default_operation,
549          '#prefix' => '<div id="views-bulk-operations-dropdown">',
550          '#suffix' => '</div>',
551        );
552        $form['select']['submit'] = array(
553          '#type' => 'submit',
554          '#value' => t('Execute'),
555          '#prefix' => '<div id="views-bulk-operations-submit">',
556          '#suffix' => '</div>',
557        );
558      }
559      else {
560        // Create buttons for actions.
561        foreach ($plugin->get_selected_operations() as $md5 => $description) {
562          $form['select'][$md5] = array(
563            '#type' => 'submit',
564            '#value' => $description,
565            '#hash' => $md5,
566          );
567        }
568      }
569      break;
570
571    case VBO_STEP_SINGLE:
572      $operation_keys = array_keys($plugin->get_selected_operations());
573      $operation = $plugin->get_operation_info($operation_keys[0]);
574      $form['operation'] = array('#type' => 'value', '#value' => $operation_keys[0]);
575      if ($operation['configurable']) {
576        $form += _views_bulk_operations_action_form(
577          $operation,
578          $plugin->view,
579          $plugin->result,
580          $operation['options']['settings']
581        );
582      }
583      $form['submit'] = array(
584        '#type' => 'submit',
585        '#value' => $operation['label'],
586        '#prefix' => '<div id="views-bulk-operations-submit">',
587        '#suffix' => '</div>',
588      );
589      $form['objects'] = array(
590        '#type' => 'views_node_selector',
591        '#view' => $plugin->view,
592        '#sets' => $plugin->sets,
593        '#default_value' => $default_objects,
594        '#prefix' => '<div class="views-node-selector">',
595        '#suffix' => '</div>',
596      );
597      break;
598
599    case VBO_STEP_CONFIG:
600      $operation = $form_state['storage']['operation'];
601      $form += _views_bulk_operations_action_form(
602        $operation,
603        $plugin->view,
604        _views_bulk_operations_get_selection_full($plugin, $form_state),
605        $operation['options']['settings']
606      );
607      $form['execute'] = array(
608        '#type' => 'submit',
609        '#value' => t('Next'),
610        '#weight' => 98,
611      );
612      $query = drupal_query_string_encode($_GET, array('q'));
613      $form['cancel'] = array(
614        '#type' => 'markup',
615        '#value' => l('Cancel', $_GET['q'], array('query' => $query)),
616        '#weight' => 99,
617      );
618      drupal_set_title(t('Set parameters for %operation', array('%operation' => $operation['label'])));
619      break;
620
621    case VBO_STEP_CONFIRM:
622      $operation = $form_state['storage']['operation'];
623      $query = drupal_query_string_encode($_GET, array('q'));
624      $form = confirm_form($form,
625        t('Are you sure you want to perform %operation on the selected items?', array('%operation' => $operation['label'])),
626        array('path' => $_GET['q'], 'query' => $query),
627        theme('views_bulk_operations_confirmation', $form_state['storage']['selection'], $form_state['storage']['selectall'], $plugin->view)
628      );
629      break;
630  }
631
632  // Use views_bulk_operations_form_submit() for form submit, regardless of form_id.
633  $form['#submit'][] = 'views_bulk_operations_form_submit';
634  $form['#validate'][] = 'views_bulk_operations_form_validate';
635  $form['#attributes']['class'] = 'views-bulk-operations-form views-bulk-operations-form-step-' . $step;
636
637  // A view with ajax enabled, and a space in the url, has to be decoded to work fine,
638  // @see http://drupal.org/node/1325632
639  $form['#action'] = urldecode(request_uri());
640
641  return $form;
642}
643
644/**
645 * Implementation of hook_form_alter().
646 */
647function views_bulk_operations_form_alter(&$form, &$form_state) {
648  // Get the form ID here to add the JS settings.
649  if (!empty($form['form_id']) && strpos($form['form_id']['#value'], 'views_bulk_operations_form') === 0 && !empty($form['#plugin'])) {
650    _views_bulk_operations_add_js($form['#plugin'], $form['#id'], $form['form_id']['#value']);
651  }
652}
653
654/**
655 * Form validate function for views_bulk_operations_form().
656 */
657function views_bulk_operations_form_validate($form, &$form_state) {
658  $form_id = $form_state['values']['form_id'];
659  $plugin = $form['#plugin'];
660  $view_id = _views_bulk_operations_view_id($plugin->view);
661  $view_name = $plugin->view->name;
662
663  switch ($form_state['values']['step']) {
664    case VBO_STEP_VIEW:
665      if (!array_filter($form_state['values']['objects']['selection']) && (empty($_SESSION['vbo_values'][$view_name][$view_id]) || !array_filter($_SESSION['vbo_values'][$view_name][$view_id]['selection']))) {
666        form_set_error('objects', t('No item selected. Please select one or more items.'));
667      }
668      if (!empty($form_state['clicked_button']['#hash'])) {
669        $form_state['values']['operation'] = $form_state['clicked_button']['#hash'];
670      }
671      if (!$form_state['values']['operation']) { // No action selected
672        form_set_error('operation', t('No operation selected. Please select an operation to perform.'));
673      }
674      if (form_get_errors()) {
675        _views_bulk_operations_add_js($plugin, $form['#id'], $form_id);
676      }
677      break;
678
679    case VBO_STEP_SINGLE:
680      if (!array_filter($form_state['values']['objects']['selection']) && (empty($_SESSION['vbo_values'][$view_name][$view_id]) || !array_filter($_SESSION['vbo_values'][$view_name][$view_id]['selection']))) {
681        form_set_error('objects', t('No item selected. Please select one or more items.'));
682      }
683      $operation = $plugin->get_operation_info($form_state['values']['operation']);
684      if ($operation['configurable']) {
685        _views_bulk_operations_action_validate($operation, $form, $form_state);
686      }
687      if (form_get_errors()) {
688        _views_bulk_operations_add_js($plugin, $form['#id'], $form_id);
689      }
690      break;
691
692    case VBO_STEP_CONFIG:
693      $operation = $form_state['storage']['operation'];
694      _views_bulk_operations_action_validate($operation, $form, $form_state);
695
696      // If the action validation fails, Form API will bring us back to this step.
697      // We need to strip the view here because the form function will not be called.
698      // Also, the $plugin variable above was carried over from last submission, so it
699      // does not represent the current instance of the plugin.
700      // That's why we had to store instances of the plugin in this global array.
701      if (form_get_errors()) {
702        global $vbo_plugins;
703        if (isset($vbo_plugins[$form_id])) {
704          _views_bulk_operations_strip_view($vbo_plugins[$form_id]->view);
705        }
706      }
707      break;
708  }
709}
710
711/**
712 * Form submit function for views_bulk_operations_form().
713 */
714function views_bulk_operations_form_submit($form, &$form_state) {
715  $form_id = $form_state['values']['form_id'];
716  $plugin = $form['#plugin'];
717  $view = $plugin->view;
718  $view_id = _views_bulk_operations_view_id($view);
719  $view_name = $view->name;
720
721  $form_state['storage']['step'] = $step = $form_state['values']['step'];
722  switch ($step) {
723    case VBO_STEP_VIEW:
724      $form_state['storage']['selection'] = _views_bulk_operations_get_selection($plugin, $form_state, $form_id);
725      $form_state['storage']['selectall'] = $form_state['values']['objects']['selectall'];
726      $form_state['storage']['operation'] = $operation = $plugin->get_operation_info($form_state['values']['operation']);
727      $_SESSION['vbo_values'][$view_name][$view_id]['operation'] = $operation['key'];
728      if (!$operation['configurable'] && !empty($operation['options']['skip_confirmation'])) {
729        break; // Go directly to execution
730      }
731      return;
732
733    case VBO_STEP_SINGLE:
734      $form_state['storage']['selection'] = _views_bulk_operations_get_selection($plugin, $form_state, $form_id);
735      $form_state['storage']['selectall'] = $form_state['values']['objects']['selectall'];
736      $form_state['storage']['operation'] = $operation = $plugin->get_operation_info($form_state['values']['operation']);
737      $_SESSION['vbo_values'][$view_name][$view_id]['operation'] = $operation['key'];
738      if ($operation['configurable']) {
739        $form_state['storage']['operation_arguments'] = _views_bulk_operations_action_submit($operation, $form, $form_state);
740      }
741      if (!empty($operation['options']['skip_confirmation'])) {
742        break; // Go directly to execution
743      }
744      return;
745
746    case VBO_STEP_CONFIG:
747      $operation = $form_state['storage']['operation'];
748      $form_state['storage']['operation_arguments'] = _views_bulk_operations_action_submit($operation, $form, $form_state);
749      if (!empty($operation['options']['skip_confirmation'])) {
750        break; // Go directly to execution
751      }
752      return;
753
754    case VBO_STEP_CONFIRM:
755      break;
756  }
757
758  // Clean up unneeded SESSION variables.
759  unset($_SESSION['vbo_values'][$view->name]);
760
761  // Execute the VBO.
762  $objects = _views_bulk_operations_get_selection_full($plugin, $form_state);
763  $operation = $form_state['storage']['operation'];
764  $operation_arguments = array();
765  if ($operation['configurable']) {
766    $operation_arguments = $form_state['storage']['operation_arguments'];
767  }
768  _views_bulk_operations_execute(
769    $view,
770    $objects,
771    $operation,
772    $operation_arguments,
773    array(
774      'execution_type' => $plugin->options['execution_type'],
775      'display_result' => $plugin->options['display_result'],
776      'max_performance' => $plugin->options['max_performance'],
777      'settings' => $operation['options']['settings'],
778    )
779  );
780
781  // Clean up the form.
782  $query = drupal_query_string_encode($_GET, array('q'));
783  $form_state['redirect'] = array('path' => $view->get_url(), 'query' => $query);
784  unset($form_state['storage']);
785}
786
787/**
788 * Compute the selection based on the settings.
789 */
790function _views_bulk_operations_get_selection($plugin, $form_state, $form_id) {
791  $result = $plugin->result;
792  $selection = $form_state['values']['objects']['selection'];
793  if ($plugin->options['preserve_selection']) {
794    $view_id = _views_bulk_operations_view_id($plugin->view);
795    $view_name = $plugin->view->name;
796    $result = $_SESSION['vbo_values'][$view_name][$view_id]['result'];
797    $selection = $_SESSION['vbo_values'][$view_name][$view_id]['selection'];
798  }
799  $selection = $form_state['values']['objects']['selectall'] ?
800    array_intersect_key($result, array_filter($selection, '_views_bulk_operations_filter_invert')) :
801    array_intersect_key($result, array_filter($selection));
802  return $selection;
803}
804
805/**
806 * Compute the actual selected objects based on the settings.
807 */
808function _views_bulk_operations_get_selection_full($plugin, $form_state) {
809  // Get the objects from the view if selectall was chosen.
810  $view = $plugin->view;
811  if ($form_state['storage']['selectall']) {
812    $view_copy = views_get_view($view->name);
813    $view_copy->set_exposed_input($view->exposed_input);
814    $view_copy->set_arguments($view->args);
815    $view_copy->set_items_per_page(0);
816    $view_copy->skip_render = TRUE; // signal our plugin to skip the rendering
817    $view_copy->render($view->current_display);
818    $objects = array();
819    foreach ($view_copy->result as $row) {
820      $objects[_views_bulk_operations_hash_object($row, $view_copy)] = $row;
821    }
822    $view_copy->destroy();
823    $objects = array_diff_key($objects, $form_state['storage']['selection']);
824  }
825  else {
826    $objects = $form_state['storage']['selection'];
827  }
828  return $objects;
829}
830
831/**
832 * Compute the actual number of selected items.
833 */
834function _views_bulk_operations_get_selection_count($plugin, $selection) {
835  if ($plugin->options['preserve_selection']) {
836    $view_id = _views_bulk_operations_view_id($plugin->view);
837    $view_name = $plugin->view->name;
838    $selection = $_SESSION['vbo_values'][$view_name][$view_id];
839  }
840  return array(
841    'selectall' => $selection['selectall'],
842    'selected'  => $selection['selectall'] ?
843      $plugin->view->total_rows - count(array_filter($selection['selection'], '_views_bulk_operations_filter_invert')) :
844      count(array_filter($selection['selection']))
845  );
846}
847
848/**
849 * Theme function to show the confirmation page before executing the action.
850 */
851function theme_views_bulk_operations_confirmation($objects, $invert, $view) {
852  $selectall = $invert ? (count($objects) == 0) : (count($objects) == $view->total_rows);
853  if ($selectall) {
854    $output = format_plural(
855      $view->total_rows,
856      'You selected the only item in this view.',
857      'You selected all <strong>@count</strong> items in this view.'
858    );
859  }
860  else {
861    $object_info = _views_bulk_operations_object_info_for_view($view);
862    $items = array();
863    foreach ($objects as $row) {
864      $oid = $row->{$view->base_field};
865      if ($object = call_user_func($object_info['load'], $oid)) {
866        $items[] = check_plain((string)$object->{$object_info['title']});
867      }
868    }
869    $output = theme('item_list', $items,
870      $invert ?
871      format_plural(
872        count($objects),
873        'You selected all ' . $view->total_rows . ' but the following item:',
874        'You selected all ' . $view->total_rows . ' but the following <strong>@count</strong> items:'
875      ) :
876      format_plural(
877        count($objects),
878        'You selected the following item:',
879        'You selected the following <strong>@count</strong> items:'
880      )
881    );
882  }
883  return $output;
884}
885
886/**
887 * Implementation of hook_forms().
888 *
889 * Force each instance of function to use the same callback.
890 */
891function views_bulk_operations_forms($form_id, $args) {
892  // Ensure we map a callback for our form and not something else.
893  $forms = array();
894  if (strpos($form_id, 'views_bulk_operations_form_') === 0) {
895    // Let the forms API know where to get the form data corresponding
896    // to this form id.
897    $forms[$form_id] = array(
898      'callback' => 'views_bulk_operations_form',
899      'callback arguments' => array($form_id),
900    );
901  }
902  return $forms;
903}
904
905/**
906 * Implementation of hook_views_bulk_operations_object_info()
907 *
908 * Hook used by VBO to be able to handle different objects as does Views 2 and the Drupal core action system.
909 *
910 * The array returned for each object type contains:
911 *  'type' (required) => the object type name, should be the same as 'type' field in hook_action_info().
912 *  'context' (optional) => the context name that should receive the object, defaults to the value of 'type' above.
913 *  'base_table' (required) => the Views 2 table name corresponding to that object type, should be the same as the $view->base_table attribute.
914 *  'oid' (currently unused) => an attribute on the object that returns the unique object identifier (should be the same as $view->base_field).
915 *  'load' (required) => a function($oid) that returns the corresponding object.
916 *  'title' (required) => an attribute on the object that returns a human-friendly identifier of the object.
917 *  'access' (optional) => a function($op, $node, $account = NULL) that behaves like node_access().
918 *
919 * The following attributes allow VBO to show actions on view types different than the action's type:
920 *  'hook' (optional) => the name of the hook supported by this object type, as defined in the 'hooks' attribute of hook_action_info().
921 *  'normalize' (optional) => a function($type, $object) that takes an object type and the object instance, returning additional context information for cross-type
922 *
923 *  e.g., an action declaring hooks => array('user') while of type 'system' will be shown on user views, and VBO will call the user's 'normalize' function to
924 *        prepare the action to fit the user context.
925 */
926function views_bulk_operations_views_bulk_operations_object_info() {
927  $object_info = array(
928    'node' => array(
929      'type' => 'node',
930      'base_table' => 'node',
931      'load' => '_views_bulk_operations_node_load',
932      'oid' => 'nid',
933      'title' => 'title',
934      'access' => 'node_access',
935      'hook' => 'nodeapi',
936      'normalize' => '_views_bulk_operations_normalize_node_context',
937    ),
938    'user' => array(
939      'type' => 'user',
940      'base_table' => 'users',
941      'load' => 'user_load',
942      'oid' => 'uid',
943      'title' => 'name',
944      'context' => 'account',
945      'hook' => 'user',
946      'normalize' => '_views_bulk_operations_normalize_user_context',
947    ),
948    'comment' => array(
949      'type' => 'comment',
950      'base_table' => 'comments',
951      'load' => '_comment_load',
952      'oid' => 'cid',
953      'title' => 'subject',
954      'hook' => 'comment',
955      'normalize' => '_views_bulk_operations_normalize_comment_context',
956    ),
957    'term' => array(
958      'type' => 'term',
959      'base_table' => 'term_data',
960      'load' => 'taxonomy_get_term',
961      'oid' => 'tid',
962      'title' => 'name',
963      'hook' => 'taxonomy',
964    ),
965    'node_revision' => array(
966      'type' => 'node_revision',
967      'base_table' => 'node_revisions',
968      'load' => '_views_bulk_operations_node_revision_load',
969      'title' => 'name',
970    ),
971    'file' => array(
972      'type' => 'file',
973      'base_table' => 'files',
974      'load' => '_views_bulk_operations_file_load',
975      'oid' => 'fid',
976      'title' => 'filename',
977    ),
978  );
979  return $object_info;
980}
981
982/**
983 * Load function for objects of type 'node'.
984 */
985function _views_bulk_operations_node_load($nid) {
986  return node_load($nid, NULL, TRUE);
987}
988
989/**
990 * Load function for objects of type 'file'.
991 */
992function _views_bulk_operations_file_load($fid) {
993  return db_fetch_object(db_query("SELECT * FROM {files} WHERE fid = %d", $fid));
994}
995
996/**
997 * Load function for node revisions.
998 */
999function _views_bulk_operations_node_revision_load($vid) {
1000  $nid = db_result(db_query("SELECT nid FROM {node_revisions} WHERE vid = %d", $vid));
1001  return node_load($nid, $vid, TRUE);
1002}
1003
1004/**
1005 * Normalize function for node context.
1006 *
1007 * @see _trigger_normalize_node_context()
1008 */
1009function _views_bulk_operations_normalize_node_context($type, $node) {
1010  switch ($type) {
1011    // If an action that works on comments is being called in a node context,
1012    // the action is expecting a comment object. But we do not know which comment
1013    // to give it. The first? The most recent? All of them? So comment actions
1014    // in a node context are not supported.
1015
1016    // An action that works on users is being called in a node context.
1017    // Load the user object of the node's author.
1018    case 'user':
1019      return user_load(array('uid' => $node->uid));
1020  }
1021}
1022
1023/**
1024 * Normalize function for comment context.
1025 *
1026 * @see _trigger_normalize_comment_context()
1027 */
1028function _views_bulk_operations_normalize_comment_context($type, $comment) {
1029  switch ($type) {
1030    // An action that works with nodes is being called in a comment context.
1031    case 'node':
1032      return node_load(is_array($comment) ? $comment['nid'] : $comment->nid);
1033
1034    // An action that works on users is being called in a comment context.
1035    case 'user':
1036      return user_load(array('uid' => is_array($comment) ? $comment['uid'] : $comment->uid));
1037  }
1038}
1039
1040/**
1041 * Normalize function for user context.
1042 *
1043 * @see _trigger_normalize_user_context()
1044 */
1045function _views_bulk_operations_normalize_user_context($type, $account) {
1046  switch ($type) {
1047    // If an action that works on comments is being called in a user context,
1048    // the action is expecting a comment object. But we have no way of
1049    // determining the appropriate comment object to pass. So comment
1050    // actions in a user context are not supported.
1051
1052    // An action that works with nodes is being called in a user context.
1053    // If a single node is being viewed, return the node.
1054    case 'node':
1055      // If we are viewing an individual node, return the node.
1056      if ((arg(0) == 'node') && is_numeric(arg(1)) && (arg(2) == NULL)) {
1057        return node_load(array('nid' => arg(1)));
1058      }
1059  }
1060}
1061
1062/**
1063 * Implementation of hook_init().
1064 */
1065function views_bulk_operations_init() {
1066  // Make sure our actions are loaded.
1067  _views_bulk_operations_load_actions();
1068}
1069
1070/**
1071 * Implementation of hook_action_info().
1072 */
1073function views_bulk_operations_action_info() {
1074  $actions = array();
1075  foreach (_views_bulk_operations_load_actions() as $file) {
1076    $action_info_fn = 'views_bulk_operations_'. $file .'_action_info';
1077    $action_info = call_user_func($action_info_fn);
1078    if (is_array($action_info)) {
1079      $actions += $action_info;
1080    }
1081  }
1082
1083  // Add VBO's own programmatic action.
1084  $actions['views_bulk_operations_action'] = array(
1085    'description' => t('Execute a VBO programmatically'),
1086    'type' => 'system',
1087    'configurable' => TRUE,
1088    'rules_ignore' => TRUE,
1089  );
1090
1091  return $actions;
1092}
1093
1094/**
1095 * Implementation of hook_menu().
1096 */
1097function views_bulk_operations_menu() {
1098  $items['views-bulk-operations/js/action'] = array(
1099    'title' => 'VBO action form',
1100    'description' => 'AHAH callback to display action form on VBO action page.',
1101    'page callback' => 'views_bulk_operations_form_ahah',
1102    'page arguments' => array('views_bulk_operations_action_form_operation'),
1103    'access arguments' => array('access content'),
1104    'type' => MENU_CALLBACK,
1105  );
1106  $items['views-bulk-operations/js/select'] = array(
1107    'title' => 'VBO select handler',
1108    'description' => 'AJAX callback to update selection.',
1109    'page callback' => 'views_bulk_operations_select',
1110    'access arguments' => array('access content'),
1111    'type' => MENU_CALLBACK,
1112  );
1113  return $items;
1114}
1115
1116/**
1117 * AJAX callback to update selection.
1118 */
1119function views_bulk_operations_select() {
1120  $view_id = $_REQUEST['view_id'];
1121  $view_name = $_REQUEST['view_name'];
1122  foreach (json_decode($_REQUEST['selection'], TRUE) as $selection => $value) {
1123    switch ($selection) {
1124    case 'operation':
1125      $_SESSION['vbo_values'][$view_name][$view_id]['operation'] = $value;
1126      break;
1127    case 'selectall':
1128      $_SESSION['vbo_values'][$view_name][$view_id]['selectall'] = $value > 0;
1129      if ($value == -1) { // -1 => reset selection
1130        $_SESSION['vbo_values'][$view_name][$view_id]['selection'] = array();
1131      }
1132      break;
1133    default:
1134      $_SESSION['vbo_values'][$view_name][$view_id]['selection'][$selection] = $value > 0;
1135      break;
1136    }
1137  }
1138  drupal_json(array(
1139    'selected'   => count(array_filter($_SESSION['vbo_values'][$view_name][$view_id]['selection'])),
1140    'unselected' => count(array_filter($_SESSION['vbo_values'][$view_name][$view_id]['selection'], '_views_bulk_operations_filter_invert')),
1141    'selectall'  => $_SESSION['vbo_values'][$view_name][$view_id]['selectall'],
1142  ));
1143  exit;
1144}
1145
1146/**
1147 * Form function for views_bulk_operations_action action.
1148 */
1149function views_bulk_operations_action_form($context) {
1150  // Some views choke on being rebuilt at this moment because of validation errors in the action form.
1151  // So we save the error state, reset it, build the views, then reinstate the errors.
1152  // Also unset the error messages because they'll be displayed again after the loop.
1153  $errors = form_get_errors();
1154  if (!empty($errors)) foreach ($errors as $message) {
1155    unset($_SESSION['messages']['error'][array_search($message, $_SESSION['messages']['error'])]);
1156  }
1157  form_set_error(NULL, '', TRUE);
1158
1159  // Look for all views with VBO styles, and for each find the operations they use.
1160  // Distinguish between overridden and default views to simplify export.
1161  $views[0] = t('- Choose a view -');
1162  $operations[0] = t('- Choose an operation -');
1163  foreach (views_get_all_views() as $name => $view) {
1164    foreach (array_keys($view->display) as $display) {
1165      $display_options = &$view->display[$display]->display_options;
1166      if (isset($display_options['style_plugin']) && $display_options['style_plugin'] == 'bulk') {
1167        $vid = $view->name;
1168        $views[$vid] = $view->name . (!empty($view->description) ? ': ' . $view->description : '');
1169
1170        $view_clone = clone($view);
1171        $style_plugin = views_get_plugin('style', $display_options['style_plugin']);
1172        $style_plugin->init($view_clone, $view_clone->display[$display], $display_options['style_options']);
1173        if (isset($context['view_vid']) && $vid == $context['view_vid']) {
1174          $form['#plugin'] = $style_plugin;
1175        }
1176        unset($view_clone);
1177
1178        if (!empty($display_options['style_options']['operations'])) foreach ($display_options['style_options']['operations'] as $key => $options) {
1179          if (empty($options['selected'])) continue;
1180          $operations[$key] = $views_operations[$vid][$key] = $style_plugin->all_operations[$key]['label'];
1181          if (isset($context['operation_key']) && isset($context['view_vid']) && $key == $context['operation_key'] && $vid == $context['view_vid']) {
1182            $form['#operation'] = $style_plugin->get_operation_info($key);
1183          }
1184        }
1185      }
1186    }
1187  }
1188
1189  if (!empty($errors)) foreach ($errors as $name => $message) {
1190    form_set_error($name, $message);
1191  }
1192
1193  drupal_add_js(array('vbo' => array('action' => array('views_operations' => $views_operations))), 'setting');
1194  drupal_add_js(drupal_get_path('module', 'views_bulk_operations') . '/js/views_bulk_operations.action.js');
1195
1196  $form['view_vid'] = array(
1197    '#type' => 'select',
1198    '#title' => t('View'),
1199    '#description' => t('Select the VBO to be executed.'),
1200    '#options' => $views,
1201    '#default_value' => @$context['view_vid'],
1202    '#attributes' => array('onchange' => 'Drupal.vbo.action.updateOperations(this.options[this.selectedIndex].value, true);'),
1203  );
1204  $form['operation_key'] = array(
1205    '#type' => 'select',
1206    '#title' => t('Operation'),
1207    '#description' => t('Select the operation to be executed.'),
1208    '#options' => $operations,
1209    '#default_value' => @$context['operation_key'],
1210    '#ahah' => array(
1211      'path' => 'views-bulk-operations/js/action',
1212      'wrapper' => 'operation-wrapper',
1213      'method' => 'replace',
1214    ),
1215  );
1216  $form['operation_arguments'] = array(
1217    '#type' => 'fieldset',
1218    '#title' => t('Operation arguments'),
1219    '#description' => t('If the selected action is configurable, this section will show the action\'s arguments form,
1220                         followed by a text field where a PHP script can be entered to programmatically assemble the arguments.
1221                        '),
1222  );
1223  $form['operation_arguments']['wrapper'] = array(
1224    '#type' => 'markup',
1225    '#value' => '',
1226    '#prefix' => '<div id="operation-wrapper">',
1227    '#suffix' => '</div>',
1228  );
1229  if (isset($form['#operation']) && $form['#operation']['configurable'] && isset($form['#plugin'])) {
1230    $form['operation_arguments']['wrapper']['operation_form'] = _views_bulk_operations_action_form(
1231      $form['#operation'],
1232      $form['#plugin']->view,
1233      NULL,
1234      $form['#operation']['options']['settings'],
1235      $context
1236    );
1237    if (!empty($form['#operation']['form properties'])) foreach ($form['#operation']['form properties'] as $property) {
1238      if (isset($form['operation_arguments']['wrapper']['operation_form'][$property])) {
1239        $form[$property] = $form['operation_arguments']['wrapper']['operation_form'][$property];
1240      }
1241    }
1242    $form['operation_arguments']['wrapper']['operation_arguments'] = array(
1243      '#type' => 'textarea',
1244      '#title' => t('Operation arguments'),
1245      '#description' => t('Enter PHP script that will assemble the operation arguments (and will override the arguments above).
1246                           These arguments should be of the form: <code>return array(\'argument1\' => \'value1\', ...);</code>
1247                           and they should correspond to the values returned by the action\'s form submit function.
1248                           The variables <code>&$object</code> and <code>$context</code> are available to this script.
1249                          '),
1250      '#default_value' => @$context['operation_arguments'],
1251    );
1252  }
1253  else {
1254    $form['operation_arguments']['wrapper']['operation_form'] = array(
1255      '#type' => 'markup',
1256      '#value' => t('This operation is not configurable.'),
1257    );
1258    $form['operation_arguments']['wrapper']['operation_arguments'] = array('#type' => 'value', '#value' => '');
1259  }
1260  $form['view_exposed_input'] = array(
1261    '#type' => 'textarea',
1262    '#title' => t('View exposed input'),
1263    '#description' => t('Enter PHP script that will assemble the view exposed input (if the view accepts exposed input).
1264                         These inputs should be of the form: <code>return array(\'input1\' => \'value1\', ...);</code>
1265                         and they should correspond to the query values used on the view URL when exposed filters are applied.
1266                         The variables <code>&$object</code> and <code>$context</code> are available to this script.
1267                        '),
1268    '#default_value' => @$context['view_exposed_input'],
1269  );
1270  $form['view_arguments'] = array(
1271    '#type' => 'textarea',
1272    '#title' => t('View arguments'),
1273    '#description' => t('Enter PHP script that will assemble the view arguments (if the view accepts arguments).
1274                         These arguments should be of the form: <code>return array(\'value1\', ...);</code>
1275                         and they should correspond to the arguments defined in the view.
1276                         The variables <code>&$object</code> and <code>$context</code> are available to this script.
1277                        '),
1278    '#default_value' => @$context['view_arguments'],
1279  );
1280  $form['respect_limit'] = array(
1281    '#type' => 'checkbox',
1282    '#title' => t('Respect the view\'s item limit'),
1283    '#default_value' => @$context['respect_limit'],
1284  );
1285  return $form;
1286}
1287
1288/**
1289 * Generic AHAH callback to manipulate a form.
1290 */
1291function views_bulk_operations_form_ahah($callback) {
1292  $form_state = array('submitted' => FALSE);
1293  $form_build_id = $_POST['form_build_id'];
1294  // Add the new element to the stored form. Without adding the element to the
1295  // form, Drupal is not aware of this new elements existence and will not
1296  // process it. We retreive the cached form, add the element, and resave.
1297  $form = form_get_cache($form_build_id, $form_state);
1298
1299  // Invoke the callback that will populate the form.
1300  $render =& $callback($form, array('values' => $_POST));
1301
1302  form_set_cache($form_build_id, $form, $form_state);
1303  $form += array(
1304    '#post' => $_POST,
1305    '#programmed' => FALSE,
1306  );
1307  // Rebuild the form.
1308  $form = form_builder($_POST['form_id'], $form, $form_state);
1309
1310  // Render the new output.
1311  $output_html = drupal_render($render);
1312  $output_js = drupal_get_js();
1313  print drupal_to_js(array('data' => theme('status_messages') . $output_html . $output_js, 'status' => TRUE));
1314  exit();
1315}
1316
1317/**
1318 * Form callback to update an action form when a new action is selected in views_bulk_operations_action form.
1319 */
1320function& views_bulk_operations_action_form_operation(&$form, $form_state) {
1321  // TODO: Replace this with autoloading of style plugin and view definitions to use $form['#plugin'].
1322  $view = views_get_view($form_state['values']['view_vid']);
1323  $vd = new views_bulk_operations_destructor($view); // this will take care of calling $view->destroy() on exit.
1324  foreach (array_keys($view->display) as $display) {
1325    $display_options = &$view->display[$display]->display_options;
1326    if (isset($display_options['style_plugin']) && $display_options['style_plugin'] == 'bulk') {
1327      $plugin = views_get_plugin('style', $display_options['style_plugin']);
1328      $plugin->init($view, $view->display[$display], $display_options['style_options']);
1329      break;
1330    }
1331  }
1332  $form['#operation'] = $plugin->get_operation_info($form_state['values']['operation_key']);
1333  if ($form['#operation']['configurable']) {
1334    $form['operation_arguments']['wrapper'] = array(
1335      '#type' => 'markup',
1336      '#value' => '',
1337      '#prefix' => '<div id="operation-wrapper">',
1338      '#suffix' => '</div>',
1339    );
1340    $form['operation_arguments']['wrapper']['operation_form'] = _views_bulk_operations_action_form(
1341      $form['#operation'],
1342      $plugin->view,
1343      NULL,
1344      $form['#operation']['options']['settings']
1345    );
1346    if (!empty($form['#operation']['form properties'])) foreach ($form['#operation']['form properties'] as $property) {
1347      if (isset($form['operation_arguments']['wrapper']['operation_form'][$property])) {
1348        $form[$property] = $form['operation_arguments']['wrapper']['operation_form'][$property];
1349      }
1350    }
1351    $form['operation_arguments']['wrapper']['operation_arguments'] = array(
1352      '#type' => 'textarea',
1353      '#title' => t('Operation arguments'),
1354      '#description' => t('Enter PHP script that will assemble the operation arguments (and will override the operation form above).
1355                           These arguments should be of the form: <code>return array(\'argument1\' => \'value1\', ...);</code>
1356                           and they should correspond to the values returned by the action\'s form submit function.
1357                           The variables <code>&$object</code> and <code>$context</code> are available to this script.
1358                          '),
1359    );
1360  }
1361  else {
1362    $form['operation_arguments']['wrapper']['operation_form'] = array(
1363      '#type' => 'markup',
1364      '#value' => t('This operation is not configurable.'),
1365    );
1366    $form['operation_arguments']['wrapper']['operation_arguments'] = array('#type' => 'value', '#value' => '');
1367  }
1368  return $form['operation_arguments']['wrapper'];
1369}
1370
1371/**
1372 * Form validate function for views_bulk_operations_action action.
1373 */
1374function views_bulk_operations_action_validate($form, $form_state) {
1375  if (empty($form_state['values']['view_vid'])) {
1376    form_set_error('view_vid', t('You must choose a view to be executed.'));
1377  }
1378  if (empty($form_state['values']['operation_key'])) {
1379    form_set_error('operation_callback', t('You must choose an operation to be executed.'));
1380  }
1381  if ($form['#operation']) {
1382    module_invoke_all('action_info'); // some validate functions are created dynamically...
1383    _views_bulk_operations_action_validate($form['#operation'], $form, $form_state);
1384  }
1385}
1386
1387/**
1388 * Form submit function for views_bulk_operations_action action.
1389 */
1390function views_bulk_operations_action_submit($form, $form_state) {
1391  $submit = array(
1392    'view_vid' => $form_state['values']['view_vid'],
1393    'operation_key' => $form_state['values']['operation_key'],
1394    'operation_arguments' => $form_state['values']['operation_arguments'],
1395    'view_exposed_input' => $form_state['values']['view_exposed_input'],
1396    'view_arguments' => $form_state['values']['view_arguments'],
1397    'respect_limit' => $form_state['values']['respect_limit'],
1398  );
1399  if ($form['#operation'] && function_exists($form['#operation']['callback'] . '_submit')) {
1400    $submit = array_merge($submit, _views_bulk_operations_action_submit($form['#operation'], $form, $form_state));
1401  }
1402  return $submit;
1403}
1404
1405/**
1406 * Execution function for views_bulk_operations_action action.
1407 */
1408function views_bulk_operations_action(&$object, $context) {
1409  $view_exposed_input = array();
1410  if (!empty($context['view_exposed_input'])) {
1411    $view_exposed_input = eval($context['view_exposed_input']);
1412  }
1413  $view_arguments = array();
1414  if (!empty($context['view_arguments'])) {
1415    $view_arguments = eval($context['view_arguments']);
1416  }
1417  if (!empty($context['operation_arguments'])) {
1418    $operation_arguments = eval($context['operation_arguments']);
1419  }
1420  else {
1421    $operation_arguments = $context;
1422    foreach (array('operation_key', 'operation_arguments', 'views_vid', 'view_exposed_input', 'view_arguments') as $key) {
1423      unset($operation_arguments[$key]);
1424    }
1425  }
1426  views_bulk_operations_execute($context['view_vid'], $context['operation_key'], $operation_arguments, $view_exposed_input, $view_arguments, $context['respect_limit']);
1427}
1428
1429/**
1430 * Helper function to execute the chosen action upon selected objects.
1431 */
1432function _views_bulk_operations_execute($view, $objects, $operation, $operation_arguments, $options) {
1433  global $user;
1434
1435  // Get the object info we're dealing with.
1436  $object_info = _views_bulk_operations_object_info_for_view($view);
1437  if (!$object_info) return;
1438
1439  // Add action arguments.
1440  $params = array();
1441  if ($operation['configurable'] && is_array($operation_arguments)) {
1442    $params += $operation_arguments;
1443  }
1444  // Add static callback arguments. Note that in the case of actions, static arguments
1445  // are picked up from the database in actions_do().
1446  if (isset($operation['callback arguments'])) {
1447    $params += $operation['callback arguments'];
1448  }
1449  // Add this view as parameter.
1450  $params['view'] = array(
1451    'vid' => !empty($view->vid) ? $view->vid : $view->name,
1452    'exposed_input' => $view->get_exposed_input(),
1453    'arguments' => $view->args,
1454  );
1455  // Add static settings to the params.
1456  if (!empty($options['settings'])) {
1457    $params['settings'] = $options['settings'];
1458  }
1459  // Add object info to the params.
1460  $params['object_info'] = $object_info;
1461
1462  if ($operation['aggregate'] != VBO_AGGREGATE_FORCED && $options['execution_type'] == VBO_EXECUTION_BATCH) {
1463    // Save the options in the session because Batch API doesn't give a way to
1464    // send a parameter to the finished callback.
1465    $_SESSION['vbo_options']['display_result'] = $options['display_result'];
1466    $_SESSION['vbo_options']['operation'] = $operation;
1467    $_SESSION['vbo_options']['params'] = $params;
1468    $_SESSION['vbo_options']['object_info'] = $object_info;
1469
1470    $batch = array(
1471      'title' => t('Performing %operation on selected items...', array('%operation' => $operation['label'])),
1472      'finished' => '_views_bulk_operations_execute_finished',
1473    );
1474    // If they have max performance checked, use the high performant batch process.
1475    if ($options['max_performance']) {
1476      $batch += array(
1477        'operations' => array(
1478          array('_views_bulk_operations_execute_multiple', array($view->base_field, $operation, $objects, $params, $object_info, TRUE)),
1479        ),
1480      );
1481    }
1482    else {
1483      $operations = array();
1484      foreach ($objects as $num => $row) {
1485        $oid = $row->{$view->base_field};
1486        $operations[] = array('_views_bulk_operations_execute_single', array($oid, $row));
1487      }
1488      $batch += array(
1489        'operations' => $operations,
1490      );
1491    }
1492    batch_set($batch);
1493  }
1494  else if ($operation['aggregate'] != VBO_AGGREGATE_FORCED && module_exists('drupal_queue') && $options['execution_type'] == VBO_EXECUTION_QUEUE) {
1495    drupal_queue_include();
1496    foreach ($objects as $row) {
1497      $oid = $row->{$view->base_field};
1498      $job = array(
1499        'description' => t('Perform %operation on @type %oid.', array(
1500          '%operation' => $operation['label'],
1501          '@type' => t($object_info['type']),
1502          '%oid' => $oid
1503        )),
1504        'arguments' => array($oid, $row, $operation, $params, $user->uid, $options['display_result'], $object_info),
1505      );
1506      $queue = DrupalQueue::get('views_bulk_operations');
1507      $queue->createItem($job);
1508      $oids[] = $oid;
1509    }
1510    if ($options['display_result']) {
1511      drupal_set_message(t('Enqueued %operation on @types %oid.', array(
1512        '%operation' => $operation['label'],
1513        '@types' => format_plural(count($objects), $object_info['type'], $object_info['type'] . 's'),
1514        '%oid' => implode(', ', $oids),
1515      )));
1516    }
1517  }
1518  else /*if ($options['execution_type'] == VBO_EXECUTION_DIRECT)*/ {
1519    @set_time_limit(0);
1520
1521    $context['results']['rows'] = 0;
1522    $context['results']['time'] = microtime(TRUE);
1523
1524    _views_bulk_operations_execute_multiple($view->base_field, $operation, $objects, $params, $object_info, FALSE, $context);
1525    _views_bulk_operations_execute_finished(TRUE, $context['results'], array(), $options + array('operation' => $operation, 'params' => $params));
1526  }
1527}
1528
1529/**
1530 * Helper function to handle Drupal Queue operations.
1531 */
1532function _views_bulk_operations_execute_queue($data) {
1533  module_load_include('inc', 'node', 'node.admin');
1534
1535  list($oid, $row, $operation, $params, $uid, $display_result, $object_info)  = $data['arguments'];
1536  $object = call_user_func($object_info['load'], $oid);
1537  if (!$object) {
1538    watchdog('vbo', 'Skipped %operation on @type id %oid because it was not found.', array(
1539      '%operation' => $operation['label'],
1540      '@type' => t($operation['type']),
1541      '%oid' => $oid,
1542    ), WATCHDOG_ALERT);
1543    return;
1544  }
1545
1546  $account = user_load(array('uid' => $uid));
1547  if (!_views_bulk_operations_object_permission($operation, $object, $object_info, $account)) {
1548    watchdog('vbo', 'Skipped %operation on @type %title due to insufficient permissions.', array(
1549      '%operation' => $operation['label'],
1550      '@type' => t($object_info['type']),
1551      '%title' => $object->{$object_info['title']},
1552    ), WATCHDOG_ALERT);
1553    return;
1554  }
1555
1556  _views_bulk_operations_action_do($operation, $oid, $object, $row, $params, $object_info, $account);
1557
1558  if ($display_result) {
1559    watchdog('vbo', 'Performed %operation on @type %title.', array(
1560      '%operation' => $operation['label'],
1561      '@type' => t($object_info['type']),
1562      '%title' => $object->{$object_info['title']},
1563    ), WATCHDOG_INFO);
1564  }
1565}
1566
1567/**
1568 * Helper function to handle Batch API operations.
1569 */
1570function _views_bulk_operations_execute_single($oid, $row, &$context) {
1571  module_load_include('inc', 'node', 'node.admin');
1572
1573  $operation = $_SESSION['vbo_options']['operation'];
1574  $params = $_SESSION['vbo_options']['params'];
1575  $object_info = $_SESSION['vbo_options']['object_info'];
1576
1577  if (!isset($context['results']['time'])) {
1578    $context['results']['time'] = microtime(TRUE);
1579    $context['results']['rows'] = 0;
1580  }
1581
1582  $object = call_user_func($object_info['load'], $oid);
1583  if (!$object) {
1584    $context['results']['log'][] = t('Skipped %operation on @type id %oid because it was not found.', array(
1585      '%operation' => $operation['label'],
1586      '@type' => t($operation['type']),
1587      '%oid' => $oid,
1588    ));
1589    return;
1590  }
1591
1592  if (!_views_bulk_operations_object_permission($operation, $object, $object_info)) {
1593    $context['results']['log'][] = t('Skipped %operation on @type %title due to insufficient permissions.', array(
1594      '%operation' => $operation['label'],
1595      '@type' => t($object_info['type']),
1596      '%title' => $object->{$object_info['title']},
1597    ));
1598    return;
1599  }
1600
1601  _views_bulk_operations_action_do($operation, $oid, $object, $row, $params, $object_info);
1602
1603  $context['results']['log'][] = $context['message'] = t('Performed %operation on @type %title.', array(
1604    '%operation' => $operation['label'],
1605    '@type' => t($object_info['type']),
1606    '%title' => $object->{$object_info['title']},
1607  ));
1608
1609  $context['results']['rows'] += 1;
1610}
1611
1612/**
1613 * Gets the next item in the loop taking into consideration server limits for high performance batching.
1614 *
1615 * @return - The next object in the objects array.
1616 */
1617function _views_bulk_operations_execute_next($index, $objects, $batch) {
1618  static $loop = 0, $last_mem = -1, $last_time = 0, $memory_limit = 0, $time_limit = 0;
1619
1620  // Early return if we're done.
1621  if ($index >= count($objects)) {
1622    return FALSE;
1623  }
1624
1625  // Get the array keys.
1626  $keys = array_keys($objects);
1627
1628  if ($batch) {
1629    // Keep track of how many loops we have taken.
1630    $loop++;
1631
1632    // Memory limit in bytes.
1633    $memory_limit = $memory_limit ? $memory_limit : ((int)preg_replace('/[^\d\s]/', '', ini_get('memory_limit'))) * 1048576;
1634
1635    // Max time execution limit.
1636    $time_limit = $time_limit ? $time_limit : (int)ini_get('max_execution_time');
1637
1638    // Current execution time in seconds.
1639    $current_time = time() - $_SERVER['REQUEST_TIME'];
1640    $time_left = $time_limit - $current_time;
1641
1642    if ($loop == 1) {
1643      $last_time = $current_time;
1644      // Never break the first loop.
1645      return $objects[$keys[$index]];
1646    }
1647
1648    // Break when current free memory past threshold.  Default to 32 MB.
1649    if (($memory_limit - memory_get_usage()) < variable_get('batch_free_memory_threshold', 33554432)) {
1650      return FALSE;
1651    }
1652
1653    // Break when peak free memory past threshold.  Default to 8 MB.
1654    if (($memory_limit - memory_get_peak_usage()) < variable_get('batch_peak_free_memory_threshold', 8388608)) {
1655      return $objects[$keys[$index]];
1656    }
1657
1658    // Break when execution time remaining past threshold.  Default to 15 sec.
1659    if (($time_limit - $current_time) < variable_get('batch_time_remaining_threshold', 15)) {
1660      return FALSE;
1661    }
1662
1663    $last_time = $current_time;
1664    return $objects[$keys[$index]];
1665  }
1666  else {
1667    return $objects[$keys[$index]];
1668  }
1669}
1670
1671/**
1672 * Helper function for multiple execution operations.
1673 */
1674function _views_bulk_operations_execute_multiple($base_field, $operation, $objects, $params, $object_info, $batch, &$context) {
1675  // Setup our batch process.
1676  if (empty($context['sandbox'])) {
1677    $context['sandbox']['progress'] = 0;
1678    $context['sandbox']['max'] = count($objects);
1679  }
1680  if (empty($context['results']['time'])) {
1681    $context['results']['time'] = microtime(TRUE);
1682    $context['results']['rows'] = 0;
1683  }
1684
1685  if ($operation['aggregate'] != VBO_AGGREGATE_FORBIDDEN) {
1686    $oids = array();
1687    while ($row = _views_bulk_operations_execute_next($context['sandbox']['progress'], $objects, $batch)) {
1688      $context['sandbox']['progress']++;
1689      $oid = $row->{$base_field};
1690      if (isset($object_info['access'])) {
1691        $object = call_user_func($object_info['load'], $oid);
1692        if (!$object) {
1693          unset($objects[$num]);
1694          $context['results']['log'][] = t('Skipped %operation on @type %oid because it was not found.', array(
1695            '%operation' => $operation['label'],
1696            '@type' => t($operation['type']),
1697            '%oid' => $oid,
1698          ));
1699          continue;
1700        }
1701        if (!_views_bulk_operations_object_permission($operation, $object, $object_info)) {
1702          unset($objects[$num]);
1703          $context['results']['log'][] = t('Skipped %operation on @type %title due to insufficient permissions.', array(
1704            '%operation' => $operation['label'],
1705            '@type' => t($object_info['type']),
1706            '%title' => $object->{$object_info['title']},
1707          ));
1708          continue;
1709        }
1710      }
1711      $oids[] = $oid;
1712    }
1713
1714    if (!empty($objects)) {
1715      _views_bulk_operations_action_aggregate_do($operation, $oids, $objects, $params, $object_info);
1716
1717      $context['results']['log'][] = t('Performed aggregate %operation on @types %oids.', array(
1718        '%operation' => $operation['label'],
1719        '@types' => format_plural(count($objects), $object_info['type'], $object_info['type'] . 's'),
1720        '%oids' => implode(',', $oids),
1721      ));
1722      $context['message'] = t('Performed aggregate %operation on !count @types.', array(
1723        '%operation' => $operation['label'],
1724        '!count' => count($objects),
1725        '@types' => format_plural(count($objects), $object_info['type'], $object_info['type'] . 's'),
1726      ));
1727      $context['results']['rows'] += count($objects);
1728    }
1729  }
1730  else {
1731    $oids = array();
1732    while ($row = _views_bulk_operations_execute_next($context['sandbox']['progress'], $objects, $batch)) {
1733      $context['sandbox']['progress']++;
1734      $oid = $row->{$base_field};
1735      $object = call_user_func($object_info['load'], $oid);
1736      if (!$object) {
1737        $context['results']['log'][] = t('Skipped %operation on @type id %oid because it was not found.', array(
1738          '%operation' => $operation['label'],
1739          '@type' => t($operation['type']),
1740          '%oid' => $oid,
1741        ));
1742        continue;
1743      }
1744      if (!_views_bulk_operations_object_permission($operation, $object, $object_info)) {
1745        $context['results']['log'][] = t('Skipped %operation on @type %title due to insufficient permissions.', array(
1746          '%operation' => $operation['label'],
1747          '@type' => t($object_info['type']),
1748          '%title' => $object->{$object_info['title']},
1749        ));
1750        continue;
1751      }
1752
1753      _views_bulk_operations_action_do($operation, $oid, $object, $row, $params, $object_info);
1754
1755      $context['results']['log'][] = t('Performed %operation on @type %title.', array(
1756        '%operation' => $operation['label'],
1757        '@type' => t($object_info['type']),
1758        '%title' => $object->{$object_info['title']},
1759      ));
1760      $context['results']['rows'] += 1;
1761      $oids[] = $oid;
1762    }
1763
1764    $context['message'] = t('Performed %operation on !count @types.', array(
1765      '%operation' => $operation['label'],
1766      '!count' => count($oids),
1767      '@types' => format_plural(count($oids), $object_info['type'], $object_info['type'] . 's'),
1768    ));
1769  }
1770
1771  // Update batch progress.
1772  $context['finished'] = empty($context['sandbox']['max']) ? 1 : ($context['sandbox']['progress'] / $context['sandbox']['max']);
1773}
1774
1775/**
1776 * Helper function to cleanup operations.
1777 */
1778function _views_bulk_operations_execute_finished($success, $results, $operations, $options = NULL) {
1779  if ($success) {
1780    if ($results['rows'] > 0) {
1781      $message = t('!results items processed in about !time ms:', array('!results' => $results['rows'], '!time' => round((microtime(TRUE) - $results['time']) * 1000)));
1782    }
1783    else {
1784      $message = t('No items were processed:');
1785    }
1786    $message .= "\n". theme('item_list', $results['log']);
1787  }
1788  else {
1789    // An error occurred.
1790    // $operations contains the operations that remained unprocessed.
1791    $error_operation = reset($operations);
1792    $message = t('An error occurred while processing @operation with arguments: @arguments',
1793      array('@operation' => $error_operation[0], '@arguments' => print_r($error_operation[0], TRUE)));
1794  }
1795  if (empty($options)) {
1796    $options = $_SESSION['vbo_options'];
1797  }
1798
1799  // Inform other modules that VBO has finished executing.
1800  module_invoke_all('views_bulk_operations_finish', $options['operation'], $options['params'], array('results' => $results));
1801
1802  if (!empty($options['display_result'])) {
1803    drupal_set_message($message);
1804  }
1805  unset($_SESSION['vbo_options']); // unset the options which were used for just one invocation
1806}
1807
1808/**
1809 * Helper function to execute one operation.
1810 */
1811function _views_bulk_operations_action_do($operation, $oid, $object, $row, $params, $object_info, $account = NULL) {
1812  _views_bulk_operations_action_permission($operation, $account);
1813
1814  // Add the object to the context.
1815  if (!empty($object_info['context'])) {
1816    $params[$object_info['context']] = $object;
1817  }
1818  else {
1819    $params[$object_info['type']] = $object;
1820  }
1821  // If the operation type is different from the view type, normalize the context first.
1822  $actual_object = $object;
1823  if ($object_info['type'] != $operation['type']) {
1824    if (isset($object_info['normalize']) && function_exists($object_info['normalize'])) {
1825      $actual_object = call_user_func($object_info['normalize'], $operation['type'], $object);
1826    }
1827    $params['hook'] = $object_info['hook'];
1828  }
1829  if (is_null($actual_object)) { // Normalize function can return NULL: we don't want that
1830    $actual_object = $object;
1831  }
1832  $params['row'] = $row; // Expose the original view row to the action
1833
1834  if ($operation['source'] == 'action') {
1835    actions_do($operation['callback'], $actual_object, $params);
1836    if ($operation['type'] == 'node' && ($operation['access op'] & VBO_ACCESS_OP_UPDATE)) { // Save nodes explicitly if needed
1837      $node_options = variable_get('node_options_'. $actual_object->type, array('status', 'promote'));
1838      if (in_array('revision', $node_options) && !isset($actual_object->revision)) {
1839        $actual_object->revision = TRUE;
1840        $actual_object->log = '';
1841      }
1842      node_save($actual_object);
1843    }
1844  }
1845  else { // source == 'operation'
1846    $args = array_merge(array(array($oid)), $params);
1847    call_user_func_array($operation['callback'], $args);
1848  }
1849}
1850
1851/**
1852 * Helper function to execute an aggregate operation.
1853 */
1854function _views_bulk_operations_action_aggregate_do($operation, $oids, $objects, $params, $object_info) {
1855  _views_bulk_operations_action_permission($operation);
1856
1857  $params[$operation['type']] = $objects;
1858  if ($operation['source'] == 'action') {
1859    actions_do($operation['callback'], $oids, $params);
1860  }
1861  else {
1862    $args = array_merge(array($oids), $params);
1863    call_user_func_array($operation['callback'], $args);
1864  }
1865}
1866
1867/**
1868 * Helper function to verify access permission to execute operation.
1869 */
1870function _views_bulk_operations_action_permission($operation, $account = NULL) {
1871  if (module_exists('actions_permissions')) {
1872    $perm = actions_permissions_get_perm($operation['perm label'], $operation['callback']);
1873    if (!user_access($perm, $account)) {
1874      global $user;
1875      watchdog('vbo', 'An attempt by user %user to !perm was blocked due to insufficient permissions.', array(
1876        '!perm' => $perm,
1877        '%user' => isset($account) ? $account->name : $user->name
1878      ), WATCHDOG_ALERT);
1879      drupal_access_denied();
1880      exit();
1881    }
1882  }
1883
1884  // Check against additional permissions.
1885  if (!empty($operation['permissions'])) foreach ($operation['permissions'] as $perm) {
1886    if (!user_access($perm, $account)) {
1887      global $user;
1888      watchdog('vbo', 'An attempt by user %user to !perm was blocked due to insufficient permissions.', array(
1889        '!perm' => $perm,
1890        '%user' => isset($account) ? $account->name : $user->name
1891      ), WATCHDOG_ALERT);
1892      drupal_access_denied();
1893      exit();
1894    }
1895  }
1896}
1897
1898/**
1899 * Helper function to verify access permission to operate on object.
1900 */
1901function _views_bulk_operations_object_permission($operation, $object, $object_info, $account = NULL) {
1902  // Check against object access permissions.
1903  if (!isset($object_info['access'])) return TRUE;
1904
1905  $access_ops = array(
1906    VBO_ACCESS_OP_VIEW => 'view',
1907    VBO_ACCESS_OP_UPDATE => 'update',
1908    VBO_ACCESS_OP_CREATE => 'create',
1909    VBO_ACCESS_OP_DELETE => 'delete',
1910  );
1911  foreach ($access_ops as $bit => $op) {
1912    if ($operation['access op'] & $bit) {
1913      if (!call_user_func($object_info['access'], $op, $object, $account)) {
1914        return FALSE;
1915      }
1916    }
1917  }
1918
1919  return TRUE;
1920}
1921
1922/**
1923 * Helper function to let the configurable action provide its configuration form.
1924 */
1925function _views_bulk_operations_action_form($action, $view, $selection, $settings, $context = array()) {
1926  $action_form = $action['callback'] . '_form';
1927  $context = array_merge($context, array('view' => $view, 'selection' => $selection, 'settings' => $settings, 'object_info' => _views_bulk_operations_object_info_for_view($view)));
1928  if (isset($action['callback arguments'])) {
1929    $context = array_merge($context, $action['callback arguments']);
1930  }
1931
1932  $form = call_user_func($action_form, $context);
1933  return is_array($form) ? $form : array();
1934}
1935
1936/**
1937 * Helper function to let the configurable action validate the form if it provides a validator.
1938 */
1939function _views_bulk_operations_action_validate($action, $form, $form_values) {
1940  $action_validate = $action['callback'] . '_validate';
1941  if (function_exists($action_validate)) {
1942    call_user_func($action_validate, $form, $form_values);
1943  }
1944}
1945
1946/**
1947 * Helper function to let the configurable action process the configuration form.
1948 */
1949function _views_bulk_operations_action_submit($action, $form, &$form_state) {
1950  $action_submit = $action['callback'] . '_submit';
1951  return call_user_func($action_submit, $form, $form_state);
1952}
1953
1954/**
1955 * Helper function to return all object info.
1956 */
1957function _views_bulk_operations_get_object_info($reset = FALSE) {
1958  static $object_info = array();
1959  if ($reset || empty($object_info)) {
1960    $object_info = module_invoke_all('views_bulk_operations_object_info');
1961  }
1962  drupal_alter('views_bulk_operations_object_info', $object_info);
1963  return $object_info;
1964}
1965
1966/**
1967 * Helper function to return object info for a given view.
1968 */
1969function _views_bulk_operations_object_info_for_view($view) {
1970  foreach (_views_bulk_operations_get_object_info() as $object_info) {
1971    if ($object_info['base_table'] == $view->base_table) {
1972      return $object_info + array(
1973        'context' => '',
1974        'oid' => '',
1975        'access' => NULL,
1976        'hook' => '',
1977        'normalize' => NULL,
1978      );
1979    }
1980  }
1981  watchdog('vbo', 'Could not find object info for view table @table.', array('@table' => $view->base_table), WATCHDOG_ERROR);
1982  return NULL;
1983}
1984
1985/**
1986 * Helper to include all action files.
1987 */
1988function _views_bulk_operations_load_actions() {
1989  static $files = NULL;
1990  if (!empty($files)) {
1991    return $files;
1992  }
1993  $files = cache_get('views_bulk_operations_actions');
1994  if (empty($files) || empty($files->data)) {
1995    $files = array();
1996    foreach (file_scan_directory(drupal_get_path('module', 'views_bulk_operations') . '/actions', '\.action\.inc$') as $file) {
1997      list($files[],) = explode('.', $file->name);
1998    }
1999    cache_set('views_bulk_operations_actions', $files);
2000  }
2001  else {
2002    $files = $files->data;
2003  }
2004  foreach ($files as $file) {
2005    module_load_include('inc', 'views_bulk_operations', "actions/$file.action");
2006  }
2007  return $files;
2008}
2009
2010/**
2011 * Helper callback for array_walk().
2012 */
2013function _views_bulk_operations_get_oid($row, $base_field) {
2014  return $row->$base_field;
2015}
2016
2017/**
2018 * Helper callback for array_filter().
2019 */
2020function _views_bulk_operations_filter_invert($item) {
2021  return empty($item);
2022}
2023
2024/**
2025 * Helper to add needed JavaScript files to VBO.
2026 */
2027function _views_bulk_operations_add_js($plugin, $form_dom_id, $form_id) {
2028  static $views = NULL;
2029  if (!isset($views[$form_id])) {
2030    drupal_add_js(drupal_get_path('module', 'views_bulk_operations') . '/js/views_bulk_operations.js');
2031    drupal_add_js(drupal_get_path('module', 'views_bulk_operations') . '/js/json2.js');
2032    drupal_add_css(drupal_get_path('module', 'views_bulk_operations') . '/js/views_bulk_operations.css', 'module');
2033    drupal_add_js(array('vbo' => array($form_dom_id => array(
2034      'form_id' => $form_id,
2035      'view_name' => $plugin->view->name,
2036      'view_id' => _views_bulk_operations_view_id($plugin->view),
2037      'options' => $plugin->options,
2038      'ajax_select' => url('views-bulk-operations/js/select'),
2039      'view_path' => url($plugin->view->get_path()),
2040      'total_rows' => $plugin->view->total_rows,
2041    ))), 'setting');
2042    $views[$form_id] = TRUE;
2043  }
2044}
2045
2046/**
2047 * Implement hook_ajax_data_alter().
2048 */
2049function views_bulk_operations_ajax_data_alter(&$object, $type, $view) {
2050  if ($type == 'views' && $view->display_handler->get_option('style_plugin') == 'bulk') {
2051    $object->vbo = array(
2052      'view_id' => _views_bulk_operations_view_id($view),
2053      'form_id' => $view->style_plugin->form_id,
2054    );
2055    $object->__callbacks[] = 'Drupal.vbo.ajaxViewResponse';
2056  }
2057}
2058
2059/**
2060 * Helper function to calculate hash of an object.
2061 *
2062 * The default "hashing" is to use the object's primary/unique id. This would fail for VBOs that return many rows with
2063 * the same primary key (e.g. a *node* view returning all node *comments*).  Because we don't know in advance what kind of
2064 * hashing is needed, we allow for a module to implement its own hashing via
2065 *
2066 * hook_views_bulk_operations_object_hash_alter(&$hash, $object, $view).
2067 */
2068function _views_bulk_operations_hash_object($object, $view) {
2069  $hash = $object->{$view->base_field};
2070  drupal_alter('views_bulk_operations_object_hash', $hash, $object, $view);
2071  return $hash;
2072}
2073
2074/**
2075 * Helper function to strip of a view of all decorations.
2076 */
2077function _views_bulk_operations_strip_view($view) {
2078  if (isset($view->query->pager)) {
2079    $view->query->pager = NULL;
2080  }
2081  else {
2082    $view->set_use_pager(FALSE);
2083  }
2084  $view->exposed_widgets = NULL;
2085  $view->display_handler->set_option('header', '');
2086  $view->display_handler->set_option('footer', '');
2087  $view->display_handler->set_option('use_pager', FALSE);
2088  $view->attachment_before = '';
2089  $view->attachment_after = '';
2090  $view->feed_icon = NULL;
2091}
2092
2093/**
2094 * Helper function to get a unique ID for a view, taking arguments and exposed filters into consideration.
2095 */
2096function _views_bulk_operations_view_id($view) {
2097  // Normalize exposed input.
2098  $exposed_input = array();
2099  foreach ($view->filter as $filter) {
2100    if (!empty($filter->options['exposed']) && isset($view->exposed_input[ $filter->options['expose']['identifier'] ])) {
2101      $exposed_input[ $filter->options['expose']['identifier'] ] = $view->exposed_input[ $filter->options['expose']['identifier'] ];
2102    }
2103  }
2104  $exposed_input = array_filter($exposed_input);
2105  $view_id = md5(serialize(array($view->name, $view->args, $exposed_input)));
2106  return $view_id;
2107}
2108
2109/**
2110 * Helper function to identify VBO displays for a view.
2111 */
2112function _views_bulk_operations_displays($view) {
2113  $displays = array();
2114  foreach ($view->display as $display_id => $display) {
2115    if ($display->get_option('style_plugin') == 'bulk') {
2116      $displays[] = $display_id;
2117    }
2118  }
2119  return $displays;
2120}
2121
2122/**
2123 * Functor to destroy view on exit.
2124 */
2125class views_bulk_operations_destructor {
2126  function __construct($view) {
2127    $this->view = $view;
2128  }
2129  function __destruct() {
2130    $this->view->destroy();
2131  }
2132  private $view;
2133}
Nota: Vea TracBrowser para ayuda de uso del navegador del repositorio.