commit 653895481aaff7832f56dd0d7d2643dc3329941a
Author: Assos Assos <assos+forge@centrale-marseille.fr>
Date:   Wed Aug 28 23:31:23 2019 +0200

    Weekly update of contrib modules

diff --git a/drupal7/sites/all/modules/webform_validation/modules/webform_validation_testing/webform_validation_testing.features.inc b/drupal7/sites/all/modules/webform_validation/modules/webform_validation_testing/webform_validation_testing.features.inc
new file mode 100644
index 00000000..04329104
--- /dev/null
+++ b/drupal7/sites/all/modules/webform_validation/modules/webform_validation_testing/webform_validation_testing.features.inc
@@ -0,0 +1,24 @@
+<?php
+
+/**
+ * @file
+ * webform_validation_testing.features.inc
+ */
+
+/**
+ * Implements hook_node_info().
+ */
+function webform_validation_testing_node_info() {
+  $items = array(
+    'webform' => array(
+      'name' => t('Webform'),
+      'base' => 'node_content',
+      'description' => t('Create a new form or questionnaire accessible to users. Submission results and statistics are recorded and accessible to privileged users.'),
+      'has_title' => '1',
+      'title_label' => t('Title'),
+      'help' => '',
+    ),
+  );
+  drupal_alter('node_info', $items);
+  return $items;
+}
diff --git a/drupal7/sites/all/modules/webform_validation/modules/webform_validation_testing/webform_validation_testing.features.uuid_node.inc b/drupal7/sites/all/modules/webform_validation/modules/webform_validation_testing/webform_validation_testing.features.uuid_node.inc
new file mode 100644
index 00000000..89240a82
--- /dev/null
+++ b/drupal7/sites/all/modules/webform_validation/modules/webform_validation_testing/webform_validation_testing.features.uuid_node.inc
@@ -0,0 +1,120 @@
+<?php
+
+/**
+ * @file
+ * webform_validation_testing.features.uuid_node.inc
+ */
+
+/**
+ * Implements hook_uuid_features_default_content().
+ */
+function webform_validation_testing_uuid_features_default_content() {
+  $nodes = array();
+
+  $nodes[] = array(
+    'title' => 'webform_validation uuid_features test',
+    'log' => '',
+    'status' => 1,
+    'comment' => 0,
+    'promote' => 1,
+    'sticky' => 0,
+    'type' => 'webform',
+    'language' => 'und',
+    'created' => 1504429473,
+    'tnid' => 0,
+    'translate' => 0,
+    'uuid' => '4ca52408-9bbd-49fa-baae-698c37dc5f42',
+    'webform' => array(
+      'nid' => 1,
+      'next_serial' => 1,
+      'confirmation' => '',
+      'confirmation_format' => NULL,
+      'redirect_url' => '<confirmation>',
+      'status' => 1,
+      'block' => 0,
+      'allow_draft' => 0,
+      'auto_save' => 0,
+      'submit_notice' => 1,
+      'confidential' => 0,
+      'submit_text' => '',
+      'submit_limit' => -1,
+      'submit_interval' => -1,
+      'total_submit_limit' => -1,
+      'total_submit_interval' => -1,
+      'progressbar_bar' => 1,
+      'progressbar_page_number' => 0,
+      'progressbar_percent' => 0,
+      'progressbar_pagebreak_labels' => 1,
+      'progressbar_include_confirmation' => 1,
+      'progressbar_label_first' => 'Start',
+      'progressbar_label_confirmation' => 'Complete',
+      'preview' => 0,
+      'preview_next_button_label' => '',
+      'preview_prev_button_label' => '',
+      'preview_title' => '',
+      'preview_message' => '',
+      'preview_message_format' => NULL,
+      'preview_excluded_components' => array(),
+      'record_exists' => TRUE,
+      'roles' => array(
+        0 => 1,
+        1 => 2,
+      ),
+      'emails' => array(),
+      'components' => array(
+        1 => array(
+          'nid' => 1,
+          'cid' => 1,
+          'pid' => 0,
+          'form_key' => 'answer',
+          'name' => 'What’s the answer to the ultimate question?',
+          'type' => 'number',
+          'value' => '',
+          'extra' => array(
+            'title_display' => 'before',
+            'description_above' => 0,
+            'private' => 0,
+            'wrapper_classes' => '',
+            'css_classes' => '',
+            'integer' => 1,
+            'type' => 'textfield',
+            'field_prefix' => '',
+            'field_suffix' => '',
+            'disabled' => 0,
+            'unique' => 0,
+            'description' => '',
+            'attributes' => array(),
+            'analysis' => FALSE,
+            'min' => '',
+            'max' => '',
+            'step' => '',
+            'decimals' => '',
+            'point' => '.',
+            'separator' => ',',
+            'excludezero' => 0,
+          ),
+          'required' => 1,
+          'weight' => 0,
+          'page_num' => 1,
+        ),
+      ),
+      'conditionals' => array(),
+      'validation' => array(
+        'answer_42' => array(
+          'rulename' => 'answer_42',
+          'validator' => 'specific_value',
+          'data' => 42,
+          'error_message' => 'That’s the wrong answer.',
+          'negate' => FALSE,
+          'weight' => 0,
+          'components' => array(
+            1 => 1,
+          ),
+        ),
+      ),
+    ),
+    'date' => '2017-09-03 09:04:33 +0000',
+    'user_uuid' => '155c7ec5-8f9d-4934-888d-6951b7ac0728',
+  );
+  return $nodes;
+}
diff --git a/drupal7/sites/all/modules/webform_validation/modules/webform_validation_testing/webform_validation_testing.info b/drupal7/sites/all/modules/webform_validation/modules/webform_validation_testing/webform_validation_testing.info
index 038d9239..d9bd13a8 100644
--- a/drupal7/sites/all/modules/webform_validation/modules/webform_validation_testing/webform_validation_testing.info
+++ b/drupal7/sites/all/modules/webform_validation/modules/webform_validation_testing/webform_validation_testing.info
@@ -7,9 +7,12 @@ dependencies[] = node
 dependencies[] = uuid_features
 dependencies[] = webform_validation
 hidden = TRUE
