diff --git a/actions/rules.action.inc b/actions/rules.action.inc
deleted file mode 100644
index 280937c..0000000
--- a/actions/rules.action.inc
+++ /dev/null
@@ -1,48 +0,0 @@
-<?php
-
-/**
- * Rules 2 actions are not integrated through Drupal actions, so no need
- * for this hook to do anything.
- */
-function views_bulk_operations_rules_action_info() {
-  return FALSE;
-}
-
-function views_bulk_operations_rules_action($entity, $context) {
-  // If there was a config form, there's a rules_element.
-  // If not, fallback to the component key.
-  if (isset($context['rules_element'])) {
-    $element = $context['rules_element'];
-  }
-  else {
-   $element = rules_action('component_' . $context['component_key']);
-  }
-  $result = $element->execute($entity);
-}
-
-function views_bulk_operations_rules_action_form($form, &$form_state, $context) {
-  $entity_key = $context['action']['callback arguments']['entity_key'];
-  $entity_type = $context['action']['type'];
-  $info = entity_get_info($entity_type);
-
-  $set = rules_action_set(array($entity_key => array('type' => $entity_type, 'label' => $info['label'])));
-  $action = rules_action('component_' . $context['action']['key'], array($entity_key . ':select' => $entity_key));
-  $set->action($action);
-  $action->form($form, $form_state);
-
-  // Remove the form element for the "entity" param. It will be passed in manually.
-  unset($form['parameter'][$entity_key]);
-
-  return $form;
-}
-
-function views_bulk_operations_rules_action_validate($form, $form_state) {
-  rules_ui_form_rules_config_validate($form, $form_state);
-}
-
-function views_bulk_operations_rules_action_submit($form, $form_state) {
-  return array(
-    // Pass on the whole-action set for execution.
-    'rules_element' => $form_state['rules_element']->root(),
-  );
-}
diff --git a/plugins/operation_types/action.class.php b/plugins/operation_types/action.class.php
new file mode 100644
index 0000000..80fad48
--- /dev/null
+++ b/plugins/operation_types/action.class.php
@@ -0,0 +1,135 @@
+<?php
+
+/**
+ * @file
+ * Defines the class for core actions.
+ * Autoloaded by CTools. Belongs to the "action" operation type plugin.
+ */
+
+class ViewsBulkOperationsAction implements ViewsBulkOperationsBaseOperation {
+  /**
+   * Contains the options provided by the user in the configuration form.
+   *
+   * @var array
+   */
+  public $formOptions;
+
+  /**
+   * Returns the access bitmask for the operation, used for entity access checks.
+   */
+  public function getAccessMask() {
+    // Assume edit by default.
+    if (!isset($this->operationInfo['behavior'])) {
+      $this->operationInfo['behavior'] = array('changes_property');
+    }
+
+    $mask = 0;
+    if (in_array('views_property', $this->operationInfo['behavior'])) {
+      $mask |= VBO_ACCESS_OP_VIEW;
+    }
+    if (in_array('changes_property', $this->operationInfo['behavior'])) {
+      $mask |= VBO_ACCESS_OP_UPDATE;
+    }
+    if (in_array('creates_property', $this->operationInfo['behavior'])) {
+      $mask |= VBO_ACCESS_OP_CREATE;
+    }
+    if (in_array('deletes_property', $this->operationInfo['behavior'])) {
+      $mask |= VBO_ACCESS_OP_DELETE;
+    }
+    return $mask;
+  }
+
+  /**
+   * Returns whether the provided account has access to execute the operation.
+   *
+   * @param $account
+   */
+  public function access($account) {
+    if (module_exists('actions_permissions')) {
+      $perm = actions_permissions_get_perm($this->operationInfo['original label'], $this->operationInfo['callback']);
+      if (!user_access($perm, $account)) {
+        return FALSE;
+      }
+    }
+
+    // Check against additional permissions.
+    if (!empty($this->operationInfo['permissions'])) {
+      foreach ($this->operationInfo['permissions'] as $perm) {
+        if (!user_access($perm, $account)) {
+          return FALSE;
+        }
+      }
+    }
+  }
+
+  /**
+   * Returns the configuration form for the operation.
+   * Only called if the operation is declared as configurable.
+   *
+   * @param $form
+   *   The views form.
+   * @param $form_state
+   *   The current state of the form, can be modified by reference.
+   * @param $context
+   *  An array of related data provided by the caller ("selection", for example).
+   */
+  public function form($form, &$form_state, $context) {
+    $form_callback = $this->operationInfo['callback'] . '_form';
+    return $form_callback($context);
+  }
+
+  /**
+   * Validates the configuration form.
+   * Only called if the operation is declared as configurable.
+   *
+   * @param $form
+   * @param $form_state
+   */
+  public function formValidate($form, &$form_state) {
+    $validation_callback = $this->operationInfo['callback'] . '_validate';
+    if (function_exists($validation_callback)) {
+      $validation_callback($form, $form_state);
+    }
+  }
+
+  /**
+   * Handles the submitted configuration form.
+   * This is where the operation can transform and store the submitted data.
+   * Only called if the operation is declared as configurable.
+   *
+   * @param $form
+   * @param $form_state
+   */
+  public function formSubmit($form, &$form_state) {
+    $submit_callback = $this->operationInfo['callback'] . '_submit';
+    $this->formOptions = $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.
+   */
+  public function needsRows() {
+    return !empty($this->operationInfo['pass rows']);
+  }
+
+  /**
+   * Executes the selected operation on the provided entity.
+   *
+   * @param $entity
+   *   The selected entity, or an array of entities, if aggregation is used.
+   * @param $context
+   *   An array of related data provided by the caller.
+   */
+  public function execute($entity, $context) {
+    $context['entity_type'] = $this->operationInfo['type'];
+    $context += $this->formOptions;
+    $context += $this->operationInfo['parameters'];
+
+    actions_do($this->operationInfo['callback'], $entity, $context);
+    // Actions that specify 'changes_property' need to be explicitly saved.
+    if (in_array('changes_property', $this->operationInfo['behavior'])) {
+      entity_save($this->entityType, $entity);
+    }
+  }
+}
diff --git a/plugins/operation_types/action.inc b/plugins/operation_types/action.inc
new file mode 100644
index 0000000..977f1d1
--- /dev/null
+++ b/plugins/operation_types/action.inc
@@ -0,0 +1,36 @@
+<?php
+
+/**
+ * @file
+ * CTools plugin. Provides support for core actions.
+ */
+
+$plugin = array(
+  'title' => t('Action'),
+  'list callback' => 'views_bulk_operations_operation_action_list',
+  'handler' => array(
+    'class' => 'action.class.php',
+  ),
+);
+
+function views_bulk_operations_operation_action_list() {
+  $action_operations = actions_list();
+  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']),
+    );
+  }
+
+  return $operations;
+}
diff --git a/plugins/operation_types/base.class.php b/plugins/operation_types/base.class.php
new file mode 100644
index 0000000..e5b4a7b
--- /dev/null
+++ b/plugins/operation_types/base.class.php
@@ -0,0 +1,142 @@
+<?php
+
+/**
+ * @file
+ * Defines the base class for operations.
+ */
+
+abstract class ViewsBulkOperationsBaseOperation {
+  /**
+   * Contains information about the current operation, as generated
+   * by the "list callback" function in the plugin file.
+   *
+   * @var array
+   */
+  protected $operationInfo;
+
+  /**
+   * Contains the options set by the admin for the current operation.
+   *
+   * @var array
+   */
+  protected $adminOptions;
+
+  /**
+   * The entity type that the operation is operating on.
+   *
+   * Not the same as $operationInfo['type'] since that value can be just
+   * "entity", which means "available to every entity type".
+   */
+  public $entityType;
+
+  public function __construct($operationInfo, $adminOptions, $entityType) {
+    $this->operationInfo = $operationInfo;
+    $this->adminOptions = $adminOptions;
+    $this->entityType = $entityType;
+  }
+
+  /**
+   * Returns the value of an admin option.
+   */
+  public function getAdminOption($key) {
+    return isset($this->adminOptions[$key]) ? $this->adminOptions[$key] : NULL;
+  }
+
+  /**
+   * Returns the access bitmask for the operation, used for entity access checks.
+   */
+  public function getAccessMask() {
+    // Assume edit by default.
+    return VBO_ACCESS_OP_UPDATE;
+  }
+
+  /**
+   * Returns the human-readable name of the operation, meant to be shown
+   * to the user.
+   */
+  public function label() {
+    $label = $this->getAdminOption('label');
+    // If the admin didn't specify any label, fallback to the default one.
+    if (!$label) {
+      $label = $this->operationInfo['label'];
+    }
+    return $label;
+  }
+
+  /**
+   * Returns whether the operation is configurable. Used to determine
+   * whether the operation's form methods should be invoked.
+   */
+  public function configurable() {
+    return !empty($this->operationInfo['configurable']);
+  }
+
+  /**
+   * Returns whether the provided account has access to execute the operation.
+   *
+   * @param $account
+   */
+  public function access($account) {
+    return TRUE;
+  }
+
+  /**
+   * Returns the configuration form for the operation.
+   * Only called if the operation is declared as configurable.
+   *
+   * @param $form
+   *   The views form.
+   * @param $form_state
+   *   The current state of the form, can be modified by reference.
+   * @param $context
+   *  An array of related data provided by the caller ("selection", for example).
+   */
+  abstract function form($form, &$form_state, $context);
+
+  /**
+   * Validates the configuration form.
+   * Only called if the operation is declared as configurable.
+   *
+   * @param $form
+   * @param $form_state
+   */
+  abstract function formValidate($form, $form_state);
+
+  /**
+   * Handles the submitted configuration form.
+   * This is where the operation can transform and store the submitted data.
+   * Only called if the operation is declared as configurable.
+   *
+   * @param $form
+   * @param $form_state
+   */
+  abstract function formSubmit($form, $form_state);
+
+  /**
+   * Returns whether the selected entities should be aggregated
+   * (loaded in bulk and passed in together).
+   * To be avoided if possible, since aggregation makes it impossible to use
+   * Batch API or the Drupal Queue for execution.
+   */
+  public function aggregate() {
+    return FALSE;
+  }
+
+  /**
+   * Returns whether the operation needs the full selected views rows to be
+   * passed to execute() as a part of $context.
+   */
+  public function needsRows() {
+    return FALSE;
+  }
+
+  /**
+   * Executes the selected operation on the provided entity.
+   *
+   * @param $entity
+   *   The selected entity, or an array of entities, if aggregation is used.
+   * @param $context
+   *   An array of related data provided by the caller.
+   */
+  abstract function execute($entity, $context);
+}
diff --git a/plugins/operation_types/rules_component.class.php b/plugins/operation_types/rules_component.class.php
new file mode 100644
index 0000000..4259a91
--- /dev/null
+++ b/plugins/operation_types/rules_component.class.php
@@ -0,0 +1,90 @@
+<?php
+
+/**
+ * @file
+ * Defines the class for rules components (rule, ruleset, action).
+ * Autoloaded by CTools. Belongs to the "rules_component" operation type plugin.
+ */
+
+class ViewsBulkOperationsRulesComponent implements ViewsBulkOperationsBaseOperation {
+  /**
+   * Returns whether the provided account has access to execute the operation.
+   *
+   * @param $account
+   */
+  public function access($account) {
+    return rules_action('component_' . $this->operationInfo['key'])->access();
+  }
+
+  /**
+   * Returns the configuration form for the operation.
+   * Only called if the operation is declared as configurable.
+   *
+   * @param $form
+   *   The views form.
+   * @param $form_state
+   *   The current state of the form, can be modified by reference.
+   * @param $context
+   *  An array of related data provided by the caller ("selection", for example).
+   */
+  public function form($form, &$form_state, $context) {
+    $entity_key = $this->operationInfo['parameters']['entity_key'];
+    $entity_type = $this->operationInfo['type'];
+    $info = entity_get_info($entity_type);
+
+    // The component is wrapped in an action set so that the configuration form
+    // has access to the entity's tokens.
+    $set = rules_action_set(array($entity_key => array('type' => $entity_type, 'label' => $info['label'])));
+    $action = rules_action('component_' . $context['action']['key'], array($entity_key . ':select' => $entity_key));
+    $set->action($action);
+    $action->form($form, $form_state);
+
+    // Remove the form element for the "entity" param. It will be passed in manually.
+    unset($form['parameter'][$entity_key]);
+
+    return $form;
+  }
+
+  /**
+   * Validates the configuration form.
+   * Only called if the operation is declared as configurable.
+   *
+   * @param $form
+   * @param $form_state
+   */
+  public function formValidate($form, $form_state) {
+    rules_ui_form_rules_config_validate($form, $form_state);
+  }
+
+  /**
+   * Stores the rules element added to the form state in form(), so that it
+   * can be used in execute().
+   * Only called if the operation is declared as configurable.
+   *
+   * @param $form
+   * @param $form_state
+   */
+  public function formSubmit($form, $form_state) {
+    $this->rulesElement = $form_state['rules_element']->root();
+  }
+
+  /**
+   * Executes the selected operation on the provided entity.
+   *
+   * @param $entity
+   *   The selected entity.
+   * @param $context
+   *   An array of related data provided by the caller.
+   */
+  public function execute($entity, $context) {
+    // If there was a config form, there's a rules_element.
+    // If not, fallback to the component key.
+    if ($this->configurable()) {
+      $element = $this->rulesElement;
+    }
+    else {
+     $element = rules_action('component_' . $context['component_key']);
+    }
+    $element->execute($entity);
+  }
+}
diff --git a/plugins/operation_types/rules_component.inc b/plugins/operation_types/rules_component.inc
new file mode 100644
index 0000000..7a28afa
--- /dev/null
+++ b/plugins/operation_types/rules_component.inc
@@ -0,0 +1,49 @@
+<?php
+
+/**
+ * @file
+ * CTools plugin. Provides support for rules components (rule, ruleset, action).
+ */
+
+$plugin = array(
+  'title' => t('Rules component'),
+  'list callback' => 'views_bulk_operations_operation_rules_component_list',
+  'handler' => array(
+    'class' => 'rules_component.class.php',
+  ),
+);
+
+function views_bulk_operations_operation_rules_component_list() {
+  if (!module_exists('rules')) {
+    return;
+  }
+
+  $entity_info = entity_get_info();
+  $entity_types = array_keys($entity_info);
+  $supported_types = array('entity');
+  foreach ($entity_types as $type) {
+    $supported_types[] = $type;
+  }
+  foreach (rules_get_components(FALSE, 'action') as $component_key => $component) {
+    $parameter_info = $component->parameterInfo();
+    $first_parameter = reset($parameter_info);
+    $parameter_keys = array_keys($parameter_info);
+    $entity_key = reset($parameter_keys);
+    // If the first param is not an entity type, skip the component.
+    if (!in_array($first_parameter['type'], $supported_types)) {
+      continue;
+    }
+
+    $operations[$component_key] = array(
+      'plugin' => 'rules_component',
+      'key' => $component_key,
+      'label' => $component->label,
+      'callback arguments' => array('component_key' => $component_key, 'entity_key' => $entity_key),
+      'configurable' => count($parameter_info) > 1,
+      'type' => $first_parameter['type'],
+      'access operation' => VBO_ACCESS_OP_UPDATE, // assume edit by default.
+    );
+  }
+
+  return $operations;
+}
diff --git a/views/views_bulk_operations_handler_field_operations.inc b/views/views_bulk_operations_handler_field_operations.inc
index 164ff1d..22a61d2 100644
--- a/views/views_bulk_operations_handler_field_operations.inc
+++ b/views/views_bulk_operations_handler_field_operations.inc
@@ -267,31 +267,15 @@ class views_bulk_operations_handler_field_operations extends views_handler_field
         continue;
       }
 
