aDriv4 - MANAGER
Edit File: webform_validation.module
<?php /** * @file * Add validation rules to webforms. */ include_once 'webform_validation.validators.inc'; include_once 'webform_validation.rules.inc'; /** * Implements hook_menu(). */ function webform_validation_menu() { $items = array(); $items['node/%webform_menu/webform/validation'] = array( 'title' => 'Form validation', 'page callback' => 'webform_validation_manage', 'page arguments' => array(1), 'access callback' => 'node_access', 'access arguments' => array('update', 1), 'file' => 'webform_validation.admin.inc', 'weight' => 3, 'type' => MENU_LOCAL_TASK, ); $items['node/%webform_menu/webform/validation/add/%'] = array( 'title' => 'Add validation', 'page callback' => 'drupal_get_form', 'page arguments' => array('webform_validation_manage_rule', 1, 'add', 5), 'access callback' => 'node_access', 'access arguments' => array('update', 1), 'file' => 'webform_validation.admin.inc', 'type' => MENU_CALLBACK, ); $items['node/%webform_menu/webform/validation/edit/%/%webform_validation_rule'] = array( 'title' => 'Edit rule', 'page callback' => 'drupal_get_form', 'page arguments' => array('webform_validation_manage_rule', 1, 'edit', 5, 6), 'access callback' => 'node_access', 'access arguments' => array('update', 1), 'file' => 'webform_validation.admin.inc', 'type' => MENU_CALLBACK, ); $items['node/%webform_menu/webform/validation/delete/%webform_validation_rule'] = array( 'title' => 'Delete rule', 'page callback' => 'drupal_get_form', 'page arguments' => array('webform_validation_delete_rule', 5), 'access callback' => 'node_access', 'access arguments' => array('update', 1), 'file' => 'webform_validation.admin.inc', 'type' => MENU_CALLBACK, ); return $items; } /** * Load a validation rule. * * @param int $ruleid * The rule ID. * * @return array|false * The rule or FALSE if no rule exists. */ function webform_validation_rule_load($ruleid) { $result = db_query("SELECT ruleid, rulename, nid, validator, data, error_message, negate, weight FROM {webform_validation_rule} WHERE ruleid = :ruleid", array(':ruleid' => $ruleid), array('fetch' => PDO::FETCH_ASSOC)); $rule = $result->fetchAssoc(); if (!$rule) { return FALSE; } $rule['components'] = webform_validation_get_rule_components($ruleid, $rule['nid']); $rule['negate'] = (bool) $rule['negate']; return $rule; } /** * Implements hook_theme(). */ function webform_validation_theme() { return array( 'webform_validation_manage_add_rule' => array( 'variables' => array( 'nid' => NULL, ), ), 'webform_validation_manage_overview_form' => array( 'render element' => 'form', ), ); } /** * Implements hook_form_BASE_FORM_ID_alter(). */ function webform_validation_form_webform_client_form_alter(&$form, &$form_state, $form_id) { $form['#validate'][] = 'webform_validation_validate'; if (module_exists('maxlength')) { $nid = substr($form_id, strlen('webform_client_form') + 1); $rules = webform_validation_get_node_rules($nid); foreach ($rules as $ruleid => $rule) { if ($rule['validator'] == 'max_length') { $length_limit = $rule['data']; $components = $rule['components']; foreach ($components as $cid => $component) { // Define $form_element as the webform element representing this // component, even if it's nested in multiple arrays, as webform // elemens often are (e.g., fieldsets). Assign by reference here, // since we need to modify the form element itself and don't know // its array depth or keys by which to access it. $form_element = &_webform_validation_get_webform_element($component, $form); // Append to this form element the relevant properties which are // supported by maxlength module. $form_element['#pre_render'][] = 'maxlength_pre_render'; $form_element['#maxlength'] = $length_limit; $form_element['#maxlength_js'] = TRUE; } } } } } /** * Implements hook_i18n_string_info(). */ function webform_validation_i18n_string_info() { $groups = array(); $groups['webform_validation'] = array( 'title' => t('Webform Validation'), 'description' => t('Translatable strings for webform validation translation'), // This group doesn't have strings with format. 'format' => FALSE, // This group cannot list all strings. 'list' => FALSE, 'refresh callback' => 'webform_validation_i18n_string_refresh', ); return $groups; } /** * Webform validation handler to validate against the given rules. */ function webform_validation_validate($form, &$form_state) { $static_error_messages = &drupal_static(__FUNCTION__, array()); $page_count = 1; $nid = $form_state['values']['details']['nid']; $node = node_load($nid); $values = isset($form_state['values']['submitted']) ? $form_state['values']['submitted'] : NULL; $flat_values = _webform_client_form_submit_flatten($node, $values); $rules = webform_validation_get_node_rules($nid); $sid = empty($form_state['values']['details']['sid']) ? 0 : $form_state['values']['details']['sid']; // Get number of pages for this webform. if (isset($form_state['webform']['page_count'])) { $page_count = $form_state['webform']['page_count']; } elseif (isset($form_state['storage']['page_count'])) { $page_count = $form_state['storage']['page_count']; } // Filter out rules that don't apply to this step in the multistep form. if ($values && $page_count && $page_count > 1) { $validators = webform_validation_get_validators(); foreach ($rules as $ruleid => $rule) { // Skip the rule if it does not have any components on the current page. if (!array_intersect_key($flat_values, $rule['components'])) { unset($rules[$ruleid]); } // For validators that require at least 2 components, skip the rule if any // of the components are on a page past the current page. elseif (isset($validators[$rule['validator']]['min_components']) && $validators[$rule['validator']]['min_components'] > 1) { foreach (array_keys($rule['components']) as $cid) { if ($node->webform['components'][$cid]['page_num'] > $form_state['webform']['page_num']) { unset($rules[$ruleid]); break; } } } } } if ($rules) { // Remove hidden components. if (defined('WebformConditionals::componentShown')) { // New conditionals system. $sorter = webform_get_conditional_sorter($node); // If the form was retrieved from the form cache, the conditionals may not // have been executed yet. if (!$sorter->isExecuted()) { $sorter->executeConditionals(array(), 0); } foreach ($node->webform['components'] as $key => $component) { if ($sorter->componentVisibility($component['cid'], $component['page_num']) !== WebformConditionals::componentShown) { unset($flat_values[$key]); } } } else { // Old conditionals system removed in Webform 7.x-4.8. // Webform 7.x-3.x does not define WEBFORM_CONDITIONAL_INCLUDE. // Define if needed. if (!defined('WEBFORM_CONDITIONAL_INCLUDE')) { define('WEBFORM_CONDITIONAL_INCLUDE', 1); } foreach ($node->webform['components'] as $key => $component) { // In Webform 7.x-3.x, _webform_client_form_rule_check() returns // boolean. // Cast to int so that the function behaves as it does in 7.x-4.x. if (isset($flat_values[$key]) && (int) _webform_client_form_rule_check($node, $component, 0, $form_state['values']['submitted']) !== WEBFORM_CONDITIONAL_INCLUDE) { unset($flat_values[$key]); } } } foreach ($rules as $rule) { // Create a list of components that need validation against this rule // (component id => user submitted value). $items = array(); foreach ($rule['components'] as $cid => $component) { if (array_key_exists($cid, $flat_values)) { $items[$cid] = $flat_values[$cid]; } } $rule['sid'] = $sid; // Have the submitted values validated. $components = webform_validation_prefix_keys($node->webform['components']); // Allow translation for all components item names if available. if (module_exists('webform_localization')) { module_load_include('inc', 'webform_localization', 'includes/webform_localization.i18n'); foreach ($components as &$component) { $dummy_element = array( '#title' => '', ); _webform_localization_translate_component($dummy_element, $component); if (isset($dummy_element['#title']) && (string) $dummy_element['#title']) { $component['name'] = $dummy_element['#title']; } } } $errors = module_invoke_all("webform_validation_validate", $rule['validator'], webform_validation_prefix_keys($items), $components, $rule); if ($errors) { $errors = webform_validation_unprefix_keys($errors); // Create hook_webform_validation_validate_alter(). Allow other modules // to alter error messages. $context = array( 'validator_name' => $rule['validator'], 'items' => $items, 'components' => $node->webform['components'], 'rule' => $rule, ); drupal_alter('webform_validation_validate', $errors, $context); foreach ($errors as $item_key => $error) { // Do not set error message if an identical message has already been // set. if (in_array($error, $static_error_messages, TRUE)) { continue; } $static_error_messages[] = $error; // Build the proper form element error key, taking into account // hierarchy. $error_key = 'submitted][' . webform_validation_parent_tree($item_key, $node->webform['components']) . $node->webform['components'][$item_key]['form_key']; if (is_array($error)) { foreach ($error as $sub_item_key => $sub_error) { form_set_error($error_key . '][' . $sub_item_key, $sub_error); } } else { // filter_xss() is run in _webform_validation_i18n_error_message(). // @ignore security_form_set_error. form_set_error($error_key, $error); } } } } } } /** * Helper function to get all field keys (including fields in fieldsets). * * @deprecated in webform_validation:7.x-1.14 and is removed from * webform_validation:7.x-2.0. No longer used. * @see https://www.drupal.org/project/webform_validation/issues/2841817 */ function webform_validation_get_field_keys($submitted, $node) { static $fields = array(); foreach (element_children($submitted) as $child) { if (is_array($submitted[$child]) && element_children($submitted[$child])) { // Only keep searching recursively if it's a fieldset. $group_components = _webform_validation_get_group_types(); if (in_array(_webform_validation_get_component_type($node, $child), $group_components)) { webform_validation_get_field_keys($submitted[$child], $node); } else { $fields[$child] = $child; } } else { $fields[$child] = $child; } } return $fields; } /** * Recursively add the parents for the element. * * These are used as the first argument to form_set_error(). */ function webform_validation_parent_tree($cid, $components) { $output = ''; if ($pid = $components[$cid]['pid']) { $output .= webform_validation_parent_tree($pid, $components); $output .= $components[$pid]['form_key'] . ']['; } return $output; } /** * Get array of formkeys for all components that have been assigned to a rule. * * @deprecated in webform_validation:7.x-1.14 and is removed from * webform_validation:7.x-2.0. No longer used. * @see https://www.drupal.org/project/webform_validation/issues/2841817 */ function webform_validation_rule_get_formkeys($rule) { $formkeys = array(); if (isset($rule['components'])) { foreach ($rule['components'] as $cid => $component) { $formkeys[] = $component['form_key']; } } return $formkeys; } /** * Prefix numeric array keys to avoid them being reindexed. * * Reindexing done in module_invoke_all(). * * Opposite of webform_validation_unprefix_keys(). */ function webform_validation_prefix_keys($arr) { $ret = array(); foreach ($arr as $k => $v) { $ret['item_' . $k] = $v; } return $ret; } /** * Undo prefixing numeric array keys. * * Opposite of webform_validation_prefix_keys(). */ function webform_validation_unprefix_keys($arr) { $ret = array(); foreach ($arr as $k => $v) { $new_key = str_replace('item_', '', $k); $ret[$new_key] = $v; } return $ret; } /** * Theme the 'add rule' list. */ function theme_webform_validation_manage_add_rule($variables) { $nid = $variables['nid']; $output = ''; $validators = webform_validation_get_validators(); if ($validators) { $results = db_query('SELECT DISTINCT type FROM {webform_component} WHERE nid = :nid', array('nid' => $nid)); $types = array(); while ($item = $results->fetch()) { $types[] = $item->type; } $output = '<h3>' . t('Add a validation rule') . '</h3>'; $output .= '<dl>'; foreach ($validators as $validator_key => $validator_info) { $validator_types = webform_validation_valid_component_types($validator_key); $title = $validator_info['name']; if (array_intersect($types, $validator_types)) { $url = 'node/' . $nid . '/webform/validation/add/' . $validator_key; $title = l($title, $url, array('query' => drupal_get_destination())); $component_list_postfix = ''; } else { $component_list_postfix = '; ' . t('none present in this form'); } $item = '<dt>' . $title . '</dt>'; $item .= '<dd>'; $item .= $validator_info['description']; $item .= ' ' . t('Works with: @component_types.', array('@component_types' => implode(', ', $validator_types) . $component_list_postfix)) . '</dd>'; $output .= $item; } $output .= '</dl>'; } return $output; } /** * Implements hook_webform_validation(). */ function webform_validation_webform_validation($type, $op, $data) { if ($type == 'rule' && in_array($op, array('add', 'edit'))) { if (module_exists('i18n_string') && isset($data['error_message'])) { i18n_string_update('webform_validation:error_message:' . $data['ruleid'] . ':message', $data['error_message']); } } } /** * Implements hook_node_insert(). */ function webform_validation_node_insert($node) { if (module_exists('clone') && in_array($node->type, webform_variable_get('webform_node_types'))) { webform_validation_node_clone($node); } } /** * Implements hook_node_delete(). */ function webform_validation_node_delete($node) { $rules = webform_validation_get_node_rules($node->nid); if ($rules) { $transaction = db_transaction(); foreach (array_keys($rules) as $ruleid) { webform_dynamic_delete_rule($ruleid); } } } /** * Adds support for node_clone module. */ function webform_validation_node_clone($node) { if (!in_array($node->type, webform_variable_get('webform_node_types'))) { return; } if (isset($node->clone_from_original_nid)) { $original_nid = $node->clone_from_original_nid; // Get existing rules for original node. $rules = webform_validation_get_node_rules($original_nid); if ($rules) { foreach ($rules as $orig_ruleid => $rule) { unset($rule['ruleid']); $rule['action'] = 'add'; // Attach existing rules to new node. $rule['nid'] = $node->nid; $rule['rule_components'] = $rule['components']; webform_validation_rule_save($rule); } } } } /** * Save a validation rule. * * Data comes from the admin form or nodeapi function in case of node clone. * * @param array $values * An associative array containing: * - action: "add" or "edit". * - ruleid: ID of the rule to edit. Do not set for "add". * - nid: Node ID of the Webform. * - validator: Machine name of the validator used by this validation rule. * - rulename: Human-readable name for this validation rule. * - rule_components: An array in which the keys and the values are the cid's * of the Webform components that this rule applies to. * * @return int * The $ruleid of the rule added or edited. */ function webform_validation_rule_save(array $values) { if ($values['action'] === 'add') { $primary_keys = array(); } elseif ($values['action'] === 'edit') { $primary_keys = array('ruleid'); } else { return FALSE; } $transaction = db_transaction(); drupal_write_record('webform_validation_rule', $values, $primary_keys); // Delete existing component records for this ruleid. if ($values['action'] === 'edit') { db_delete('webform_validation_rule_components') ->condition('ruleid', $values['ruleid']) ->execute(); } $components = array_filter($values['rule_components']); if ($values['ruleid'] && $components) { webform_validation_save_rule_components($values['ruleid'], $components); module_invoke_all('webform_validation', 'rule', $values['action'], $values); } return $values['ruleid']; } /** * Save components attached to a specific rule. * * @param int $ruleid * The ruleid of the rule being saved. * @param array $components * An array in which the keys are the cid's of the components attached to the * rule. * * @return array * An array of the return statuses for each query keyed by cid. */ function webform_validation_save_rule_components($ruleid, array $components) { $return_status = array(); foreach ($components as $cid => $component) { $return_status[$cid] = db_merge('webform_validation_rule_components') ->key(array( 'ruleid' => $ruleid, 'cid' => $cid, )) ->fields(array( 'ruleid' => $ruleid, 'cid' => $cid, )) ->execute(); } return $return_status; } /** * Given a webform node, get the component type based on a given component key. */ function _webform_validation_get_component_type($node, $component_key) { if ($node->webform['components']) { foreach ($node->webform['components'] as $component) { if ($component['form_key'] == $component_key) { return $component['type']; } } } return FALSE; } /** * Get all webform components that are defined as a group. */ function _webform_validation_get_group_types() { $types = array(); foreach (webform_components() as $name => $component) { if (isset($component['features']['group']) && $component['features']['group']) { $types[] = $name; } } return $types; } /** * Implements hook_webform_validator_alter(). */ function webform_validation_webform_validator_alter(&$validators) { // Add support for the Select (or Other) module. if (module_exists('select_or_other')) { // If this module exists, all select components can now except user input. // Thus we provide those components the same rules as a textfield. if ($validators) { foreach ($validators as $validator_name => $validator_info) { if (in_array('textfield', $validator_info['component_types'])) { $validators[$validator_name]['component_types'][] = 'select'; } $validators[$validator_name]['component_types'] = array_unique($validators[$validator_name]['component_types']); } } } } /** * Implements hook_uuid_node_features_export_alter(). */ function webform_validation_uuid_node_features_export_alter(&$data, $node, $module) { $nid = entity_get_id_by_uuid('node', array($node->uuid)); $nid = reset($nid); if (webform_validation_get_node_rules($nid)) { $data['dependencies']['webform_validation'] = 'webform_validation'; } } /** * Implements hook_uuid_node_features_export_render_alter(). */ function webform_validation_uuid_node_features_export_render_alter(&$export, $node, $module) { if (!empty($node->webform)) { $rules = webform_validation_get_node_rules_assoc($node->nid); foreach ($rules as &$rule) { unset($rule['nid']); unset($rule['ruleid']); } $export->webform['validation'] = $rules; } } /** * Implements hook_entity_uuid_save(). */ function webform_validation_entity_uuid_save($node, $entity_type) { if ($entity_type == 'node') { if (isset($node->webform['validation'])) { $rules = $node->webform['validation']; $orig_rules = webform_validation_get_node_rules_assoc($node->nid); $transaction = db_transaction(); // Delete obsolete rules. $delete = array_diff_key($orig_rules, $rules); foreach ($delete as $rule) { webform_dynamic_delete_rule($rule['ruleid']); } // Add new rules. $new = array_diff_key($rules, $orig_rules); foreach ($new as $rule) { $rule['action'] = 'add'; $rule['nid'] = $node->nid; $rule['rule_components'] = $rule['components']; webform_validation_rule_save($rule); } // Update existing rules. $existing = array_diff_key($rules, $new + $delete); foreach ($existing as $name => $rule) { $orig_rule = $orig_rules[$name]; $rule['nid'] = $orig_rule['nid']; $rule['ruleid'] = $orig_rule['ruleid']; if ($rule != $orig_rule) { $rule['action'] = 'edit'; $rule['rule_components'] = $rule['components']; webform_validation_rule_save($rule); } } } } } /** * Get a reference to a specific webform element. * * (For a given webform_validation rule component, and a given Drupal webform * form, get a reference to the webform element represented by the rule * component; return the correct element regardless of how deeply it's nested * in webform fieldsets or other wrappers.) * * @param array $component * Webform validation rule component. * @param array $form * Drupal webform form. * * @return array * Reference to the webform element represented by the rule component. */ function &_webform_validation_get_webform_element(array $component, array &$form) { // Define an array of ancestors, beginning with the component itself. $component_ancestors = array($component['form_key']); // Define the parent-id, starting with the parent-id of the component itself, // if any. $pid = $component['pid']; // Look into $form['#node']->webform['components'][$pid] to get any parent // of the component, and continue working up the family tree until there is // no more parent-id. while ($pid) { $parent = $form['#node']->webform['components'][$pid]; // Prepend the parent form_key to the array of ancestors. This causes the // array of ancestors to be ordered from ancestor to descendant. array_unshift($component_ancestors, $parent['form_key']); // Note this parent's parent-id, if any. $pid = $parent['pid']; } // $component_ancestors now contains the ordered ancestry. Cycle through it to // get the correct member of $form['submitted']. Assign by reference so that // we have a good reference to $webform_element to return. $webform_element = &$form['submitted']; foreach ($component_ancestors as $ancestor) { $webform_element = &$webform_element[$ancestor]; } return $webform_element; }