+features[features_api][] = api:2
+features[node][] = webform
+features[uuid_node][] = 4ca52408-9bbd-49fa-baae-698c37dc5f42
 
-; Information added by Drupal.org packaging script on 2018-10-01
-version = "7.x-1.15"
+; Information added by Drupal.org packaging script on 2019-08-26
+version = "7.x-1.16"
 core = "7.x"
 project = "webform_validation"
-datestamp = "1538404086"
+datestamp = "1566849187"
diff --git a/drupal7/sites/all/modules/webform_validation/modules/webform_validation_testing/webform_validation_testing.module b/drupal7/sites/all/modules/webform_validation/modules/webform_validation_testing/webform_validation_testing.module
index a2ce58f9..63539052 100644
--- a/drupal7/sites/all/modules/webform_validation/modules/webform_validation_testing/webform_validation_testing.module
+++ b/drupal7/sites/all/modules/webform_validation/modules/webform_validation_testing/webform_validation_testing.module
@@ -5,6 +5,8 @@
  * Module file for webform_validation_testing.
  */
 
+include_once 'webform_validation_testing.features.inc';
+
 /**
  * Implements hook_webform_validation_validators().
  */
diff --git a/drupal7/sites/all/modules/webform_validation/tests/WebformValidationTestCase.test b/drupal7/sites/all/modules/webform_validation/tests/WebformValidationTestCase.test
index 429b7a1f..1c6864d4 100644
--- a/drupal7/sites/all/modules/webform_validation/tests/WebformValidationTestCase.test
+++ b/drupal7/sites/all/modules/webform_validation/tests/WebformValidationTestCase.test
@@ -46,7 +46,8 @@ class WebformValidationTestCase extends WebformTestCase {
 
     // Rule creation page.
     $this->clickLink('Minimum length');
-    $this->assertTitle('Add validation | Drupal');
+    $this->assertTitle('Add validation rule | Drupal');
+    $this->assertText('Minimum length');
     $element = $this->xpath("//input[@id='edit-data'][@name='data'][@type='text']");
     $this->assertTrue($element, 'Page contains "edit-data" input.');
 
@@ -59,6 +60,27 @@ class WebformValidationTestCase extends WebformTestCase {
     $this->drupalPost(NULL, $values, t('Add rule'));
     $this->assertText(htmlspecialchars($values['rulename'], ENT_QUOTES), 'Rule name appears on page.');
 
+    // Test rule creation validation.
+    $this->clickLink('Compare two values');
+    $this->assertTitle('Add validation rule | Drupal');
+    $this->assertText('Compare two values');
+    $values = array(
+      'rulename' => $this->randomString(),
+      'data' => 'invalid',
+    );
+    $this->drupalPost(NULL, $values, t('Add rule'));
+    $this->assertText('Comparison operator is invalid.', 'Custom data validation fails.');
+    $this->assertText('You need to select exactly 2 components.', 'Custom data validation fails.');
+    $this->assertText('Custom error message field is required.', 'Custom data validation fails.');
+
+    $values = array(
+      'rule_components[20]' => TRUE,
+      'rule_components[21]' => TRUE,
+      'data' => '<',
+      'error_message' => 'Error message.',
+    );
+    $this->drupalPost(NULL, $values, t('Add rule'));
+
     $this->clickLink('Webform Validation Testing 1');
     $element = $this->xpath("//textarea[@id='edit-data'][@name='data']");
     $this->assertTrue($element, 'Page contains "edit-data" textarea.');
diff --git a/drupal7/sites/all/modules/webform_validation/tests/WebformValidationUnitTestCase.test b/drupal7/sites/all/modules/webform_validation/tests/WebformValidationUnitTestCase.test
new file mode 100644
index 00000000..a8c3cff9
--- /dev/null
+++ b/drupal7/sites/all/modules/webform_validation/tests/WebformValidationUnitTestCase.test
@@ -0,0 +1,67 @@
+<?php
+
+/**
+ * Unit test Webform Validation module.
+ */
+class WebformValidationUnitTestCase extends DrupalUnitTestCase {
+
+  /**
+   * {@inheritdoc}
+   */
+  public static function getInfo() {
+    return array(
+      'name' => t('Webform Validation Unit Tests'),
+      'description' => t('Unit tests for Webform Validation module.'),
+      'group' => t('Webform'),
+    );
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function setUp() {
+    parent::setUp();
+  }
+
+  /**
+   * The tests.
+   */
+  public function test() {
+
+    $validator_name = 'comparison';
+    $items = array(
+      'one' => array(
+        'hour' => 12,
+        'minute' => 1,
+        'ampm' => 'AM',
+      ),
+      'two' => array(
+        'hour' => 12,
+        'minute' => 4,
+        'ampm' => 'AM',
+      ),
+    );
+    $components = array(
+      'one' => array(
+        'type' => 'time',
+      ),
+      'two' => array(
+        'type' => 'time',
+      ),
+    );
+    $rule = array(
+      'data' => '<',
+      'components' => $components,
+      'error_message' => 'Error message.',
+    );
+    $test_value = webform_validation_webform_validation_validate($validator_name, $items, $components, $rule);
+    $value = array();
+    $this->assertIdentical($test_value, $value, 'No error for passing validation.');
+
+    $rule['data'] = '>';
+    $test_value = webform_validation_webform_validation_validate($validator_name, $items, $components, $rule);
+    $value = array('two' => 'Error message.');
+    $this->assertIdentical($test_value, $value, 'Error for failing validation.');
+  }
+
+}
diff --git a/drupal7/sites/all/modules/webform_validation/tests/WebformValidationUuidFeaturesTestCase.test b/drupal7/sites/all/modules/webform_validation/tests/WebformValidationUuidFeaturesTestCase.test
new file mode 100644
index 00000000..0b9985b9
--- /dev/null
+++ b/drupal7/sites/all/modules/webform_validation/tests/WebformValidationUuidFeaturesTestCase.test
@@ -0,0 +1,76 @@
+<?php
+
+/**
+ * Test the features integration of webform_validation.
+ */
+class WebformValidationUuidFeaturesTestCase extends DrupalWebTestCase {
+
+  /**
+   * {@inheritdoc}
+   */
+  public static function getInfo() {
+    return array(
+      'name' => t('Webform Validation: UUID features integration'),
+      'description' => t('Test the integration of webform_validation and uuid_features.'),
+      'group' => t('Webform'),
+      'dependencies' => array('webform_validation_testing'),
+    );
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function setUp() {
+    parent::setUp(array('webform_validation_testing'));
+    module_load_include('entity.inc', 'uuid');
+  }
+
+  /**
+   * Test whether the validations are loaded properly by enabling the feature.
+   */
+  public function testValidationsLoaded() {
+    $uuids = array('4ca52408-9bbd-49fa-baae-698c37dc5f42');
+    $nids = entity_get_id_by_uuid('node', $uuids);
+    $nid = reset($nids);
+    $rules = webform_validation_get_node_rules($nid);
+    $this->assertEqual(1, count($rules));
+    $rule = reset($rules);
+    unset($rule['ruleid']);
+    unset($rule['nid']);
+    $k = array_keys($rule['components']);
+    $rule['components'] = array_combine($k, $k);
+    $this->assertEqual(array(
+      'rulename' => 'answer_42',
+      'validator' => 'specific_value',
+      'data' => '42',
+      'error_message' => 'That’s the wrong answer.',
+      'negate' => FALSE,
+      'weight' => '0',
+      'components' => array(
+        1 => 1,
+      ),
+    ), $rule);
+  }
+
+  /**
+   * Test whether re-exporting the feature yields the same result.
+   */
+  public function testValidationExport() {
+    features_include_defaults();
+
+    // Get data for a new export.
+    $uuids = array('4ca52408-9bbd-49fa-baae-698c37dc5f42');
+    $data = features_invoke('uuid_node', 'features_export_render', 'webform_validation_uuid_features_test', $uuids);
+    $nodes = eval($data['uuid_features_default_content']);
+    $this->assertEqual(1, count($nodes));
+    $node = (object) reset($nodes);
+    $this->assertEqual($uuids[0], $node->uuid);
+    $this->assertTrue(!empty($node->webform['validation']));
+
+    // Compare data with the defaults in the feature.
+    $defaults = features_get_default('uuid_node', 'webform_validation_testing');
+    $default_node = (object) reset($defaults);
+    $this->assertEqual($default_node->webform['validation'], $node->webform['validation']);
+  }
+
+}
diff --git a/drupal7/sites/all/modules/webform_validation/webform_validation.admin.inc b/drupal7/sites/all/modules/webform_validation/webform_validation.admin.inc
index 902cfee0..03b50bd9 100644
--- a/drupal7/sites/all/modules/webform_validation/webform_validation.admin.inc
+++ b/drupal7/sites/all/modules/webform_validation/webform_validation.admin.inc
@@ -173,12 +173,13 @@ function webform_validation_manage_rule($form, $form_state, $node, $action, $val
   $form = array();
   $rule_validator = webform_validation_get_validator_info($validator);
 
-  $form['rule'] = array(
-    '#type' => 'fieldset',
-    '#title' => ($action == 'edit') ? t('Edit rule') : t('Add rule'),
-    '#collapsible' => FALSE,
-    '#collapsed' => FALSE,
-  );
+  if ($action === 'edit') {
+    $title = t('Edit validation rule %title', array('%title' => $rule['rulename']));
+  }
+  else {
+    $title = t('Add validation rule');
+  }
+  drupal_set_title($title, PASS_THROUGH);
 
   $form['rule']['validator'] = array(
     '#type' => 'hidden',
@@ -208,10 +209,16 @@ function webform_validation_manage_rule($form, $form_state, $node, $action, $val
     );
   }
 
+  $form['rule']['type'] = array(
+    '#type' => 'item',
+    '#title' => t('Rule type'),
+    '#markup' => $rule_validator['name'],
+  );
+
   $form['rule']['rulename'] = array(
     '#type' => 'textfield',
     '#title' => t('Rule name'),
-    '#default_value' => (isset($rule['rulename'])) ? $rule['rulename'] : '',
+    '#default_value' => isset($rule['rulename']) ? $rule['rulename'] : NULL,
     '#required' => TRUE,
     '#size' => 60,
     '#maxlength' => 255,
@@ -224,7 +231,7 @@ function webform_validation_manage_rule($form, $form_state, $node, $action, $val
     '#weight' => 3,
     '#description' => t('Select the components to be validated by this validation rule'),
     '#options' => webform_validation_get_webform_components($node, $validator),
-    '#default_value' => (isset($rule['components'])) ? array_keys($rule['components']) : array(),
+    '#default_value' => isset($rule['components']) ? array_keys($rule['components']) : array(),
   );
 
   if (!empty($rule_validator['custom_data']) && is_array($rule_validator['custom_data'])) {
@@ -272,7 +279,7 @@ function webform_validation_manage_rule($form, $form_state, $node, $action, $val
 
   $form['rule']['submit'] = array(
     '#type' => 'submit',
-    '#value' => (isset($rule['ruleid'])) ? t('Save rule') : t('Add rule'),
+    '#value' => isset($rule['ruleid']) ? t('Save rule') : t('Add rule'),
     '#weight' => 25,
   );
 
@@ -297,19 +304,25 @@ function webform_validation_manage_rule_validate($form, &$form_state) {
   }
 
   $rule_validator = webform_validation_get_validator_info($values['validator']);
+
+  // Validate custom data.
+  if ($values['data'] && !empty($rule_validator['custom_data']['validate_regex']) && !preg_match($rule_validator['custom_data']['validate_regex'], $values['data'])) {
+    form_set_error('data', $rule_validator['custom_data']['label'] . ' ' . t('is invalid.'));
+  }
+
   $selected_components = count(array_filter($values['rule_components']));
   // Check validator min_components and min_components property when they are
   // equal.
   if (isset($rule_validator['min_components']) && isset($rule_validator['max_components']) && $rule_validator['min_components'] === $rule_validator['max_components'] && $selected_components !== $rule_validator['min_components']) {
-    form_set_error('rule_components', format_plural($rule_validator['min_components'], 'You need to select exactly @count component', 'You need to select exactly @count components'));
+    form_set_error('rule_components', format_plural($rule_validator['min_components'], 'You need to select exactly @count component.', 'You need to select exactly @count components.'));
   }
   // Check validator min_components property.
   elseif (isset($rule_validator['min_components']) && $selected_components < $rule_validator['min_components']) {
-    form_set_error('rule_components', format_plural($rule_validator['min_components'], 'You need to select at least @count component', 'You need to select at least @count components'));
+    form_set_error('rule_components', format_plural($rule_validator['min_components'], 'You need to select at least @count component.', 'You need to select at least @count components.'));
   }
   // Check validator max_components property.
   elseif (isset($rule_validator['max_components']) && $selected_components > $rule_validator['max_components']) {
-    form_set_error('rule_components', format_plural($rule_validator['max_components'], 'You can select @count component at most', 'You can select @count components at most'));
+    form_set_error('rule_components', format_plural($rule_validator['max_components'], 'You can select @count component at most.', 'You can select @count components at most.'));
   }
 }
 
diff --git a/drupal7/sites/all/modules/webform_validation/webform_validation.info b/drupal7/sites/all/modules/webform_validation/webform_validation.info
index 2529c332..79c4c74b 100644
--- a/drupal7/sites/all/modules/webform_validation/webform_validation.info
+++ b/drupal7/sites/all/modules/webform_validation/webform_validation.info
@@ -5,10 +5,12 @@ package = Webform
 dependencies[] = webform
 test_dependencies[] = webform_validation_testing
 files[] = tests/WebformValidationTestCase.test
+files[] = tests/WebformValidationUnitTestCase.test
+files[] = tests/WebformValidationUuidFeaturesTestCase.test
 php = 5.3
 
-; Information added by Drupal.org packaging script on 2018-10-01
-version = "7.x-1.15"
+; Information added by Drupal.org packaging script on 2019-08-26
+version = "7.x-1.16"
 core = "7.x"
 project = "webform_validation"
-datestamp = "1538404086"
+datestamp = "1566849187"
diff --git a/drupal7/sites/all/modules/webform_validation/webform_validation.module b/drupal7/sites/all/modules/webform_validation/webform_validation.module
index 6fcd079f..11437ce1 100644
--- a/drupal7/sites/all/modules/webform_validation/webform_validation.module
+++ b/drupal7/sites/all/modules/webform_validation/webform_validation.module
@@ -234,7 +234,9 @@ function webform_validation_validate($form, &$form_state) {
 /**
  * Helper function to get all field keys (including fields in fieldsets).
  *
- * @deprecated No longer used and will be removed in 7.x-2.x.
+ * @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();
@@ -274,7 +276,9 @@ function webform_validation_parent_tree($cid, $components) {
 /**
  * Get array of formkeys for all components that have been assigned to a rule.
  *
- * @deprecated No longer used and will be removed in 7.x-2.x.
+ * @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();
@@ -531,3 +535,65 @@ function webform_validation_webform_validator_alter(&$validators) {
     }
   }
 }
+
+/**
+ * Implements hook_uuid_node_features_export_alter().
+ */
+function webform_validation_uuid_node_features_export_alter(&$data, $node, $module) {
+  $nid = reset(entity_get_id_by_uuid('node', array($node->uuid)));
+  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_uuid_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);
+      // 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);
+        }
+      }
+    }
+  }
+
+}
diff --git a/drupal7/sites/all/modules/webform_validation/webform_validation.rules.inc b/drupal7/sites/all/modules/webform_validation/webform_validation.rules.inc
index 8466db77..9f731701 100644
--- a/drupal7/sites/all/modules/webform_validation/webform_validation.rules.inc
+++ b/drupal7/sites/all/modules/webform_validation/webform_validation.rules.inc
@@ -18,6 +18,14 @@ function webform_validation_get_rule($ruleid) {
 
 /**
  * Get an array of rules assigned to a webform node.
+ *
+ * @param int $nid
+ *   The node ID.
+ *
+ * @return array
+ *   The rules for a node keyed by their rule ID.
+ *
+ * @see webform_validation_get_node_rules_assoc()
  */
 function webform_validation_get_node_rules($nid) {
   $rules = array();
@@ -30,6 +38,34 @@ function webform_validation_get_node_rules($nid) {
   return $rules;
 }
 
+/**
+ * Get an array of rules assigned to a webform node keyed by their rulename.
+ *
+ * @param int $nid
+ *   The node ID.
+ *
+ * @return array
+ *   The rules for a node keyed by their rulename.
+ *
+ * @see webform_validation_get_node_rules()
+ */
+function webform_validation_get_node_rules_assoc($nid) {
+  $assoc_rules = array();
+  if ($rules = webform_validation_get_node_rules($nid)) {
+    foreach ($rules as $rule) {
+      $components = array();
+      foreach ($rule['components'] as $cid => $component) {
+        $components[$cid] = $cid;
+      }
+      ksort($components);
+      $rule['components'] = $components;
+      $assoc_rules[$rule['rulename']] = $rule;
+    }
+    ksort($assoc_rules);
+  }
+  return $assoc_rules;
+}
+
 /**
  * Get an array of components linked to a rule.
  */
diff --git a/drupal7/sites/all/modules/webform_validation/webform_validation.validators.inc b/drupal7/sites/all/modules/webform_validation/webform_validation.validators.inc
index 5f4e94af..a2c61042 100644
--- a/drupal7/sites/all/modules/webform_validation/webform_validation.validators.inc
+++ b/drupal7/sites/all/modules/webform_validation/webform_validation.validators.inc
@@ -36,8 +36,19 @@ function webform_validation_webform_validation_validators() {
         'textfield',
       ),
       'custom_data' => array(
-        'label' => t('Specify numeric validation range'),
-        'description' => t('Optionally specify the minimum-maximum range to validate the user-entered numeric value against. Usage:') . theme('item_list', array('items' => array(t('empty: no value validation'), t('"100": greater than or equal to 100'), t('"|100": less than or equal to 100 (including negative numbers)'), t('"0|100": greater than or equal to 0 &amp; less than or equal to 100'), t('"10|100": greater than or equal to 10 &amp; less than or equal to 100'), t('"-100|-10": greater than or equal to -100 &amp; less than or equal to -10')))),
+        'label' => t('Numeric validation range'),
+        'description' => t('Optionally specify the minimum-maximum range to validate the user-entered numeric value against. Usage:') . theme('table', array(
+          'header' => array(t('Value'), t('Meaning')),
+          'rows' => array(
+            array(t('(empty)'), t('No value validation')),
+            array('100', t('Greater than or equal to 100')),
+            array('|100', t('Less than or equal to 100 (including negative numbers)')),
+            array('0|100', t('Greater than or equal to 0 &amp; less than or equal to 100')),
+            array('10|100', t('Greater than or equal to 10 &amp; less than or equal to 100')),
+            array('-100|-10', t('Greater than or equal to -100 &amp; less than or equal to -10')),
+          ),
+        )),
+        'validate_regex' => '/^((-?[\d.]+)?\|)?(-?[\d.]+)$/',
         'required' => FALSE,
       ),
       'description' => t('Verifies that user-entered values are numeric, with the option to specify min and / or max values.'),
@@ -54,6 +65,7 @@ function webform_validation_webform_validation_validators() {
       'custom_data' => array(
         'label' => t('Minimum number of characters'),
         'description' => t('Specify the minimum number of characters that have to be entered to pass validation.'),
+        'validate_regex' => '/^\d+$/',
       ),
       'description' => t('Verifies that a user-entered value contains at least the specified number of characters.'),
     ),
@@ -69,6 +81,7 @@ function webform_validation_webform_validation_validators() {
       'custom_data' => array(
         'label' => t('Maximum number of characters'),
         'description' => t('Specify the maximum number of characters that can be entered to pass validation.'),
+        'validate_regex' => '/^\d+$/',
       ),
       'description' => t('Verifies that a user-entered value contains at most the specified number of characters.'),
     ),
@@ -83,6 +96,7 @@ function webform_validation_webform_validation_validators() {
       'custom_data' => array(
         'label' => t('Minimum number of words'),
         'description' => t('Specify the minimum number of words that have to be entered to pass validation. Words are defined as strings of letters separated by spaces.'),
+        'validate_regex' => '/^\d+$/',
       ),
       'description' => t('Verifies that a user-entered value contains at least the specified number of words.'),
     ),
@@ -97,6 +111,7 @@ function webform_validation_webform_validation_validators() {
       'custom_data' => array(
         'label' => t('Maximum number of words'),
         'description' => t('Specify the maximum number of words that have to be entered to pass validation. Words are defined as strings of letters separated by spaces.'),
+        'validate_regex' => '/^\d+$/',
       ),
       'description' => t('Verifies that a user-entered value contains at most the specified number of words.'),
     ),
@@ -117,6 +132,7 @@ function webform_validation_webform_validation_validators() {
             t('Enter "<=20" if the components must add up to less than or equal to 20.'),
           ),
         )),
+        'validate_regex' => '/^(=|>|>=|<|<=)(-?[\d.]+)$/',
       ),
       'description' => t('Require the values of the selected fields to add up to exactly, greater than or equal to, or less than or equal to a specified number.'),
     ),
@@ -152,6 +168,7 @@ function webform_validation_webform_validation_validators() {
       'custom_data' => array(
         'label' => t('Comparison operator'),
         'description' => t('Specify the comparison operator you want to use. Must be one of: >, >=, <, <=. The validator will compare the first component with the second using this operator. If the components are of type email, they will get case-insensitive comparison.'),
+        'validate_regex' => '/^(>|>=|<|<=)$/',
       ),
       'min_components' => 2,
       'max_components' => 2,
@@ -229,6 +246,7 @@ function webform_validation_webform_validation_validators() {
             t('Enter "<=2" if the user must complete <b>at most</b> 2 of the selected components.'),
           ),
         )),
