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 | */ |
---|
16 | function 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 | */ |
---|
80 | function 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. |
---|
168 | define('VBO_STEP_VIEW', 1); |
---|
169 | define('VBO_STEP_CONFIG', 2); |
---|
170 | define('VBO_STEP_CONFIRM', 3); |
---|
171 | define('VBO_STEP_SINGLE', 4); |
---|
172 | |
---|
173 | // Types of bulk execution. |
---|
174 | define('VBO_EXECUTION_DIRECT', 1); |
---|
175 | define('VBO_EXECUTION_BATCH', 2); |
---|
176 | define('VBO_EXECUTION_QUEUE', 3); |
---|
177 | |
---|
178 | // Types of aggregate actions. |
---|
179 | define('VBO_AGGREGATE_FORCED', 1); |
---|
180 | define('VBO_AGGREGATE_FORBIDDEN', 0); |
---|
181 | define('VBO_AGGREGATE_OPTIONAL', 2); |
---|
182 | |
---|
183 | // Access operations. |
---|
184 | define('VBO_ACCESS_OP_VIEW', 0x01); |
---|
185 | define('VBO_ACCESS_OP_UPDATE', 0x02); |
---|
186 | define('VBO_ACCESS_OP_CREATE', 0x04); |
---|
187 | define('VBO_ACCESS_OP_DELETE', 0x08); |
---|
188 | |
---|
189 | /** |
---|
190 | * Implementation of hook_cron_queue_info(). |
---|
191 | */ |
---|
192 | function 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 | */ |
---|
204 | function views_bulk_operations_views_api() { |
---|
205 | return array( |
---|
206 | 'api' => 2.0, |
---|
207 | ); |
---|
208 | } |
---|
209 | |
---|
210 | /** |
---|
211 | * Implementation of hook_elements(). |
---|
212 | */ |
---|
213 | function 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 | */ |
---|
227 | function 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 | */ |
---|
264 | function 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 | */ |
---|
295 | function 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 | */ |
---|
302 | function 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 | */ |
---|
353 | function 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 | */ |
---|
428 | function 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" /> |
---|
444 | EOF; |
---|
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 | */ |
---|
451 | function 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 | */ |
---|
647 | function 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 | */ |
---|
657 | function 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 | */ |
---|
714 | function 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 | */ |
---|
790 | function _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 | */ |
---|
808 | function _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 | */ |
---|
834 | function _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 | */ |
---|
851 | function 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 | */ |
---|
891 | function 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 | */ |
---|
926 | function 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 | */ |
---|
987 | function _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 | */ |
---|
994 | function _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 | */ |
---|
1001 | function _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 | */ |
---|
1008 | function _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 | */ |
---|
1015 | function _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 | */ |
---|
1025 | function _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 | */ |
---|
1044 | function _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 | */ |
---|
1061 | function _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 | */ |
---|
1081 | function 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 | */ |
---|
1089 | function 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 | */ |
---|
1113 | function 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 | */ |
---|
1135 | function 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 | */ |
---|
1165 | function 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 | */ |
---|
1307 | function 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 | */ |
---|
1336 | function& 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 | */ |
---|
1390 | function 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 | */ |
---|
1406 | function 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 | */ |
---|
1424 | function 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 | */ |
---|
1448 | function _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 | */ |
---|
1548 | function _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 | */ |
---|
1586 | function _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 | */ |
---|
1633 | function _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 | */ |
---|
1690 | function _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 | */ |
---|
1794 | function _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 | */ |
---|
1827 | function _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 | */ |
---|
1870 | function _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 | */ |
---|
1886 | function _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 | */ |
---|
1917 | function _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 | */ |
---|
1941 | function _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 | */ |
---|
1955 | function _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 | */ |
---|
1965 | function _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 | */ |
---|
1973 | function _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 | */ |
---|
1985 | function _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 | */ |
---|
2004 | function _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 | */ |
---|
2029 | function _views_bulk_operations_get_oid($row, $base_field) { |
---|
2030 | return $row->$base_field; |
---|
2031 | } |
---|
2032 | |
---|
2033 | /** |
---|
2034 | * Helper callback for array_filter(). |
---|
2035 | */ |
---|
2036 | function _views_bulk_operations_filter_invert($item) { |
---|
2037 | return empty($item); |
---|
2038 | } |
---|
2039 | |
---|
2040 | /** |
---|
2041 | * Helper to add needed JavaScript files to VBO. |
---|
2042 | */ |
---|
2043 | function _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 | */ |
---|
2065 | function 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 | */ |
---|
2084 | function _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 | */ |
---|
2093 | function _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 | */ |
---|
2112 | function _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 | */ |
---|
2128 | function _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 | */ |
---|
2141 | class 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 | } |
---|