diff --git a/actions_permissions.module b/actions_permissions.module index 5987d1ee3b0f676366f60ed3127a2f9684c3b9a2..8b120b8ae2e9029ba197bc6cce8d8527bfd6080a 100644 --- a/actions_permissions.module +++ b/actions_permissions.module @@ -8,7 +8,7 @@ function actions_permissions_permission() { $actions = actions_list() + _actions_permissions_advanced_actions_list(); foreach ($actions as $callback => $action) { $key = !empty($action['key']) ? $action['key'] : $callback; - $permission = actions_permissions_get_perm($action['label'], $callback); + $permission = actions_permissions_get_perm($action['label'], $key); $permissions[$permission] = array( 'title' => t('Execute %label', array('%label' => $action['label'])), diff --git a/plugins/operation_types/action.class.php b/plugins/operation_types/action.class.php index 4758152bb681ac452cb8998f9e1d9daa3aee25f6..e564be699ec9cd9ab6638f6ecee430e2eeda7535 100644 --- a/plugins/operation_types/action.class.php +++ b/plugins/operation_types/action.class.php @@ -47,7 +47,7 @@ class ViewsBulkOperationsAction extends ViewsBulkOperationsBaseOperation { public function access($account) { // Use actions_permissions if enabled. if (module_exists('actions_permissions')) { - $perm = actions_permissions_get_perm($this->operationInfo['label'], $this->operationInfo['callback']); + $perm = actions_permissions_get_perm($this->operationInfo['label'], $this->operationInfo['key']); if (!user_access($perm, $account)) { return FALSE; } @@ -76,6 +76,7 @@ class ViewsBulkOperationsAction extends ViewsBulkOperationsBaseOperation { * An array of related data provided by the caller. */ public function form($form, &$form_state, array $context) { + $context['settings'] = $this->getAdminOption('settings', array()); $form_callback = $this->operationInfo['callback'] . '_form'; return $form_callback($context); } @@ -90,6 +91,12 @@ class ViewsBulkOperationsAction extends ViewsBulkOperationsBaseOperation { * An array containing the current state of the form. */ public function formValidate($form, &$form_state) { + // Some modules (including this one) place their action callbacks + // into separate files. At this point those files might no longer be + // included due to a page reload, so we call actions_list() to trigger + // inclusion. The same thing is done by actions_do() on execute. + actions_list(); + $validation_callback = $this->operationInfo['callback'] . '_validate'; if (function_exists($validation_callback)) { $validation_callback($form, $form_state); @@ -107,10 +114,103 @@ class ViewsBulkOperationsAction extends ViewsBulkOperationsBaseOperation { * An array containing the current state of the form. */ public function formSubmit($form, &$form_state) { + // Some modules (including this one) place their action callbacks + // into separate files. At this point those files might no longer be + // included due to a page reload, so we call actions_list() to trigger + // inclusion. The same thing is done by actions_do() on execute. + actions_list(); + $submit_callback = $this->operationInfo['callback'] . '_submit'; $this->formOptions = $submit_callback($form, $form_state); } + /** + * Returns the admin options form for the operation. + * + * The admin options form is embedded into the VBO field settings and used + * to configure operation behavior. The options can later be fetched + * through the getAdminOption() method. + * + * @param $dom_id + * The dom path to the level where the admin options form is embedded. + * Needed for #dependency. + */ + public function adminOptionsForm($dom_id) { + $form = parent::adminOptionsForm($dom_id); + + $settings_form_callback = $this->operationInfo['callback'] . '_views_bulk_operations_form'; + if (function_exists($settings_form_callback)) { + $settings = $this->getAdminOption('settings', array()); + + $form['settings'] = array( + '#type' => 'fieldset', + '#title' => t('Operation settings'), + '#collapsible' => TRUE, + '#dependency' => array( + $dom_id . '-selected' => array(1), + ), + ); + $form['settings'] += $settings_form_callback($settings); + } + + return $form; + } + + /** + * Validates the admin options form. + * + * @param $form + * The admin options form. + * @param $form_state + * An array containing the current state of the form. Note that this array + * is constructed by the VBO views field handler, so it's not a real form + * state, it contains only the 'values' key. + * @param $error_element_base + * The base to prepend to field names when using form_set_error(). + * Needed because the admin settings form is embedded into a bigger form. + */ + public function adminOptionsFormValidate($form, &$form_state, $error_element_base) { + parent::adminOptionsFormValidate($form, $form_state, $error_element_base); + + if (!empty($form['settings'])) { + $settings_validation_callback = $this->operationInfo['callback'] . '_views_bulk_operations_form_validate'; + if (function_exists($settings_validation_callback)) { + $fake_form = $form['settings']; + $fake_form_state = array('values' => &$form_state['values']['settings']); + $error_element_base .= 'settings]['; + + $settings_validation_callback($fake_form, $fake_form_state, $error_element_base); + } + } + } + + /** + * Handles the submitted admin options form. + * Note that there is no need to handle saving the options, that is done + * by the VBO views field handler, which also injects the options into the + * operation object upon instantiation. + * + * @param $form + * The admin options form. + * @param $form_state + * An array containing the current state of the form. Note that this array + * is constructed by the VBO views field handler, so it's not a real form + * state, it contains only the 'values' key. + */ + public function adminOptionsFormSubmit($form, &$form_state) { + parent::adminOptionsFormSubmit($form, $form_state); + + if (!empty($form['settings'])) { + $settings_submit_callback = $this->operationInfo['callback'] . '_views_bulk_operations_form_submit'; + if (function_exists($settings_submit_callback)) { + $fake_form = $form['settings']; + $fake_form_state = array('values' => &$form_state['values']['settings']); + + $settings_submit_callback($form, $form_state); + } + } + } + /** * Returns whether the operation needs the full selected views rows to be * passed to execute() as a part of $context. diff --git a/plugins/operation_types/action.inc b/plugins/operation_types/action.inc index 61a6cc517e16fa29f71ae01458e5b304dafc2d04..3476df713fd8d408b518977eec8ed03534ab7969 100644 --- a/plugins/operation_types/action.inc +++ b/plugins/operation_types/action.inc @@ -14,26 +14,50 @@ $plugin = array( ), ); -function views_bulk_operations_operation_action_list() { - $action_operations = actions_list() + views_bulk_operations_operation_advanced_action_list(); - $operations = array(); - foreach ($action_operations as $callback => $operation) { - $key = isset($operation['key']) ? $operation['key'] : $callback; - $operations[$key] = array( - 'plugin' => 'action', - 'key' => $key, - 'label' => isset($operation['label']) ? $operation['label'] : '', - 'callback' => $callback, - 'parameters' => isset($operation['parameters']) ? $operation['parameters'] : array(), - 'configurable' => !empty($operation['configurable']), - 'type' => $operation['type'], - 'aggregate' => !empty($operation['aggregate']), - 'behavior' => isset($operation['behavior']) ? $operation['behavior'] : array(), - 'permissions' => isset($operation['permissions']) ? $operation['permissions'] : NULL, - 'pass rows' => !empty($operation['pass rows']), - ); +/** + * Returns a prepared list of available actions. + * + * Actions are fetched by invoking hook_action_info() and by loading + * advanced actions from the database. + * + * @param $operation_id + * The full, prefixed operation_id of the operation (in this case, action) + * to return, or NULL to return an array with all operations. + */ +function views_bulk_operations_operation_action_list($operation_id = NULL) { + $operations = &drupal_static(__FUNCTION__); + + if (!isset($operations)) { + $actions_list = actions_list() + views_bulk_operations_operation_advanced_action_list(); + $operations = array(); + foreach ($actions_list as $callback => $action) { + $key = isset($action['key']) ? $action['key'] : $callback; + // All operations must be prefixed with the operation type. + $new_operation_id = 'action::' . $key; + + $operations[$new_operation_id] = array( + 'operation_type' => 'action', + 'type' => $action['type'], + // Keep the unprefixed key around, for use in admin labels and action permissions. + 'key' => $key, + 'callback' => $callback, + 'label' => isset($action['label']) ? $action['label'] : '', + 'parameters' => isset($action['parameters']) ? $action['parameters'] : array(), + 'configurable' => !empty($action['configurable']), + 'aggregate' => !empty($action['aggregate']), + 'behavior' => isset($action['behavior']) ? $action['behavior'] : array(), + 'permissions' => isset($action['permissions']) ? $action['permissions'] : NULL, + 'pass rows' => !empty($action['pass rows']), + ); + } + } + + if (isset($operation_id)) { + return isset($operations[$operation_id]) ? $operations[$operation_id] : FALSE; + } + else { + return $operations; } - return $operations; } /** diff --git a/plugins/operation_types/base.class.php b/plugins/operation_types/base.class.php index ee49634e822e611a4ab23afe7d5c6b49adbc881d..0f9b68bec0e18c6e34854cc987f9e0cc49854fbd 100644 --- a/plugins/operation_types/base.class.php +++ b/plugins/operation_types/base.class.php @@ -6,6 +6,14 @@ */ abstract class ViewsBulkOperationsBaseOperation { + /** + * The id of the operation. + * + * Composed of the operation_type plugin name and the operation key, + * divided by '::'. For example: action::node_publish_action. + */ + public $operationId; + /** * The entity type that the operation is operating on. * @@ -32,6 +40,8 @@ abstract class ViewsBulkOperationsBaseOperation { /** * Constructs an operation object. * + * @param $operationId + * The id of the operation. * @param $entityType * The entity type that the operation is operating on. * @param $operationInfo @@ -39,7 +49,8 @@ abstract class ViewsBulkOperationsBaseOperation { * @param $adminOptions * An array of options set by the admin. */ - public function __construct($entityType, array $operationInfo, array $adminOptions) { + public function __construct($operationId, $entityType, array $operationInfo, array $adminOptions) { + $this->operationId = $operationId; $this->entityType = $entityType; $this->operationInfo = $operationInfo; $this->adminOptions = $adminOptions; @@ -60,6 +71,20 @@ abstract class ViewsBulkOperationsBaseOperation { return VBO_ACCESS_OP_UPDATE; } + /** + * Returns the id of the operation. + */ + public function id() { + return $this->operationId; + } + + /** + * Returns the name of the operation_type plugin that provides the operation. + */ + public function type() { + return $this->operationInfo['operation_type']; + } + /** * Returns the human-readable name of the operation, meant to be shown * to the user. @@ -76,6 +101,15 @@ abstract class ViewsBulkOperationsBaseOperation { return $label; } + /** + * Returns the human-readable name of the operation, meant to be shown + * to the admin. + */ + public function adminLabel() { + $label = $this->operationInfo['label'] . ' (' . $this->operationInfo['key'] . ')'; + return $label; + } + /** * Returns whether the operation is configurable. Used to determine * whether the operation's form methods should be invoked. @@ -129,6 +163,82 @@ abstract class ViewsBulkOperationsBaseOperation { */ abstract function formSubmit($form, &$form_state); + /** + * Returns the admin options form for the operation. + * + * The admin options form is embedded into the VBO field settings and used + * to configure operation behavior. The options can later be fetched + * through the getAdminOption() method. + * + * @param $dom_id + * The dom path to the level where the admin options form is embedded. + * Needed for #dependency. + */ + public function adminOptionsForm($dom_id) { + $label = $this->getAdminOption('label', ''); + + $form = array(); + $form['override_label'] = array( + '#type' => 'checkbox', + '#title' => t('Override label'), + '#default_value' => $label !== '', + '#dependency' => array( + $dom_id . '-selected' => array(1), + ), + ); + $form['label'] = array( + '#type' => 'textfield', + '#title' => t('Provide label'), + '#title_display' => 'invisible', + '#default_value' => $label, + '#dependency' => array( + $dom_id . '-selected' => array(1), + $dom_id . '-override-label' => array(1), + ), + '#dependency_count' => 2, + ); + + return $form; + } + + /** + * Validates the admin options form. + * + * @param $form + * The admin options form. + * @param $form_state + * An array containing the current state of the form. Note that this array + * is constructed by the VBO views field handler, so it's not a real form + * state, it contains only the 'values' key. + * @param $error_element_base + * The base to prepend to field names when using form_set_error(). + * Needed because the admin options form is embedded into a bigger form. + */ + public function adminOptionsFormValidate($form, &$form_state, $error_element_base) { + // No need to do anything, but make the function have a body anyway + // so that it's callable by overriding methods. + } + + /** + * Handles the submitted admin options form. + * Note that there is no need to handle saving the options, that is done + * by the VBO views field handler, which also injects the options into the + * operation object upon instantiation. + * + * @param $form + * The admin options form. + * @param $form_state + * An array containing the current state of the form. Note that this array + * is constructed by the VBO views field handler, so it's not a real form + * state, it contains only the 'values' key. + */ + public function adminOptionsFormSubmit($form, &$form_state) { + // If the "Override label" checkbox was deselected, clear the entered value. + if (empty($form_state['values']['override_label'])) { + $form_state['values']['label'] = ''; + } + } + /** * Returns whether the selected entities should be aggregated * (loaded in bulk and passed in together). diff --git a/plugins/operation_types/rules_component.inc b/plugins/operation_types/rules_component.inc index 94a1e740e064c6c071e2982761e233ebeda853d8..61f8f71254fb7184b982f34bba05a7dc5c3380f6 100644 --- a/plugins/operation_types/rules_component.inc +++ b/plugins/operation_types/rules_component.inc @@ -14,7 +14,14 @@ $plugin = array( ), ); -function views_bulk_operations_operation_rules_component_list() { +/** + * Returns a prepared list of available rules components. + * + * @param $operation_id + * The full, prefixed operation_id of the operation (in this case, rules + * component) to return, or NULL to return an array with all operations. + */ +function views_bulk_operations_operation_rules_component_list($operation_id = NULL) { if (!module_exists('rules')) { return array(); } @@ -29,8 +36,17 @@ function views_bulk_operations_operation_rules_component_list() { $list_types[] = "list<$type>"; } + $components = array(); + if (isset($operation_id)) { + $id_fragments = explode('::', $operation_id); + $components[$id_fragments[1]] = rules_config_load($id_fragments[1]); + } + else { + $components = rules_get_components(FALSE, 'action'); + } + $operations = array(); - foreach (rules_get_components(FALSE, 'action') as $component_key => $component) { + foreach ($components as $name => $component) { $parameter_info = $component->parameterInfo(); $first_parameter = reset($parameter_info); $parameter_keys = array_keys($parameter_info); @@ -51,16 +67,24 @@ function views_bulk_operations_operation_rules_component_list() { $aggregate = FALSE; } - $operations[$component_key] = array( - 'plugin' => 'rules_component', - 'key' => $component_key, + // All operations must be prefixed with the operation type. + $new_operation_id = 'rules_component::' . $name; + $operations[$new_operation_id] = array( + 'operation_type' => 'rules_component', + // Keep the unprefixed key around, for use in admin labels. + 'key' => $name, 'label' => $component->label, - 'parameters' => array('component_key' => $component_key, 'entity_key' => $entity_key), + 'parameters' => array('component_key' => $name, 'entity_key' => $entity_key), 'configurable' => count($parameter_info) > 1, 'type' => $type, 'aggregate' => $aggregate, ); } - return $operations; + if (isset($operation_id)) { + return isset($operations[$operation_id]) ? $operations[$operation_id] : FALSE; + } + else { + return $operations; + } } diff --git a/views/views_bulk_operations.views_default.inc b/views/views_bulk_operations.views_default.inc index e1c600667a8d8ef7b0869724e517d91677cae119..d86e1ebc3783c4ea0e163a0d0abe0cf92a4a6ac3 100644 --- a/views/views_bulk_operations.views_default.inc +++ b/views/views_bulk_operations.views_default.inc @@ -114,83 +114,83 @@ function views_bulk_operations_views_default_views() { $handler->display->display_options['fields']['views_bulk_operations']['empty_zero'] = 0; $handler->display->display_options['fields']['views_bulk_operations']['hide_alter_empty'] = 0; $handler->display->display_options['fields']['views_bulk_operations']['vbo']['operations'] = array( - 'node_assign_owner_action' => array( + 'action::node_assign_owner_action' => array( 'selected' => 1, 'use_queue' => 0, 'skip_confirmation' => 0, 'override_label' => 0, 'label' => '', ), - 'views_bulk_operations_delete_item' => array( + 'action::views_bulk_operations_delete_item' => array( 'selected' => 1, 'use_queue' => 0, 'skip_confirmation' => 0, 'override_label' => 0, 'label' => '', ), - 'views_bulk_operations_script_action' => array( + 'action::views_bulk_operations_script_action' => array( 'selected' => 1, 'use_queue' => 0, 'skip_confirmation' => 0, 'override_label' => 0, 'label' => '', ), - 'node_make_sticky_action' => array( + 'action::node_make_sticky_action' => array( 'selected' => 1, 'use_queue' => 0, 'skip_confirmation' => 0, 'override_label' => 0, 'label' => '', ), - 'node_make_unsticky_action' => array( + 'action::node_make_unsticky_action' => array( 'selected' => 1, 'use_queue' => 0, 'skip_confirmation' => 0, 'override_label' => 0, 'label' => '', ), - 'views_bulk_operations_argument_selector_action' => array( + 'action::views_bulk_operations_argument_selector_action' => array( 'selected' => 0, 'skip_confirmation' => 0, 'override_label' => 0, 'label' => '', ), - 'node_promote_action' => array( + 'action::node_promote_action' => array( 'selected' => 1, 'use_queue' => 0, 'skip_confirmation' => 0, 'override_label' => 0, 'label' => '', ), - 'node_publish_action' => array( + 'action::node_publish_action' => array( 'selected' => 1, 'use_queue' => 0, 'skip_confirmation' => 0, 'override_label' => 0, 'label' => '', ), - 'node_unpromote_action' => array( + 'action::node_unpromote_action' => array( 'selected' => 1, 'use_queue' => 0, 'skip_confirmation' => 0, 'override_label' => 0, 'label' => '', ), - 'node_save_action' => array( + 'action::node_save_action' => array( 'selected' => 0, 'use_queue' => 0, 'skip_confirmation' => 0, 'override_label' => 0, 'label' => '', ), - 'node_unpublish_action' => array( + 'action::node_unpublish_action' => array( 'selected' => 1, 'use_queue' => 0, 'skip_confirmation' => 0, 'override_label' => 0, 'label' => '', ), - 'node_unpublish_by_keyword_action' => array( + 'action::node_unpublish_by_keyword_action' => array( 'selected' => 1, 'use_queue' => 0, 'skip_confirmation' => 0, @@ -308,9 +308,9 @@ function views_bulk_operations_views_default_views() { $handler->display->display_options['fields']['status']['hide_empty'] = 0; $handler->display->display_options['fields']['status']['empty_zero'] = 0; $handler->display->display_options['fields']['status']['not'] = 0; - /* Field: Content: Edit link */ + /* Field: Node: Edit link */ $handler->display->display_options['fields']['edit_node']['id'] = 'edit_node'; - $handler->display->display_options['fields']['edit_node']['table'] = 'node'; + $handler->display->display_options['fields']['edit_node']['table'] = 'views_entity_node'; $handler->display->display_options['fields']['edit_node']['field'] = 'edit_node'; $handler->display->display_options['fields']['edit_node']['label'] = 'Edit'; $handler->display->display_options['fields']['edit_node']['alter']['alter_text'] = 0; @@ -386,31 +386,6 @@ function views_bulk_operations_views_default_views() { /* Display: Page */ $handler = $view->new_display('page', 'Page', 'page'); $handler->display->display_options['path'] = 'admin/content2'; - $translatables['admin_content'] = array( - t('Master'), - t('Content'), - t('more'), - t('Apply'), - t('Reset'), - t('Sort by'), - t('Asc'), - t('Desc'), - t('Items per page'), - t('- All -'), - t('Offset'), - t('No content available.'), - t('author'), - t('Title'), - t('New?'), - t('Type'), - t('Author'), - t('Published'), - t('Edit'), - t('Title contains'), - t('Node: Type'), - t('Promoted'), - t('Page'), - ); $views[$view->name] = $view; $view = new view; @@ -509,42 +484,42 @@ function views_bulk_operations_views_default_views() { $handler->display->display_options['fields']['views_bulk_operations']['empty_zero'] = 0; $handler->display->display_options['fields']['views_bulk_operations']['hide_alter_empty'] = 0; $handler->display->display_options['fields']['views_bulk_operations']['vbo']['operations'] = array( - 'system_block_ip_action' => array( + 'action::system_block_ip_action' => array( 'selected' => 0, 'use_queue' => 0, 'skip_confirmation' => 0, 'override_label' => 0, 'label' => '', ), - 'user_block_user_action' => array( + 'action::user_block_user_action' => array( 'selected' => 1, 'use_queue' => 0, 'skip_confirmation' => 0, 'override_label' => 0, 'label' => '', ), - 'views_bulk_operations_delete_item' => array( + 'action::views_bulk_operations_delete_item' => array( 'selected' => 1, 'use_queue' => 0, 'skip_confirmation' => 0, 'override_label' => 0, 'label' => '', ), - 'views_bulk_operations_script_action' => array( + 'action::views_bulk_operations_script_action' => array( 'selected' => 1, 'use_queue' => 0, 'skip_confirmation' => 0, 'override_label' => 0, 'label' => '', ), - 'views_bulk_operations_user_roles_action' => array( + 'action::views_bulk_operations_user_roles_action' => array( 'selected' => 1, 'use_queue' => 0, 'skip_confirmation' => 0, 'override_label' => 0, 'label' => '', ), - 'views_bulk_operations_argument_selector_action' => array( + 'action::views_bulk_operations_argument_selector_action' => array( 'selected' => 0, 'skip_confirmation' => 0, 'override_label' => 0, @@ -718,27 +693,6 @@ function views_bulk_operations_views_default_views() { /* Display: Page */ $handler = $view->new_display('page', 'Page', 'page'); $handler->display->display_options['path'] = 'admin/people2'; - $translatables['admin_people'] = array( - t('Master'), - t('People'), - t('more'), - t('Apply'), - t('Reset'), - t('Sort by'), - t('Asc'), - t('Desc'), - t('Items per page'), - t('- All -'), - t('Offset'), - t('Username'), - t('Active'), - t('Member for'), - t('Roles'), - t('Last access'), - t('Operations'), - t('Role'), - t('Page'), - ); $views[$view->name] = $view; return $views; diff --git a/views/views_bulk_operations_handler_field_operations.inc b/views/views_bulk_operations_handler_field_operations.inc index 86406e03cb88c149c9dcdf0f491a87f6f680f383..db5edc039a1f50fa66b4abe06655812f7c596ed7 100644 --- a/views/views_bulk_operations_handler_field_operations.inc +++ b/views/views_bulk_operations_handler_field_operations.inc @@ -7,22 +7,36 @@ */ class views_bulk_operations_handler_field_operations extends views_handler_field { - var $all_operations = array(); var $revision = FALSE; function init(&$view, &$options) { parent::init($view, $options); - $this->populate_operations(); - // Update old settings. if (!empty($options['vbo']['selected_operations'])) { - foreach (array_filter($options['vbo']['selected_operations']) as $key) { - $this->options['vbo']['operations'][$key]['selected'] = TRUE; - $this->options['vbo']['operations'][$key]['skip_confirmation'] = $options['vbo']['skip_confirmation']; + foreach (array_filter($options['vbo']['selected_operations']) as $operation_id) { + $this->options['vbo']['operations'][$operation_id]['selected'] = TRUE; + $this->options['vbo']['operations'][$operation_id]['skip_confirmation'] = $options['vbo']['skip_confirmation']; } unset($this->options['vbo']['selected_operations']); } + // Prefix all un-prefixed operations. + foreach ($this->options['vbo']['operations'] as $operation_id => &$operation_options) { + if (strpos($operation_id, '::') === FALSE) { + $operations = views_bulk_operations_get_operation_info(); + // Basically, guess. + foreach (array('action', 'rules_component') as $operation_type) { + $new_operation_id = $operation_type . '::' . $operation_id; + if (isset($operations[$new_operation_id])) { + $this->options['vbo']['operations'][$new_operation_id] = $operation_options; + break; + } + } + + // Remove the old operation in any case. + unset($this->options['vbo']['operations'][$operation_id]); + } + } } function option_definition() { @@ -100,106 +114,71 @@ class views_bulk_operations_handler_field_operations extends views_handler_field '#collapsible' => TRUE, '#collapsed' => FALSE, ); - foreach ($this->get_operations_options() as $key => $label) { - $options = isset($this->options['vbo']['operations'][$key]) ? $this->options['vbo']['operations'][$key] : array(); - $dom_id = 'edit-options-vbo-operations-' . str_replace(array('_', ':'), array('-', ''), $key); - $form['vbo']['operations'][$key]['selected'] = array( + $entity_type = $this->get_entity_type(); + $options = $this->options['vbo']['operations']; + foreach (views_bulk_operations_get_applicable_operations($entity_type, $options) as $operation_id => $operation) { + $operation_options = isset($options[$operation_id]) ? $options[$operation_id] : array(); + + $dom_id = 'edit-options-vbo-operations-' . str_replace(array('_', ':'), array('-', ''), $operation_id); + $form['vbo']['operations'][$operation_id]['selected'] = array( '#type' => 'checkbox', - '#title' => $label, - '#default_value' => !empty($options['selected']), + '#title' => $operation->adminLabel(), + '#default_value' => !empty($operation_options['selected']), ); - if (!$this->all_operations[$key]['aggregate']) { - $form['vbo']['operations'][$key]['use_queue'] = array( + if (!$operation->aggregate()) { + $form['vbo']['operations'][$operation_id]['use_queue'] = array( '#type' => 'checkbox', '#title' => t('Enqueue the operation instead of executing it directly'), - '#default_value' => !empty($options['use_queue']), + '#default_value' => !empty($operation_options['use_queue']), '#dependency' => array( $dom_id . '-selected' => array(1), ), ); } - $form['vbo']['operations'][$key]['skip_confirmation'] = array( + $form['vbo']['operations'][$operation_id]['skip_confirmation'] = array( '#type' => 'checkbox', '#title' => t('Skip confirmation step'), - '#default_value' => !empty($options['skip_confirmation']), - '#dependency' => array( - $dom_id . '-selected' => array(1), - ), - ); - $show_label = isset($options['label']) ? $options['label'] : ''; - $form['vbo']['operations'][$key]['override_label'] = array( - '#type' => 'checkbox', - '#title' => t('Override label'), - '#default_value' => $show_label !== '', - '#dependency' => array( - $dom_id . '-selected' => array(1), - ), - ); - $form['vbo']['operations'][$key]['label'] = array( - '#type' => 'textfield', - '#title' => t('Provide label'), - '#title_display' => 'invisible', - '#default_value' => $show_label, + '#default_value' => !empty($operation_options['skip_confirmation']), '#dependency' => array( $dom_id . '-selected' => array(1), - $dom_id . '-override-label' => array(1), ), - '#dependency_count' => 2, ); - $form_function = $this->all_operations[$key]['callback'] . '_views_bulk_operations_form'; - if (function_exists($form_function)) { - $settings = isset($options['settings']) ? $options['settings'] : array(); - $form_settings = call_user_func($form_function, $settings); - $form['vbo']['operations'][$key]['settings'] = array( - '#type' => 'fieldset', - '#title' => t('Operation settings'), - '#collapsible' => TRUE, - '#dependency' => array( - $dom_id . '-selected' => array(1), - ), - ); - $form['vbo']['operations'][$key]['settings'] += $form_settings; - } + + $form['vbo']['operations'][$operation_id] += $operation->adminOptionsForm($dom_id); } } function options_validate(&$form, &$form_state) { parent::options_validate($form, $form_state); - foreach ($form_state['values']['options']['vbo']['operations'] as $key => &$options) { - if (empty($options['selected']) || !isset($options['settings'])) { + $entity_type = $this->get_entity_type(); + foreach ($form_state['values']['options']['vbo']['operations'] as $operation_id => &$options) { + if (empty($options['selected'])) { continue; } - $operation = $this->all_operations[$key]; - $form_function = $operation['callback'] . '_views_bulk_operations_form_validate'; - if (function_exists($form_function)) { - $options['settings']['_error_element_base'] = 'vbo][operations][' . $key . '][settings]['; - call_user_func($form_function, $form, array('values' => $options['settings'])); - } + + $operation = views_bulk_operations_get_operation($operation_id, $entity_type, $options); + $fake_form = $form['vbo']['operations'][$operation_id]; + $fake_form_state = array('values' => &$options); + $error_element_base = 'vbo][operations][' . $operation_id . ']['; + $operation->adminOptionsFormValidate($fake_form, $fake_form_state, $error_element_base); } } function options_submit(&$form, &$form_state) { parent::options_submit($form, $form_state); - foreach ($form_state['values']['options']['vbo']['operations'] as $key => &$options) { + $entity_type = $this->get_entity_type(); + foreach ($form_state['values']['options']['vbo']['operations'] as $operation_id => &$options) { if (empty($options['selected'])) { continue; } - // If the "Override label" checkbox was deselected, clear the entered value. - if (empty($options['override_label'])) { - $options['label'] = ''; - } - // This action has its own settings, call the submit function that - // should handle them (if any). - if (!empty($options['settings'])) { - $operation = $this->all_operations[$key]; - $form_function = $operation['callback'] . '_views_bulk_operations_form_submit'; - if (function_exists($form_function)) { - call_user_func($form_function, $form, array('values' => $options['settings'])); - } - } + + $operation = views_bulk_operations_get_operation($operation_id, $entity_type, $options); + $fake_form = $form['vbo']['operations'][$operation_id]; + $fake_form_state = array('values' => &$options); + $operation->adminOptionsFormSubmit($fake_form, $fake_form_state); } } @@ -255,77 +234,39 @@ class views_bulk_operations_handler_field_operations extends views_handler_field } } - function get_selected_operations() { + public function get_selected_operations() { global $user; + $entity_type = $this->get_entity_type(); $selected = array(); - foreach ($this->options['vbo']['operations'] as $key => $options) { - if (empty($options['selected']) || !isset($this->all_operations[$key])) { + foreach ($this->options['vbo']['operations'] as $operation_id => $options) { + if (empty($options['selected'])) { continue; } - $operation = $this->get_operation($key); - if (!$operation->access($user)) { + $operation = views_bulk_operations_get_operation($operation_id, $entity_type, $options); + if (!$operation || !$operation->access($user)) { continue; } - $selected[$key] = $operation->label(); - } - return $selected; - } - - function get_operation($key) { - if (empty($this->all_operations[$key])) { - return NULL; + $selected[$operation_id] = $operation; } - $operations = &drupal_static(__FUNCTION__); - if (!isset($operations[$key])) { - $operation_info = $this->all_operations[$key]; - $entity_type = $this->get_entity_type(); - $plugin = views_bulk_operations_get_operation_type($operation_info['plugin']); - - $operations[$key] = new $plugin['handler']['class']($entity_type, $operation_info, $this->options['vbo']['operations'][$key]); - } - return $operations[$key]; - } - - private function get_operations_options() { - $options = array(); - $entity_type = $this->get_entity_type(); - foreach ($this->all_operations as $key => $operation) { - if ($operation['type'] == 'entity' || $operation['type'] == 'system') { // Actions that accept any entity type. - $operation['type'] = $entity_type; - } - if ($operation['type'] == $entity_type) { - $options[$key] = $operation['label'] .' ('. $key .')'; - } - } - return $options; + return $selected; } - private function populate_operations() { - // The operations have already been populated. - if (!empty($this->all_operations)) { - return; - } - - $operations = array(); - $plugins = views_bulk_operations_get_operation_types(); - foreach ($plugins as $plugin) { - if (isset($plugin['list callback']) && function_exists($plugin['list callback'])) { - $operations += $plugin['list callback'](); - } - } - - uasort($operations, create_function('$a, $b', 'return strcasecmp($a["label"], $b["label"]);')); - $this->all_operations = $operations; + /** + * Returns the options stored for the provided operation id. + */ + public function get_operation_options($operation_id) { + $options = $this->options['vbo']['operations']; + return isset($options[$operation_id]) ? $options[$operation_id] : array(); } /** * Determine the base table of the VBO field, and then use it to determine * the entity type that VBO is operating on. */ - function get_entity_type() { + public function get_entity_type() { $base_table = $this->view->base_table; // If the current field is under a relationship you can't be sure that the diff --git a/views_bulk_operations.drush.inc b/views_bulk_operations.drush.inc index e9dca0465e5ab3f133a4e8c8831fe7d29a77bf7b..8604434b43ec9ae19d13aa25e68b193f21a5f5b4 100644 --- a/views_bulk_operations.drush.inc +++ b/views_bulk_operations.drush.inc @@ -25,15 +25,15 @@ function views_bulk_operations_drush_command() { 'description' => 'Execute a bulk operation based on a Views Bulk Operations (VBO) view.', 'arguments' => array( 'vid' => 'ID or name of the view to be executed.', - 'operation' => 'Callback name of the operation to be applied on the view results.', + 'operation_id' => 'ID of the operation to be applied on the view results.', 'type:[name=]value ...' => 'Parameters to be passed as view input filters, view arguments or operation arguments, where type is respectively {input, argument, operation}.', ), 'examples' => array( - '$ drush vbo-execute admin_content node_publish_action' => + '$ drush vbo-execute action::admin_content node_publish_action' => 'Publish nodes returned by view admin_content.', - '$ drush vbo-execute 44 node_assign_owner_action operation:owner_uid=3' => + '$ drush vbo-execute 44 action::node_assign_owner_action operation:owner_uid=3' => 'Change node ownership on nodes returned by view #44, passing argument owner_uid=3 to the action.', - '$ drush vbo-execute admin_content node_unpublish_action input:type=expense argument:3' => + '$ drush vbo-execute admin_content action::node_unpublish_action input:type=expense argument:3' => 'Unpublish nodes returned by view admin_content, filtering results of type expense and passing value 3 as first view argument.', ), ); @@ -56,8 +56,8 @@ function views_bulk_operations_drush_list() { $vbo = _views_bulk_operations_get_field($view); if ($vbo) { $operations = array(); - foreach ($vbo->get_selected_operations() as $operation => $label) { - $operations[] = $label .' ('. $operation .')'; + foreach ($vbo->get_selected_operations() as $operation_id => $operation) { + $operations[] = $operation->label() .' ('. $operation_id .')'; } $operations[] = "---------------"; $rows[] = array( @@ -74,13 +74,13 @@ function views_bulk_operations_drush_list() { /** * Implementation of 'vbo execute' command. */ -function views_bulk_operations_drush_execute($vid = NULL, $operation = NULL) { +function views_bulk_operations_drush_execute($vid = NULL, $operation_id = NULL) { // Parse arguments. if (is_null($vid)) { drush_set_error('VIEWS_BULK_OPERATIONS_MISSING_VID', dt('Please specify a view ID to execute.')); return; } - if (is_null($operation)) { + if (is_null($operation_id)) { drush_set_error('VIEWS_BULK_OPERATIONS_MISSING_OPERATION', dt('Please specify an operation to execute.')); return; } @@ -116,5 +116,5 @@ function views_bulk_operations_drush_execute($vid = NULL, $operation = NULL) { drupal_save_session(FALSE); // Execute the VBO. - views_bulk_operations_execute($vid, $operation, $operation_arguments, $view_exposed_input, $view_arguments); + views_bulk_operations_execute($vid, $operation_id, $operation_arguments, $view_exposed_input, $view_arguments); } diff --git a/views_bulk_operations.module b/views_bulk_operations.module index c270b7addbe8cf2903e73b4dca430b4f9609a5b1..26f2f936619564183c02154fc2db0611cd25afd5 100644 --- a/views_bulk_operations.module +++ b/views_bulk_operations.module @@ -101,6 +101,7 @@ function views_bulk_operations_theme() { $themes += call_user_func($action_theme_fn); } } + return $themes; } @@ -151,6 +152,88 @@ function views_bulk_operations_get_operation_types() { 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. * @@ -323,11 +406,10 @@ function views_bulk_operations_form($form, &$form_state, $vbo) { '#value' => TRUE, ); - $selected_operations = array_keys($vbo->get_selected_operations()); - $operation = $vbo->get_operation($selected_operations[0]); + $selected_operations = $vbo->get_selected_operations(); + $operation = reset($selected_operations); if ($operation->configurable()) { $context = array( - 'settings' => $operation->getAdminOption('settings', array()), // Pass the View along. Needed by views_send 7.x-1.x. // Has no performance penalty since objects are passed by reference, // but needing the full views object in a core action is in most cases @@ -357,10 +439,15 @@ function views_bulk_operations_form($form, &$form_state, $vbo) { '#attributes' => array('class' => array('container-inline')), ); if ($vbo->options['vbo']['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' => array(0 => t('- Choose an operation -')) + $vbo->get_selected_operations(), + '#options' => $options, ); $form['select']['submit'] = array( '#type' => 'submit', @@ -369,11 +456,11 @@ function views_bulk_operations_form($form, &$form_state, $vbo) { } else { // Create buttons for operations. - foreach ($vbo->get_selected_operations() as $md5 => $description) { - $form['select'][$md5] = array( + foreach ($vbo->get_selected_operations() as $operation_id => $operation) { + $form['select'][$operation_id] = array( '#type' => 'submit', - '#value' => $description, - '#hash' => $md5, + '#value' => $operation->label(), + '#operation_id' => $operation_id, ); } } @@ -408,7 +495,6 @@ function views_bulk_operations_config_form($form, &$form_state, $view, $output) drupal_set_title(t('Set parameters for %operation', array('%operation' => $operation->label())), PASS_THROUGH); $context = array( - 'settings' => $operation->getAdminOption('settings', array()), // Pass the View along. Needed by views_send 7.x-1.x. // Has no performance penalty since objects are passed by reference, // but needing the full views object in a core action is in most cases @@ -541,8 +627,8 @@ function views_bulk_operations_form_validate($form, &$form_state) { } } else { - if (!empty($form_state['triggering_element']['#hash'])) { - $form_state['values']['operation'] = $form_state['triggering_element']['#hash']; + 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.')); @@ -573,6 +659,7 @@ function views_bulk_operations_form_validate($form, &$form_state) { */ 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': @@ -594,7 +681,8 @@ function views_bulk_operations_form_submit($form, &$form_state) { $form_state['step'] = 'views_bulk_operations_confirm_form'; } else { - $form_state['operation'] = $operation = $vbo->get_operation($form_state['values']['operation']); + $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 } @@ -1062,7 +1150,7 @@ function _views_bulk_operations_entity_label($entity_type, $entity) { /** * API function to programmatically invoke a VBO. */ -function views_bulk_operations_execute($vid, $operation_callback, $operation_arguments = array(), $view_exposed_input = array(), $view_arguments = array()) { +function views_bulk_operations_execute($vid, $operation_id, $operation_arguments = array(), $view_exposed_input = array(), $view_arguments = array()) { $view = views_get_view($vid); if (!is_object($view)) { _views_bulk_operations_report_error('Could not find view %vid.', array('%vid' => $vid)); @@ -1087,11 +1175,11 @@ function views_bulk_operations_execute($vid, $operation_callback, $operation_arg // Find the selected operation. $operations = $vbo->get_selected_operations(); - if (!isset($operations[$operation_callback])) { - _views_bulk_operations_report_error('Could not find operation %operation in view %vid.', array('%operation' => $operation_callback, '%vid' => $vid)); + if (!isset($operations[$operation_id])) { + _views_bulk_operations_report_error('Could not find operation %operation_id in view %vid.', array('%operation_id' => $operation_id, '%vid' => $vid)); return; } - $operation = $vbo->get_operation($operation_callback); + $operation = views_bulk_operations_get_operation($operation_id, $vbo->get_entity_type(), $vbo->get_operation_options($operation_id)); if ($operation_arguments) { $operation->formOptions = $operation_arguments; }