+        'validate_regex' => '/^[><]?=\d+$/',
       ),
       'min_components' => 2,
       'description' => t('Requires the user to complete some number of components out of a group of components. For example, complete at least 2 out of 3, complete at most 4 out of 6, or complete exactly 3 our of 4.'),
@@ -241,6 +259,7 @@ function webform_validation_webform_validation_validators() {
       'custom_data' => array(
         'label' => t('Minimum number of selections'),
         'description' => t('Specify the minimum number of options a user should select.'),
+        'validate_regex' => '/^\d+$/',
       ),
       'description' => t('Forces the user to select at least a defined number of options from the specified webform components.'),
     ),
@@ -252,6 +271,7 @@ function webform_validation_webform_validation_validators() {
       'custom_data' => array(
         'label' => t('Maximum number of selections'),
         'description' => t('Specify the maximum number of options a user can select.'),
+        'validate_regex' => '/^\d+$/',
       ),
       'description' => t('Forces the user to select at most a defined number of options from the specified webform components.'),
     ),
@@ -264,6 +284,7 @@ function webform_validation_webform_validation_validators() {
       'custom_data' => array(
         'label' => t('Number of selections'),
         'description' => t('Specify how many options a user can select.'),
+        'validate_regex' => '/^\d+$/',
       ),
       'description' => t('Forces the user to select exactly the defined number of options from the specified webform components.'),
     ),
