diff --git a/includes/VersioncontrolBackend.php b/includes/VersioncontrolBackend.php index 0934ae5..b6aca72 100644 --- a/includes/VersioncontrolBackend.php +++ b/includes/VersioncontrolBackend.php @@ -358,6 +358,8 @@ abstract class VersioncontrolBackend { /** * Verify that a plugin object conforms to the required interface. * + * @todo Plugin slot is a repository specific concept, move this there? + * * @param mixed $entity * A VersioncontrolEntity object, or string name of a VersioncontrolEntity * class, for which we are verifying a plugin. diff --git a/includes/VersioncontrolRepository.php b/includes/VersioncontrolRepository.php index d70816c..1031ec5 100644 --- a/includes/VersioncontrolRepository.php +++ b/includes/VersioncontrolRepository.php @@ -89,14 +89,14 @@ abstract class VersioncontrolRepository implements VersioncontrolEntityInterface /** * An array describing the plugins that will be used for this repository. * - * The current plugin types(array keys) are: + * The current plugin slots(array keys) are: * - author_mapper * - committer_mapper * - webviewer_url_handler * - repomgr * - auth_handler * - reposync - * - event_processor + * - event_processors * * @var array */ @@ -106,7 +106,7 @@ abstract class VersioncontrolRepository implements VersioncontrolEntityInterface * An array of plugin instances (instanciated plugin objects). * * There is one plugin per plugin slot in most cases. For now only - * event_processor has multiple plugins associated. + * event_processors can have multiple plugins associated. * * @var array */ @@ -882,29 +882,59 @@ abstract class VersioncontrolRepository implements VersioncontrolEntityInterface return $this->pluginInstances['webviewer_url_handler']; } + protected function isPluginSlotMultiple($plugin_slot) { + // @todo Maybe define metadata per plugin slot? + return $plugin_slot == 'event_processors'; + } + + protected function isPluginSlotRequired($plugin_slot) { + return $plugin_slot != 'event_processors'; + } + /** - * Get a ctools plugin based on plugin slot passed. + * Get a list of ctools plugins based on plugin slot and type passed. */ - protected function getPlugin($plugin_slot, $plugin_type) { + public function getPlugins($plugin_slot, $plugin_type) { ctools_include('plugins'); - $plugin_name = $this->getPluginName($plugin_slot); - - $plugin = ctools_get_plugins('versioncontrol', $plugin_type, $plugin_name); - if (!is_array($plugin) || empty($plugin)) { - throw new Exception("Attempted to get a plugin of type '$plugin_type' named '$plugin_name', but no such plugin could be found.", E_WARNING); + // It can be an array, some plugin slots accept multiple values. + $plugin_names = $this->getPluginName($plugin_slot); + if ($plugin_names == NULL) { + return array(); + } + if (!is_array($plugin_names)) { + $plugin_names = array($plugin_names); } - // If $plugin_name is empty ctools_get_plugins() returns an array of plugins - // instead of a single one. Default to the first one. - if (!$plugin_name) { - return reset($plugin); + // If $plugin_name is empty ctools_get_plugins() returns an array of + // plugins. + if (empty($plugin_names) && !$multiple) { + return ctools_get_plugins('versioncontrol', $plugin_type); } - else { - return $plugin; + + $plugins = array(); + foreach ($plugin_names as $plugin_name) { + $plugin = ctools_get_plugins('versioncontrol', $plugin_type, $plugin_name); + if (!is_array($plugin) || empty($plugin)) { + throw new Exception("Attempted to get a plugin of type '$plugin_type' named '$plugin_name', but no such plugin could be found.", E_WARNING); + } + $plugins[] = $plugin; } + return $plugins; } + /** + * Retrieve the first plugin associated with the plugin slot. + * + * @fixme review visibility + */ + protected function getPlugin($plugin_slot, $plugin_type) { + $plugins = $this->getPlugins($plugin_slot, $plugin_type); + if ($this->isPluginSlotMultiple($plugin_slot)) { + throw new Exception("Attempted to use getPlugin() with a multiple plugin slot named '$plugin_slot' of plugin type '$plugin_type'. Please use getPlugins() instead.", E_WARNING); + } + return reset($plugins); + } /** * Retrieves the ctools plugin name to use, based on the plugin slot passed. @@ -943,8 +973,11 @@ abstract class VersioncontrolRepository implements VersioncontrolEntityInterface return $plugin_name; } - // Could not find any default. - throw new Exception("A default plugin name could not be retrieved for plugin type '$plugin_slot'.", E_WARNING); + if ($this->isPluginSlotRequired($plugin_slot)) { + // Could not find any default. + throw new Exception("A default plugin name could not be retrieved for plugin type '$plugin_slot'.", E_WARNING); + } + return NULL; } public function getSynchronizerOptions() { @@ -992,6 +1025,26 @@ abstract class VersioncontrolRepository implements VersioncontrolEntityInterface } /** + * @see getPluginClass(). + */ + protected function getPluginClasses($plugin_slot, $plugin_type, $class_type) { + $plugin_objects = array(); + + foreach ($this->getPlugins($plugin_slot, $plugin_type) as $plugin) { + $class_name = ctools_plugin_get_class($plugin, $class_type); + if (!class_exists($class_name)) { + throw new Exception("Plugin slot '$plugin_slot' of type '$plugin_type' contains an invalid class name in handler slot '$class_type', named '$class_name' class", E_WARNING); + return FALSE; + } + + $plugin_object = new $class_name(); + $this->getBackend()->verifyPluginInterface($this, $plugin_slot, $plugin_object); + $plugin_objects[$plugin['name']] = $plugin_object; + } + return $plugin_objects; + } + + /** * Return the auth handler plugin object that this repository is * configured to use for implementing ACLs on this repository. * @@ -1056,31 +1109,17 @@ abstract class VersioncontrolRepository implements VersioncontrolEntityInterface * configured to use. * * @return array(VersioncontrolSynchronizationEventProcessorInterface) + * Keyed by plugin name. */ public function getEventProcessors() { - if (!isset($this->pluginInstances['event_processor'])) { - $processors = array(); - // Potentially several plugins on the slot, so do it manually. - ctools_include('plugins'); - - if (!$plugins = ctools_get_plugins('versioncontrol', 'event_processor')) { - return array(); - } - - foreach ($plugins as $plugin) { - $class_name = ctools_plugin_get_class($plugin, 'handler'); - if (!class_exists($class_name)) { - throw new Exception("Plugin slot 'event_processor' of type 'event_processor' contains an invalid class name in handler slot 'handler', named '$class_name' class", E_WARNING); - } - $plugin_object = new $class_name(); - $this->getBackend()->verifyPluginInterface($this, 'event_processor', $plugin_object); - $plugin_object->setRepository($this); - $processors = $plugin_object; + if (!isset($this->pluginInstances['event_processors'])) { + $this->pluginInstances['event_processors'] = $this->getPluginClasses('event_processors', 'event_processor', 'handler'); + foreach ($this->pluginInstances['event_processors'] as $event_processor_object) { + $event_processor_object->setRepository($this); } - $this->pluginInstances['event_processor'] = $processors; } - return $this->pluginInstances['event_processor']; + return $this->pluginInstances['event_processors']; } /** diff --git a/includes/interfaces.inc b/includes/interfaces.inc index 3fae24d..fccaffa 100644 --- a/includes/interfaces.inc +++ b/includes/interfaces.inc @@ -499,36 +499,46 @@ interface VersioncontrolSynchronizationEventProcessorInterface { /** * Defines a way for plugins to define configuration. * - * This relies on the plugin itself having on its definition array set to TRUE - * the 'has_configuration_form' key. - * * @todo Currently it is only used on versioncontrol synchronization event * processor plugin. It's pending to be used on other plugins. + * @todo Add a way to validate the form. Not added for now until we can pass + * context to the plugin to know in which part of the form_state hierarchy to + * find the values. Probably make a new method that receives only the known + * context of form_state instead of the full context. */ interface VersioncontrolPluginConfigurationInterface { /** * Build a configuration form to be used to configure a plugin instance. * - * @param array $form - * A form array. - * @param array $form_state - * the corresponding form state. + * @param array $data + * the corresponding data to initialize values. + * i.e. + * array( + * 'behavior' => 1, + * 'message' => 'test', + * ) + * + * @return array + * A form array to include. */ - public function buildForm(&$form, &$form_state); + public function buildForm($default_data); /** - * Validate the form built at buildForm(). + * Converts the buildForm() form data into an array to be saved. * * @param array $form * A form array. - * @param array $form_state - * the corresponding form state. + * @param array $form_state_values_data + * A subset of $form_state with corresponding data. */ - public function validateForm(&$form, &$form_state); + public function getFormData($form_state_values_data); /** * Handle any special handling on form submission. * + * Probably empty in most cases, getFormData should provide enough for + * storing the configuration. + * * @param array $form * A form array. * @param array $form_state diff --git a/includes/plugins/event_processor/VersioncontrolEventProcessorNone.inc b/includes/plugins/event_processor/VersioncontrolEventProcessorNone.inc index 0695694..65bd9b7 100644 --- a/includes/plugins/event_processor/VersioncontrolEventProcessorNone.inc +++ b/includes/plugins/event_processor/VersioncontrolEventProcessorNone.inc @@ -28,26 +28,31 @@ class VersioncontrolEventProcessorNone implements VersioncontrolSynchronizationE $this->repository = $repository; } - public function buildForm(&$form, &$form_state) { - $form['behavior'] = array( + public function buildForm($default_data) { + $form['event-processor-none-behavior'] = array( '#type' => 'select', '#title' => 'Behavior', + '#default_value' => !empty($default_data['behavior']) ? $default_data['behavior'] : self::BEHAVIOR_A, '#options' => array( self::BEHAVIOR_A => t('Behavior A'), self:: BEHAVIOR_B => t('Behavior B'), ), '#description' => t('An option that will not change any behavior.'), ); - $form['message'] = array( + $form['event-processor-none-message'] = array( '#type' => 'textfield', '#title' => 'Message', '#description' => t('One message that will not be displayed.'), - '#required' => TRUE, + '#default_value' => !empty($default_data['message']) ? $default_data['message'] : '', ); + return $form; } - public function validateForm(&$form, &$form_state) { - // Nothing special. + public function getFormData($form_state_values_data) { + return array( + 'behavior' => $form_state_values_data['event-processor-none-behavior'], + 'message' => $form_state_values_data['event-processor-none-message'], + ); } public function submitForm(&$form, &$form_state) { diff --git a/includes/plugins/event_processor/none.inc b/includes/plugins/event_processor/none.inc index c2eecf4..57d53e1 100644 --- a/includes/plugins/event_processor/none.inc +++ b/includes/plugins/event_processor/none.inc @@ -9,7 +9,7 @@ $plugin = array( // The versioncontrol system machine name as the backend declares. - 'vcs' => 'test', + 'vcs' => VERSIONCONTROL_PLUGIN_VCS_AGNOSTIC, // This title is going to be shown on the repository edition, for the // user to identify the plugin. diff --git a/versioncontrol.admin.inc b/versioncontrol.admin.inc index cdb640b..2ebd077 100644 --- a/versioncontrol.admin.inc +++ b/versioncontrol.admin.inc @@ -269,6 +269,95 @@ function versioncontrol_admin_views_sets_edit_submit($form, &$form_state) { } /** + * Form callback for 'admin/settings/versioncontrol-settings/event-processors'. + */ +function versioncontrol_admin_event_processors_edit($form, &$form_state) { + ctools_include('dependent'); + $form = array(); + + $form['info'] = array( + '#type' => 'item', + '#markup' => t("This form allows you to manage Version Control API's event processors to be used by default on each backend, setting a configuration if it allows it."), + ); + + $backends = versioncontrol_get_backends(); + foreach ($backends as $vcs => $backend) { + $form[$vcs] = array( + '#type' => 'fieldset', + '#title' => $backend->name, + '#collapsible' => TRUE, + '#collapsed' => FALSE, + '#tree' => TRUE, + ); + $variable_name = "versioncontrol_repository_plugin_defaults_{$backend->type}_event_processors"; + $default_event_processor_names = variable_get($variable_name, array()); + $default_event_processor_data = variable_get("{$variable_name}_data", array()); + foreach (versioncontrol_plugin_get_by_vcs('event_processor', $vcs) as $processor_name => $processor_plugin) { + $form[$vcs]["processor-$processor_name"] = array( + '#type' => 'checkbox', + '#title' => $processor_plugin['title'], + '#default_value' => in_array($processor_name, $default_event_processor_names), + '#id' => "processor-$processor_name", + ); + $class_name = ctools_plugin_get_class($processor_plugin, 'handler'); + $event_processor = new $class_name(); + if (!$event_processor instanceof VersioncontrolPluginConfigurationInterface) { + continue; + } + $default_data = isset($default_event_processor_data[$vcs][$processor_name]) ? $default_event_processor_data[$vcs][$processor_name] : array(); + $settings_form = $event_processor->buildForm($default_data); + $form[$vcs]["processor-settings-$processor_name"] = array( + '#type' => 'fieldset', + '#title' => t('%name options', array('%name' => $processor_plugin['title'])), + '#process' => array('ctools_dependent_process'), + '#dependency' => array("processor-$processor_name" => array(1)), + ); + $form[$vcs]["processor-settings-$processor_name"] += $settings_form; + } + } + + $form['submit'] = array( + '#type' => 'submit', + '#value' => t('Save configuration'), + ); + + return $form; +} + +/** + * Form submit callback for 'admin/settings/versioncontrol-settings/event-processors'. + */ +function versioncontrol_admin_event_processors_edit_submit($form, &$form_state) { + $backends = versioncontrol_get_backends(); + foreach ($backends as $vcs => $backend) { + // Remove variables. + $variable_name = "versioncontrol_repository_plugin_defaults_{$backend->type}_event_processors"; + variable_del($variable_name); + variable_del("{$variable_name}_data"); + // Process new values. + $active_processors = array(); + $processors_data = array(); + foreach (versioncontrol_plugin_get_by_vcs('event_processor', $vcs) as $processor_name => $processor_plugin) { + if (empty($form_state['values'][$vcs]["processor-$processor_name"])) { + continue; + } + $active_processors[] = $processor_name; + if (!empty($form_state['values'][$vcs]["processor-settings-$processor_name"])) { + $class_name = ctools_plugin_get_class($processor_plugin, 'handler'); + $event_processor = new $class_name(); + if (!$event_processor instanceof VersioncontrolPluginConfigurationInterface) { + continue; + } + $processors_data[$vcs][$processor_name] = $event_processor->getFormData($form_state['values'][$vcs]["processor-settings-$processor_name"]); + } + } + // Save them. + variable_set($variable_name, $active_processors); + variable_set("{$variable_name}_data", $processors_data); + } +} + +/** * Form callback for "admin/settings/versioncontrol-settings/plugins": * Provide a form for settings about Version Control API plugins. */ @@ -279,6 +368,8 @@ function versioncontrol_admin_settings_plugins($form, &$form_state) { $plugin_entity_types = versioncontrol_plugins_get_information(); // view_sets have its own settings page, so avoid it. unset($plugin_entity_types['view']); + // event processors has its own settings page too. + unset($plugin_entity_types['repository']['event_processor']); foreach ($backends as $vcs => $backend) { $form['versioncontrol_plugins_' . $vcs] = array( @@ -545,34 +636,14 @@ function versioncontrol_admin_repository_edit($form, &$form_state, $repository, '#options' => versioncontrol_webviewer_url_handlers_get_names($form['#vcs']), ); - $form['event_processor'] = array( - '#type' => 'fieldset', - '#title' => t('Event processor'), - '#collapsible' => TRUE, - '#collapsed' => FALSE, - '#weight' => 7, - ); - if ($repository_exists) { - $default_event_processors = $repository->plugins['webviewer_url_handler']; - } - else { - $default_event_processors = versioncontrol_event_processor_get_default_plugins($form['#vcs']); - } - $form['event_processor']['processor'] = array( - '#type' => 'select', - '#multiple' => TRUE, - '#title' => t('Processors'), - '#description' => t('Which event processors will be used by this repository.'), - '#default_value' => $default_event_processors, - '#options' => versioncontrol_event_processor_get_names($form['#vcs']), - ); - $form['submit'] = array( '#type' => 'submit', '#value' => t('Save repository'), '#weight' => 100, ); + //@todo Support per repository configuration for event processor plugins. + return $form; } diff --git a/versioncontrol.module b/versioncontrol.module index f65375b..01f5595 100644 --- a/versioncontrol.module +++ b/versioncontrol.module @@ -72,6 +72,12 @@ define('VERSIONCONTROL_ITEM_DIRECTORY_DELETED', 4); //@} /** + * An identifier to use on vcs key for plugins which can work with any version + * control system. + */ +define('VERSIONCONTROL_PLUGIN_VCS_AGNOSTIC', 1); + +/** * Implements hook_flush_caches(). * * Triggers backend mode determination. @@ -144,6 +150,14 @@ function versioncontrol_menu() { 'type' => MENU_LOCAL_TASK, ) + $admin; + $items['admin/config/development/versioncontrol-settings/event-processors'] = array( + 'title' => 'Event processors', + 'description' => 'Configure default event processors to use per backend.', + 'page arguments' => array('versioncontrol_admin_event_processors_edit'), + 'type' => MENU_LOCAL_TASK, + 'weight' => 10, + ) + $admin; + $items['admin/config/development/versioncontrol-settings/plugins'] = array( 'title' => 'Plugins', 'description' => 'Default plugins and its configuration per backend.', @@ -571,32 +585,56 @@ function versioncontrol_plugin_get_names($plugin_type) { * * To be used at forms. * + * @param string $plugin_type + * This can be: webviewer_url_handlers, event_processor. + * @param string $vcs + * Version Control System machine name as defined by + * hook_versioncontrol_backends(). If empty, no filter is applied. + * + * @return array + * The list of plugin titles indexed by plugin name. + */ +function versioncontrol_plugin_get_names_by_vcs($plugin_type, $vcs = '') { + $names = array(); + $plugins = versioncontrol_plugin_get_by_vcs($plugin_type, $vcs); + foreach ($plugins as $name => $plugin) { + $names[$name] = $plugin['title']; + } + asort($names); + return $names; +} + +/** + * Load ctools plugins with the specified vcs. + * * @todo: Let all plugins to define an optional vcs, so this can live inside * versioncontrol_plugin_get_names(). * * @param string $plugin_type * This can be: webviewer_url_handlers, event_processor. + * @param string $vcs + * Version Control System machine name as defined by + * hook_versioncontrol_backends(). If empty, no filter is applied. * * @return array - * The list of plugin titles indexed by plugin name. + * The list of plugins. */ -function versioncontrol_plugin_get_names_by_vcs($plugin_type, $vcs='') { +function versioncontrol_plugin_get_by_vcs($plugin_type, $vcs = '') { ctools_include('plugins'); - $names = array(); + $plugins = array(); foreach (ctools_get_plugins('versioncontrol', $plugin_type) as $name => $plugin) { if (!empty($vcs)) { - if ($plugin['vcs'] == $vcs) { - $names[$name] = $plugin['title']; + if ($plugin['vcs'] == $vcs || $plugin['vcs'] == VERSIONCONTROL_PLUGIN_VCS_AGNOSTIC) { + $plugins[$name] = $plugin; } } else { - $names[$name] = $plugin['title']; + $plugins[$name] = $plugin; } } - asort($names); - return $names; + return $plugins; } /** @@ -752,3 +790,19 @@ function versioncontrol_plugins_get_information() { ), ); } + +/** + * Implements hook_versioncontrol_code_arrival(). + * + * Calls event processor plugins. + */ +function versioncontrol_versioncontrol_code_arrival(VersioncontrolRepository $repository, VersioncontrolEvent $event) { + foreach($repository->getEventProcessors() as $event_processor) { + try { + $event_processor->process($event); + } + catch (VersioncontrolSynchronizationEventProcessorException $exception) { + watchdog('versioncontrol', 'Event processor using class "%class" aborted execution with message "%message".', array('%class' => get_class($event_processor), '%message' => $exception->getMessage()), WATCHDOG_WARNING); + } + } +}