diff --git a/includes/rules.core.inc b/includes/rules.core.inc
index de99e3d..b0500ac 100644
--- a/includes/rules.core.inc
+++ b/includes/rules.core.inc
@@ -1690,7 +1690,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 +1710,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 +1745,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 +1869,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 +1938,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,18 +1973,21 @@ 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 {
+abstract class RulesPluginHandlerBase extends FacesExtender implements RulesPluginHandlerInterface {
 
-  public function execute() {
-    throw new RulesEvaluationException($this->object->getPluginName() .": Execution implementation is missing.", array(), $this->object, RulesLog::ERROR);
+  /**
+   * @var RulesAbstractPlugin
+   */
+  protected $element;
+
+  /**
+   * Overridden to provide $this->element to make the code more meaningful.
+   */
+  public function __construct(FacesExtendable $object) {
+    parent::__construct($object);
+    $this->element = $object;
   }
 
   /**
@@ -1975,6 +2006,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/modules/node.eval.inc b/modules/node.eval.inc
index ee96e15..6f6f72d 100644
--- a/modules/node.eval.inc
+++ b/modules/node.eval.inc
@@ -9,31 +9,128 @@
  */
 
 /**
+ * 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 {
+
+  /**
+   * Defines the condition.
+   */
+  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;
+  }
+
+  /**
+   * Executes the condition.
+   */
+  public function execute($node, $types) {
+    return in_array($node->type, $types);
+  }
+
+  /**
+   * Provides the content type of a node as asserted metadata.
+   */
+  function assertions() {
+    return array('node' => array('bundle' => $this->element->settings['type']));
+  }
 }
 
 /**
- * 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 {
+
+  /**
+   * Defines the condition.
+   */
+  public static function getInfo() {
+    return self::defaults() + array(
+      'name' => 'node_is_published',
+      'label' => t('Content is published'),
+    );
+  }
+
+  /**
+   * Executes the condition.
+   */
+  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 {
+
+  /**
+   * Defines the condition.
+   */
+  public static function getInfo() {
+    return self::defaults() + array(
+      'name' => 'node_is_sticky',
+      'label' => t('Content is sticky'),
+    );
+  }
+
+  /**
+   * Executes the condition.
+   */
+  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 {
+
+  /**
+   * Defines the condition.
+   */
+  public static function getInfo() {
+    return self::defaults() + array(
+      'name' => 'node_is_promoted',
+      'label' => t('Content is promoted to frontpage'),
+    );
+  }
+
+  /**
+   * Executes the condition.
+   */
+  public function execute($node) {
+    return $node->promote == 1;
+  }
 }
 
 /**
diff --git a/modules/node.rules.inc b/modules/node.rules.inc
index 9fea011..a126931 100644
--- a/modules/node.rules.inc
+++ b/modules/node.rules.inc
@@ -89,51 +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) {
-  return array('node' => array('bundle' => $element->settings['type']));
-}
-
-/**
  * Implements hook_rules_action_info() on behalf of the node module.
  */
 function rules_node_action_info() {
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..a092f3f 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,41 @@ 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'));
+      $info['class'] = $plugin_class;
+      $items[$info['name']] = $info;
+    }
+  }
+  return $items;
+}
+
+/**
  * Gets a rules cache entry.
  */
 function &rules_get_cache($cid = 'data') {