-      if (module_exists('actions_permissions')) {
-        $perm = actions_permissions_get_perm($this->all_operations[$key]['label'], $this->all_operations[$key]['callback']);
-        if (!user_access($perm)) {
-          continue;
-        }
-      }
-      if (!empty($this->all_operations[$key]['permissions'])) {
-        foreach ($this->all_operations[$key]['permissions'] as $perm) {
-          if (!user_access($perm)) {
-            continue 2;
-          }
-        }
-      }
-      if ($this->all_operations[$key]['source'] == 'rules_action') {
-        if (!rules_action('component_' . $key)->access()) {
-          continue 2;
-        }
-      }
+      $plugin = views_bulk_operations_get_operation_type($this->all_operations[$key]['plugin']);
+      $class = ctools_plugin_get_class($plugin, 'handler');
+      $operation = new $class($operation_info, $options, $entity_type);
 
-      if (!empty($this->options['vbo']['operations'][$key]['label'])) {
-        $selected[$key] = t($this->options['vbo']['operations'][$key]['label']);
-      }
-      else {
-        $selected[$key] = $this->all_operations[$key]['label'];
+      if (!$operation->access()) {
+        continue;
       }
+
+      $selected[$key] = $operation->label();
     }
     return $selected;
   }
@@ -300,18 +284,17 @@ class views_bulk_operations_handler_field_operations extends views_handler_field
     if (empty($this->all_operations[$key])) {
       return NULL;
     }
