diff --git a/includes/rules.core.inc b/includes/rules.core.inc index b0500ac..c512ba3 100644 --- a/includes/rules.core.inc +++ b/includes/rules.core.inc @@ -1469,6 +1469,15 @@ abstract class RulesAbstractPlugin extends RulesPlugin { // @see self::forceSetup(). $this->infoLoaded = TRUE; + // Register the defined class, if any. + if (isset($this->info['class'])) { + $this->faces['RulesPluginImplInterface'] = 'RulesPluginImplInterface'; + $face_methods = get_class_methods('RulesPluginImplInterface'); + $class_info = array(1 => $this->info['class']); + foreach ($face_methods as $method) { + $this->facesMethods[$method] = $class_info; + } + } // Add in per-plugin implementation callbacks if any. if (!empty($this->info['faces_cache'])) { foreach ($this->info['faces_cache'] as $face => $data) { @@ -1710,7 +1719,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))) { + // Note that this skips RulesPluginImplInterface, which is not + // implemented by plugin handlers. + elseif (isset($info['class']) && is_subclass_of($info['class'], $interface)) { $methods[$method][1] = $info['class']; } elseif (function_exists($function = $info['base'] . '_' . $method)) { @@ -1755,6 +1766,13 @@ abstract class RulesAbstractPlugin extends RulesPlugin { module_load_include('inc', $module, $name); } } + foreach (module_implements('rules_directory') as $module) { + // Include all files once, so the discovery can find them. + $directory = module_invoke($module, 'rules_directory'); + foreach (glob(drupal_get_path('module', $module) . "/$directory/*.{inc,php}", GLOB_BRACE) as $filename) { + include_once $filename; + } + } $included = TRUE; } } @@ -1939,11 +1957,31 @@ interface RulesPluginHandlerInterface { /** * Interface for implementing conditions via classes. + * + * In addition to the interface an execute() and a static getInfo() method must + * be implemented. The static getInfo() method has to return the info as + * returned by hook_rules_condition_info() but including an additional 'name' + * key, specifying the plugin name. + * The execute method is the equivalent to the usual execution callback and + * gets the parameters passed as specified in the info array. + * + * See RulesNodeConditionType for an example and rules_discover_plugins() + * for information about class discovery. */ interface RulesConditionHandlerInterface extends RulesPluginHandlerInterface {} /** * Interface for implementing actions via classes. + * + * In addition to the interface an execute() and a static getInfo() method must + * be implemented. The static getInfo() method has to return the info as + * returned by hook_rules_action_info() but including an additional 'name' key, + * specifying the plugin name. + * The execute method is the equivalent to the usual execution callback and + * gets the parameters passed as specified in the info array. + * + * See RulesNodeConditionType for an example and rules_discover_plugins() + * for information about class discovery. */ interface RulesActionHandlerInterface extends RulesPluginHandlerInterface {} @@ -1986,7 +2024,7 @@ abstract class RulesPluginHandlerBase extends FacesExtender implements RulesPlug * Overridden to provide $this->element to make the code more meaningful. */ public function __construct(FacesExtendable $object) { - parent::__construct($object); + $this->object = $object; $this->element = $object; } diff --git a/includes/rules.event.inc b/includes/rules.event.inc index 8302f09..27f6a33 100755 --- a/includes/rules.event.inc +++ b/includes/rules.event.inc @@ -28,6 +28,10 @@ * rules_invoke_event('node_view', $node, $view_mode); * @endcode * + * Rules event handlers have to be declared using the 'class' key in + * hook_rules_event_info(), or may be discovered automatically, see + * rules_discover_plugins() for details. + * * @see RulesEventHandlerBase * @see RulesEventDefaultHandler */ diff --git a/modules/node.eval.inc b/modules/node.eval.inc index 6f6f72d..39922b0 100644 --- a/modules/node.eval.inc +++ b/modules/node.eval.inc @@ -15,7 +15,6 @@ abstract class RulesNodeConditionBase extends RulesConditionHandlerBase { public static function defaults() { return array( - 'module' => 'rules', 'parameter' => array( 'node' => array('type' => 'node', 'label' => t('Content')), ), diff --git a/rules.api.php b/rules.api.php index e672117..1361007 100644 --- a/rules.api.php +++ b/rules.api.php @@ -30,6 +30,10 @@ * placed into the file MODULENAME.rules.inc, which gets automatically included * when the hook is invoked. * + * However, as an alternative to implementing this hook, class based plugin + * handlers may be provided by implementing RulesActionHandlerInterface. See + * the interface for details. + * * @return * An array of information about the module's provided rules actions. * The array contains a sub-array for each action, with the action name as @@ -171,6 +175,25 @@ function hook_rules_file_info() { } /** + * Specifies directories for class-based plugin handler discovery. + * + * Implementing this hook is not a requirement, it is just one option to load + * the files containing the classes during discovery - see + * rules_discover_plugins(). + * + * @return string + * A directory relative to the module directory, which holds the files + * containing rules plugin handlers. All files in those directory having + * a 'php' or 'inc' file extension will be loaded during discovery. + * Optionally, wildcards ('*') may be used to match multiple directories. + * + * @see rules_discover_plugins() + */ +function hook_rules_directory() { + return 'lib/Drupal/fluxtwitter/Rules/*'; +} + +/** * The execution callback for an action. * * It should be placed in any file included by your module or in a file @@ -203,6 +226,10 @@ function rules_action_execution_callback($node, $title, $settings) { * placed into the file MODULENAME.rules.inc, which gets automatically included * when the hook is invoked. * + * However, as an alternative to implementing this hook, class based plugin + * handlers may be provided by implementing RulesConditionHandlerInterface. See + * the interface for details. + * * Adding conditions works exactly the same way as adding actions, with the * exception that conditions can't provide variables and cannot save parameters. * Thus the 'provides' attribute is not supported. Furthermore the condition @@ -234,6 +261,10 @@ function hook_rules_condition_info() { * usually it's invoked directly from the providing module but wrapped by a * module_exists('rules') check. * + * However, as an alternative to implementing this hook, class based event + * handlers may be provided by implementing RulesEventHandlerInterface. See + * the interface for details. + * * @return * An array of information about the module's provided rules events. The array * contains a sub-array for each event, with the event name as the key. The diff --git a/rules.module b/rules.module index a092f3f..def7c06 100644 --- a/rules.module +++ b/rules.module @@ -234,12 +234,27 @@ function rules_fetch_data($hook) { /** * Discover plugin implementations. * + * Class based plugin handlers must be loaded when rules caches are rebuilt, + * such that they get discovered properly. You have the following options: + * - Put it into a regular module file (discouraged) + * - Put it into your module.rules.inc file + * - Put it in any file and declare it using hook_rules_file_info() + * - Put it in any file and declare it using hook_rules_directory() + * + * In addition to that, the class must be loadable via regular class + * auto-loading, thus put the file holding the class in your info file or use + * another class-loader. + * * @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. + * + * @see RulesActionHandlerInterface + * @see RulesConditionHandlerInterface + * @see RulesEventHandlerInterface */ function rules_discover_plugins($class) { // Make sure all files possibly holding plugins are included. @@ -250,6 +265,7 @@ function rules_discover_plugins($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; + $info['module'] = _rules_discover_module($plugin_class); $items[$info['name']] = $info; } } @@ -257,6 +273,27 @@ function rules_discover_plugins($class) { } /** + * Determines the module providing the given class. + */ +function _rules_discover_module($class) { + $paths = &drupal_static(__FUNCTION__); + + if (!isset($paths)) { + // Build up a map of modules keyed by their directory. + foreach (system_list('module_enabled') as $name => $module_info) { + $paths[dirname($module_info->filename)] = $name; + } + } + // Go up the path until we match a module up. + $reflection = new ReflectionClass($class); + $file_path = str_replace(DRUPAL_ROOT . '/', '', $reflection->getFileName()); + while (!isset($paths[$file_path]) && strpos($file_path, DIRECTORY_SEPARATOR) !== FALSE) { + $file_path = dirname($file_path); + } + return isset($paths[$file_path]) ? $paths[$file_path] : FALSE; +} + +/** * Gets a rules cache entry. */ function &rules_get_cache($cid = 'data') { @@ -722,7 +759,7 @@ function rules_entity_info() { * Implementation of hook_hook_info(). */ function rules_hook_info() { - foreach(array('plugin_info', 'data_info', 'condition_info', 'action_info', 'event_info', 'file_info', 'evaluator_info', 'data_processor_info') as $hook) { + foreach(array('plugin_info', 'rules_directory', 'data_info', 'condition_info', 'action_info', 'event_info', 'file_info', 'evaluator_info', 'data_processor_info') as $hook) { $hooks['rules_' . $hook] = array( 'group' => 'rules', );