Creating Flexible Profiles in Drupal 7

Updated: 06/09/2012 I post the Webform Multiple Conditions
module , which implements the functionality described in the article without modifying the Webform module.

The article proposed a patch for the Webform module , often used in CMF Drupal to create questionnaires and questionnaires. The patch allows you to specify several conditions for form components through the administrative interface, as well as control the logic of their joint work.

Introduction


I believe that any web developer will sooner or later face the task of creating a questionnaire or questionnaire. Of course, in our time it is not so difficult to find any ready-made solution, and it’s good that we have plenty of options here:
  • You can find and use some powerful and independent product, where, in fact, a ready-made system is integrated into the site.
  • The second option is to use one of a wide variety of thematic services, such as SurveyMonkey or Wufoo .
Each of the options listed above has its advantages and disadvantages, which I will not focus on, but will propose to consider a solution, in my opinion, useful to all developers using Drupal in their work.

Dynamic questioning.


Suppose that we are faced with the task of creating several complex and dynamic questionnaires in which the questions themselves depend on user answers on the previous pages, and where you need to provide the site’s end administrator with a convenient interface for editing the questions themselves and the logic of their work.
Denote the product requirements:
  1. A mechanism for creating and editing questionnaires through the administrative interface is needed;
  2. The ability to display questions gradually across multiple pages is needed;
  3. The ability to display questions only if certain conditions are met;
  4. Conditions can be of the following types: “some of the listed values ​​is selected”, “some of the listed values ​​is not selected”;
  5. There can be several conditions at once;
  6. When combining conditions, the AND / OR operators can be used.

Here is an example of such a questionnaire:

image

There are three pages with questions and one with results. Page 2 may not be shown to the user at all if he answered “YES” to the first two questions . The number of questions on the third page, where the user always gets, varies depending on the answers to the first two questions, only the conditions are already combined through “OR” . A thank you text message is always displayed on the results page. Also, if the user was displayed page 2, and he answered the third question positively, then some additional text is displayed.

Creating a flexible profile in Drupal.


A powerful Webform module has been developed for Drupal , which just implements many of the above requirements, but, unfortunately, not all.
We can create and edit forms through the administrative interface, we can attach arbitrary components to them, we can even add conditions to the components, but we cannot set several conditions and combine them. In addition, conditions of the Markup type cannot be assigned, which does not allow the creation of dynamic feedback pages with extremely static content.
This article is devoted to the correction of this shortcoming.
For work, we need:
  1. Drupal 7.14
  2. Webform 7.x-3.18
Install Drupal and the Webform module . After that, go to the page for adding materials [Add content -> Webform | / node / add / webform), where we indicate the name of our questionnaire and enter its description.
After saving the new node, we return to the page for its editing and select the Webform tab .
On this tab, attach to the form all the necessary components that are responsible for the questions and pages of the questionnaire.

image

To designate questions, we will use coding according to the template “question_X”, where X is the serial number of the question. Possible answers ( Options ) for ease of use are the same everywhere, and encode according to the requirements of component type Select (key | output_value ).

image

Note that for a number of components, starting from the second page, it is possible to specify a condition (condtional rules), but only one.

Extending Webform Features


We will solve the task of modifying the behavior of the Webform module somewhat crudely and atypically for Drupal, making a number of changes directly in order to save time. In the future, it will be possible to separate them separately and create a separate module (see Webform Multiple Conditions ).
First, modify the interface. Let’s increase the number of possible conditions displayed on the component configuration page from 1 to 10. For elements in Drupal themselves, there is always a function that returns the Forms API structure . To find its exact name, it is enough in client HTML, for example using Firebug, to determine the value of the input [name = ”form_id”] element , which is located inside the form.

image

In this case, the value is"Webform_component_edit_form" . This function is located in the file "/webform/includes/webform.components.inc" .

On line 529, the code responsible for the conditions begins. Now we need to modify it, without breaking the functionality of the functions that process input to the form when submitting (e.g. webform_component_edit_form_validate , webform_component_edit_form_submit ).
When debugging the code, you can notice all the additional configuration of the component is stored in the Extra field in the component table.