-    $operation = $this->all_operations[$key];
-    if ($operation['type'] == 'entity' || $operation['type'] == 'system') { // Actions that accept any entity type.
-      $entity_type = $this->get_entity_type();
-      $operation['type'] = $entity_type;
-    }
+    $operation_info = $this->all_operations[$key];
+    $entity_type = $this->get_entity_type();
     // Remember the original label for use in permissions.
-    $operation['original label'] = $operation['label'];
+    $operation_info['original label'] = $operation_info['label'];
     if (!empty($this->options['vbo']['operations'][$key]['label'])) {
-      $operation['label'] = t($this->options['vbo']['operations'][$key]['label']);
+      $operation_info['label'] = t($this->options['vbo']['operations'][$key]['label']);
     }
-    $operation['options'] = $this->options['vbo']['operations'][$key] + array('settings' => NULL);
-    return $operation;
+
+    $plugin = views_bulk_operations_get_operation_type($operation_info['plugin']);
+    $class = ctools_plugin_get_class($plugin, 'handler');
+    return new $class($operation_info, $this->options['vbo']['operations'][$key], $entity_type);
   }
 
   private function get_operations_options() {
@@ -335,58 +318,10 @@ class views_bulk_operations_handler_field_operations extends views_handler_field
     }
 
     $operations = array();
