diff --git a/includes/rules.core.inc b/includes/rules.core.inc
index de99e3d..3a5f605 100644
--- a/includes/rules.core.inc
+++ b/includes/rules.core.inc
@@ -258,6 +258,7 @@ class RulesEntityController extends EntityAPIControllerExportable {
    */
   public function delete($ids, DatabaseTransaction $transaction = NULL) {
     $transaction = isset($transaction) ? $transaction : db_transaction();
+
     // Use entity-load as ids may be the names as well as the ids.
     $configs = $ids ? entity_load('rules_config', $ids) : array();
     if ($configs) {
@@ -1690,7 +1691,15 @@ abstract class RulesAbstractPlugin extends RulesPlugin {
         'callbacks' => array(),
       );
       unset($info['arguments'], $info['new variables']);
-      $info['callbacks'] += array('execute' => $info['base']);
+
+      if (function_exists($info['base'])) {
+        $info['callbacks'] += array('execute' => $info['base']);
+      }
+
+      // We do not need to build a faces cache for RulesPluginHandlerInterface,
+      // which gets added in automatically as its a parent of
+      // RulesPluginImplInterface.
+      unset($this->faces['RulesPluginHandlerInterface']);
 
       // Build up the per plugin implementation faces cache.
       foreach ($this->faces as $interface) {
@@ -1702,6 +1711,9 @@ abstract class RulesAbstractPlugin extends RulesPlugin {
             $methods[$method][0] = $function;
             $file_names[$method] = $this->getFileName($function, $includes);
           }
+          elseif (isset($info['class']) && ($interface == 'RulesPluginImplInterface' || is_subclass_of($info['class'], $interface))) {
+            $methods[$method][1] = $info['class'];
+          }
           elseif (function_exists($function = $info['base'] . '_' . $method)) {
             $methods[$method][0] = $function;
             $file_names[$method] = $this->getFileName($function, $includes);
@@ -1734,7 +1746,7 @@ abstract class RulesAbstractPlugin extends RulesPlugin {
   /**
    * Make sure all supported destinations are included.
    */
-  protected static function includeFiles() {
+  public static function includeFiles() {
     static $included;
 
     if (!isset($included)) {
@@ -1858,15 +1870,9 @@ interface RulesTriggerableInterface {
 }
 
 /**
- * Provides the interface used for implementing an abstract plugin by using
- * the Faces extension mechanism.
+ * Provides the base interface for implementing abstract plugins via classes.
  */
-interface RulesPluginImplInterface {
-
-  /**
-   * Execute the action or condition making use of the parameters as specified.
-   */
-  public function execute();
+interface RulesPluginHandlerInterface {
 
   /**
    * Validates $settings independent from a form submission.
@@ -1933,6 +1939,29 @@ interface RulesPluginImplInterface {
 }
 
 /**
+ * Interface for implementing conditions via classes.
+ */
+interface RulesConditionHandlerInterface extends RulesPluginHandlerInterface {}
+
+/**
+ * Interface for implementing actions via classes.
+ */
+interface RulesActionHandlerInterface extends RulesPluginHandlerInterface {}
+
+/**
+ *
+ * Provides the interface used for implementing an abstract plugin by using
+ * the Faces extension mechanism.
+ */
+interface RulesPluginImplInterface extends RulesPluginHandlerInterface {
+
+  /**
+   * Execute the action or condition making use of the parameters as specified.
+   */
+  public function execute();
+}
+
+/**
  * Interface for optimizing evaluation.
  *
  * @see RulesContainerPlugin::optimize()
@@ -1945,19 +1974,9 @@ interface RulesOptimizationInterface {
 }
 
 /**
- * Class providing default implementations of the methods of the RulesPluginImplInterface.
- *
- * If a plugin implementation does not provide a function for a method, the
- * default method of this class will be invoked.
- *
- * @see RulesPluginImplInterface
- * @see RulesAbstractPlugin
+ * Base class for implementing abstract plugins via classes.
  */
-class RulesAbstractPluginDefaults extends FacesExtender implements RulesPluginImplInterface {
-
-  public function execute() {
-    throw new RulesEvaluationException($this->object->getPluginName() .": Execution implementation is missing.", array(), $this->object, RulesLog::ERROR);
-  }
+abstract class RulesPluginHandlerBase extends FacesExtender implements RulesPluginHandlerInterface {
 
   /**
    * Implements RulesPluginImplInterface.
@@ -1975,6 +1994,32 @@ class RulesAbstractPluginDefaults extends FacesExtender implements RulesPluginIm
 }
 
 /**
+ * Base class for implementing conditions via classes.
+ */
+abstract class RulesConditionHandlerBase extends RulesPluginHandlerBase implements RulesConditionHandlerInterface {}
+
+/**
+ * Base class for implementing actions via classes.
+ */
+abstract class RulesActionHandlerBase extends RulesPluginHandlerBase implements RulesActionHandlerInterface {}
+
+/**
+ * Class providing default implementations of the methods of the RulesPluginImplInterface.
+ *
+ * If a plugin implementation does not provide a function for a method, the
+ * default method of this class will be invoked.
+ *
+ * @see RulesPluginImplInterface
+ * @see RulesAbstractPlugin
+ */
+class RulesAbstractPluginDefaults extends RulesPluginHandlerBase implements RulesPluginImplInterface {
+
+  public function execute() {
+    throw new RulesEvaluationException($this->object->getPluginName() .": Execution implementation is missing.", array(), $this->object, RulesLog::ERROR);
+  }
+}
+
+/**
  * A RecursiveIterator for rule elements.
  */
 class RulesRecursiveElementIterator extends ArrayIterator implements RecursiveIterator {
diff --git a/includes/rules.plugins.inc b/includes/rules.plugins.inc
index 85b7296..b6d0588 100644
--- a/includes/rules.plugins.inc
+++ b/includes/rules.plugins.inc
@@ -770,6 +770,7 @@ class RulesEventSet extends RulesRuleSet {
         if (empty($events[$event_base_name])) {
           continue;
         }
+
         // Create an event set if not yet done.
         if (!isset($sets[$event_name])) {
           $handler = rules_get_event_handler($event_name, $rule->getEventSettings($event_name));
diff --git a/modules/node.eval.inc b/modules/node.eval.inc
index ee96e15..f1ea69e 100644
--- a/modules/node.eval.inc
+++ b/modules/node.eval.inc
@@ -9,31 +9,97 @@
  */
 
 /**
+ * Base class providing node condition defaults.
+ */
+abstract class RulesNodeConditionBase extends RulesConditionHandlerBase {
+
+  public static function defaults() {
+    return array(
+      'module' => 'rules',
+      'parameter' => array(
+        'node' => array('type' => 'node', 'label' => t('Content')),
+      ),
+      'group' => t('Node'),
+      'access callback' => 'rules_node_integration_access',
+    );
+  }
+}
+
+/**
  * Condition: Check for selected content types
  */
-function rules_condition_node_is_of_type($node, $types) {
-  return in_array($node->type, $types);
+class RulesNodeConditionType extends RulesNodeConditionBase {
+
+  public static function getInfo() {
+    $info = self::defaults() + array(
+      'name' => 'node_is_of_type',
+      'label' => t('Content is of type'),
+      'help' => t('Evaluates to TRUE if the given content is of one of the selected content types.'),
+    );
+    $info['parameter']['type'] = array(
+      'type' => 'list<text>',
+      'label' => t('Content types'),
+      'options list' => 'node_type_get_names',
+      'description' => t('The content type(s) to check for.'),
+      'restriction' => 'input',
+    );
+    return $info;
+  }
+
+  public function execute($node, $types) {
+    return in_array($node->type, $types);
+  }
 }
 
 /**
- * Condition: Check if the node is published
+ * Condition: Check if the node is published.
  */
-function rules_condition_node_is_published($node) {
-  return $node->status == 1;
+class RulesNodeConditionPublished extends RulesNodeConditionBase {
+
+  public static function getInfo() {
+    return self::defaults() + array(
+      'name' => 'node_is_published',
+      'label' => t('Content is published'),
+    );
+  }
+
+  public function execute($node) {
+    return $node->status == 1;
+  }
 }
 
 /**
- * Condition: Check if the node is sticky
+ * Condition: Check if the node is sticky.
  */
-function rules_condition_node_is_sticky($node) {
-  return $node->sticky == 1;
+class RulesNodeConditionSticky extends RulesNodeConditionBase {
+
+  public static function getInfo() {
+    return self::defaults() + array(
+      'name' => 'node_is_sticky',
+      'label' => t('Content is sticky'),
+    );
+  }
+
+  public function execute($node) {
+    return $node->sticky == 1;
+  }
 }
 
 /**
  * Condition: Check if the node is promoted to the frontpage
  */
-function rules_condition_node_is_promoted($node) {
-  return $node->promote == 1;
+class RulesNodeConditionPromoted extends RulesNodeConditionBase {
+
+  public static function getInfo() {
+    return self::defaults() + array(
+      'name' => 'node_is_promoted',
+      'label' => t('Content is promoted to frontpage'),
+    );
+  }
+
+  public function execute($node) {
+    return $node->promote == 1;
+  }
 }
 
 /**
diff --git a/modules/node.rules.inc b/modules/node.rules.inc
index 9fea011..ee64586 100644
--- a/modules/node.rules.inc
+++ b/modules/node.rules.inc
@@ -89,44 +89,6 @@ function rules_events_node_variables($node_label, $update = FALSE) {
 }
 
 /**
- * Implements hook_rules_condition_info() on behalf of the node module.
- */
-function rules_node_condition_info() {
-  $defaults = array(
-    'parameter' => array(
-      'node' => array('type' => 'node', 'label' => t('Content')),
-    ),
-    'group' => t('Node'),
-    'access callback' => 'rules_node_integration_access',
-  );
-  $items['node_is_of_type'] = $defaults + array(
-    'label' => t('Content is of type'),
-    'help' => t('Evaluates to TRUE if the given content is of one of the selected content types.'),
-    'base' => 'rules_condition_node_is_of_type',
-  );
-  $items['node_is_of_type']['parameter']['type'] = array(
-    'type' => 'list<text>',
-    'label' => t('Content types'),
-    'options list' => 'node_type_get_names',
-    'description' => t('The content type(s) to check for.'),
-    'restriction' => 'input',
-  );
-  $items['node_is_published'] = $defaults + array(
-    'label' => t('Content is published'),
-    'base' => 'rules_condition_node_is_published',
-  );
-  $items['node_is_sticky'] = $defaults + array(
-    'label' => t('Content is sticky'),
-    'base' => 'rules_condition_node_is_sticky',
-  );
-  $items['node_is_promoted'] = $defaults + array(
-    'label' => t('Content is promoted to frontpage'),
-    'base' => 'rules_condition_node_is_promoted',
-  );
-  return $items;
-}
-
-/**
  * Provides the content type of a node as asserted metadata.
  */
 function rules_condition_node_is_of_type_assertions($element) {
diff --git a/modules/rules_core.rules.inc b/modules/rules_core.rules.inc
index 9edf446..4b8889b 100644
--- a/modules/rules_core.rules.inc
+++ b/modules/rules_core.rules.inc
@@ -117,6 +117,11 @@ function rules_rules_core_data_info() {
         'group' => t('Entity'),
         'ui class' => empty($info['exportable']) ? 'RulesDataUIEntity' : 'RulesDataUIEntityExportable',
       );
+      // If this entity type serves as bundle for another one, provide an
+      // options list for selecting a bundle entity.
+      if (!empty($info['bundle of'])) {
+        $return[$type]['ui class'] =  'RulesDataUIBundleEntity';
+      }
     }
   }
 
diff --git a/rules.api.php b/rules.api.php
index e672117..772f5c4 100644
--- a/rules.api.php
+++ b/rules.api.php
@@ -343,7 +343,9 @@ function hook_rules_event_info() {
  *     configuration UI to configure parameters of this type. The given class
  *     must extend RulesDataUI and may implement the
  *     RulesDataDirectInputFormInterface in order to allow the direct data input
- *     configuration mode. Defaults to RulesDataUI.
+ *     configuration mode. For supporting selecting values from options lists,
+ *     the UI class may implement RulesDataInputOptionsListInterface also.
+ *     Defaults to RulesDataUI.
  *   - wrap: (optional) If set to TRUE, the data is wrapped internally using
  *     wrappers provided by the entity API module. This is required for entities
  *     and data structures to support selecting a property via the data selector
@@ -973,5 +975,37 @@ function hook_rules_ui_menu_alter(&$items, $base_path, $base_count) {
 }
 
 /**
+ * Control access to Rules configurations.
+ *
+ * Modules may implement this hook if they want to have a say in whether or not
+ * a given user has access to perform a given operation on a Rules
+ * configuration.
+ *
+ * @param $op
+ *   The operation being performed. One of 'view', 'create', 'update' or
+ *   'delete'.
+ * @param $rules_config
+ *   (optional) A Rules configuration to check access for. If nothing is given,
+ *   access for all Rules configurations is determined.
+ * @param $account
+ *   (optional) The user to check for. If no account is passed, access is
+ *   determined for the current user.
+ * @return boolean
+ *   Return TRUE to grant access, FALSE to explicitly deny access. Return NULL
+ *   or nothing to not affect the operation.
+ *   Access is granted as soon as a module grants access and no one denies
+ *   access. Thus if no module explicitly grants access, access will be denied.
+ *
+ * @see rules_config_access()
+ */
+function hook_rules_config_access($op, $rules_config = NULL, $account = NULL) {
+  // Instead of returning FALSE return nothing, so others still can grant
+  // access.
+  if (isset($rules_config) && $rules_config->owner == 'mymodule' && user_access('my modules permission')) {
+    return TRUE;
+  }
+}
+
+/**
  * @}
  */
diff --git a/rules.info b/rules.info
index d7ab7fe..e80769d 100644
--- a/rules.info
+++ b/rules.info
@@ -11,6 +11,7 @@ files[] = includes/rules.processor.inc
 files[] = includes/rules.plugins.inc
 files[] = includes/rules.state.inc
 files[] = includes/rules.dispatcher.inc
+files[] = modules/node.eval.inc
 files[] = modules/php.eval.inc
 files[] = modules/rules_core.eval.inc
 files[] = modules/system.eval.inc
diff --git a/rules.module b/rules.module
index 97f4c71..69de703 100644
--- a/rules.module
+++ b/rules.module
@@ -206,6 +206,11 @@ function rules_log($msg, $args = array(), $priority = RulesLog::INFO, RulesPlugi
  */
 function rules_fetch_data($hook) {
   $data = &drupal_static(__FUNCTION__, array());
+  static $discover = array(
+    'action_info' => 'RulesActionHandlerInterface',
+    'condition_info' => 'RulesConditionHandlerInterface',
+    'event_info' => 'RulesEventHandlerInterface',
+  );
 
   if (!isset($data[$hook])) {
     foreach (module_implements('rules_' . $hook) as $module) {
@@ -217,12 +222,40 @@ function rules_fetch_data($hook) {
         }
       }
     }
+    // Support class discovery.
+    if (isset($discover[$hook])) {
+      $data[$hook] += rules_discover_plugins($discover[$hook]);
+    }
     drupal_alter('rules_'. $hook, $data[$hook]);
   }
   return $data[$hook];
 }
 
 /**
+ * Discover plugin implementations.
+ *
+ * @param string $class
+ *   The class or interface the plugins must implement. For a plugin to be
+ *   discovered it must have a static getInfo() method also.
+ *
+ * @return array
+ *   An info-hook style array containing info about discovered plugins.
+ */
+function rules_discover_plugins($class) {
+  // Make sure all files possibly holding plugins are included.
+  RulesAbstractPlugin::includeFiles();
+
+  $items = array();
+  foreach (get_declared_classes() as $plugin_class) {
+    if (is_subclass_of($plugin_class, $class) && method_exists($plugin_class, 'getInfo')) {
+      $info = call_user_func(array($plugin_class, 'getInfo'));
+      $items[$info['name']] = $info;
+    }
+  }
+  return $items;
+}
+
+/**
  * Gets a rules cache entry.
  */
 function &rules_get_cache($cid = 'data') {
@@ -1381,10 +1414,32 @@ function rules_config_access($op, $rules_config = NULL, $account = NULL) {
   if (user_access('bypass rules access', $account)) {
     return TRUE;
   }
-  if (!isset($rules_config) || (isset($account) && $account->uid != $GLOBALS['user']->uid)) {
+  // Allow modules to grant / deny access.
+  $access = module_invoke_all('rules_config_access', $op, $rules_config, $account);
+
+  // Only grant access if at least one module granted access and no one denied
+  // access.
+  if (in_array(FALSE, $access, TRUE)) {
     return FALSE;
   }
-  return user_access('administer rules', $account) && ($op == 'view' || $rules_config->access());
+  elseif (in_array(TRUE, $access, TRUE)) {
+    return TRUE;
+  }
+  return FALSE;
+}
+
+/**
+ * Implements hook_rules_config_access().
+ */
+function rules_rules_config_access($op, $profile = NULL, $account = NULL) {
+  // Instead of returning FALSE return nothing, so others still can grant
+  // access.
+  if (!isset($rules_config) || (isset($account) && $account->uid != $GLOBALS['user']->uid)) {
+    return;
+  }
+  if (user_access('administer rules', $account) && ($op == 'view' || $rules_config->access())) {
+    return TRUE;
+  }
 }
 
 /**
diff --git a/rules_scheduler/rules_scheduler.rules.inc b/rules_scheduler/rules_scheduler.rules.inc
index 473a810..e35cc1f 100644
--- a/rules_scheduler/rules_scheduler.rules.inc
+++ b/rules_scheduler/rules_scheduler.rules.inc
@@ -73,7 +73,7 @@ function rules_scheduler_component_options_list() {
  * Base action implementation for scheduling components.
  */
 function rules_scheduler_action_schedule($args, $element) {
-  $state = $args['state'];
+  $state = $args['data'];
   if ($component = rules_get_cache('comp_' . $args['component'])) {
     // Manually create a new evaluation state for scheduling the evaluation.
     $new_state = new RulesState();
diff --git a/ui/ui.core.inc b/ui/ui.core.inc
index 163c0f8..8b47b0b 100644
--- a/ui/ui.core.inc
+++ b/ui/ui.core.inc
@@ -671,15 +671,20 @@ class RulesPluginUI extends FacesExtender implements RulesPluginUIInterface {
    * @param $data_type
    *   The name of the data typ
    * @param $parameter_info
-   *   (optional) An array of info about the to be configured parameter.
+   *   (optional) An array of info about the to be configured parameter. If
+   *   given, this array is complemented with data type defaults also.
    */
-  public function getDataTypeClass($data_type, $parameter_info = array()) {
-    if (!empty($parameter_info['ui class'])) {
-      return $parameter_info['ui class'];
-    }
+  public function getDataTypeClass($data_type, &$parameter_info = array()) {
     $cache = rules_get_cache();
     $data_info = $cache['data_info'];
-    return (is_string($data_type) && isset($data_info[$data_type]['ui class'])) ? $data_info[$data_type]['ui class'] : 'RulesDataUI';
+    // Add in data-type defaults.
+    if (empty($parameter_info['ui class'])) {
+      $parameter_info['ui class'] = (is_string($data_type) && isset($data_info[$data_type]['ui class'])) ? $data_info[$data_type]['ui class'] : 'RulesDataUI';
+    }
+    if (is_subclass_of($parameter_info['ui class'], 'RulesDataInputOptionsListInterface')) {
+      $parameter_info['options list'] = array($parameter_info['ui class'], 'optionsList');
+    }
+    return $parameter_info['ui class'];
   }
 
   /**
@@ -711,8 +716,8 @@ class RulesPluginUI extends FacesExtender implements RulesPluginUIInterface {
         );
       }
       elseif (isset($this->element->settings[$name]) && (!isset($parameter['default value']) || $parameter['default value'] != $this->element->settings[$name])) {
-        $method = empty($parameter['options list']) ? 'render' : 'renderOptionsLabel';
         $class = $this->getDataTypeClass($parameter['type'], $parameter);
+        $method = empty($parameter['options list']) ? 'render' : 'renderOptionsLabel';
         // We cannot use method_exists() here as it would trigger a PHP bug,
         // @see http://drupal.org/node/1258284
         $element = call_user_func(array($class, $method), $this->element->settings[$name], $name, $parameter, $this->element);
diff --git a/ui/ui.data.inc b/ui/ui.data.inc
index 41e2d66..8ee91f0 100644
--- a/ui/ui.data.inc
+++ b/ui/ui.data.inc
@@ -29,6 +29,31 @@ interface RulesDataDirectInputFormInterface {
 }
 
 /**
+ * Interface for data UI classes providing an options list.
+ */
+interface RulesDataInputOptionsListInterface extends RulesDataDirectInputFormInterface {
+
+  /**
+   * Returns the options list for the data type.
+   *
+   * @param RulesPlugin $element
+   *   The rules element to get the options for.
+   * @param string $name
+   *   The name of the parameter for which to get options.
+   *
+   * For retrieving information about the used data type and parameter, the
+   * helper RulesDataUI::getTypeInfo() may be used as following:
+   * @code
+   *   list($type, $parameter_info) = RulesDataUI::getTypeInfo($element, $name);
+   * @endcode
+   *
+   * @return
+   *   An array of options as used by hook_options_list().
+   */
+  public static function optionsList(RulesPlugin $element, $name);
+}
+
+/**
  * Default UI related class for data types.
  */
 class RulesDataUI {
@@ -127,7 +152,7 @@ class RulesDataUI {
   public static function renderOptionsLabel($value, $name, $info, RulesPlugin $element) {
     if (!empty($info['options list'])) {
       $element->call('loadBasicInclude');
-      $options = entity_property_options_flatten($info['options list']($element, $name));
+      $options = entity_property_options_flatten(call_user_func($info['options list'], $element, $name));
       if (!is_array($value) && isset($options[$value])) {
         $value = $options[$value];
       }
@@ -145,6 +170,17 @@ class RulesDataUI {
       );
     }
   }
+
+  /**
+   * Returns the data type and parameter information for the given arguments.
+   *
+   * This helper may be used by options list callbacks operation at data-type
+   * level, see RulesDataInputOptionsListInterface.
+   */
+  public static function getTypeInfo(RulesPlugin $element, $name) {
+    $parameters = $element->pluginParameterInfo();
+    return array($parameters[$name]['type'], $parameters[$name]);
+  }
 }
 
 /**
@@ -472,25 +508,49 @@ class RulesDataUIEntityExportable extends RulesDataUIEntity {
 }
 
 /**
- * UI for taxonomy vocabularies.
+ * Data UI variant displaying a select list of available bundle entities.
  *
- * @see RulesTaxonomyVocabularyWrapper
+ * This is used for "bundle entities" implemented via the 'bundle of' feature
+ * of entity.module.
  */
-class RulesDataUITaxonomyVocabulary extends RulesDataUIEntity {
+class RulesDataUIBundleEntity extends RulesDataUIEntity implements RulesDataInputOptionsListInterface {
 
   public static function getDefaultMode() {
     return 'input';
   }
 
-  public static function inputForm($name, $info, $settings, RulesPlugin $element) {
-    // Add an options list of all vocabularies if there is none yet.
-    if (!isset($info['options list'])) {
-      $info['options list'] = array('RulesDataUITaxonomyVocabulary', 'optionsList');
+  /**
+   * Implements RulesDataInputOptionsListInterface::optionsList().
+   */
+  public static function optionsList(RulesPlugin $element, $name) {
+    list($data_type, $parameter_info) = RulesDataUI::getTypeInfo($element, $name);
+    $bundles = array();
+    $entity_info = entity_get_info();
+    $bundle_of_type = $entity_info[$data_type]['bundle of'];
+    if (isset($entity_info[$bundle_of_type]['bundles'])) {
+      foreach ($entity_info[$bundle_of_type]['bundles'] as $bundle_name => $bundle_info) {
+        $bundles[$bundle_name] = $bundle_info['label'];
+      }
     }
-    return parent::inputForm($name, $info, $settings, $element);
+    return $bundles;
   }
+}
 
-  public static function optionsList() {
+/**
+ * UI for taxonomy vocabularies.
+ *
+ * @see RulesTaxonomyVocabularyWrapper
+ */
+class RulesDataUITaxonomyVocabulary extends RulesDataUIEntity implements RulesDataInputOptionsListInterface {
+
+  public static function getDefaultMode() {
+    return 'input';
+  }
+
+  /**
+   * Implements RulesDataInputOptionsListInterface::optionsList().
+   */
+  public static function optionsList(RulesPlugin $element, $name) {
     $options = array();
     foreach (taxonomy_vocabulary_get_names() as $machine_name => $vocab) {
       $options[$machine_name] = $vocab->name;