image

Therefore, it will not be a problem if we expand it by simply cloning the elements 10 times. We make the magic number 10 a weak constant, that is, we define it through the Drupal functionvariable_get so that there is a hypothetical opportunity to make an administrative interface to modify this value.
Replace all the code representing the conditional components with the following:
if ($conditional_components) {
  $extra_cond = array(
    'conditional_component' => array(
      '#type' => 'select', 
      '#title' => t('Component'), 
      '#options' => webform_component_list($node, $conditional_components, FALSE, TRUE), 
      '#description' => t('Select another component to decide whether to show or hide this component. You can only select components occurring before the most recent pagebreak.') 
    ), 
    'conditional_operator' => array(
      '#type' => 'select', 
      '#title' => t('Operator'), 
      '#options' => array(
        '=' => t('Is one of'), 
        '!=' => t('Is not one of') 
      ), 
      '#description' => t('Determines whether the list below is inclusive or exclusive.') 
    ), 
    'conditional_values' => array(
      '#type' => 'textarea', 
      '#title' => t('Values'), 
      '#description' => t('List values, one per line, that will trigger this action. If you leave this blank, this component will always display.') 
    ) 
  );
  $extra = $component['extra'];
  for ($i = 0; $i < variable_get('webform:max_conditional_rules', 10); $i++) {
    $condition_id = 'condition_'. $i;
    if (isset($extra[$condition_id]['values'])) {
      $condition_component_struct = $extra[$condition_id]['values'];
      $extra_cond['conditional_component']['#default_value'] = $condition_component_struct['conditional_component']; 
      $extra_cond['conditional_operator']['#default_value'] = $condition_component_struct['conditional_operator']; 
      $extra_cond['conditional_values']['#default_value'] = $condition_component_struct['conditional_values'];       
    } 
    $form['extra'][$condition_id] = array(
      '#type' => 'fieldset',
      '#title' => t('Conditional rule !num', array('!num' => $i + 1, )),
      '#collapsed' => TRUE,
      '#collapsible' => TRUE,
      '#tree' => TRUE,    
      'values' => $extra_cond, 
    );
  }        
}


Perform a check. We’ll go to the settings page for an arbitrary component located on any page except the first one. You may notice that the number of conditional components has increased.
We add one more additional setting to this form - the choice of an algorithm by which the final result of fulfilling the conditions is calculated if there are several of them at once. Let us have two algorithms: AND and OR . To do this, after the previous code, immediately add the following:
$form['extra']['algorithm'] = array(
  '#type' => 'select', 
  '#options' => array(
    'and' => t('AND'), 
    'or' => t('OR'), 
  ), 
  '#title' => t('Algorithm to evaluate conditions'), 
  '#default_value' => isset($extra['algorithm']) ? $extra['algorithm'] : 'and', 
);


Now we have several conditions at once and an algorithm for combining them, which is already good, but only from the administrative interface. When processing the final client web-form, when the user fills in the fields, the old code remains that can work with at most one condition and does not know anything about combination algorithms.
It remains to find this code. We will go to the form page from the client side (simply by clicking View) and find the corresponding input [name = "form_id"] .

image

Its value is equal to webform_client_form_1 and when searching for exactly this function, the result is deplorable. But, as you know, in Drupal there is a way to set the rules for matching the form_id of the final function through hook_forms (), and if you find the corresponding hook, then it has an indication of callback webform_client_form , and this function has the place to be:
/**
 * Implements hook_forms().
 *
 * All webform_client_form forms share the same form handler
 */functionwebform_forms($form_id){
  $forms = array();
  if (strpos($form_id, 'webform_client_form_') === 0) {
    $forms[$form_id]['callback'] = 'webform_client_form';
  }
  return $forms;
}

In this function (and in the standard callback performed upon submission - webform_client_form_submit ) there is nothing that processes the conditions, but if you analyze its code, you can notice the overriding of the submitted callbacks through:
$form['#submit'] = array('webform_client_form_pages', 'webform_client_form_submit');