@@ -431,6 +452,19 @@ function webform_validation_webform_validation_validate($validator_name, $items,
     return NULL;
   }
 
+  // Allow translation for all components item names if available.
+  if (module_exists('webform_localization')) {
+    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'];
+      }
+    }
+  }
+
   // For certain validators, if all components to be compared are email
   // components, make the values lower-case to avoid case-sensitive comparison.
   if (in_array($validator_name, array('equal', 'comparison', 'unique'))) {
@@ -624,11 +658,16 @@ function webform_validation_webform_validation_validate($validator_name, $items,
           }
         }
         elseif ($components[$entry[$count]['key']]['type'] === 'time') {
-          $time = $value['hour'] . ':' . $value['minute'];
-          if (!empty($value['ampm'])) {
-            $time .= ' ' . $value['ampm'];
+          if (isset($value['hour']) && isset($value['minute'])) {
+            $time = $value['hour'] . ':' . str_pad($value['minute'], 2, '0', STR_PAD_LEFT);
+            if (!empty($value['ampm'])) {
+              $time .= ' ' . $value['ampm'];
+            }
+            $time = strtotime($time);
+          }
+          else {
+            $time = NULL;
           }
-          $time = strtotime($time);
           if ($time) {
             $entry[$count]['value'] = date('H:i', $time);
           }