-
-    // Merge in Drupal actions, including those declared by VBO itself.
-    $action_operations = actions_list();
-    foreach ($action_operations as $callback => $operation) {
-      $key = isset($operation['key']) ? $operation['key'] : $callback;
-      $operations[$key] = array(
-        'key' => $key,
-        'label' => isset($operation['label']) ? $operation['label'] : '',
-        'callback' => $callback,
-        'callback arguments' => isset($operation['parameters']) ? $operation['parameters'] : array(),
-        'configurable' => !empty($operation['configurable']),
-        'source' => 'action',
-        'type' => $operation['type'],
-        'aggregate' => !empty($operation['aggregate']),
-        'behavior' => isset($operation['behavior']) ? $operation['behavior'] : array(),
-        'access operation' => $this->get_access_op($operation),
-        'permissions' => isset($operation['permissions']) ? $operation['permissions'] : NULL,
-        'pass rows' => !empty($operation['pass rows']),
-      );
-    }
-
-    // Merge in actions provided by Rules 2.
-    if (module_exists('rules')) {
-      $entity_info = entity_get_info();
-      $entity_types = array_keys($entity_info);
-      $supported_types = array('entity');
-      foreach ($entity_types as $type) {
-        $supported_types[] = $type;
-      }
-      foreach (rules_get_components(FALSE, 'action') as $component_key => $component) {
-        $parameter_info = $component->parameterInfo();
-        $first_parameter = reset($parameter_info);
-        $parameter_keys = array_keys($parameter_info);
-        $entity_key = reset($parameter_keys);
-        // If the first param is not an entity type, skip the component.
-        if (!in_array($first_parameter['type'], $supported_types)) {
-          continue;
-        }
-
-        $operations[$component_key] = array(
-          'key' => $component_key,
-          'label' => $component->label,
-          'callback' => 'views_bulk_operations_rules_action',
-          'callback arguments' => array('component_key' => $component_key, 'entity_key' => $entity_key),
-          'configurable' => count($parameter_info) > 1,
-          'source' => 'rules_action',
-          'type' => $first_parameter['type'],
-          'aggregate' => FALSE,
-          'access operation' => VBO_ACCESS_OP_UPDATE, // assume edit by default.
-          'permissions' => NULL,
-          'pass rows' => FALSE,
-        );
+    $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']();
       }
     }
 
@@ -394,28 +329,6 @@ class views_bulk_operations_handler_field_operations extends views_handler_field
     $this->all_operations = $operations;
   }
 
-  private function get_access_op($operation) {
-    // Assume edit by default.
-    if (!isset($operation['behavior'])) {
-      $operation['behavior'] = array('changes_property');
-    }
-
-    $access_op = 0;
-    if (in_array('views_property', $operation['behavior'])) {
-      $access_op |= VBO_ACCESS_OP_VIEW;
-    }
-    if (in_array('changes_property', $operation['behavior'])) {
-      $access_op |= VBO_ACCESS_OP_UPDATE;
-    }
-    if (in_array('creates_property', $operation['behavior'])) {
-      $access_op |= VBO_ACCESS_OP_CREATE;
-    }
-    if (in_array('deletes_property', $operation['behavior'])) {
-      $access_op |= VBO_ACCESS_OP_DELETE;
-    }
-    return $access_op;
-  }
-
   function get_operation_settings($operation) {
     return (isset($this->options[$operation['key']]) ? $this->options[$operation['key']] : NULL);
   }
