Index: CHANGELOG.txt =================================================================== RCS file: /cvs/drupal-contrib/contributions/modules/form_controller/CHANGELOG.txt,v retrieving revision 1.3 diff -u -p -r1.3 CHANGELOG.txt --- CHANGELOG.txt 14 Nov 2009 23:46:39 -0000 1.3 +++ CHANGELOG.txt 19 Nov 2009 11:42:04 -0000 @@ -4,8 +4,9 @@ Form controller x.x-x.x, xxxx-xx-xx ----------------------------------- -Form controller 6.x-1.x, xxxx-xx-xx +Form controller 6.x-2.x, xxxx-xx-xx ----------------------------------- +#602600 by sun: Revamped module; pre-alpha code to support many implementations. #602600 by sun: Very rough port to D6. Index: form_controller.info =================================================================== RCS file: /cvs/drupal-contrib/contributions/modules/form_controller/form_controller.info,v retrieving revision 1.3 diff -u -p -r1.3 form_controller.info --- form_controller.info 14 Nov 2009 23:46:39 -0000 1.3 +++ form_controller.info 19 Nov 2009 11:26:12 -0000 @@ -3,3 +3,4 @@ name = Form controller description = Helper module for managing form elements of other modules. package = Development core = 6.x +dependencies[] = contact Index: form_controller.module =================================================================== RCS file: /cvs/drupal-contrib/contributions/modules/form_controller/form_controller.module,v retrieving revision 1.4 diff -u -p -r1.4 form_controller.module --- form_controller.module 14 Nov 2009 23:46:39 -0000 1.4 +++ form_controller.module 19 Nov 2009 11:39:54 -0000 @@ -3,7 +3,9 @@ /** * @file - * Loader and callback functions for Form controller. + * Form controller. + * + * @todo Increase module weight, since #process does not work on 'form' in D6. */ /** @@ -18,7 +20,8 @@ function form_controller_perm() { */ function form_controller_menu() { $items['form_controller'] = array( - 'page callback' => 'form_controller', + 'page callback' => 'drupal_get_form', + 'page arguments' => array('form_controller_form'), 'access arguments' => array('administer forms'), 'type' => MENU_CALLBACK, ); @@ -27,115 +30,271 @@ function form_controller_menu() { /** * Implements hook_form_alter(). - * - * Attaches Form controller to all forms, if there are any hook_form_controller() - * implementations. */ function form_controller_form_alter(&$form, &$form_state, $form_id) { - if (!user_access('administer forms') || $form_id == 'form_controller_form' || !form_controller_get_modules()) { + static $implementations; + if (!isset($implementations)) { + $implementations = (bool) module_implements('form_controller_info'); + } + if ($form_id == 'form_controller_form' || !$implementations) { return; } - $form['form_controller'] = array( - '#prefix' => '
', - '#value' => l(t('Configure this form'), "form_controller/form_controller_form/$form_id"), - '#suffix' => '
', - '#weight' => -1000, - ); -} + // @todo This breaks form submissions in D6, so we have to inline the code :( + /* + $form['#input'] = TRUE; + $form['#process'][] = 'form_controller_process_form'; + */ + form_controller_process_form($form, array(), $form_state); -/** - * Menu callback; retrieves and renders a form. - */ -function form_controller($form_id, $target_form_id = NULL) { - // Output target form_id as title in form settings widget. - if (empty($_POST) && $form_id == 'form_controller_form') { - echo '

'. t('Form settings for %form_id', array('%form_id' => $target_form_id)) .'

'; + if (!user_access('administer forms')) { + return; } - $output = drupal_get_form($form_id, $target_form_id); - // Output and clear form validation error messages, if there are any. - echo theme_status_messages(); - echo $output; - exit; + // @todo Toggle this + form_controller globally, auto-disable after 6 hours. + if (!isset($form['#cache'])) { + $form['#cache'] = TRUE; + } + + // Store page title for potential usage in implementation forms. + $form['#form_controller']['title'] = drupal_set_title(); + + // Output a contextual configuration link. + $output = '
'; + $output .= l(t('Configure this form'), 'form_controller/' . $form['form_build_id']['#value']); + $output .= '
'; + if (!isset($form['#prefix'])) { + $form['#prefix'] = ''; + } + $form['#prefix'] .= $output . $form['#prefix']; } /** - * Builds a form based on hook_form_controller() implementations. + * Form builder to control form alterations. */ -function form_controller_form($target_form_id) { - $form = array(); +function form_controller_form(&$form_state, $form_build_id, $target_form_id = NULL) { + // Reload form from cache. + $form_state = array('storage' => NULL, 'submitted' => FALSE); + $cached_form = form_get_cache($form_build_id, $form_state); + $cached_form['#post'] = array(); + + // Rebuild cached form. + $form_id = $cached_form['form_id']['#value']; + $processed_form = form_builder($form_id, $cached_form, $form_state); + + // Build form information. + $form_elements = form_controller_get_elements($processed_form); + $context = array( + 'form_id' => $form_id, + 'form' => $processed_form, + 'elements' => $form_elements, + ); + $form_state['form_controller'] = $context; + + drupal_set_title(t('Configure %title form (@form_id)', array( + '%title' => $cached_form['#form_controller']['title'], + '@form_id' => $form_id, + ))); - $form['form_controller']['target_form_id'] = array( - '#type' => 'value', - '#value' => $target_form_id, - ); - foreach (form_controller_get_modules() as $module) { - $function = $module .'_form_controller'; - $form['form_controller'][$module] = $function('settings', $target_form_id); - $form['form_controller'][$module]['#tree'] = TRUE; + $form = array(); + $alterations = module_invoke_all('form_controller_info'); + foreach ($alterations as $name => $info) { + // Fetch configuration form. + // @todo Take over configuration storage for most/simple implementations. + if (isset($info['form callback']) && function_exists($info['form callback'])) { + $function = $info['form callback']; + $settings = $function($form, $form_id, $context); + if (isset($settings) && is_array($settings)) { + $form[$name] = array( + '#type' => 'fieldset', + '#title' => check_plain($info['title']), + '#description' => check_plain($info['description']), + '#collapsible' => TRUE, + //'#collapsed' => TRUE, + '#tree' => TRUE, + ); + $form[$name] += $settings; + } + } } - $form['submit'] = array( - '#type' => 'submit', - '#value' => t('Save'), - ); + $form['submit'] = array('#type' => 'submit', '#value' => t('Save')); + return $form; } /** - * Render and return a Form controller settings form. + * Helper function to retrieve all user input fields in a form. * - * To ensure that no other module adds elements to our form, we simply render - * everything below $form['form_controller'] only. + * This was mainly designed for Form controller implementations like Mollom, + * which want to do something with all exposed user input widgets in a form. + * I currently can't think of other implementations that would want to retrieve + * all widgets, so we limit this to real user input elements. */ -function theme_form_controller_form($form) { - foreach ($form as $key => $value) { - if ($key[0] != '#' && !in_array($key, array('form_controller', 'submit', 'form_token', 'form_id'))) { - unset($form[$key]); +function form_controller_get_elements($elements) { + static $input = array('textfield', 'textarea'); + + $items = array(); + foreach (element_children($elements) as $key) { + // @todo Find a better way to skip form_controller-controlled elements. + // Wait. Why should we skip any possibly valid and useful form elements? + // Anything that's contained in the form may be used by any implementation, no? + if (isset($elements[$key]['#form_controller'])) { + continue; + } + // Skip autocomplete widgets. + if (!empty($elements[$key]['#autocomplete_path'])) { + continue; + } + // Skip elements without a #type. + if (isset($elements[$key]['#type']) && in_array($elements[$key]['#type'], $input)) { + $items[] = $elements[$key]; + } + // Recurse into children, if there are any. + if (element_children($elements[$key])) { + // @todo Do we want to key by $key? + $items = array_merge($items, form_controller_get_elements($elements[$key])); } } - return drupal_render($form); + return $items; } -/** - * Form controller settings validation callback. - * - * Allows hook_form_controller() implementations to react on form submissions. - * To invalidate the form, implementations should invoke form_set_error(). - */ -function form_controller_form_validate($form_id, $form_values) { - foreach (form_controller_get_modules() as $module) { - $form_values[$module]['#form_id'] = $form_values['target_form_id']; - $function = $module .'_form_controller'; - $function('validate', $form_values[$module]); +function form_controller_process_form(&$element, $edit, &$form_state) { + // Build form information. + $form_id = $element['form_id']['#value']; + $form_elements = form_controller_get_elements($element); + $context = array( + 'form_id' => $form_id, + 'form' => $element, + 'elements' => $form_elements, + ); + $form_state['form_controller'] = $context; + + // @todo Cache this. + foreach (module_invoke_all('form_controller_info') as $name => $info) { + if (!empty($info['process callback']) && function_exists($info['process callback'])) { + $function = $info['process callback']; + $function($element, $form_state, $form_id, $form_elements); + } } + + // return $element; +} + +function form_controller_get_value($form_state, $parents) { + $values = $form_state['values']; + foreach ($parents as $parent) { + $values = (isset($values[$parent]) ? $values[$parent] : NULL); + } + return $values; } +// @see http://drupal.org/node/633058 for a D7 patch. +function form_controller_validate_username($element, &$form_state) { + if (!empty($element['#value']) && !($account = user_load(array('name' => $element['#value'])))) { + form_error($element, t('The username %name does not exist.', array('%name' => $element['#value']))); + } +} + + + + /** - * Form controller settings submit callback. + * Implements hook_form_controller_info(). * - * Returns form values to hook_form_controller() implementations. + * Example implementation for Contact module. Replace 'contact' with 'mollom' + * or whatever to imagine other implementations. */ -function form_controller_form_submit($form_id, $form_values) { - foreach (form_controller_get_modules() as $module) { - $form_values[$module]['#form_id'] = $form_values['target_form_id']; - $function = $module .'_form_controller'; - $function('submit', $form_values[$module]); +function contact_form_controller_info() { + $alterations['contact_webmaster'] = array( + 'title' => t('Contact webmaster'), + 'description' => t('Sends the chosen field contents to a selected user.'), + 'form callback' => 'contact_form_configure_webmaster', + 'process callback' => 'contact_form_process_webmaster', + ); + return $alterations; +} + +function contact_form_configure_webmaster(&$complete_form, $form_id, $context) { + // Fetch configuration. + // @todo Take over configuration storage for most/simple implementations. + $config = variable_get('contact_webmaster_forms', array()); + + // I really wonder why Form API allows #element_validate, but not #element_submit... 15/11/2009 sun + $form['#submit'][] = 'contact_form_configure_webmaster_submit'; + + $form['status'] = array( + '#type' => 'checkbox', + '#title' => t('Send an e-mail notification upon form submission'), + '#default_value' => isset($config[$form_id]), + ); + if (!isset($config[$form_id])) { + $config[$form_id] = array(); + } + $config[$form_id] += array( + 'recipient' => '', + 'message' => '', + ); + $form['recipient'] = array( + '#type' => 'textfield', + '#title' => t('Recipient'), + '#default_value' => $config[$form_id]['recipient'], + '#autocomplete_path' => 'user/autocomplete', + '#element_validate' => array('form_controller_validate_username'), + ); + $options = array(); + foreach ($context['elements'] as $key => $element) { + if (isset($element['#title'])) { + $parents = implode('][', $element['#parents']); + $options[$parents] = $element['#title']; + } } - echo TRUE; - exit; + $form['element'] = array( + '#type' => 'select', + '#title' => t('Field containing message'), + '#default_value' => $config[$form_id]['message'], + '#options' => $options, + ); + $complete_form['#submit'][] = 'contact_form_configure_webmaster_submit'; + + return $form; } -/** - * Get a list of current hook_form_controller() implementations. - */ -function form_controller_get_modules() { - // Development 15/11/2009 sun - return TRUE; - static $modules; - if (!isset($modules)) { - $modules = module_implements('form_controller'); +function contact_form_configure_webmaster_submit($form, &$form_state) { + // @todo Take over configuration storage for most/simple implementations. + + $config = variable_get('contact_webmaster_forms', array()); + $form_id = $form_state['form_controller']['form_id']; + if ($form_state['values']['contact_webmaster']['status']) { + $elements = $form_state['form_controller']['elements']; + + $config[$form_id] = array( + 'recipient' => $form_state['values']['contact_webmaster']['recipient'], + 'element' => explode('][', $form_state['values']['contact_webmaster']['element']), + ); + } + else { + unset($config[$form_id]); } - return $modules; + variable_set('contact_webmaster_forms', $config); +} + +function contact_form_process_webmaster(&$form, &$form_state, $form_id, $form_elements) { + $config = variable_get('contact_webmaster_forms', array()); + if (isset($config[$form_id])) { + $form['#submit'][] = 'contact_form_webmaster_submit'; + } +} + +function contact_form_webmaster_submit($form, &$form_state) { + $config = variable_get('contact_webmaster_forms', array()); + $form_id = $form['form_id']['#value']; + + $recipient = $config[$form_id]['recipient']; + $message = form_controller_get_value($form_state, $config[$form_id]['element']); + + drupal_set_message('
' . var_export(array(
+    'recipient' => $recipient,
+    'message' => $message,
+  ), TRUE) . '
'); }