source: sipes/modules_contrib/views_bulk_operations/views_bulk_operations.module @ c43ea01

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

se actualizo el modulo

  • Propiedad mode establecida a 100755
File size: 78.2 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      'access' => '_views_bulk_operations_user_access',
946      'hook' => 'user',
947      'normalize' => '_views_bulk_operations_normalize_user_context',
948    ),
949    'comment' => array(
950      'type' => 'comment',
951      'base_table' => 'comments',
952      'load' => '_comment_load',
953      'oid' => 'cid',
954      'title' => 'subject',
955      'access' => '_views_bulk_operations_comment_access',
956      'hook' => 'comment',
957      'normalize' => '_views_bulk_operations_normalize_comment_context',
958    ),
959    'term' => array(
960      'type' => 'term',
961      'base_table' => 'term_data',
962      'load' => 'taxonomy_get_term',
963      'oid' => 'tid',
964      'title' => 'name',
965      'hook' => 'taxonomy',
966    ),
967    'node_revision' => array(
968      'type' => 'node_revision',
969      'base_table' => 'node_revisions',
970      'load' => '_views_bulk_operations_node_revision_load',
971      'title' => 'name',
972    ),
973    'file' => array(
974      'type' => 'file',
975      'base_table' => 'files',
976      'load' => '_views_bulk_operations_file_load',
977      'oid' => 'fid',
978      'title' => 'filename',
979    ),
980  );
981  return $object_info;
982}
983
984/**
985 * Access function for objects of type 'user'.
986 */
987function _views_bulk_operations_user_access($op, $user, $account = NULL) {
988  return user_access('access user profiles', $account);
989}
990
991/**
992 * Access function for objects of type 'comments'.
993 */
994function _views_bulk_operations_comment_access($op, $comment, $account = NULL) {
995  return user_access('access comments', $account);
996}
997
998/**
999 * Load function for objects of type 'node'.
1000 */
1001function _views_bulk_operations_node_load($nid) {
1002  return node_load($nid, NULL, TRUE);
1003}
1004
1005/**
1006 * Load function for objects of type 'file'.
1007 */
1008function _views_bulk_operations_file_load($fid) {
1009  return db_fetch_object(db_query("SELECT * FROM {files} WHERE fid = %d", $fid));
1010}
1011
1012/**
1013 * Load function for node revisions.
1014 */
1015function _views_bulk_operations_node_revision_load($vid) {
1016  $nid = db_result(db_query("SELECT nid FROM {node_revisions} WHERE vid = %d", $vid));
1017  return node_load($nid, $vid, TRUE);
1018}
1019
1020/**
1021 * Normalize function for node context.
1022 *
1023 * @see _trigger_normalize_node_context()
1024 */
1025function _views_bulk_operations_normalize_node_context($type, $node) {
1026  switch ($type) {
1027    // If an action that works on comments is being called in a node context,
1028    // the action is expecting a comment object. But we do not know which comment
1029    // to give it. The first? The most recent? All of them? So comment actions
1030    // in a node context are not supported.
1031
1032    // An action that works on users is being called in a node context.
1033    // Load the user object of the node's author.
1034    case 'user':
1035      return user_load(array('uid' => $node->uid));
1036  }
1037}
1038
1039/**
1040 * Normalize function for comment context.
1041 *
1042 * @see _trigger_normalize_comment_context()
1043 */
1044function _views_bulk_operations_normalize_comment_context($type, $comment) {
1045  switch ($type) {
1046    // An action that works with nodes is being called in a comment context.
1047    case 'node':
1048      return node_load(is_array($comment) ? $comment['nid'] : $comment->nid);
1049
1050    // An action that works on users is being called in a comment context.
1051    case 'user':
1052      return user_load(array('uid' => is_array($comment) ? $comment['uid'] : $comment->uid));
1053  }
1054}
1055
1056/**
1057 * Normalize function for user context.
1058 *
1059 * @see _trigger_normalize_user_context()
1060 */
1061function _views_bulk_operations_normalize_user_context($type, $account) {
1062  switch ($type) {
1063    // If an action that works on comments is being called in a user context,
1064    // the action is expecting a comment object. But we have no way of
1065    // determining the appropriate comment object to pass. So comment
1066    // actions in a user context are not supported.
1067
1068    // An action that works with nodes is being called in a user context.
1069    // If a single node is being viewed, return the node.
1070    case 'node':
1071      // If we are viewing an individual node, return the node.
1072      if ((arg(0) == 'node') && is_numeric(arg(1)) && (arg(2) == NULL)) {
1073        return node_load(array('nid' => arg(1)));
1074      }
1075  }
1076}
1077
1078/**
1079 * Implementation of hook_init().
1080 */
1081function views_bulk_operations_init() {
1082  // Make sure our actions are loaded.
1083  _views_bulk_operations_load_actions();
1084}
1085
1086/**
1087 * Implementation of hook_action_info().
1088 */
1089function views_bulk_operations_action_info() {
1090  $actions = array();
1091  foreach (_views_bulk_operations_load_actions() as $file) {
1092    $action_info_fn = 'views_bulk_operations_'. $file .'_action_info';
1093    $action_info = call_user_func($action_info_fn);
1094    if (is_array($action_info)) {
1095      $actions += $action_info;
1096    }
1097  }
1098
1099  // Add VBO's own programmatic action.
1100  $actions['views_bulk_operations_action'] = array(
1101    'description' => t('Execute a VBO programmatically'),
1102    'type' => 'system',
1103    'configurable' => TRUE,
1104    'rules_ignore' => TRUE,
1105  );
1106
1107  return $actions;
1108}
1109
1110/**
1111 * Implementation of hook_menu().
1112 */
1113function views_bulk_operations_menu() {
1114  $items['views-bulk-operations/js/action'] = array(
1115    'title' => 'VBO action form',
1116    'description' => 'AHAH callback to display action form on VBO action page.',
1117    'page callback' => 'views_bulk_operations_form_ahah',
1118    'page arguments' => array('views_bulk_operations_action_form_operation'),
1119    'access arguments' => array('access content'),
1120    'type' => MENU_CALLBACK,
1121  );
1122  $items['views-bulk-operations/js/select'] = array(
1123    'title' => 'VBO select handler',
1124    'description' => 'AJAX callback to update selection.',
1125    'page callback' => 'views_bulk_operations_select',
1126    'access arguments' => array('access content'),
1127    'type' => MENU_CALLBACK,
1128  );
1129  return $items;
1130}
1131
1132/**
1133 * AJAX callback to update selection.
1134 */
1135function views_bulk_operations_select() {
1136  $view_id = $_REQUEST['view_id'];
1137  $view_name = $_REQUEST['view_name'];
1138  foreach (json_decode($_REQUEST['selection'], TRUE) as $selection => $value) {
1139    switch ($selection) {
1140    case 'operation':
1141      $_SESSION['vbo_values'][$view_name][$view_id]['operation'] = $value;
1142      break;
1143    case 'selectall':
1144      $_SESSION['vbo_values'][$view_name][$view_id]['selectall'] = $value > 0;
1145      if ($value == -1) { // -1 => reset selection
1146        $_SESSION['vbo_values'][$view_name][$view_id]['selection'] = array();
1147      }
1148      break;
1149    default:
1150      $_SESSION['vbo_values'][$view_name][$view_id]['selection'][$selection] = $value > 0;
1151      break;
1152    }
1153  }
1154  drupal_json(array(
1155    'selected'   => count(array_filter($_SESSION['vbo_values'][$view_name][$view_id]['selection'])),
1156    'unselected' => count(array_filter($_SESSION['vbo_values'][$view_name][$view_id]['selection'], '_views_bulk_operations_filter_invert')),
1157    'selectall'  => $_SESSION['vbo_values'][$view_name][$view_id]['selectall'],
1158  ));
1159  exit;
1160}
1161
1162/**
1163 * Form function for views_bulk_operations_action action.
1164 */
1165function views_bulk_operations_action_form($context) {
1166  // Some views choke on being rebuilt at this moment because of validation errors in the action form.
1167  // So we save the error state, reset it, build the views, then reinstate the errors.
1168  // Also unset the error messages because they'll be displayed again after the loop.
1169  $errors = form_get_errors();
1170  if (!empty($errors)) foreach ($errors as $message) {
1171    unset($_SESSION['messages']['error'][array_search($message, $_SESSION['messages']['error'])]);
1172  }
1173  form_set_error(NULL, '', TRUE);
1174
1175  // Look for all views with VBO styles, and for each find the operations they use.
1176  // Distinguish between overridden and default views to simplify export.
1177  $views[0] = t('- Choose a view -');
1178  $operations[0] = t('- Choose an operation -');
1179  foreach (views_get_all_views() as $name => $view) {
1180    foreach (array_keys($view->display) as $display) {
1181      $display_options = &$view->display[$display]->display_options;
1182      if (isset($display_options['style_plugin']) && $display_options['style_plugin'] == 'bulk') {
1183        $vid = $view->name;
1184        $views[$vid] = $view->name . (!empty($view->description) ? ': ' . $view->description : '');
1185
1186        $view_clone = clone($view);
1187        $style_plugin = views_get_plugin('style', $display_options['style_plugin']);
1188        $style_plugin->init($view_clone, $view_clone->display[$display], $display_options['style_options']);
1189        if (isset($context['view_vid']) && $vid == $context['view_vid']) {
1190          $form['#plugin'] = $style_plugin;
1191        }
1192        unset($view_clone);
1193
1194        if (!empty($display_options['style_options']['operations'])) foreach ($display_options['style_options']['operations'] as $key => $options) {
1195          if (empty($options['selected'])) continue;
1196          $operations[$key] = $views_operations[$vid][$key] = $style_plugin->all_operations[$key]['label'];
1197          if (isset($context['operation_key']) && isset($context['view_vid']) && $key == $context['operation_key'] && $vid == $context['view_vid']) {
1198            $form['#operation'] = $style_plugin->get_operation_info($key);
1199          }
1200        }
1201      }
1202    }
1203  }
1204
1205  if (!empty($errors)) foreach ($errors as $name => $message) {
1206    form_set_error($name, $message);
1207  }
1208
1209  drupal_add_js(array('vbo' => array('action' => array('views_operations' => $views_operations))), 'setting');
1210  drupal_add_js(drupal_get_path('module', 'views_bulk_operations') . '/js/views_bulk_operations.action.js');
1211
1212  $form['view_vid'] = array(
1213    '#type' => 'select',
1214    '#title' => t('View'),
1215    '#description' => t('Select the VBO to be executed.'),
1216    '#options' => $views,
1217    '#default_value' => @$context['view_vid'],
1218    '#attributes' => array('onchange' => 'Drupal.vbo.action.updateOperations(this.options[this.selectedIndex].value, true);'),
1219  );
1220  $form['operation_key'] = array(
1221    '#type' => 'select',
1222    '#title' => t('Operation'),
1223    '#description' => t('Select the operation to be executed.'),
1224    '#options' => $operations,
1225    '#default_value' => @$context['operation_key'],
1226    '#ahah' => array(
1227      'path' => 'views-bulk-operations/js/action',
1228      'wrapper' => 'operation-wrapper',
1229      'method' => 'replace',
1230    ),
1231  );
1232  $form['operation_arguments'] = array(
1233    '#type' => 'fieldset',
1234    '#title' => t('Operation arguments'),
1235    '#description' => t('If the selected action is configurable, this section will show the action\'s arguments form,
1236                         followed by a text field where a PHP script can be entered to programmatically assemble the arguments.
1237                        '),
1238  );
1239  $form['operation_arguments']['wrapper'] = array(
1240    '#type' => 'markup',
1241    '#value' => '',
1242    '#prefix' => '<div id="operation-wrapper">',
1243    '#suffix' => '</div>',
1244  );
1245  if (isset($form['#operation']) && $form['#operation']['configurable'] && isset($form['#plugin'])) {
1246    $form['operation_arguments']['wrapper']['operation_form'] = _views_bulk_operations_action_form(
1247      $form['#operation'],
1248      $form['#plugin']->view,
1249      NULL,
1250      $form['#operation']['options']['settings'],
1251      $context
1252    );
1253    if (!empty($form['#operation']['form properties'])) foreach ($form['#operation']['form properties'] as $property) {
1254      if (isset($form['operation_arguments']['wrapper']['operation_form'][$property])) {
1255        $form[$property] = $form['operation_arguments']['wrapper']['operation_form'][$property];
1256      }
1257    }
1258    $form['operation_arguments']['wrapper']['operation_arguments'] = array(
1259      '#type' => 'textarea',
1260      '#title' => t('Operation arguments'),
1261      '#description' => t('Enter PHP script that will assemble the operation arguments (and will override the arguments above).
1262                           These arguments should be of the form: <code>return array(\'argument1\' => \'value1\', ...);</code>
1263                           and they should correspond to the values returned by the action\'s form submit function.
1264                           The variables <code>&$object</code> and <code>$context</code> are available to this script.
1265                          '),
1266      '#default_value' => @$context['operation_arguments'],
1267    );
1268  }
1269  else {
1270    $form['operation_arguments']['wrapper']['operation_form'] = array(
1271      '#type' => 'markup',
1272      '#value' => t('This operation is not configurable.'),
1273    );
1274    $form['operation_arguments']['wrapper']['operation_arguments'] = array('#type' => 'value', '#value' => '');
1275  }
1276  $form['view_exposed_input'] = array(
1277    '#type' => 'textarea',
1278    '#title' => t('View exposed input'),
1279    '#description' => t('Enter PHP script that will assemble the view exposed input (if the view accepts exposed input).
1280                         These inputs should be of the form: <code>return array(\'input1\' => \'value1\', ...);</code>
1281                         and they should correspond to the query values used on the view URL when exposed filters are applied.
1282                         The variables <code>&$object</code> and <code>$context</code> are available to this script.
1283                        '),
1284    '#default_value' => @$context['view_exposed_input'],
1285  );
1286  $form['view_arguments'] = array(
1287    '#type' => 'textarea',
1288    '#title' => t('View arguments'),
1289    '#description' => t('Enter PHP script that will assemble the view arguments (if the view accepts arguments).
1290                         These arguments should be of the form: <code>return array(\'value1\', ...);</code>
1291                         and they should correspond to the arguments defined in the view.
1292                         The variables <code>&$object</code> and <code>$context</code> are available to this script.
1293                        '),
1294    '#default_value' => @$context['view_arguments'],
1295  );
1296  $form['respect_limit'] = array(
1297    '#type' => 'checkbox',
1298    '#title' => t('Respect the view\'s item limit'),
1299    '#default_value' => @$context['respect_limit'],
1300  );
1301  return $form;
1302}
1303
1304/**
1305 * Generic AHAH callback to manipulate a form.
1306 */
1307function views_bulk_operations_form_ahah($callback) {
1308  $form_state = array('submitted' => FALSE);
1309  $form_build_id = $_POST['form_build_id'];
1310  // Add the new element to the stored form. Without adding the element to the
1311  // form, Drupal is not aware of this new elements existence and will not
1312  // process it. We retreive the cached form, add the element, and resave.
1313  $form = form_get_cache($form_build_id, $form_state);
1314
1315  // Invoke the callback that will populate the form.
1316  $render =& $callback($form, array('values' => $_POST));
1317
1318  form_set_cache($form_build_id, $form, $form_state);
1319  $form += array(
1320    '#post' => $_POST,
1321    '#programmed' => FALSE,
1322  );
1323  // Rebuild the form.
1324  $form = form_builder($_POST['form_id'], $form, $form_state);
1325
1326  // Render the new output.
1327  $output_html = drupal_render($render);
1328  $output_js = drupal_get_js();
1329  print drupal_to_js(array('data' => theme('status_messages') . $output_html . $output_js, 'status' => TRUE));
1330  exit();
1331}
1332
1333/**
1334 * Form callback to update an action form when a new action is selected in views_bulk_operations_action form.
1335 */
1336function& views_bulk_operations_action_form_operation(&$form, $form_state) {
1337  // TODO: Replace this with autoloading of style plugin and view definitions to use $form['#plugin'].
1338  $view = views_get_view($form_state['values']['view_vid']);
1339  $vd = new views_bulk_operations_destructor($view); // this will take care of calling $view->destroy() on exit.
1340  foreach (array_keys($view->display) as $display) {
1341    $display_options = &$view->display[$display]->display_options;
1342    if (isset($display_options['style_plugin']) && $display_options['style_plugin'] == 'bulk') {
1343      $plugin = views_get_plugin('style', $display_options['style_plugin']);
1344      $plugin->init($view, $view->display[$display], $display_options['style_options']);
1345      break;
1346    }
1347  }
1348  $form['#operation'] = $plugin->get_operation_info($form_state['values']['operation_key']);
1349  if ($form['#operation']['configurable']) {
1350    $form['operation_arguments']['wrapper'] = array(
1351      '#type' => 'markup',
1352      '#value' => '',
1353      '#prefix' => '<div id="operation-wrapper">',
1354      '#suffix' => '</div>',
1355    );
1356    $form['operation_arguments']['wrapper']['operation_form'] = _views_bulk_operations_action_form(
1357      $form['#operation'],
1358      $plugin->view,
1359      NULL,
1360      $form['#operation']['options']['settings']
1361    );
1362    if (!empty($form['#operation']['form properties'])) foreach ($form['#operation']['form properties'] as $property) {
1363      if (isset($form['operation_arguments']['wrapper']['operation_form'][$property])) {
1364        $form[$property] = $form['operation_arguments']['wrapper']['operation_form'][$property];
1365      }
1366    }
1367    $form['operation_arguments']['wrapper']['operation_arguments'] = array(
1368      '#type' => 'textarea',
1369      '#title' => t('Operation arguments'),
1370      '#description' => t('Enter PHP script that will assemble the operation arguments (and will override the operation form above).
1371                           These arguments should be of the form: <code>return array(\'argument1\' => \'value1\', ...);</code>
1372                           and they should correspond to the values returned by the action\'s form submit function.
1373                           The variables <code>&$object</code> and <code>$context</code> are available to this script.
1374                          '),
1375    );
1376  }
1377  else {
1378    $form['operation_arguments']['wrapper']['operation_form'] = array(
1379      '#type' => 'markup',
1380      '#value' => t('This operation is not configurable.'),
1381    );
1382    $form['operation_arguments']['wrapper']['operation_arguments'] = array('#type' => 'value', '#value' => '');
1383  }
1384  return $form['operation_arguments']['wrapper'];
1385}
1386
1387/**
1388 * Form validate function for views_bulk_operations_action action.
1389 */
1390function views_bulk_operations_action_validate($form, $form_state) {
1391  if (empty($form_state['values']['view_vid'])) {
1392    form_set_error('view_vid', t('You must choose a view to be executed.'));
1393  }
1394  if (empty($form_state['values']['operation_key'])) {
1395    form_set_error('operation_callback', t('You must choose an operation to be executed.'));
1396  }
1397  if ($form['#operation']) {
1398    module_invoke_all('action_info'); // some validate functions are created dynamically...
1399    _views_bulk_operations_action_validate($form['#operation'], $form, $form_state);
1400  }
1401}
1402
1403/**
1404 * Form submit function for views_bulk_operations_action action.
1405 */
1406function views_bulk_operations_action_submit($form, $form_state) {
1407  $submit = array(
1408    'view_vid' => $form_state['values']['view_vid'],
1409    'operation_key' => $form_state['values']['operation_key'],
1410    'operation_arguments' => $form_state['values']['operation_arguments'],
1411    'view_exposed_input' => $form_state['values']['view_exposed_input'],
1412    'view_arguments' => $form_state['values']['view_arguments'],
1413    'respect_limit' => $form_state['values']['respect_limit'],
1414  );
1415  if ($form['#operation'] && function_exists($form['#operation']['callback'] . '_submit')) {
1416    $submit = array_merge($submit, _views_bulk_operations_action_submit($form['#operation'], $form, $form_state));
1417  }
1418  return $submit;
1419}
1420
1421/**
1422 * Execution function for views_bulk_operations_action action.
1423 */
1424function views_bulk_operations_action(&$object, $context) {
1425  $view_exposed_input = array();
1426  if (!empty($context['view_exposed_input'])) {
1427    $view_exposed_input = eval($context['view_exposed_input']);
1428  }
1429  $view_arguments = array();
1430  if (!empty($context['view_arguments'])) {
1431    $view_arguments = eval($context['view_arguments']);
1432  }
1433  if (!empty($context['operation_arguments'])) {
1434    $operation_arguments = eval($context['operation_arguments']);
1435  }
1436  else {
1437    $operation_arguments = $context;
1438    foreach (array('operation_key', 'operation_arguments', 'views_vid', 'view_exposed_input', 'view_arguments') as $key) {
1439      unset($operation_arguments[$key]);
1440    }
1441  }
1442  views_bulk_operations_execute($context['view_vid'], $context['operation_key'], $operation_arguments, $view_exposed_input, $view_arguments, $context['respect_limit']);
1443}
1444
1445/**
1446 * Helper function to execute the chosen action upon selected objects.
1447 */
1448function _views_bulk_operations_execute($view, $objects, $operation, $operation_arguments, $options) {
1449  global $user;
1450
1451  // Get the object info we're dealing with.
1452  $object_info = _views_bulk_operations_object_info_for_view($view);
1453  if (!$object_info) return;
1454
1455  // Add action arguments.
1456  $params = array();
1457  if ($operation['configurable'] && is_array($operation_arguments)) {
1458    $params += $operation_arguments;
1459  }
1460  // Add static callback arguments. Note that in the case of actions, static arguments
1461  // are picked up from the database in actions_do().
1462  if (isset($operation['callback arguments'])) {
1463    $params += $operation['callback arguments'];
1464  }
1465  // Add this view as parameter.
1466  $params['view'] = array(
1467    'vid' => !empty($view->vid) ? $view->vid : $view->name,
1468    'exposed_input' => $view->get_exposed_input(),
1469    'arguments' => $view->args,
1470  );
1471  // Add static settings to the params.
1472  if (!empty($options['settings'])) {
1473    $params['settings'] = $options['settings'];
1474  }
1475  // Add object info to the params.
1476  $params['object_info'] = $object_info;
1477
1478  if ($operation['aggregate'] != VBO_AGGREGATE_FORCED && $options['execution_type'] == VBO_EXECUTION_BATCH) {
1479    // Save the options in the session because Batch API doesn't give a way to
1480    // send a parameter to the finished callback.
1481    $_SESSION['vbo_options']['display_result'] = $options['display_result'];
1482    $_SESSION['vbo_options']['operation'] = $operation;
1483    $_SESSION['vbo_options']['params'] = $params;
1484    $_SESSION['vbo_options']['object_info'] = $object_info;
1485
1486    $batch = array(
1487      'title' => t('Performing %operation on selected items...', array('%operation' => $operation['label'])),
1488      'finished' => '_views_bulk_operations_execute_finished',
1489    );
1490    // If they have max performance checked, use the high performant batch process.
1491    if ($options['max_performance']) {
1492      $batch += array(
1493        'operations' => array(
1494          array('_views_bulk_operations_execute_multiple', array($view->base_field, $operation, $objects, $params, $object_info, TRUE)),
1495        ),
1496      );
1497    }
1498    else {
1499      $operations = array();
1500      foreach ($objects as $num => $row) {
1501        $oid = $row->{$view->base_field};
1502        $operations[] = array('_views_bulk_operations_execute_single', array($oid, $row));
1503      }
1504      $batch += array(
1505        'operations' => $operations,
1506      );
1507    }
1508    batch_set($batch);
1509  }
1510  else if ($operation['aggregate'] != VBO_AGGREGATE_FORCED && module_exists('drupal_queue') && $options['execution_type'] == VBO_EXECUTION_QUEUE) {
1511    drupal_queue_include();
1512    foreach ($objects as $row) {
1513      $oid = $row->{$view->base_field};
1514      $job = array(
1515        'description' => t('Perform %operation on @type %oid.', array(
1516          '%operation' => $operation['label'],
1517          '@type' => t($object_info['type']),
1518          '%oid' => $oid
1519        )),
1520        'arguments' => array($oid, $row, $operation, $params, $user->uid, $options['display_result'], $object_info),
1521      );
1522      $queue = DrupalQueue::get('views_bulk_operations');
1523      $queue->createItem($job);
1524      $oids[] = $oid;
1525    }
1526    if ($options['display_result']) {
1527      drupal_set_message(t('Enqueued %operation on @types %oid.', array(
1528        '%operation' => $operation['label'],
1529        '@types' => format_plural(count($objects), $object_info['type'], $object_info['type'] . 's'),
1530        '%oid' => implode(', ', $oids),
1531      )));
1532    }
1533  }
1534  else /*if ($options['execution_type'] == VBO_EXECUTION_DIRECT)*/ {
1535    @set_time_limit(0);
1536
1537    $context['results']['rows'] = 0;
1538    $context['results']['time'] = microtime(TRUE);
1539
1540    _views_bulk_operations_execute_multiple($view->base_field, $operation, $objects, $params, $object_info, FALSE, $context);
1541    _views_bulk_operations_execute_finished(TRUE, $context['results'], array(), $options + array('operation' => $operation, 'params' => $params));
1542  }
1543}
1544
1545/**
1546 * Helper function to handle Drupal Queue operations.
1547 */
1548function _views_bulk_operations_execute_queue($data) {
1549  module_load_include('inc', 'node', 'node.admin');
1550
1551  list($oid, $row, $operation, $params, $uid, $display_result, $object_info)  = $data['arguments'];
1552  $object = call_user_func($object_info['load'], $oid);
1553  if (!$object) {
1554    watchdog('vbo', 'Skipped %operation on @type id %oid because it was not found.', array(
1555      '%operation' => $operation['label'],
1556      '@type' => t($operation['type']),
1557      '%oid' => $oid,
1558    ), WATCHDOG_ALERT);
1559    return;
1560  }
1561
1562  $account = user_load(array('uid' => $uid));
1563  if (!_views_bulk_operations_object_permission($operation, $object, $object_info, $account)) {
1564    watchdog('vbo', 'Skipped %operation on @type %title due to insufficient permissions.', array(
1565      '%operation' => $operation['label'],
1566      '@type' => t($object_info['type']),
1567      '%title' => $object->{$object_info['title']},
1568    ), WATCHDOG_ALERT);
1569    return;
1570  }
1571
1572  _views_bulk_operations_action_do($operation, $oid, $object, $row, $params, $object_info, $account);
1573
1574  if ($display_result) {
1575    watchdog('vbo', 'Performed %operation on @type %title.', array(
1576      '%operation' => $operation['label'],
1577      '@type' => t($object_info['type']),
1578      '%title' => $object->{$object_info['title']},
1579    ), WATCHDOG_INFO);
1580  }
1581}
1582
1583/**
1584 * Helper function to handle Batch API operations.
1585 */
1586function _views_bulk_operations_execute_single($oid, $row, &$context) {
1587  module_load_include('inc', 'node', 'node.admin');
1588
1589  $operation = $_SESSION['vbo_options']['operation'];
1590  $params = $_SESSION['vbo_options']['params'];
1591  $object_info = $_SESSION['vbo_options']['object_info'];
1592
1593  if (!isset($context['results']['time'])) {
1594    $context['results']['time'] = microtime(TRUE);
1595    $context['results']['rows'] = 0;
1596  }
1597
1598  $object = call_user_func($object_info['load'], $oid);
1599  if (!$object) {
1600    $context['results']['log'][] = t('Skipped %operation on @type id %oid because it was not found.', array(
1601      '%operation' => $operation['label'],
1602      '@type' => t($operation['type']),
1603      '%oid' => $oid,
1604    ));
1605    return;
1606  }
1607
1608  if (!_views_bulk_operations_object_permission($operation, $object, $object_info)) {
1609    $context['results']['log'][] = t('Skipped %operation on @type %title due to insufficient permissions.', array(
1610      '%operation' => $operation['label'],
1611      '@type' => t($object_info['type']),
1612      '%title' => $object->{$object_info['title']},
1613    ));
1614    return;
1615  }
1616
1617  _views_bulk_operations_action_do($operation, $oid, $object, $row, $params, $object_info);
1618
1619  $context['results']['log'][] = $context['message'] = t('Performed %operation on @type %title.', array(
1620    '%operation' => $operation['label'],
1621    '@type' => t($object_info['type']),
1622    '%title' => $object->{$object_info['title']},
1623  ));
1624
1625  $context['results']['rows'] += 1;
1626}
1627
1628/**
1629 * Gets the next item in the loop taking into consideration server limits for high performance batching.
1630 *
1631 * @return - The next object in the objects array.
1632 */
1633function _views_bulk_operations_execute_next($index, $objects, $batch) {
1634  static $loop = 0, $last_mem = -1, $last_time = 0, $memory_limit = 0, $time_limit = 0;
1635
1636  // Early return if we're done.
1637  if ($index >= count($objects)) {
1638    return FALSE;
1639  }
1640
1641  // Get the array keys.
1642  $keys = array_keys($objects);
1643
1644  if ($batch) {
1645    // Keep track of how many loops we have taken.
1646    $loop++;
1647
1648    // Memory limit in bytes.
1649    $memory_limit = $memory_limit ? $memory_limit : ((int)preg_replace('/[^\d\s]/', '', ini_get('memory_limit'))) * 1048576;
1650
1651    // Max time execution limit.
1652    $time_limit = $time_limit ? $time_limit : (int)ini_get('max_execution_time');
1653
1654    // Current execution time in seconds.
1655    $current_time = time() - $_SERVER['REQUEST_TIME'];
1656    $time_left = $time_limit - $current_time;
1657
1658    if ($loop == 1) {
1659      $last_time = $current_time;
1660      // Never break the first loop.
1661      return $objects[$keys[$index]];
1662    }
1663
1664    // Break when current free memory past threshold.  Default to 32 MB.
1665    if (($memory_limit - memory_get_usage()) < variable_get('batch_free_memory_threshold', 33554432)) {
1666      return FALSE;
1667    }
1668
1669    // Break when peak free memory past threshold.  Default to 8 MB.
1670    if (($memory_limit - memory_get_peak_usage()) < variable_get('batch_peak_free_memory_threshold', 8388608)) {
1671      return $objects[$keys[$index]];
1672    }
1673
1674    // Break when execution time remaining past threshold.  Default to 15 sec.
1675    if (($time_limit - $current_time) < variable_get('batch_time_remaining_threshold', 15)) {
1676      return FALSE;
1677    }
1678
1679    $last_time = $current_time;
1680    return $objects[$keys[$index]];
1681  }
1682  else {
1683    return $objects[$keys[$index]];
1684  }
1685}
1686
1687/**
1688 * Helper function for multiple execution operations.
1689 */
1690function _views_bulk_operations_execute_multiple($base_field, $operation, $objects, $params, $object_info, $batch, &$context) {
1691  // Setup our batch process.
1692  if (empty($context['sandbox'])) {
1693    $context['sandbox']['progress'] = 0;
1694    $context['sandbox']['max'] = count($objects);
1695  }
1696  if (empty($context['results']['time'])) {
1697    $context['results']['time'] = microtime(TRUE);
1698    $context['results']['rows'] = 0;
1699  }
1700
1701  if ($operation['aggregate'] != VBO_AGGREGATE_FORBIDDEN) {
1702    $oids = array();
1703    while ($row = _views_bulk_operations_execute_next($context['sandbox']['progress'], $objects, $batch)) {
1704      $context['sandbox']['progress']++;
1705      $oid = $row->{$base_field};
1706      if (isset($object_info['access'])) {
1707        $object = call_user_func($object_info['load'], $oid);
1708        if (!$object) {
1709          unset($objects[$num]);
1710          $context['results']['log'][] = t('Skipped %operation on @type %oid because it was not found.', array(
1711            '%operation' => $operation['label'],
1712            '@type' => t($operation['type']),
1713            '%oid' => $oid,
1714          ));
1715          continue;
1716        }
1717        if (!_views_bulk_operations_object_permission($operation, $object, $object_info)) {
1718          unset($objects[$num]);
1719          $context['results']['log'][] = t('Skipped %operation on @type %title due to insufficient permissions.', array(
1720            '%operation' => $operation['label'],
1721            '@type' => t($object_info['type']),
1722            '%title' => $object->{$object_info['title']},
1723          ));
1724          continue;
1725        }
1726      }
1727      $oids[] = $oid;
1728    }
1729
1730    if (!empty($objects)) {
1731      _views_bulk_operations_action_aggregate_do($operation, $oids, $objects, $params, $object_info);
1732
1733      $context['results']['log'][] = t('Performed aggregate %operation on @types %oids.', array(
1734        '%operation' => $operation['label'],
1735        '@types' => format_plural(count($objects), $object_info['type'], $object_info['type'] . 's'),
1736        '%oids' => implode(',', $oids),
1737      ));
1738      $context['message'] = t('Performed aggregate %operation on !count @types.', array(
1739        '%operation' => $operation['label'],
1740        '!count' => count($objects),
1741        '@types' => format_plural(count($objects), $object_info['type'], $object_info['type'] . 's'),
1742      ));
1743      $context['results']['rows'] += count($objects);
1744    }
1745  }
1746  else {
1747    $oids = array();
1748    while ($row = _views_bulk_operations_execute_next($context['sandbox']['progress'], $objects, $batch)) {
1749      $context['sandbox']['progress']++;
1750      $oid = $row->{$base_field};
1751      $object = call_user_func($object_info['load'], $oid);
1752      if (!$object) {
1753        $context['results']['log'][] = t('Skipped %operation on @type id %oid because it was not found.', array(
1754          '%operation' => $operation['label'],
1755          '@type' => t($operation['type']),
1756          '%oid' => $oid,
1757        ));
1758        continue;
1759      }
1760      if (!_views_bulk_operations_object_permission($operation, $object, $object_info)) {
1761        $context['results']['log'][] = t('Skipped %operation on @type %title due to insufficient permissions.', array(
1762          '%operation' => $operation['label'],
1763          '@type' => t($object_info['type']),
1764          '%title' => $object->{$object_info['title']},
1765        ));
1766        continue;
1767      }
1768
1769      _views_bulk_operations_action_do($operation, $oid, $object, $row, $params, $object_info);
1770
1771      $context['results']['log'][] = t('Performed %operation on @type %title.', array(
1772        '%operation' => $operation['label'],
1773        '@type' => t($object_info['type']),
1774        '%title' => $object->{$object_info['title']},
1775      ));
1776      $context['results']['rows'] += 1;
1777      $oids[] = $oid;
1778    }
1779
1780    $context['message'] = t('Performed %operation on !count @types.', array(
1781      '%operation' => $operation['label'],
1782      '!count' => count($oids),
1783      '@types' => format_plural(count($oids), $object_info['type'], $object_info['type'] . 's'),
1784    ));
1785  }
1786
1787  // Update batch progress.
1788  $context['finished'] = empty($context['sandbox']['max']) ? 1 : ($context['sandbox']['progress'] / $context['sandbox']['max']);
1789}
1790
1791/**
1792 * Helper function to cleanup operations.
1793 */
1794function _views_bulk_operations_execute_finished($success, $results, $operations, $options = NULL) {
1795  if ($success) {
1796    if ($results['rows'] > 0) {
1797      $message = t('!results items processed in about !time ms:', array('!results' => $results['rows'], '!time' => round((microtime(TRUE) - $results['time']) * 1000)));
1798    }
1799    else {
1800      $message = t('No items were processed:');
1801    }
1802    $message .= "\n". theme('item_list', $results['log']);
1803  }
1804  else {
1805    // An error occurred.
1806    // $operations contains the operations that remained unprocessed.
1807    $error_operation = reset($operations);
1808    $message = t('An error occurred while processing @operation with arguments: @arguments',
1809      array('@operation' => $error_operation[0], '@arguments' => print_r($error_operation[0], TRUE)));
1810  }
1811  if (empty($options)) {
1812    $options = $_SESSION['vbo_options'];
1813  }
1814
1815  // Inform other modules that VBO has finished executing.
1816  module_invoke_all('views_bulk_operations_finish', $options['operation'], $options['params'], array('results' => $results));
1817
1818  if (!empty($options['display_result'])) {
1819    drupal_set_message($message);
1820  }
1821  unset($_SESSION['vbo_options']); // unset the options which were used for just one invocation
1822}
1823
1824/**
1825 * Helper function to execute one operation.
1826 */
1827function _views_bulk_operations_action_do($operation, $oid, $object, $row, $params, $object_info, $account = NULL) {
1828  _views_bulk_operations_action_permission($operation, $account);
1829
1830  // Add the object to the context.
1831  if (!empty($object_info['context'])) {
1832    $params[$object_info['context']] = $object;
1833  }
1834  else {
1835    $params[$object_info['type']] = $object;
1836  }
1837  // If the operation type is different from the view type, normalize the context first.
1838  $actual_object = $object;
1839  if ($object_info['type'] != $operation['type']) {
1840    if (isset($object_info['normalize']) && function_exists($object_info['normalize'])) {
1841      $actual_object = call_user_func($object_info['normalize'], $operation['type'], $object);
1842    }
1843    $params['hook'] = $object_info['hook'];
1844  }
1845  if (is_null($actual_object)) { // Normalize function can return NULL: we don't want that
1846    $actual_object = $object;
1847  }
1848  $params['row'] = $row; // Expose the original view row to the action
1849
1850  if ($operation['source'] == 'action') {
1851    actions_do($operation['callback'], $actual_object, $params);
1852    if ($operation['type'] == 'node' && ($operation['access op'] & VBO_ACCESS_OP_UPDATE)) { // Save nodes explicitly if needed
1853      $node_options = variable_get('node_options_'. $actual_object->type, array('status', 'promote'));
1854      if (in_array('revision', $node_options) && !isset($actual_object->revision)) {
1855        $actual_object->revision = TRUE;
1856        $actual_object->log = '';
1857      }
1858      node_save($actual_object);
1859    }
1860  }
1861  else { // source == 'operation'
1862    $args = array_merge(array(array($oid)), $params);
1863    call_user_func_array($operation['callback'], $args);
1864  }
1865}
1866
1867/**
1868 * Helper function to execute an aggregate operation.
1869 */
1870function _views_bulk_operations_action_aggregate_do($operation, $oids, $objects, $params, $object_info) {
1871  _views_bulk_operations_action_permission($operation);
1872
1873  $params[$operation['type']] = $objects;
1874  if ($operation['source'] == 'action') {
1875    actions_do($operation['callback'], $oids, $params);
1876  }
1877  else {
1878    $args = array_merge(array($oids), $params);
1879    call_user_func_array($operation['callback'], $args);
1880  }
1881}
1882
1883/**
1884 * Helper function to verify access permission to execute operation.
1885 */
1886function _views_bulk_operations_action_permission($operation, $account = NULL) {
1887  if (module_exists('actions_permissions')) {
1888    $perm = actions_permissions_get_perm($operation['perm label'], $operation['callback']);
1889    if (!user_access($perm, $account)) {
1890      global $user;
1891      watchdog('vbo', 'An attempt by user %user to !perm was blocked due to insufficient permissions.', array(
1892        '!perm' => $perm,
1893        '%user' => isset($account) ? $account->name : $user->name
1894      ), WATCHDOG_ALERT);
1895      drupal_access_denied();
1896      exit();
1897    }
1898  }
1899
1900  // Check against additional permissions.
1901  if (!empty($operation['permissions'])) foreach ($operation['permissions'] as $perm) {
1902    if (!user_access($perm, $account)) {
1903      global $user;
1904      watchdog('vbo', 'An attempt by user %user to !perm was blocked due to insufficient permissions.', array(
1905        '!perm' => $perm,
1906        '%user' => isset($account) ? $account->name : $user->name
1907      ), WATCHDOG_ALERT);
1908      drupal_access_denied();
1909      exit();
1910    }
1911  }
1912}
1913
1914/**
1915 * Helper function to verify access permission to operate on object.
1916 */
1917function _views_bulk_operations_object_permission($operation, $object, $object_info, $account = NULL) {
1918  // Check against object access permissions.
1919  if (!isset($object_info['access'])) return TRUE;
1920
1921  $access_ops = array(
1922    VBO_ACCESS_OP_VIEW => 'view',
1923    VBO_ACCESS_OP_UPDATE => 'update',
1924    VBO_ACCESS_OP_CREATE => 'create',
1925    VBO_ACCESS_OP_DELETE => 'delete',
1926  );
1927  foreach ($access_ops as $bit => $op) {
1928    if ($operation['access op'] & $bit) {
1929      if (!call_user_func($object_info['access'], $op, $object, $account)) {
1930        return FALSE;
1931      }
1932    }
1933  }
1934
1935  return TRUE;
1936}
1937
1938/**
1939 * Helper function to let the configurable action provide its configuration form.
1940 */
1941function _views_bulk_operations_action_form($action, $view, $selection, $settings, $context = array()) {
1942  $action_form = $action['callback'] . '_form';
1943  $context = array_merge($context, array('view' => $view, 'selection' => $selection, 'settings' => $settings, 'object_info' => _views_bulk_operations_object_info_for_view($view)));
1944  if (isset($action['callback arguments'])) {
1945    $context = array_merge($context, $action['callback arguments']);
1946  }
1947
1948  $form = call_user_func($action_form, $context);
1949  return is_array($form) ? $form : array();
1950}
1951
1952/**
1953 * Helper function to let the configurable action validate the form if it provides a validator.
1954 */
1955function _views_bulk_operations_action_validate($action, $form, $form_values) {
1956  $action_validate = $action['callback'] . '_validate';
1957  if (function_exists($action_validate)) {
1958    call_user_func($action_validate, $form, $form_values);
1959  }
1960}
1961
1962/**
1963 * Helper function to let the configurable action process the configuration form.
1964 */
1965function _views_bulk_operations_action_submit($action, $form, &$form_state) {
1966  $action_submit = $action['callback'] . '_submit';
1967  return call_user_func($action_submit, $form, $form_state);
1968}
1969
1970/**
1971 * Helper function to return all object info.
1972 */
1973function _views_bulk_operations_get_object_info($reset = FALSE) {
1974  static $object_info = array();
1975  if ($reset || empty($object_info)) {
1976    $object_info = module_invoke_all('views_bulk_operations_object_info');
1977  }
1978  drupal_alter('views_bulk_operations_object_info', $object_info);
1979  return $object_info;
1980}
1981
1982/**
1983 * Helper function to return object info for a given view.
1984 */
1985function _views_bulk_operations_object_info_for_view($view) {
1986  foreach (_views_bulk_operations_get_object_info() as $object_info) {
1987    if ($object_info['base_table'] == $view->base_table) {
1988      return $object_info + array(
1989        'context' => '',
1990        'oid' => '',
1991        'access' => NULL,
1992        'hook' => '',
1993        'normalize' => NULL,
1994      );
1995    }
1996  }
1997  watchdog('vbo', 'Could not find object info for view table @table.', array('@table' => $view->base_table), WATCHDOG_ERROR);
1998  return NULL;
1999}
2000
2001/**
2002 * Helper to include all action files.
2003 */
2004function _views_bulk_operations_load_actions() {
2005  static $files = NULL;
2006  if (!empty($files)) {
2007    return $files;
2008  }
2009  $files = cache_get('views_bulk_operations_actions');
2010  if (empty($files) || empty($files->data)) {
2011    $files = array();
2012    foreach (file_scan_directory(drupal_get_path('module', 'views_bulk_operations') . '/actions', '\.action\.inc$') as $file) {
2013      list($files[],) = explode('.', $file->name);
2014    }
2015    cache_set('views_bulk_operations_actions', $files);
2016  }
2017  else {
2018    $files = $files->data;
2019  }
2020  foreach ($files as $file) {
2021    module_load_include('inc', 'views_bulk_operations', "actions/$file.action");
2022  }
2023  return $files;
2024}
2025
2026/**
2027 * Helper callback for array_walk().
2028 */
2029function _views_bulk_operations_get_oid($row, $base_field) {
2030  return $row->$base_field;
2031}
2032
2033/**
2034 * Helper callback for array_filter().
2035 */
2036function _views_bulk_operations_filter_invert($item) {
2037  return empty($item);
2038}
2039
2040/**
2041 * Helper to add needed JavaScript files to VBO.
2042 */
2043function _views_bulk_operations_add_js($plugin, $form_dom_id, $form_id) {
2044  static $views = NULL;
2045  if (!isset($views[$form_id])) {
2046    drupal_add_js(drupal_get_path('module', 'views_bulk_operations') . '/js/views_bulk_operations.js');
2047    drupal_add_js(drupal_get_path('module', 'views_bulk_operations') . '/js/json2.js');
2048    drupal_add_css(drupal_get_path('module', 'views_bulk_operations') . '/js/views_bulk_operations.css', 'module');
2049    drupal_add_js(array('vbo' => array($form_dom_id => array(
2050      'form_id' => $form_id,
2051      'view_name' => $plugin->view->name,
2052      'view_id' => _views_bulk_operations_view_id($plugin->view),
2053      'options' => $plugin->options,
2054      'ajax_select' => url('views-bulk-operations/js/select'),
2055      'view_path' => url($plugin->view->get_path()),
2056      'total_rows' => $plugin->view->total_rows,
2057    ))), 'setting');
2058    $views[$form_id] = TRUE;
2059  }
2060}
2061
2062/**
2063 * Implement hook_ajax_data_alter().
2064 */
2065function views_bulk_operations_ajax_data_alter(&$object, $type, $view) {
2066  if ($type == 'views' && $view->display_handler->get_option('style_plugin') == 'bulk') {
2067    $object->vbo = array(
2068      'view_id' => _views_bulk_operations_view_id($view),
2069      'form_id' => $view->style_plugin->form_id,
2070    );
2071    $object->__callbacks[] = 'Drupal.vbo.ajaxViewResponse';
2072  }
2073}
2074
2075/**
2076 * Helper function to calculate hash of an object.
2077 *
2078 * The default "hashing" is to use the object's primary/unique id. This would fail for VBOs that return many rows with
2079 * the same primary key (e.g. a *node* view returning all node *comments*).  Because we don't know in advance what kind of
2080 * hashing is needed, we allow for a module to implement its own hashing via
2081 *
2082 * hook_views_bulk_operations_object_hash_alter(&$hash, $object, $view).
2083 */
2084function _views_bulk_operations_hash_object($object, $view) {
2085  $hash = $object->{$view->base_field};
2086  drupal_alter('views_bulk_operations_object_hash', $hash, $object, $view);
2087  return $hash;
2088}
2089
2090/**
2091 * Helper function to strip of a view of all decorations.
2092 */
2093function _views_bulk_operations_strip_view($view) {
2094  if (isset($view->query->pager)) {
2095    $view->query->pager = NULL;
2096  }
2097  else {
2098    $view->set_use_pager(FALSE);
2099  }
2100  $view->exposed_widgets = NULL;
2101  $view->display_handler->set_option('header', '');
2102  $view->display_handler->set_option('footer', '');
2103  $view->display_handler->set_option('use_pager', FALSE);
2104  $view->attachment_before = '';
2105  $view->attachment_after = '';
2106  $view->feed_icon = NULL;
2107}
2108
2109/**
2110 * Helper function to get a unique ID for a view, taking arguments and exposed filters into consideration.
2111 */
2112function _views_bulk_operations_view_id($view) {
2113  // Normalize exposed input.
2114  $exposed_input = array();
2115  foreach ($view->filter as $filter) {
2116    if (!empty($filter->options['exposed']) && isset($view->exposed_input[ $filter->options['expose']['identifier'] ])) {
2117      $exposed_input[ $filter->options['expose']['identifier'] ] = $view->exposed_input[ $filter->options['expose']['identifier'] ];
2118    }
2119  }
2120  $exposed_input = array_filter($exposed_input);
2121  $view_id = md5(serialize(array($view->name, $view->args, $exposed_input)));
2122  return $view_id;
2123}
2124
2125/**
2126 * Helper function to identify VBO displays for a view.
2127 */
2128function _views_bulk_operations_displays($view) {
2129  $displays = array();
2130  foreach ($view->display as $display_id => $display) {
2131    if ($display->get_option('style_plugin') == 'bulk') {
2132      $displays[] = $display_id;
2133    }
2134  }
2135  return $displays;
2136}
2137
2138/**
2139 * Functor to destroy view on exit.
2140 */
2141class views_bulk_operations_destructor {
2142  function __construct($view) {
2143    $this->view = $view;
2144  }
2145  function __destruct() {
2146    $this->view->destroy();
2147  }
2148  private $view;
2149}
Nota: Vea TracBrowser para ayuda de uso del navegador del repositorio.