| 12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298 | <?php/** * @file * Allows operations to be performed on items selected in a view. */// Access operations.define('VBO_ACCESS_OP_VIEW',      0x01);define('VBO_ACCESS_OP_UPDATE',    0x02);define('VBO_ACCESS_OP_CREATE',    0x04);define('VBO_ACCESS_OP_DELETE',    0x08);/** * Implements hook_action_info(). * Registers custom VBO actions as Drupal actions. */function views_bulk_operations_action_info() {  $actions = array();  $files = views_bulk_operations_load_action_includes();  foreach ($files as $filename) {    $action_info_fn = 'views_bulk_operations_'. str_replace('.', '_', basename($filename, '.inc')).'_info';    $action_info = call_user_func($action_info_fn);    if (is_array($action_info)) {      $actions += $action_info;    }  }  return $actions;}/** * Loads the VBO actions placed in their own include files (under actions/). * * @return *   An array of containing filenames of the included actions. */function views_bulk_operations_load_action_includes() {  static $loaded = FALSE;  // The list of VBO actions is fairly static, so it's hardcoded for better  // performance (hitting the filesystem with file_scan_directory(), and then  // caching the result has its cost).  $files = array(    'archive.action',    'argument_selector.action',    'book.action',    'delete.action',    'modify.action',    'script.action',    'user_roles.action',    'user_cancel.action',  );  if (!$loaded) {    foreach ($files as $file) {      module_load_include('inc', 'views_bulk_operations', 'actions/' . $file);    }    $loaded = TRUE;  }  return $files;}/** * Implements hook_cron(). * * Deletes queue items belonging to VBO active queues (used by VBO's batches) * that are older than a day (since they can only be a result of VBO crashing * or the execution being interrupted in some other way). This is the interval * used to cleanup batches in system_cron(), so it can't be increased. * * Note: This code is specific to SystemQueue. Other queue implementations will * need to do their own garbage collection. */function views_bulk_operations_cron() {  db_delete('queue')    ->condition('name', db_like('views_bulk_operations_active_queue_'), 'LIKE')    ->condition('created', REQUEST_TIME - 86400, '<')    ->execute();}/** * Implements of hook_cron_queue_info(). */function views_bulk_operations_cron_queue_info() {  return array(    'views_bulk_operations' => array(      'worker callback' => 'views_bulk_operations_queue_item_process',      'time' => 30,    ),  );}/** * Implements hook_views_api(). */function views_bulk_operations_views_api() {  return array(    'api' => 3,    'path' => drupal_get_path('module', 'views_bulk_operations') . '/views',  );}/** * Implements hook_theme(). */function views_bulk_operations_theme() {  $themes = array(    'views_bulk_operations_select_all' => array(      'variables' => array('view' => NULL, 'enable_select_all_pages' => TRUE),    ),    'views_bulk_operations_confirmation' => array(      'variables' => array('rows' => NULL, 'vbo' => NULL, 'operation' => NULL, 'select_all_pages' => FALSE),    ),  );  $files = views_bulk_operations_load_action_includes();  foreach ($files as $filename) {    $action_theme_fn = 'views_bulk_operations_'. str_replace('.', '_', basename($filename, '.inc')).'_theme';    if (function_exists($action_theme_fn)) {      $themes += call_user_func($action_theme_fn);    }  }  return $themes;}/** * Implements hook_ctools_plugin_type(). */function views_bulk_operations_ctools_plugin_type() {  return array(    'operation_types' => array(      'classes' => array(        'handler',      ),    ),  );}/** * Implements hook_ctools_plugin_directory(). */function views_bulk_operations_ctools_plugin_directory($module, $plugin) {  if ($module == 'views_bulk_operations') {    return 'plugins/' . $plugin;  }}/** * Fetch metadata for a specific operation type plugin. * * @param $operation_type *   Name of the plugin. * * @return *   An array with information about the requested operation type plugin. */function views_bulk_operations_get_operation_type($operation_type) {  ctools_include('plugins');  return ctools_get_plugins('views_bulk_operations', 'operation_types', $operation_type);}/** * Fetch metadata for all operation type plugins. * * @return *   An array of arrays with information about all available operation types. */function views_bulk_operations_get_operation_types() {  ctools_include('plugins');  return ctools_get_plugins('views_bulk_operations', 'operation_types');}/**  * Gets the info array of an operation from the provider plugin.  *  * @param $operation_id  *   The id of the operation for which the info shall be returned, or NULL  *   to return an array with info about all operations.  */function views_bulk_operations_get_operation_info($operation_id = NULL) {  $operations = &drupal_static(__FUNCTION__);  if (!isset($operations)) {    $operations = array();    $plugins = views_bulk_operations_get_operation_types();    foreach ($plugins as $plugin) {      $operations += $plugin['list callback']();    }    uasort($operations, create_function('$a, $b', 'return strcasecmp($a["label"], $b["label"]);'));  }  if (!empty($operation_id)) {    return $operations[$operation_id];  }  else {    return $operations;  }}/** * Returns an operation instance. * * @param $operation_id *   The id of the operation to instantiate. *   For example: action::node_publish_action. * @param $entity_type *   The entity type on which the operation operates. * @param $options *   Options for this operation (label, operation settings, etc.) */function views_bulk_operations_get_operation($operation_id, $entity_type, $options) {  $operations = &drupal_static(__FUNCTION__);  if (!isset($operations[$operation_id])) {    // Intentionally not using views_bulk_operations_get_operation_info() here    // since it's an expensive function that loads all the operations on the    // system, despite the fact that we might only need a few.    $id_fragments = explode('::', $operation_id);    $plugin = views_bulk_operations_get_operation_type($id_fragments[0]);    $operation_info = $plugin['list callback']($operation_id);    if ($operation_info) {      $operations[$operation_id] = new $plugin['handler']['class']($operation_id, $entity_type, $operation_info, $options);    }    else {      $operations[$operation_id] = FALSE;    }  }  return $operations[$operation_id];}/** * Get all operations that match the current entity type. * * @param $entity_type *   Entity type. * @param $options *   An array of options for all operations, in the form of *   $operation_id => $operation_options. */function views_bulk_operations_get_applicable_operations($entity_type, $options) {  $operations = array();  foreach (views_bulk_operations_get_operation_info() as $operation_id => $operation_info) {    if ($operation_info['type'] == $entity_type || $operation_info['type'] == 'entity' || $operation_info['type'] == 'system') {      $options[$operation_id] = !empty($options[$operation_id]) ? $options[$operation_id] : array();      $operations[$operation_id] = views_bulk_operations_get_operation($operation_id, $entity_type, $options[$operation_id]);    }  }  return $operations;}/** * Gets the VBO field if it exists on the passed-in view. * * @return *  The field object if found. Otherwise, FALSE. */function _views_bulk_operations_get_field($view) {  foreach ($view->field as $field_name => $field) {    if ($field instanceof views_bulk_operations_handler_field_operations) {      // Add in the view object for convenience.      $field->view = $view;      return $field;    }  }  return FALSE;}/** * Implements hook_views_form_substitutions(). */function views_bulk_operations_views_form_substitutions() {  // Views check_plains the column label, so VBO needs to do the same  // in order for the replace operation to succeed.  $select_all_placeholder = check_plain('<!--views-bulk-operations-select-all-->');  $select_all = array(    '#type' => 'checkbox',    '#default_value' => FALSE,    '#attributes' => array('class' => array('vbo-table-select-all')),  );  return array(    $select_all_placeholder => drupal_render($select_all),  );}/** * Implements hook_form_alter(). */function views_bulk_operations_form_alter(&$form, &$form_state, $form_id) {  if (strpos($form_id, 'views_form_') === 0) {    $vbo = _views_bulk_operations_get_field($form_state['build_info']['args'][0]);  }  // Not a VBO-enabled views form.  if (empty($vbo)) {    return;  }  // Add basic VBO functionality.  if ($form_state['step'] == 'views_form_views_form') {    // The submit button added by Views Form API might be used by a non-VBO Views    // Form handler. If there's no such handler on the view, hide the button.    $has_other_views_form_handlers = FALSE;    foreach ($vbo->view->field as $field) {      if (property_exists($field, 'views_form_callback') || method_exists($field, 'views_form')) {        if (!($field instanceof views_bulk_operations_handler_field_operations)) {          $has_other_views_form_handlers = TRUE;        }      }    }    if (!$has_other_views_form_handlers) {      $form['actions']['#access'] = FALSE;    }    // The VBO field is excluded from display, stop here.    if (!empty($vbo->options['exclude'])) {      return;    }    $form = views_bulk_operations_form($form, $form_state, $vbo);  }  // Cache the built form to prevent it from being rebuilt prior to validation  // and submission, which could lead to data being processed incorrectly,  // because the views rows (and thus, the form elements as well) have changed  // in the meantime. Matching views issue: http://drupal.org/node/1473276.  $form_state['cache'] = TRUE;  if (empty($vbo->view->override_url)) {    // If the VBO view is embedded using views_embed_view(), or in a block,    // $view->get_url() doesn't point to the current page, which means that    // the form doesn't get processed.    if (!empty($vbo->view->preview) || $vbo->view->display_handler instanceof views_plugin_display_block) {      $vbo->view->override_url = $_GET['q'];      // We are changing the override_url too late, the form action was already      // set by Views to the previous URL, so it needs to be overriden as well.      $query = drupal_get_query_parameters($_GET, array('q'));      $form['#action'] = url($_GET['q'], array('query' => $query));    }  }  // Give other modules a chance to alter the form.  drupal_alter('views_bulk_operations_form', $form, $form_state, $vbo);}/** * Implements hook_views_post_build(). * * Hides the VBO field if no operations are available. * This causes the entire VBO form to be hidden. * * @see views_bulk_operations_form_alter(). */function views_bulk_operations_views_post_build(&$view) {  $vbo = _views_bulk_operations_get_field($view);  if ($vbo && count($vbo->get_selected_operations()) < 1) {    $vbo->options['exclude'] = TRUE;  }}/** * Returns the 'select all' div that gets inserted below the table header row * (for table style plugins with grouping disabled), or above the view results * (for non-table style plugins), providing a choice between selecting items * on the current page, and on all pages. * * The actual insertion is done by JS, matching the degradation behavior * of Drupal core (no JS - no select all). */function theme_views_bulk_operations_select_all($variables) {  $view = $variables['view'];  $enable_select_all_pages = $variables['enable_select_all_pages'];  $form = array();  if ($view->style_plugin instanceof views_plugin_style_table && empty($view->style_plugin->options['grouping'])) {    if (!$enable_select_all_pages) {      return '';    }    $wrapper_class = 'vbo-table-select-all-markup';    $this_page_count = format_plural(count($view->result), '1 row', '@count rows');    $this_page = t('Selected <strong>!row_count</strong> in this page.', array('!row_count' => $this_page_count));    $all_pages_count = format_plural($view->total_rows, '1 row', '@count rows');    $all_pages = t('Selected <strong>!row_count</strong> in this view.', array('!row_count' => $all_pages_count));    $form['select_all_pages'] = array(      '#type' => 'button',      '#attributes' => array('class' => array('vbo-table-select-all-pages')),      '#value' => t('Select all !row_count in this view.', array('!row_count' => $all_pages_count)),      '#prefix' => '<span class="vbo-table-this-page">' . $this_page . '  ',      '#suffix' => '</span>',    );    $form['select_this_page'] = array(      '#type' => 'button',      '#attributes' => array('class' => array('vbo-table-select-this-page')),      '#value' => t('Select only !row_count in this page.', array('!row_count' => $this_page_count)),      '#prefix' => '<span class="vbo-table-all-pages" style="display: none">' . $all_pages . '  ',      '#suffix' => '</span>',    );  }  else {    $wrapper_class = 'vbo-select-all-markup';    $form['select_all'] = array(      '#type' => 'fieldset',      '#attributes' => array('class' => array('vbo-fieldset-select-all')),    );    $form['select_all']['this_page'] = array(      '#type' => 'checkbox',      '#title' => t('Select all items on this page'),      '#default_value' => '',      '#attributes' => array('class' => array('vbo-select-this-page')),    );    if ($enable_select_all_pages) {      $form['select_all']['or'] = array(        '#type' => 'markup',        '#markup' => '<em>' . t('OR') . '</em>',      );      $form['select_all']['all_pages'] = array(        '#type' => 'checkbox',        '#title' => t('Select all items on all pages'),        '#default_value' => '',        '#attributes' => array('class' => array('vbo-select-all-pages')),      );    }  }  $output = '<div class="' . $wrapper_class . '">';  $output .= drupal_render($form);  $output .= '</div>';  return $output;}/** * Extend the views_form multistep form with elements for executing an operation. */function views_bulk_operations_form($form, &$form_state, $vbo) {  $form['#attached']['js'][] = drupal_get_path('module', 'views_bulk_operations') . '/js/views_bulk_operations.js';  $form['#attached']['js'][] = array(    'data' => array('vbo' => array(      'row_clickable' => $vbo->get_vbo_option('row_clickable'),    )),    'type' => 'setting',  );  $form['#attached']['css'][] = drupal_get_path('module', 'views_bulk_operations') . '/css/views_bulk_operations.css';  // Wrap the form in a div with specific classes for JS targeting and theming.  $class = 'vbo-views-form';  if (empty($vbo->view->result)) {    $class .= ' vbo-views-form-empty';  }  $form['#prefix'] = '<div class="' . $class . '">';  $form['#suffix'] = '</div>';  // Force browser to reload the page if Back is hit.  if (!empty($_SERVER['HTTP_USER_AGENT']) && preg_match('/msie/i', $_SERVER['HTTP_USER_AGENT'])) {    drupal_add_http_header('Cache-Control', 'no-cache'); // works for IE6+  }  else {    drupal_add_http_header('Cache-Control', 'no-store'); // works for Firefox and other browsers  }  // Set by JS to indicate that all rows on all pages are selected.  $form['select_all'] = array(    '#type' => 'hidden',    '#attributes' => array('class' => 'select-all-rows'),    '#default_value' => FALSE,  );  $form['select'] = array(    '#type' => 'fieldset',    '#title' => t('Operations'),    '#collapsible' => FALSE,    '#attributes' => array('class' => array('container-inline')),  );  if ($vbo->get_vbo_option('display_type') == 0) {    $options = array(0 => t('- Choose an operation -'));    foreach ($vbo->get_selected_operations() as $operation_id => $operation) {      $options[$operation_id] = $operation->label();    }    // Create dropdown and submit button.    $form['select']['operation'] = array(      '#type' => 'select',      '#options' => $options,    );    $form['select']['submit'] = array(      '#type' => 'submit',      '#value' => t('Execute'),      '#validate' => array('views_bulk_operations_form_validate'),      '#submit' => array('views_bulk_operations_form_submit'),    );  }  else {    // Create buttons for operations.    foreach ($vbo->get_selected_operations() as $operation_id => $operation) {      $form['select'][$operation_id] = array(        '#type' => 'submit',        '#value' => $operation->label(),        '#validate' => array('views_bulk_operations_form_validate'),        '#submit' => array('views_bulk_operations_form_submit'),        '#operation_id' => $operation_id,      );    }  }  // Adds the "select all" functionality if the view has results.  // If the view is using a table style plugin, the markup gets moved to  // a table row below the header.  // If we are using radio buttons, we don't use select all at all.  if (!empty($vbo->view->result) && !$vbo->get_vbo_option('force_single')) {    $enable_select_all_pages = FALSE;    // If the view is paginated, and "select all items on all pages" is    // enabled, tell that to the theme function.    if (count($vbo->view->result) != $vbo->view->total_rows && $vbo->get_vbo_option('enable_select_all_pages')) {      $enable_select_all_pages = TRUE;    }    $form['select_all_markup'] = array(      '#type' => 'markup',      '#markup' => theme('views_bulk_operations_select_all', array('view' => $vbo->view, 'enable_select_all_pages' => $enable_select_all_pages)),    );  }  return $form;}/** * Validation callback for the first step of the VBO form. */function views_bulk_operations_form_validate($form, &$form_state) {  $vbo = _views_bulk_operations_get_field($form_state['build_info']['args'][0]);  if (!empty($form_state['triggering_element']['#operation_id'])) {    $form_state['values']['operation'] = $form_state['triggering_element']['#operation_id'];  }  if (!$form_state['values']['operation']) {    form_set_error('operation', t('No operation selected. Please select an operation to perform.'));  }  $field_name = $vbo->options['id'];  $selection = _views_bulk_operations_get_selection($vbo, $form_state);  if (!$selection) {    form_set_error($field_name, t('Please select at least one item.'));  }}/** * Multistep form callback for the "configure" step. */function views_bulk_operations_config_form($form, &$form_state, $view, $output) {  $vbo = _views_bulk_operations_get_field($view);  $operation = $form_state['operation'];  drupal_set_title(t('Set parameters for %operation', array('%operation' => $operation->label())), PASS_THROUGH);  $context = array(    'entity_type' => $vbo->get_entity_type(),    // Pass the View along.    // Has no performance penalty since objects are passed by reference,    // but needing the full views object in a core action is in most cases    // a sign of a wrong implementation. Do it only if you have to.    'view' => $view,  );  $form += $operation->form($form, $form_state, $context);  $query = drupal_get_query_parameters($_GET, array('q'));  $form['actions'] = array(    '#type' => 'container',    '#attributes' => array('class' => array('form-actions')),    '#weight' => 999,  );  $form['actions']['submit'] = array(    '#type' => 'submit',    '#value' => t('Next'),    '#validate' => array('views_bulk_operations_config_form_validate'),    '#submit' => array('views_bulk_operations_form_submit'),    '#suffix' => l(t('Cancel'), $vbo->view->get_url(), array('query' => $query)),  );  return $form;}/** * Validation callback for the "configure" step. * Gives the operation a chance to validate its config form. */function views_bulk_operations_config_form_validate($form, &$form_state) {  $operation = &$form_state['operation'];  $operation->formValidate($form, $form_state);}/** * Multistep form callback for the "confirm" step. */function views_bulk_operations_confirm_form($form, &$form_state, $view, $output) {  $vbo = _views_bulk_operations_get_field($view);  $operation = $form_state['operation'];  $rows = $form_state['selection'];  $query = drupal_get_query_parameters($_GET, array('q'));  $title = t('Are you sure you want to perform %operation on the selected items?', array('%operation' => $operation->label()));  $form = confirm_form($form,    $title,    array('path' => $view->get_url(), 'query' => $query),    theme('views_bulk_operations_confirmation', array('rows' => $rows, 'vbo' => $vbo, 'operation' => $operation, 'select_all_pages' => $form_state['select_all_pages']))  );  // Add VBO's submit handler to the Confirm button added by config_form().  $form['actions']['submit']['#submit'] = array('views_bulk_operations_form_submit');  // We can't set the View title here as $view is just a copy of the original,  // and our settings changes won't "stick" for the first page load of the  // confirmation form. We also can't just call drupal_set_title() directly  // because our title will be clobbered by the actual View title later. So  // let's tuck the title away in the form for use later.  // @see views_bulk_operations_preprocess_views_view()  $form['#vbo_confirm_form_title'] = $title;  return $form;}/** * Theme function to show the confirmation page before executing the operation. */function theme_views_bulk_operations_confirmation($variables) {  $select_all_pages = $variables['select_all_pages'];  $vbo = $variables['vbo'];  $entity_type = $vbo->get_entity_type();  $rows = $variables['rows'];  $items = array();  // Load the entities from the current page, and show their titles.  $entities = _views_bulk_operations_entity_load($entity_type, array_values($rows), $vbo->revision);  foreach ($entities as $entity) {    $items[] = check_plain(entity_label($entity_type, $entity));  }  // All rows on all pages have been selected, so show a count of additional items.  if ($select_all_pages) {    $more_count = $vbo->view->total_rows - count($vbo->view->result);    $items[] = t('...and <strong>!count</strong> more.', array('!count' => $more_count));  }  $count = format_plural(count($entities), 'item', '@count items');  $output = theme('item_list', array('items' => $items, 'title' => t('You selected the following <strong>!count</strong>:', array('!count' => $count))));  return $output;}/** * Implements hook_preprocess_page(). * * Hide action links on the configure and confirm pages. */function views_bulk_operations_preprocess_page(&$variables) {  if (isset($_POST['select_all'], $_POST['operation'])) {    $variables['action_links'] = array();  }}/** * Implements hook_preprocess_views_view(). */function views_bulk_operations_preprocess_views_view($variables) {  // If we've stored a title for the confirmation form, retrieve it here and  // retitle the View.  // @see views_bulk_operations_confirm_form()  if (array_key_exists('rows', $variables) && is_array($variables['rows']) && array_key_exists('#vbo_confirm_form_title', $variables['rows'])) {    $variables['view']->set_title($variables['rows']['#vbo_confirm_form_title']);  }}/** * Goes through the submitted values, and returns * an array of selected rows, in the form of * $row_index => $entity_id. */function _views_bulk_operations_get_selection($vbo, $form_state) {  $selection = array();  $field_name = $vbo->options['id'];  if (!empty($form_state['values'][$field_name])) {    // If using "force single", the selection needs to be converted to an array.    if (is_array($form_state['values'][$field_name])) {      $selection = array_filter($form_state['values'][$field_name]);    }    else {      $selection = array($form_state['values'][$field_name]);    }  }  return $selection;}/** * Submit handler for all steps of the VBO multistep form. */function views_bulk_operations_form_submit($form, &$form_state) {  $vbo = _views_bulk_operations_get_field($form_state['build_info']['args'][0]);  $entity_type = $vbo->get_entity_type();  switch ($form_state['step']) {    case 'views_form_views_form':      $form_state['selection'] = _views_bulk_operations_get_selection($vbo, $form_state);      $form_state['select_all_pages'] = $form_state['values']['select_all'];      $options = $vbo->get_operation_options($form_state['values']['operation']);      $form_state['operation'] = $operation = views_bulk_operations_get_operation($form_state['values']['operation'], $entity_type, $options);      if (!$operation->configurable() && $operation->getAdminOption('skip_confirmation')) {        break; // Go directly to execution      }      $form_state['step'] = $operation->configurable() ? 'views_bulk_operations_config_form' : 'views_bulk_operations_confirm_form';      $form_state['rebuild'] = TRUE;      return;    case 'views_bulk_operations_config_form':      $form_state['step'] = 'views_bulk_operations_confirm_form';      $operation = &$form_state['operation'];      $operation->formSubmit($form, $form_state);      if ($operation->getAdminOption('skip_confirmation')) {        break; // Go directly to execution      }      $form_state['rebuild'] = TRUE;      return;    case 'views_bulk_operations_confirm_form':      break;  }  // Execute the operation.  views_bulk_operations_execute($vbo, $form_state['operation'], $form_state['selection'], $form_state['select_all_pages']);  // Redirect.  $query = drupal_get_query_parameters($_GET, array('q'));  $form_state['redirect'] = array('path' => $vbo->view->get_url(), array('query' => $query));}/** * Entry point for executing the chosen operation upon selected rows. * * If the selected operation is an aggregate operation (requiring all selected * items to be passed at the same time), restricted to a single value, or has * the skip_batching option set, the operation is executed directly. * This means that there is no batching & queueing, the PHP execution * time limit is ignored (if allowed), all selected entities are loaded and * processed. * * Otherwise, the selected entity ids are divided into groups not larger than * $entity_load_capacity, and enqueued for processing. * If all items on all pages should be processed, a batch job runs that * collects and enqueues the items from all pages of the view, page by page. * * Based on the "Enqueue the operation instead of executing it directly" * VBO field setting, the newly filled queue is either processed at cron * time by the VBO worker function, or right away in a new batch job. * * @param $vbo *   The VBO field, containing a reference to the view in $vbo->view. * @param $operation *   The operation object. * @param $selection *   An array in the form of $row_index => $entity_id. * @param $select_all_pages *   Whether all items on all pages should be selected. */function views_bulk_operations_execute($vbo, $operation, $selection, $select_all_pages = FALSE) {  global $user;  // Determine if the operation needs to be executed directly.  $aggregate = $operation->aggregate();  $skip_batching = $vbo->get_vbo_option('skip_batching');  $force_single = $vbo->get_vbo_option('force_single');  $execute_directly = ($aggregate || $skip_batching || $force_single);  // Try to load all rows without a batch if needed.  if ($execute_directly && $select_all_pages) {    views_bulk_operations_direct_adjust($selection, $vbo);  }  // Options that affect execution.  $options = array(    'revision' => $vbo->revision,    'entity_load_capacity' => $vbo->get_vbo_option('entity_load_capacity', 10),    // The information needed to recreate the view, to avoid serializing the    // whole object. Passed to the executed operation. Also used by    // views_bulk_operations_adjust_selection().    'view_info' => array(      'name' => $vbo->view->name,      'display' => $vbo->view->current_display,      'arguments' => $vbo->view->args,      'exposed_input' => $vbo->view->get_exposed_input(),    ),  );  // Create an array of rows in the needed format.  $rows = array();  $current = 1;  foreach ($selection as $row_index => $entity_id) {    $rows[$row_index] = array(      'entity_id' => $entity_id,      'views_row' => array(),      // Some operations rely on knowing the position of the current item      // in the execution set (because of specific things that need to be done      // at the beginning or the end of the set).      'position' => array(        'current' => $current++,        'total' => count($selection),      ),    );    // Some operations require full selected rows.    if ($operation->needsRows()) {      $rows[$row_index]['views_row'] = $vbo->view->result[$row_index];    }  }  if ($execute_directly) {    // Execute the operation directly and stop here.    views_bulk_operations_direct_process($operation, $rows, $options);    return;  }  // Determine the correct queue to use.  if ($operation->getAdminOption('postpone_processing')) {    // Use the site queue processed on cron.    $queue_name = 'views_bulk_operations';  }  else {    // Use the active queue processed immediately by Batch API.    $queue_name = 'views_bulk_operations_active_queue_' . db_next_id();  }  $batch = array(    'operations' => array(),    'finished' => 'views_bulk_operations_execute_finished',    'progress_message' => '',    'title' => t('Performing %operation on the selected items...', array('%operation' => $operation->label())),  );  // All items on all pages should be selected, add a batch job to gather  // and enqueue them.  if ($select_all_pages && $vbo->view->query->pager->has_more_records()) {    $total_rows = $vbo->view->total_rows;    $batch['operations'][] = array(      'views_bulk_operations_adjust_selection', array($queue_name, $operation, $options),    );  }  else {    $total_rows = count($rows);    // We have all the items that we need, enqueue them right away.    views_bulk_operations_enqueue_rows($queue_name, $rows, $operation, $options);    // Provide a status message to the user, since this is the last step if    // processing is postponed.    if ($operation->getAdminOption('postpone_processing')) {      drupal_set_message(t('Enqueued the selected operation (%operation).', array(        '%operation' => $operation->label(),      )));    }  }  // Processing is not postponed, add a batch job to process the queue.  if (!$operation->getAdminOption('postpone_processing')) {    $batch['operations'][] = array(      'views_bulk_operations_active_queue_process', array($queue_name, $operation, $total_rows),    );  }  // If there are batch jobs to be processed, create the batch set.  if (count($batch['operations'])) {    batch_set($batch);  }}/** * Batch API callback: loads the view page by page and enqueues all items. * * @param $queue_name *   The name of the queue to which the items should be added. * @param $operation *   The operation object. * @param $options *   An array of options that affect execution (revision, entity_load_capacity, *   view_info). Passed along with each new queue item. */function views_bulk_operations_adjust_selection($queue_name, $operation, $options, &$context) {  if (!isset($context['sandbox']['progress'])) {    $context['sandbox']['progress'] = 0;    $context['sandbox']['max'] = 0;  }  $view_info = $options['view_info'];  $view = views_get_view($view_info['name']);  $view->set_exposed_input($view_info['exposed_input']);  $view->set_arguments($view_info['arguments']);  $view->set_display($view_info['display']);  $view->set_offset($context['sandbox']['progress']);  $view->build();  $view->execute($view_info['display']);  // Note the total number of rows.  if (empty($context['sandbox']['max'])) {    $context['sandbox']['max'] = $view->total_rows;  }  $vbo = _views_bulk_operations_get_field($view);  $rows = array();  foreach ($view->result as $row_index => $result) {    $rows[$row_index] = array(      'entity_id' => $vbo->get_value($result),      'views_row' => array(),      'position' => array(        'current' => ++$context['sandbox']['progress'],        'total' => $context['sandbox']['max'],      ),    );    // Some operations require full selected rows.    if ($operation->needsRows()) {      $rows[$row_index]['views_row'] = $result;    }  }  // Enqueue the gathered rows.  views_bulk_operations_enqueue_rows($queue_name, $rows, $operation, $options);  if ($context['sandbox']['progress'] != $context['sandbox']['max']) {    // Provide an estimation of the completion level we've reached.    $context['finished'] = $context['sandbox']['progress'] / $context['sandbox']['max'];    $context['message'] = t('Prepared @current out of @total', array('@current' => $context['sandbox']['progress'], '@total' => $context['sandbox']['max']));  }  else {    // Provide a status message to the user if this is the last batch job.    if ($operation->getAdminOption('postpone_processing')) {      $context['results']['log'][] = t('Enqueued the selected operation (%operation).', array(        '%operation' => $operation->label(),      ));    }  }}/** * Divides the passed rows into groups and enqueues each group for processing * * @param $queue_name *   The name of the queue. * @param $rows *   The rows to be enqueued. * @param $operation *   The object representing the current operation. *   Passed along with each new queue item. * @param $options *   An array of options that affect execution (revision, entity_load_capacity). *   Passed along with each new queue item. */function views_bulk_operations_enqueue_rows($queue_name, $rows, $operation, $options) {  global $user;  $queue = DrupalQueue::get($queue_name, TRUE);  $row_groups = array_chunk($rows, $options['entity_load_capacity'], TRUE);  foreach ($row_groups as $row_group) {    $entity_ids = array();    foreach ($row_group as $row) {      $entity_ids[] = $row['entity_id'];    }    $job = array(      'title' => t('Perform %operation on @type !entity_ids.', array(        '%operation' => $operation->label(),        '@type' => $operation->entityType,        '!entity_ids' => implode(',', $entity_ids),      )),      'uid' => $user->uid,      'arguments' => array($row_group, $operation, $options),    );    $queue->createItem($job);  }}/** * Batch API callback: processes the active queue. * * @param $queue_name *   The name of the queue to process. * @param $operation *   The object representing the current operation. * @param $total_rows *   The total number of processable items (across all queue items), used *   to report progress. * * @see views_bulk_operations_queue_item_process() */function views_bulk_operations_active_queue_process($queue_name, $operation, $total_rows, &$context) {  static $queue;  // It is still possible to hit the time limit.  drupal_set_time_limit(0);  // Prepare the sandbox.  if (!isset($context['sandbox']['progress'])) {    $context['sandbox']['progress'] = 0;    $context['sandbox']['max'] = $total_rows;    $context['results']['log'] = array();  }  // Instantiate the queue.  if (!isset($queue)) {    $queue = DrupalQueue::get($queue_name, TRUE);  }  // Process the queue as long as it has items for us.  $queue_item = $queue->claimItem(3600);  if ($queue_item) {    // Process the queue item, and update the progress count.    views_bulk_operations_queue_item_process($queue_item->data, $context['results']['log']);    $queue->deleteItem($queue_item);    // Provide an estimation of the completion level we've reached.    $context['sandbox']['progress'] += count($queue_item->data['arguments'][0]);    $context['finished'] = $context['sandbox']['progress'] / $context['sandbox']['max'];    $context['message'] = t('Processed @current out of @total', array('@current' => $context['sandbox']['progress'], '@total' => $context['sandbox']['max']));  }  if (!$queue_item || $context['finished'] === 1) {    // All done. Provide a status message to the user.    $context['results']['log'][] = t('Performed %operation on @items.', array(      '%operation' => $operation->label(),      '@items' => format_plural($context['sandbox']['progress'], '1 item', '@count items'),    ));  }}/** * Processes the provided queue item. * * Used as a worker callback defined by views_bulk_operations_cron_queue_info() * to process the site queue, as well as by * views_bulk_operations_active_queue_process() to process the active queue. * * @param $queue_item_arguments *   The arguments of the queue item to process. * @param $log *   An injected array of log messages, to be modified by reference. *   If NULL, the function defaults to using watchdog. */function views_bulk_operations_queue_item_process($queue_item_data, &$log = NULL) {  list($row_group, $operation, $options) = $queue_item_data['arguments'];  $account = user_load($queue_item_data['uid']);  $entity_type = $operation->entityType;  $entity_ids = array();  foreach ($row_group as $row_index => $row) {    $entity_ids[] = $row['entity_id'];  }  $entities = _views_bulk_operations_entity_load($entity_type, $entity_ids, $options['revision']);  foreach ($row_group as $row_index => $row) {    $entity_id = $row['entity_id'];    // A matching entity couldn't be loaded. Skip this item.    if (!isset($entities[$entity_id])) {      continue;    }    if ($options['revision']) {      // Don't reload revisions for now, they are not statically cached and      // usually don't run into the edge case described below.      $entity = $entities[$entity_id];    }    else {      // A previous action might have resulted in the entity being resaved      // (e.g. node synchronization from a prior node in this batch), so try      // to reload it. If no change occurred, the entity will be retrieved      // from the static cache, resulting in no performance penalty.      $entity = entity_load_single($entity_type, $entity_id);      if (empty($entity)) {        // The entity is no longer valid.        continue;      }    }    // If the current entity can't be accessed, skip it and log a notice.    if (!_views_bulk_operations_entity_access($operation, $entity_type, $entity, $account)) {      $message = 'Skipped %operation on @type %title due to insufficient permissions.';      $arguments = array(        '%operation' => $operation->label(),        '@type' => $entity_type,        '%title' => entity_label($entity_type, $entity),      );      if ($log) {        $log[] = t($message, $arguments);      }      else {        watchdog('views bulk operations', $message, $arguments, WATCHDOG_ALERT);      }      continue;    }    $operation_context = array(      'progress' => $row['position'],      'view_info' => $options['view_info'],    );    if ($operation->needsRows()) {      $operation_context['rows'] = array($row_index => $row['views_row']);    }    $operation->execute($entity, $operation_context);    unset($row_group[$row_index]);  }}/** * Adjusts the selection for the direct execution method. * * Just like the direct method itself, this is legacy code, used only for * aggregate actions. */function views_bulk_operations_direct_adjust(&$selection, $vbo) {  // Adjust selection to select all rows across pages.  $view = views_get_view($vbo->view->name);  $view->set_exposed_input($vbo->view->get_exposed_input());  $view->set_arguments($vbo->view->args);  $view->set_display($vbo->view->current_display);  $view->display_handler->set_option('pager', array('type' => 'none', 'options' => array()));  $view->build();  // Unset every field except the VBO one (which holds the entity id).  // That way the performance hit becomes much smaller, because there is no  // chance of views_handler_field_field::post_execute() firing entity_load().  foreach ($view->field as $field_name => $field) {    if ($field_name != $vbo->options['id']) {      unset($view->field[$field_name]);    }  }  $view->execute($vbo->view->current_display);  $results = array();  foreach ($view->result as $row_index => $result) {    $results[$row_index] = $vbo->get_value($result);  }  $selection = $results;}/** * Processes the passed rows directly (without batching and queueing). */function views_bulk_operations_direct_process($operation, $rows, $options) {  global $user;  drupal_set_time_limit(0);  // Prepare an array of status information. Imitates the Batch API naming  // for consistency. Passed to views_bulk_operations_execute_finished().  $context = array();  $context['results']['progress'] = 0;  $context['results']['log'] = array();  if ($operation->aggregate()) {    // Load all entities.    $entity_type = $operation->entityType;    $entity_ids = array();    foreach ($rows as $row_index => $row) {      $entity_ids[] = $row['entity_id'];    }    $entities = _views_bulk_operations_entity_load($entity_type, $entity_ids, $options['revision']);    // Filter out entities that can't be accessed.    foreach ($entities as $id => $entity) {      if (!_views_bulk_operations_entity_access($operation, $entity_type, $entity)) {        $context['results']['log'][] = t('Skipped %operation on @type %title due to insufficient permissions.', array(          '%operation' => $operation->label(),          '@type' => $entity_type,          '%title' => entity_label($entity_type, $entity),        ));        unset($entities[$id]);      }    }    // If there are any entities left, execute the operation on them.    if ($entities) {      $operation_context = array(        'view_info' => $options['view_info'],      );      // Pass the selected rows to the operation if needed.      if ($operation->needsRows()) {        $operation_context['rows'] = array();        foreach ($rows as $row_index => $row) {          $operation_context['rows'][$row_index] = $row['views_row'];        }      }      $operation->execute($entities, $operation_context);    }  }  else {    // Imitate a queue and process the entities one by one.    $queue_item_data = array(      'uid' => $user->uid,      'arguments' => array($rows, $operation, $options),    );    views_bulk_operations_queue_item_process($queue_item_data, $context['results']['log']);  }  $context['results']['progress'] += count($rows);  $context['results']['log'][] = t('Performed %operation on @items.', array(    '%operation' => $operation->label(),    '@items' => format_plural(count($rows), '1 item', '@count items'),  ));  views_bulk_operations_execute_finished(TRUE, $context['results'], array());}/** * Helper function that runs after the execution process is complete. */function views_bulk_operations_execute_finished($success, $results, $operations) {  if ($success) {    if (count($results['log']) > 1) {      $message = theme('item_list', array('items' => $results['log']));    }    else {      $message = reset($results['log']);    }  }  else {    // An error occurred.    // $operations contains the operations that remained unprocessed.    $error_operation = reset($operations);    $message = t('An error occurred while processing @operation with arguments: @arguments',      array('@operation' => $error_operation[0], '@arguments' => print_r($error_operation[0], TRUE)));  }  _views_bulk_operations_log($message);}/** * Helper function to verify access permission to operate on an entity. */function _views_bulk_operations_entity_access($operation, $entity_type, $entity, $account = NULL) {  if (!entity_type_supports($entity_type, 'access')) {    return TRUE;  }  $access_ops = array(    VBO_ACCESS_OP_VIEW => 'view',    VBO_ACCESS_OP_UPDATE => 'update',    VBO_ACCESS_OP_CREATE => 'create',    VBO_ACCESS_OP_DELETE => 'delete',  );  foreach ($access_ops as $bit => $op) {    if ($operation->getAccessMask() & $bit) {      if (!entity_access($op, $entity_type, $entity, $account)) {        return FALSE;      }    }  }  return TRUE;}/** * Loads multiple entities by their entity or revision ids, and returns them, * keyed by the id used for loading. */function _views_bulk_operations_entity_load($entity_type, $ids, $revision = FALSE) {  if (!$revision) {    $entities = entity_load($entity_type, $ids);  }  else {    // D7 can't load multiple entities by revision_id. Lovely.    $info = entity_get_info($entity_type);    $entities = array();    foreach ($ids as $revision_id) {      $loaded_entities = entity_load($entity_type, array(), array($info['entity keys']['revision'] => $revision_id));      $entities[$revision_id] = reset($loaded_entities);    }  }  return $entities;}/** * Helper function to report an error. */function _views_bulk_operations_report_error($msg, $arg) {  watchdog('views bulk operations', $msg, $arg, WATCHDOG_ERROR);  if (function_exists('drush_set_error')) {    drush_set_error('VIEWS_BULK_OPERATIONS_EXECUTION_ERROR', strip_tags(dt($msg, $arg)));  }}/** * Display a message to the user through the relevant function. */function _views_bulk_operations_log($msg) {  // Is VBO being run through drush?  if (function_exists('drush_log')) {    drush_log(strip_tags($msg), 'ok');  }  else {    drupal_set_message($msg);  }}
 |