diff --git a/views_bulk_operations.info b/views_bulk_operations.info
index 3f4a8e5..36646be 100644
--- a/views_bulk_operations.info
+++ b/views_bulk_operations.info
@@ -5,4 +5,7 @@ dependencies[] = views
 package = Views
 core = 7.x
 
+files[] = plugins/operation_types/base.class.php
+files[] = plugins/operation_types/action.class.php
+files[] = plugins/operation_types/rules_component.class.php
 files[] = views/views_bulk_operations_handler_field_operations.inc
diff --git a/views_bulk_operations.module b/views_bulk_operations.module
index 0a82683..a999f92 100644
--- a/views_bulk_operations.module
+++ b/views_bulk_operations.module
@@ -12,7 +12,7 @@ define('VBO_ACCESS_OP_CREATE',    0x04);
 define('VBO_ACCESS_OP_DELETE',    0x08);
 
 /**
- * Implementation of hook_cron_queue_info().
+ * Implements of hook_cron_queue_info().
  */
 function views_bulk_operations_cron_queue_info() {
   return array(
@@ -74,6 +74,40 @@ function views_bulk_operations_init() {
 }
 
 /**
+ * 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');
+  $plugin = 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 VBO field if it exists on the passed-in view.
  *
  * @return
@@ -244,19 +278,21 @@ function views_bulk_operations_form($form, &$form_state, $vbo) {
       '#value' => TRUE,
     );
 
-    $ops = array_keys($vbo->get_selected_operations());
-    $operation = $vbo->get_operation_info($ops[0]);
-    $form['operation'] = array('#type' => 'value', '#value' => $ops[0]);
-    if ($operation['configurable']) {
+    $selected_operations = array_keys($vbo->get_selected_operations());
+    $operation = $vbo->get_operation_info($selected_operations[0]);
+    if ($operation->configurable()) {
       $dummy_selection = array();
       foreach ($vbo->view->result as $result) {
         $dummy_selection[$result->{$vbo->view->base_field}] = $result;
       }
-      $form += _views_bulk_operations_action_form($form, $form_state, $operation, $vbo->view, $dummy_selection, $vbo->get_operation_settings($operation));
+      $context = array('selection' => $dummy_selection);
+      $form += $operation->form($form, $form_state, $context);
     }
+    $form_state['operation'] = $operation;
+
     $form['actions']['submit'] = array(
       '#type' => 'submit',
-      '#value' => $operation['label'],
+      '#value' => $operation->label(),
     );
   }
   else {
@@ -328,16 +364,13 @@ function views_bulk_operations_config_form($form, &$form_state, $view, $output)
     '#value' => t('Next'),
     '#suffix' => l(t('Cancel'), $vbo->view->get_url(), array('query' => $query)),
   );
-  drupal_set_title(t('Set parameters for %action', array('%action' => $operation['label'])), PASS_THROUGH);
+  drupal_set_title(t('Set parameters for %action', array('%action' => $operation->label())), PASS_THROUGH);
 
-  $form += _views_bulk_operations_action_form(
-    $form,
-    $form_state,
-    $operation,
-    $vbo->view,
-    $form_state['views_bulk_operations']['views_form_views_form']['selection'],
-    $vbo->get_operation_settings($operation)
+  $context = array(
+    'selection' => $form_state['views_bulk_operations']['views_form_views_form']['selection'],
   );
+  $form += $operation->form($form, $form_state, $context);
+
   return $form;
 }
 
@@ -350,7 +383,7 @@ function views_bulk_operations_confirm_form($form, &$form_state, $view, $output)
   $rows = $form_state['views_bulk_operations']['views_form_views_form']['selection'];
   $query = drupal_get_query_parameters($_GET, array('q'));
   $form = confirm_form($form,
-    t('Are you sure you want to perform %action on selected rows?', array('%action' => $operation['label'])),
+    t('Are you sure you want to perform %action on selected rows?', array('%action' => $operation->label())),
     array('path' => $view->get_url(), 'query' => $query),
     theme('views_bulk_operations_confirmation', array('rows' => $rows, 'vbo' => $vbo))
   );
@@ -392,9 +425,9 @@ function views_bulk_operations_form_validate($form, &$form_state) {
   switch ($form_state['step']) {
     case 'views_form_views_form':
       if ($form_state['values']['single_operation']) {
-        $operation = $vbo->get_operation_info($form_state['values']['operation']);
-        if ($operation['configurable']) {
-          _views_bulk_operations_action_validate($operation, $form, $form_state);
+        $operation = $form_state['operation'];
+        if ($operation->configurable()) {
+          $operation->formValidate($form, $form_state);
         }
       }
       else {
@@ -427,8 +460,8 @@ function views_bulk_operations_form_validate($form, &$form_state) {
       break;
 
     case 'views_bulk_operations_config_form':
-      $operation = $vbo->get_operation_info($form_state['views_bulk_operations']['views_form_views_form']['operation']);
-      _views_bulk_operations_action_validate($operation, $form, $form_state);
+      $operation = $form_state['operation'];
+      $operation->formValidate($form, $form_state);
       break;
   }
 }
@@ -482,21 +515,22 @@ function views_bulk_operations_form_submit($form, &$form_state) {
         $vbo
       );
 
-      $operation = $vbo->get_operation_info($form_state['values']['operation']);
       if ($form_state['values']['single_operation']) {
-        if ($operation['configurable']) {
-          $form_state['views_bulk_operations']['operation_arguments'] = _views_bulk_operations_action_submit($operation, $form, $form_state);
+        $operation = $form_state['operation'];
+        if ($operation->configurable()) {
+          $operation->formSubmit($form, $form_state);
         }
-        if (!empty($operation['options']['skip_confirmation'])) {
+        if ($operation->getAdminOption('skip_confirmation')) {
           break; // Go directly to execution
         }
         $form_state['step'] = 'views_bulk_operations_confirm_form';
       }
       else {
-        if (!$operation['configurable'] && !empty($operation['options']['skip_confirmation'])) {
+        $operation = $vbo->get_operation_info($form_state['values']['operation']);
+        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['step'] = $operation->configurable() ? 'views_bulk_operations_config_form' : 'views_bulk_operations_confirm_form';
       }
       $form_state['single_operation'] = $form_state['values']['single_operation'];
       $form_state['rebuild'] = TRUE;
@@ -504,10 +538,9 @@ function views_bulk_operations_form_submit($form, &$form_state) {
 
     case 'views_bulk_operations_config_form':
       $form_state['step'] = 'views_bulk_operations_confirm_form';
-      $form_state['views_bulk_operations']['views_bulk_operations_config_form'] = $form_state['values'];
-      $operation = $vbo->get_operation_info($form_state['views_bulk_operations']['views_form_views_form']['operation']);
-      $form_state['views_bulk_operations']['operation_arguments'] = _views_bulk_operations_action_submit($operation, $form, $form_state);
-      if (!empty($operation['options']['skip_confirmation'])) {
+      $operation = $form_state['operation'];
+      $operation->formSubmit($form, $form_state);
+      if ($operation->getAdminOption('skip_confirmation')) {
         break; // Go directly to execution
       }
       $form_state['rebuild'] = TRUE;
@@ -521,22 +554,11 @@ function views_bulk_operations_form_submit($form, &$form_state) {
   unset($_SESSION['vbo_values'][$_GET['q']]);
 
   // Execute the VBO.
-  $operation = $vbo->get_operation_info($form_state['views_bulk_operations']['views_form_views_form']['operation']);
-  $operation_arguments = array();
-  if ($operation['configurable']) {
-    if ($form_state['single_operation']) {
-      $form_state['values'] += $form_state['views_bulk_operations']['views_form_views_form'];
-    }
-    else {
-      $form_state['values'] += $form_state['views_bulk_operations']['views_bulk_operations_config_form'];
-    }
-    $operation_arguments = $form_state['views_bulk_operations']['operation_arguments'];
-  }
+  $operation = $form_state['operation'];
   _views_bulk_operations_execute(
     $vbo,
     $form_state['views_bulk_operations']['views_form_views_form']['selection'],
     $operation,
-    $operation_arguments,
     array('execution_type' => $vbo->options['vbo']['execution_type'], 'display_result' => $vbo->options['vbo']['display_result'])
   );
 
@@ -572,34 +594,28 @@ function views_bulk_operations_form_submit($form, &$form_state) {
  *     Adds each entity separately to the queue, to be processed by a worker
  *     function, which usually happens on cron.
  */
-function _views_bulk_operations_execute($vbo, $rows, $operation, $operation_arguments, $options) {
+function _views_bulk_operations_execute($vbo, $rows, $operation, $options) {
   global $user;
 
   // Add action arguments, callback arguments, other settings needed by helper
   // functions for specific execution types.
   $params = array();
-  if ($operation['configurable'] && is_array($operation_arguments)) {
-    $params += $operation_arguments;
-  }
-  if (isset($operation['callback arguments'])) {
-    $params += $operation['callback arguments'];
-  }
   $params['revision'] = $vbo->revision;
   $params['entity_load_capacity'] = $vbo->options['vbo']['entity_load_capacity'];
 
   // If the selected rows need to be passed to the action, add them in.
-  if ($operation['pass rows']) {
+  if ($operation->needsRows()) {
     foreach ($rows as $entity_id => $row_index) {
       $rows[$entity_id] = $vbo->view->result[$row_index];
     }
   }
 
-  if (!$operation['aggregate'] && !empty($operation['options']['use_queue'])) {
+  if (!$operation->aggregate() && $operation->getAdminOption('use_queue')) {
     $entity_type = $vbo->get_entity_type();
     foreach ($rows as $entity_id => $row) {
       $job = array(
         'description' => t('Perform %action on @type !entity_id.', array(
-          '%action' => $operation['label'],
+          '%action' => $operation->label(),
           '@type' => $entity_type,
           '!entity_id' => $entity_id
         )),
@@ -610,11 +626,11 @@ function _views_bulk_operations_execute($vbo, $rows, $operation, $operation_argu
     }
     if ($options['display_result']) {
       drupal_set_message(t('Enqueued the selected operation (%action).', array(
-        '%action' => $operation['label'],
+        '%action' => $operation->label(),
       )));
     }
   }
-  elseif (!$operation['aggregate'] && $params['entity_load_capacity'] < count($rows)) {
+  elseif (!$operation->aggregate() && $params['entity_load_capacity'] < count($rows)) {
     // Save the options in the session because Batch API doesn't give a way to
     // send a parameter to the finished callback.
     $_SESSION['vbo_options']['display_result'] = $options['display_result'];
@@ -627,7 +643,7 @@ function _views_bulk_operations_execute($vbo, $rows, $operation, $operation_argu
       ),
       'finished' => '_views_bulk_operations_execute_finished',
       'progress_message' => '',
-      'title' => t('Performing %action on selected rows...', array('%action' => $operation['label'])),
+      'title' => t('Performing %action on selected rows...', array('%action' => $operation->label())),
     );
     batch_set($batch);
   }
@@ -648,7 +664,7 @@ function _views_bulk_operations_execute($vbo, $rows, $operation, $operation_argu
 function _views_bulk_operations_queue_process($data) {
   list($entity_id, $row, $operation, $params, $uid, $display_result) = $data['arguments'];
 
-  $entity_type = $operation['type'];
+  $entity_type = $operation->entityType;
   $entities = _views_bulk_operations_entity_load($entity_type, array($entity_id), $params['revision']);
   $entity = reset($entities);
   // No entity found. It might have been deleted in the meantime. Abort.
@@ -659,7 +675,7 @@ function _views_bulk_operations_queue_process($data) {
   $account = user_load($uid);
   if (!_views_bulk_operations_entity_access($operation, $entity_type, $entity, $account)) {
     watchdog('views bulk operations', 'Skipped %action on @type %title due to insufficient permissions.', array(
-      '%action' => $operation['label'],
+      '%action' => $operation->label(),
       '@type' => $entity_type,
       '%title' => _views_bulk_operations_entity_label($entity_type, $entity),
     ), WATCHDOG_ALERT);
@@ -667,14 +683,14 @@ function _views_bulk_operations_queue_process($data) {
   }
 
   // Pass the selected row to the action if needed.
-  if ($operation['pass rows']) {
+  if ($operation->needsRows()) {
     $params['rows'] = array($entity_id => $row);
   }
-  _views_bulk_operations_action_do($operation, $entity_id, $entity, $params);
+  _views_bulk_operations_action_do($operation, $entity, $params);
 
   if ($display_result) {
     watchdog('views bulk operations', 'Performed %action on @type %title.', array(
-      '%action' => $operation['label'],
+      '%action' => $operation->label(),
       '@type' => $entity_type,
       '%title' => _views_bulk_operations_entity_label($entity_type, $entity),
     ), WATCHDOG_INFO);
@@ -697,13 +713,13 @@ function _views_bulk_operations_batch_process($rows, &$context) {
   $remaining = $context['sandbox']['max'] - $context['sandbox']['progress'];
   $count = min($params['entity_load_capacity'], $remaining);
   $row_group = array_slice($rows, $context['sandbox']['progress'], $count, TRUE);
-  $entity_type = $operation['type'];
+  $entity_type = $operation->entityType;
 
   $entities = _views_bulk_operations_entity_load($entity_type, array_keys($row_group), $params['revision']);
   foreach ($entities as $entity_id => $entity) {
     if (!_views_bulk_operations_entity_access($operation, $entity_type, $entity)) {
       $context['results']['log'][] = t('Skipped %action on @type %title due to insufficient permissions.', array(
-        '%action' => $operation['label'],
+        '%action' => $operation->label(),
         '@type' => $entity_type,
         '%title' => _views_bulk_operations_entity_label($entity_type, $entity),
       ));
@@ -712,13 +728,13 @@ function _views_bulk_operations_batch_process($rows, &$context) {
     }
 
     // Pass the selected row to the action if needed.
-    if ($operation['pass rows']) {
+    if ($operation->needsRows()) {
       $params['rows'] = array($entity_id => $rows[$entity_id]);
     }
-    _views_bulk_operations_action_do($operation, $entity_id, $entity, $params);
+    _views_bulk_operations_action_do($operation, $entity, $params);
 
     $context['results']['log'][] = t('Performed %action on @type %title.', array(
-      '%action' => $operation['label'],
+      '%action' => $operation->label(),
       '@type' => $entity_type,
       '%title' => _views_bulk_operations_entity_label($entity_type, $entity),
     ));
@@ -745,14 +761,14 @@ function _views_bulk_operations_batch_process($rows, &$context) {
  * Helper function for direct execution operations.
  */
 function _views_bulk_operations_direct_process($operation, $rows, $params, &$context) {
-  $entity_type = $operation['type'];
+  $entity_type = $operation->entityType;
   $entities = _views_bulk_operations_entity_load($entity_type, array_keys($rows), $params['revision']);
 
   foreach ($entities as $id => $entity) {
     if (!_views_bulk_operations_entity_access($operation, $entity_type, $entity)) {
       unset($entities[$id]);
       $context['results']['log'][] = t('Skipped %action on @type %title due to insufficient permissions.', array(
-        '%action' => $operation['label'],
+        '%action' => $operation->label(),
         '@type' => $entity_type,
         '%title' => _views_bulk_operations_entity_label($entity_type, $entity),
       ));
@@ -762,15 +778,15 @@ function _views_bulk_operations_direct_process($operation, $rows, $params, &$con
     return;
   }
 
-  if ($operation['aggregate']) {
+  if ($operation->aggregate()) {
     // Pass the selected rows to the action if needed.
-    if ($operation['pass rows']) {
+    if ($operation->needsRows()) {
       $params['rows'] = $rows;
     }
-    _views_bulk_operations_action_aggregate_do($operation, $entities, $params);
+    _views_bulk_operations_action_do($operation, $entities, $params);
 
     $context['results']['log'][] = t('Performed aggregate %action on @types %ids.', array(
-      '%action' => $operation['label'],
+      '%action' => $operation->label(),
       '@types' => format_plural(count($entities), $entity_type, $entity_type . 's'),
       '%ids' => implode(',', array_keys($entities)),
     ));
@@ -779,13 +795,13 @@ function _views_bulk_operations_direct_process($operation, $rows, $params, &$con
   else {
     foreach ($entities as $entity_id => $entity) {
       // Pass the selected row to the action if needed.
-      if ($operation['pass rows']) {
+      if ($operation->needsRows()) {
         $params['rows'] = array($entity_id => $rows[$entity_id]);
       }
-      _views_bulk_operations_action_do($operation, $entity_id, $entity, $params);
+      _views_bulk_operations_action_do($operation, $entity, $params);
 
       $context['results']['log'][] = t('Performed %action on @type %title.', array(
-        '%action' => $operation['label'],
+        '%action' => $operation->label(),
         '@type' => $entity_type,
         '%title' => _views_bulk_operations_entity_label($entity_type, $entity),
       ));
@@ -830,37 +846,7 @@ function _views_bulk_operations_execute_finished($success, $results, $operations
 /**
  * Helper function to execute one operation.
  */
-function _views_bulk_operations_action_do($operation, $entity_id, $entity, $params, $account = NULL) {
-  _views_bulk_operations_action_permission($operation, $account);
-
-  $params['entity_type'] = $operation['type'];
-
-  if ($operation['source'] == 'action') {
-    actions_do($operation['callback'], $entity, $params);
-    // Actions that specify 'changes_property' need to be explicitly saved.
-    if (in_array('changes_property', $operation['behavior'])) {
-      entity_save($operation['type'], $entity);
-    }
-  }
-  elseif ($operation['source'] == 'rules_action') {
-    call_user_func_array($operation['callback'], array('entity' => $entity, 'context' => $params));
-  }
-}
-
-/**
- * Helper function to execute an aggregate operation.
- */
-function _views_bulk_operations_action_aggregate_do($operation, $entities, $params) {
-  _views_bulk_operations_action_permission($operation);
-
-  $params['entity_type'] = $operation['type'];
-  actions_do($operation['callback'], $entities, $params);
-}
-
-/**
- * Helper function to verify access permission to execute operation.
- */
-function _views_bulk_operations_action_permission($operation, $account = NULL) {
+function _views_bulk_operations_action_do($operation, $entity, $params, $account = NULL) {
   global $user;
 
   if (!$account) {
@@ -868,37 +854,14 @@ function _views_bulk_operations_action_permission($operation, $account = NULL) {
   }
   $user_label = _views_bulk_operations_entity_label('user', $account);
 
-  if (module_exists('actions_permissions')) {
-    $perm = actions_permissions_get_perm($operation['original label'], $operation['callback']);
-    if (!user_access($perm, $account)) {
-      watchdog('actions permissions', 'An attempt by user %user to !perm was blocked due to insufficient permissions.',
-        array('!perm' => $perm, '%user' => $user_label), WATCHDOG_ALERT);
-      drupal_access_denied();
-      drupal_exit();
-    }
+  if ($operation->access($account)) {
+    watchdog('actions permissions', 'An attempt by user %user to run operation %operation was blocked due to insufficient permissions.',
+      array('%operation' => $operation->label(), '%user' => $user_label), WATCHDOG_ALERT);
+    drupal_access_denied();
+    drupal_exit();
   }
 
-  // Check against additional permissions.
-  if (!empty($operation['permissions'])) {
-    foreach ($operation['permissions'] as $perm) {
-      if (!user_access($perm, $account)) {
-        watchdog('actions permissions', 'An attempt by user %user to !perm was blocked due to insufficient permissions.',
-          array('!perm' => $perm, '%user' => $user_label), WATCHDOG_ALERT);
-        drupal_access_denied();
-        drupal_exit();
-      }
-    }
-  }
-
-  // If this is a rules action, check its permissions.
-  if ($operation['source'] == 'rules_action') {
-    if (!rules_action('component_' . $operation['key'])->access()) {
-      watchdog('actions permissions', 'An attempt by user %user to rules action !key was blocked due to insufficient permissions.',
-        array('!key' => $operation['key'], '%user' => $user_label), WATCHDOG_ALERT);
-      drupal_access_denied();
-      drupal_exit();
-    }
-  }
+  $operation->execute($entity, $params);
 }
 
 /**
@@ -916,7 +879,7 @@ function _views_bulk_operations_entity_access($operation, $entity_type, $entity,
     VBO_ACCESS_OP_DELETE => 'delete',
   );
   foreach ($access_ops as $bit => $op) {
-    if ($operation['access operation'] & $bit) {
+    if ($operation->getAccessMask() & $bit) {
       if (!entity_access($op, $entity_type, $entity, $account)) {
         return FALSE;
       }
@@ -927,46 +890,6 @@ function _views_bulk_operations_entity_access($operation, $entity_type, $entity,
 }
 
 /**
- * Helper function to let the configurable action provide its configuration form.
- */
-function _views_bulk_operations_action_form($form, &$form_state, $action, $view, $selection, $settings) {
-  $action_form = $action['callback'].'_form';
-  $context = array(
-    'view' => $view,
-    'selection' => $selection,
-    'settings' => $settings,
-    'action' => $action,
-  );
-
-  if ($action['source'] == 'rules_action') {
-    $form = $action_form($form, $form_state, $context);
-  }
-  else {
-    $form = $action_form($context);
-  }
-
-  return $form;
-}
-
-/**
- * Helper function to let the configurable action validate the form if it provides a validator.
- */
-function _views_bulk_operations_action_validate($action, $form, $form_state) {
-  $action_validate = $action['callback'].'_validate';
-  if (function_exists($action_validate)) {
-    $action_validate($form, $form_state);
-  }
-}
-
-/**
- * Helper function to let the configurable action process the configuration form.
- */
-function _views_bulk_operations_action_submit($action, $form, $form_state) {
-  $action_submit = $action['callback'].'_submit';
-  return $action_submit($form, $form_state);
-}
-
-/**
  * Theme function to show the confirmation page before executing the action.
  */
 function theme_views_bulk_operations_confirmation($variables) {