Let's move on to the webform_client_form_pages function and find in it a call to _webform_client_form_rule_check , which returns TRUE or FALSE, and controls the display or skipping of the component. And this is just what we need.
In _webform_client_form_rule_check, the $ component argument is passed to the function , which is the configuration of some component of the web form. We get from it information about the algorithm and all the conditions due:
$conditional_values = isset($component['extra']['conditional_values']) ? $component['extra']['conditional_values'] : NULL;
$conditional_component = isset($component['extra']['conditional_component']) && isset($node->webform['components'][$component['extra']['conditional_component']]) ? $node->webform['components'][$component['extra']['conditional_component']] : NULL;
$conditional_cid = $conditional_component['cid'];


We will also set a flag at the very beginning, which will ultimately determine (depending on the chosen algorithm) whether to show the component or not:
$one_rule_passed = FALSE;

The next task is to extend the immediate verification code, which begins immediately after:
// Check the individual component rules.
$show_component = $show_parent; 

We proceed as follows: we fulfill each condition in turn, fix the verification result in an array, which we then analyze according to the algorithm. If the "OR" algorithm is used , then the presence of at least one positive result makes the entire expression positive, but if the "AND" algorithm , then, on the contrary, one negative result makes the entire expression negative.
To do this, replace the verification code with the following:
// Check the individual component rules.
$show_component = $show_parent;
if ($show_component && ($page_num == 0 || $component['page_num'] == $page_num) && $conditional_component && strlen(trim($conditional_values))) {
  $input_values = array();
  if (isset($form_state)) {
    $input_value = isset($form_state['values']['submitted'][$conditional_cid]) ? $form_state['values']['submitted'][$conditional_cid] : NULL;
    $input_values = is_array($input_value) ? $input_value : array($input_value);
  }
  elseif (isset($submission)) {
    $input_values = isset($submission->data[$conditional_cid]['value']) ? $submission->data[$conditional_cid]['value'] : array();
  }
  $test_values = array_map('trim', explode("\n", $conditional_values));
  if (empty($input_values) && !empty($test_values)) {
    $show_component = FALSE;
  }
  else {
    foreach ($input_values as $input_value) {
      if ($show_component = in_array($input_value, $test_values)) {
        break;
      }
    }
  }
  if ($component['extra']['conditional_operator'] == '!=') {
    $show_component = !$show_component;
  }
}


And we immediately determine the calculation of the result of the algorithm:
// Make the final decisionif ($component['extra']['algorithm'] == 'or') {
  $result = $one_rule_passed;
} elseif ($component['extra']['algorithm'] == 'and') {
  $result = array_search(FALSE, $show_component) !== FALSE;
} else {
  $result = TRUE;
}
// Allow other modules to alter conditional check result
$context = array('node' => $node, 'component' => $component, 'show_component' => $show_component, 'one_rule_passed' => $one_rule_passed, );
drupal_alter('webform_conditional_rules_result', $result, $context);
// Private component?if ($component['extra']['private']) {
  $result = webform_results_access($node);
}
return $result; 

Calling drupal_alter will allow other modules to redefine the result as necessary, which you should always remember when developing modules for Drupal.

Now we have everything that is needed to implement the questionnaire, which is presented above in the article.
To do this, returning to the page with the list of web form components in the administrative interface, we set the rules for the page_2 component :
  • Question 1 is one of [yes]
  • Question 2 is one of [yes]
  • We will indicate the “AND” algorithm.

image

On page 3, only the question 4 has a mapping (according to the scheme). We will configure them:
  1. Question 1 is one of [yes]
  2. Question 2 is not one of [no]
  3. We indicate the algorithm "OR".

On the results page 4, additional information is displayed only when the third question is answered positively.
Define a rule for the last component:
  1. Question 3 is one of [yes].

After all the steps described in the article, you can safely go to the form page from the client side and make sure that everything works as required.

References:

Also popular now: