From 561ce4876767e8729b8fdf5eac3e916847caab02 Mon Sep 17 00:00:00 2001
From: Dan Chadwick <dan899@gmail.com>
Date: Wed, 25 Mar 2015 11:20:21 -0400
Subject: [PATCH] Issue #1885056 by DanChadwick: Conditional actions: required
 / optional.

---
 includes/webform.conditionals.inc        |    1 +
 includes/webform.webformconditionals.inc |   39 ++++++++++++++++++++++++++----
 js/webform.js                            |   14 ++++++++++
 webform.module                           |   20 ++++++++++-----
 4 files changed, 62 insertions(+), 12 deletions(-)

diff --git a/includes/webform.conditionals.inc b/includes/webform.conditionals.inc
index dfc9e34..b6c48a9 100644
--- a/includes/webform.conditionals.inc
+++ b/includes/webform.conditionals.inc
@@ -73,6 +73,7 @@ function webform_conditionals_form($form, &$form_state, $node) {
       '#sources' => $source_list,
       '#actions' => array(
         'show' => t('shown'),
+        'require' => t('required'),
       ),
       '#targets' => $target_list,
       '#parents' => array('conditionals', $rgid),
diff --git a/includes/webform.webformconditionals.inc b/includes/webform.webformconditionals.inc
index e99560b..fbe3e0c 100644
--- a/includes/webform.webformconditionals.inc
+++ b/includes/webform.webformconditionals.inc
@@ -27,6 +27,7 @@ class WebformConditionals {
   protected $pageMap;
   protected $childrenMap;
   protected $visibilityMap;
+  protected $requiredMap;
 
   public $errors;
 
@@ -300,10 +301,14 @@ class WebformConditionals {
       array_walk_recursive($this->visibilityMap, function (&$status) {
         $status = WebformConditionals::componentShown;
       });
+      // Create an empty required map
+      $this->requiredMap = array_fill(1, count($this->pageMap), array());
+
     } else {
       array_walk($this->visibilityMap[$page_num], function (&$status) {
         $status = WebformConditionals::componentShown;
       });
+      $this->requiredMap[$page_num] = array();
     }
 
     module_load_include('inc', 'webform', 'includes/webform.conditionals');
@@ -365,15 +370,18 @@ class WebformConditionals {
 
         foreach ($conditional['actions'] as $action) {
           $action_result = $action['invert'] ? !$conditional_result : $conditional_result;
+          $target = $action['target'];
+          $page_num = $components[$target]['page_num'];
           switch ($action['action']) {
             case 'show':
               if (!$action_result) {
-                $target = $action['target'];
-                $page_num = $components[$target]['page_num'];
                 $this->visibilityMap[$page_num][$target] = in_array($page_num, $source_page_nums) ? self::componentDependent : self::componentHidden;
                 $this->deleteFamily($input_values, $target, $this->visibilityMap[$page_num]);
               }
               break;
+            case 'require':
+              $this->requiredMap[$page_num][$target] = $action_result;
+              break;
           }
         }
 
@@ -391,8 +399,7 @@ class WebformConditionals {
    * Assumes that the conditionals have already been executed on the given page.
    *
    * @param integer $cid
-   *   The component id of the component whose visibilty is being sought, or 0
-   *   for the entire page.
+   *   The component id of the component whose visibilty is being sought.
    * @param integer $page_num
    *   The page number that the component is on.
    * @return integer
@@ -433,4 +440,26 @@ class WebformConditionals {
     return $result;
   }
 
- }
+  /**
+   * Returns whether a given component is always required, always opption, or
+   * unchanged by conditional logic.
+   *
+   * Assumes that the conditionals have already been executed on the given page.
+   *
+   * @param integer $cid
+   *   The component id of the component whose required state is being sought
+   * @param integer $page_num
+   *   The page number that the component is on.
+   * @return integer
+   *   self::componentHidden, ...Shown, or ...Dependent.
+   */
+  function componentRequired($cid, $page_num) {
+    if (!$this->requiredMap) {
+      // The conditionals have not yet been executed on a submission.
+      $this->executeConditionals(array(), 0);
+      watchdog('drivingevals', 'WebformConditionals::componentRequired called prior to evaluating a submission.', 'error');
+    }
+    return isset($this->requiredMap[$page_num][$cid]) ? $this->requiredMap[$page_num][$cid] : NULL;
+  }
+
+}
diff --git a/js/webform.js b/js/webform.js
index 57d5f04..523911f 100644
--- a/js/webform.js
+++ b/js/webform.js
@@ -174,6 +174,20 @@ Drupal.webform.doCondition = function($form, settings, rgid_key) {
           }
         }
         break;
+      case 'require':
+        var $requiredSpan = $target.find('.form-required, .form-optional').first();
+        if (actionResult != $requiredSpan.hasClass('form-required')) {
+          // Rather than hide the required tag, remove it so that other jQuery can respond via Drupal behaviors.
+          Drupal.detachBehaviors($requiredSpan);
+          if (actionResult) {
+            $requiredSpan.replaceWith('<span class="form-required" title="' + Drupal.t('This field is required.') + '">*</span>');
+          }
+          else {
+            $requiredSpan.replaceWith('<span class="form-optional"></span>');
+          }
+          Drupal.attachBehaviors($requiredSpan);
+        }
+        break;
     }
   });
 }
diff --git a/webform.module b/webform.module
index 996b746..18187ac 100644
--- a/webform.module
+++ b/webform.module
@@ -2775,18 +2775,24 @@ function webform_client_form_validate($form, &$form_state) {
  * a different property to ensure that validation has occurred.
  */
 function _webform_client_form_validate(&$elements, &$form_state, $form_id = NULL, $input_values = NULL) {
-  // Webform-specific enhancement, only validate the field if it was used in
-  // this submission. This both skips validation on the field and sets the value
-  // of the field to NULL, preventing any dangerous input.
   if (isset($input_values) && isset($elements['#webform_component'])) {
+    $sorter = webform_get_conditional_sorter($form_state['complete form']['#node']);
+    $cid = $elements['#webform_component']['cid'];
+    $page_num = $form_state['values']['details']['page_num'];
+    // Webform-specific enhancement, only validate the field if it was used in this submission.
+    // This both skips validation on the field and sets the value of the field to NULL, preventing any dangerous input.
     // Short-circuit validation for a hidden component (hidden by rules dependent upon component on previous pages),
     // or a component this is dependent upon values on the current page, but is hidden based upon their current values.
-    if (webform_get_conditional_sorter($form_state['complete form']['#node'])
-          ->componentVisibility($elements['#webform_component']['cid'],
-                                $form_state['values']['details']['page_num']) != WebformConditionals::componentShown) {
+    if ($sorter->componentVisibility($cid, $page_num) != WebformConditionals::componentShown) {
       form_set_value($elements, NULL, $form_state);
       return;
     }
+
+    // Check for changes in requires status made by conditionals.
+    $required = $sorter->componentRequired($cid, $page_num);
+    if (isset($required)) {
+      $elements['#required'] = $required;
+    }
   }
 
   // Recurse through all children.
@@ -2796,7 +2802,7 @@ function _webform_client_form_validate(&$elements, &$form_state, $form_id = NULL
     }
   }
   // Validate the current input.
-  if (isset($elements['#webform_validated']) && $elements['#webform_validated'] == FALSE) {
+  if (isset($elements['#webform_validated']) && !$elements['#webform_validated']) {
     if (isset($elements['#needs_validation'])) {
       // Make sure a value is passed when the field is required.
       // A simple call to empty() will not cut it here as some fields, like
-- 
1.7.8.msysgit.0

