Index: webform.module
===================================================================
RCS file: /cvs/drupal-contrib/contributions/modules/webform/webform.module,v
retrieving revision 1.162
diff -u -r1.162 webform.module
--- webform.module 23 Jan 2010 22:56:02 -0000 1.162
+++ webform.module 25 Jan 2010 04:28:44 -0000
@@ -204,6 +204,16 @@
'weight' => 5,
'type' => MENU_LOCAL_TASK,
);
+ $items['node/%webform_menu/webform-results/analysis/%webform_menu_component'] = array(
+ 'title' => 'Analysis',
+ 'load arguments' => array(1, 4),
+ 'page callback' => 'webform_results_analysis',
+ 'page arguments' => array(1, array(), 4),
+ 'access callback' => 'webform_results_access',
+ 'access arguments' => array(1),
+ 'file' => 'includes/webform.report.inc',
+ 'type' => MENU_CALLBACK,
+ );
$items['node/%webform_menu/webform-results/table'] = array(
'title' => 'Table',
'page callback' => 'webform_results_table',
@@ -546,6 +556,10 @@
'arguments' => array('element' => NULL),
'file' => 'includes/webform.report.inc',
),
+ 'webform_results_analysis' => array(
+ 'arguments' => array('node' => NULL, 'data' => NULL, 'sids' => array(), 'component' => NULL),
+ 'file' => 'includes/webform.report.inc',
+ ),
// webform.submissions.inc
'webform_submission_page' => array(
'arguments' => array('submission' => NULL, 'submission_navigation' => NULL, 'submission_information' => NULL),
@@ -1402,22 +1416,28 @@
}
}
-function webform_client_form_validate($form, $form_state) {
+function webform_client_form_validate($form, &$form_state) {
$node = node_load($form_state['values']['details']['nid']);
// Run all #element_validate and #required checks. These are skipped initially
// by setting #validated = TRUE on all components when they are added.
_webform_client_form_validate($form, $form_state);
- // Flatten trees within the submission.
- $form_state['values']['submitted_tree'] = $form_state['values']['submitted'];
- $form_state['values']['submitted'] = _webform_client_form_submit_flatten($node, $form_state['values']['submitted']);
-
+ // TODO: Remove additional validation (and submission) handling entirely? This
+ // is a terrible hack where we need to flatten the submission for validation
+ // but then restore the tree version so that the submit handling functions.
if (trim($node->webform['additional_validate'])) {
+ // Flatten trees within the submission.
+ $form_state['values']['submitted_tree'] = $form_state['values']['submitted'];
+ $form_state['values']['submitted'] = _webform_client_form_submit_flatten($node, $form_state['values']['submitted']);
+
// Support for Drupal 5 validation code.
$form_values =& $form_state['values'];
// We use eval here (rather than drupal_eval) because the user needs access to local variables.
eval('?>'. $node->webform['additional_validate']);
+
+ // Restore the original form state so that the submit handler can reflatten.
+ $form_state['values']['submitted'] = $form_state['values']['submitted_tree'];
}
}
@@ -1783,8 +1803,11 @@
if (isset($node->webform['components'][$cid])) {
// Call the component process submission function.
$component = $node->webform['components'][$cid];
- if ((!isset($types) || in_array($component['type'], $types)) && ($new_value = webform_component_invoke($component['type'], 'submit', $component, $form_values[$component['form_key']]))) {
- $form_values[$component['form_key']] = $new_value;
+ if ((!isset($types) || in_array($component['type'], $types))) {
+ $new_value = webform_component_invoke($component['type'], 'submit', $component, $form_values[$component['form_key']]);
+ if ($new_value !== NULL) {
+ $form_values[$component['form_key']] = $new_value;
+ }
}
}
}
Index: components/select.inc
===================================================================
RCS file: /cvs/drupal-contrib/contributions/modules/webform/components/select.inc,v
retrieving revision 1.32
diff -u -r1.32 select.inc
--- components/select.inc 23 Jan 2010 22:56:02 -0000 1.32
+++ components/select.inc 25 Jan 2010 04:28:44 -0000
@@ -22,6 +22,8 @@
'items' => '',
'multiple' => NULL,
'aslist' => NULL,
+ 'other_option' => NULL,
+ 'other_text' => t('Other...'),
'description' => '',
),
);
@@ -77,6 +79,21 @@
'#default_value' => $component['extra']['aslist'],
'#description' => t('Check this option if you want the select component to be of listbox type instead of radiobuttons or checkboxes.'),
);
+ $form['extra']['other_option'] = array(
+ '#type' => 'checkbox',
+ '#title' => t('Allow "Other..." option'),
+ '#return_value' => 'Y',
+ '#default_value' => $component['extra']['other_option'],
+ '#description' => t('Check this option if you want to allow users to enter an option not on the list.'),
+ '#access' => module_exists('select_or_other'),
+ );
+ $form['extra']['other_text'] = array(
+ '#type' => 'textfield',
+ '#title' => t('Text for "Other..." option'),
+ '#default_value' => $component['extra']['other_text'],
+ '#description' => t('If allowing other options, enter text to be used for other-enabling option.'),
+ '#access' => module_exists('select_or_other'),
+ );
return $form;
}
@@ -179,8 +196,29 @@
$element['#default_value'] = $default_value;
}
}
+ elseif ($component['extra']['multiple'] === 'Y') {
+ $element['#default_value'] = array();
+ }
- if ($component['extra']['aslist'] === 'Y') {
+ if ($component['extra']['other_option'] === 'Y' && module_exists('select_or_other')) {
+ // Set display as a select list:
+ $element['#type'] = 'select_or_other';
+ $element['#other'] = empty($component['extra']['other_text']) ? $component['extra']['other_text'] : t('Other...');
+ $element['#other_unknown_defaults'] = 'other';
+ $element['#other_delimiter'] = ', ';
+ if ($component['extra']['multiple'] === 'Y') {
+ $element['#multiple'] = TRUE;
+ $element['#select_type'] = 'checkboxes';
+ }
+ else {
+ $element['#multiple'] = FALSE;
+ $element['#select_type'] = 'radios';
+ }
+ if ($component['extra']['aslist'] === 'Y') {
+ $element['#select_type'] = 'select';
+ }
+ }
+ elseif ($component['extra']['aslist'] === 'Y') {
// Set display as a select list:
$element['#type'] = 'select';
if ($component['extra']['multiple'] === 'Y') {
@@ -228,6 +266,7 @@
* Implementation of _webform_display_component().
*/
function _webform_display_select($component, $value, $format = 'html') {
+ ksort($value);
return array(
'#title' => $component['name'],
'#weight' => $component['weight'],
@@ -250,17 +289,31 @@
if (is_array($value)) {
foreach ($value as $key => $option_value) {
- if ($option_value != '') {
- $value[$key] = $options[$key];
+ // Handle options that are specified options.
+ if ($option_value !== '' && isset($options[$key])) {
+ // Checkboxes submit an *integer* value of 0 when not checked.
+ if ($option_value === 0 && $options[$key] != '0' && $component['extra']['aslist'] !== 'Y' && $component['extra']['multiple'] === 'Y') {
+ unset($value[$key]);
+ }
+ else {
+ $value[$key] = $options[$key];
+ }
}
- // Checkboxes submit a value of 0 when not checked.
- elseif ($option_value == 0 && $component['extra']['aslist'] !== 'Y' && $component['extra']['multiple'] === 'Y') {
- unset($value[$key]);
+ // Handle options that are added through the "other" field.
+ elseif ($component['extra']['other_option'] === 'Y' && module_exists('select_or_other')) {
+ $value[$key] = $option_value;
}
else {
unset($value[$key]);
}
}
+
+ // Always save at least something, even if it's an empty single value.
+ if (empty($value)) {
+ $value[0] = '';
+ }
+
+ $value = array_values($value);
}
return $value;
@@ -378,15 +431,27 @@
if ($component['extra']['multiple']) {
foreach ((array) $element['#value'] as $option_value) {
if ($option_value !== '') {
+ // Administer provided values.
if (isset($options[$option_value])) {
$items[] = $options[$option_value];
}
+ // User-specified in the "other" field.
+ else {
+ $items[] = $option_value;
+ }
}
}
}
else {
- if (isset($element['#value'][0]) && $element['#value'][0] !== '' && $options[$element['#value'][0]]) {
- $items[] = $options[$element['#value'][0]];
+ if (isset($element['#value'][0]) && $element['#value'][0] !== '') {
+ // Administer provided values.
+ if (isset($options[$element['#value'][0]])) {
+ $items[] = $options[$element['#value'][0]];
+ }
+ // User-specified in the "other" field.
+ else {
+ $items[] = $element['#value'][0];
+ }
}
}
@@ -412,28 +477,57 @@
/**
* Implementation of _webform_analysis_component().
*/
-function _webform_analysis_select($component, $sids = array()) {
+function _webform_analysis_select($component, $sids = array(), $single = FALSE) {
$options = _webform_select_options($component['extra']['items'], TRUE);
- $placeholders = count($sids) ? array_fill(0, count($sids), "'%s'") : array();
- $sidfilter = count($sids) ? " AND sid in (".implode(",", $placeholders).")" : "";
+ $show_other_results = $single;
- $query = 'SELECT data, count(data) as datacount '.
- ' FROM {webform_submitted_data} '.
- ' WHERE nid = %d '.
- ' AND cid = %d '.
- " AND data != '0' AND data != '' $sidfilter ".
+ $sid_placeholders = count($sids) ? array_fill(0, count($sids), "'%s'") : array();
+ $sid_filter = count($sids) ? " AND sid IN (".implode(",", $sid_placeholders).")" : "";
+
+ $not = $show_other_results ? 'NOT ' : '';
+ $placeholders = count($options) ? array_fill(0, count($options), "'%s'") : array();
+ $query = 'SELECT data, count(data) as datacount ' .
+ ' FROM {webform_submitted_data} ' .
+ ' WHERE nid = %d ' .
+ ' AND cid = %d ' .
+ " AND data != ''" . $sid_filter .
+ ($placeholders ? ' AND data ' . $not . 'IN (' . implode(',', $placeholders) . ')' : '') .
' GROUP BY data ';
- $result = db_query($query, array_merge(array($component['nid'], $component['cid']), $sids));
+
+ $count_query = 'SELECT count(*) as datacount ' .
+ ' FROM {webform_submitted_data} ' .
+ ' WHERE nid = %d ' .
+ ' AND cid = %d ' .
+ " AND data != ''" . $sid_filter;
+
+ $result = db_query($query, array_merge(array($component['nid'], $component['cid']), $sids, array_keys($options)));
$rows = array();
+ $normal_count = 0;
while ($data = db_fetch_array($result)) {
- if (isset($options[$data['data']])) {
- $display_option = $options[$data['data']];
- }
- else {
- $display_option = $data['data'];
+ $display_option = $single ? $data['data'] : $options[$data['data']];
+ $rows[$data['data']] = array(check_plain($display_option), $data['datacount']);
+ $normal_count += $data['datacount'];
+ }
+
+ if (!$show_other_results) {
+ // Order the results according to the normal options array.
+ $ordered_rows = array();
+ foreach (array_intersect_key($options, $rows) as $key => $label) {
+ $ordered_rows[] = $rows[$key];
+ }
+
+ // Add a row for any unknown or user-entered values.
+ if ($component['extra']['other_option'] === 'Y') {
+ $full_count = db_result(db_query($count_query, array_merge(array($component['nid'], $component['cid']), $sids)));
+ $other_count = $full_count - $normal_count;
+ $display_option = empty($component['extra']['other_text']) ? $component['extra']['other_text'] : t('Other...');
+ $other_text = $other_count ? $other_count . ' (' . l(t('view'), 'node/' . $component['nid'] . '/webform-results/analysis/' . $component['cid']) . ')' : $other_count;
+ $ordered_rows[] = array(check_plain($display_option), $other_text);
}
- $rows[] = array($display_option, $data['datacount']);
+
+ $rows = $ordered_rows;
}
+
return $rows;
}
@@ -445,7 +539,7 @@
// Set the value as a single string.
if (is_array($value)) {
foreach ($value as $option_value) {
- if ($option_value !== '0') {
+ if ($option_value !== '') {
$output .= check_plain($option_value) .'
';
}
}
@@ -470,6 +564,10 @@
$headers[0][] = '';
$headers[1][] = $component['name'];
$items = _webform_select_options($component['extra']['items'], TRUE);
+ if ($component['extra']['other_option'] === 'Y') {
+ $other_label = !empty($component['extra']['other_text']) ? $component['extra']['other_text'] : t('Other...');
+ $items[$other_label] = $other_label;
+ }
$count = 0;
foreach ($items as $key => $item) {
// Empty column per sub-field in main header.
@@ -498,13 +596,20 @@
if ($component['extra']['multiple']) {
foreach ($options as $key => $item) {
- if (in_array($key, (array) $value) === TRUE) {
+ $index = array_search($key, (array) $value);
+ if ($index !== FALSE) {
$return[] = ($export_options['select_format'] == 'separate') ? 'X' : $key;
+ unset($value[$index]);
}
elseif ($export_options['select_format'] == 'separate') {
$return[] = '';
}
}
+
+ // Any remaining items in the $value array will be user-added options.
+ if ($component['extra']['other_option'] === 'Y') {
+ $return[] = count($value) ? implode(',', $value) : '';
+ }
}
else {
$return = $value[0];
Index: includes/webform.report.inc
===================================================================
RCS file: /cvs/drupal-contrib/contributions/modules/webform/includes/webform.report.inc,v
retrieving revision 1.14
diff -u -r1.14 webform.report.inc
--- includes/webform.report.inc 24 Jan 2010 00:07:17 -0000 1.14
+++ includes/webform.report.inc 25 Jan 2010 04:28:44 -0000
@@ -614,26 +614,66 @@
/**
* Provides a simple analysis of all submissions to a webform.
+ *
+ * @param $node
+ * The webform node on which to generate the analysis.
+ * @param $sids
+ * An array of submission IDs to which this analysis may be filtered. May be
+ * used to generate results that are per-user or other groups of submissions.
+ * @param $component
+ * A webform component. If passed in, additional information may be returned
+ * relating specifically to that component's analysis, such as a list of
+ * "Other" values within a select list.
*/
-function webform_results_analysis($node, $sids = array()) {
+function webform_results_analysis($node, $sids = array(), $single_component = NULL) {
+ if (!is_array($sids)) {
+ $sids = array();
+ }
+
+ // If showing a component's details, we don't want to loose the menu tabs.
+ if ($single_component) {
+ $item = menu_get_item('node/' . $node->nid . '/webform-results/analysis');
+ menu_set_item(NULL, $item);
+ }
+
+ $components = isset($single_component) ? array($single_component['cid'] => $single_component) : $node->webform['components'];
+ $data = array();
+ foreach ($components as $cid => $component) {
+ // Do component specific call.
+ if ($row_data = webform_component_invoke($component['type'], 'analysis', $component, $sids, isset($single_component))) {
+ $data[$cid] = $row_data;
+ }
+ }
+
+ return theme('webform_results_analysis', $node, $data, $sids, $single_component);
+}
+
+/**
+ * Output the content of the Analysis page.
+ *
+ * @see webform_results_analysis()
+ */
+function theme_webform_results_analysis($node, $data, $sids = array(), $component = NULL) {
+
$rows = array();
$question_number = 0;
+ $single = isset($component);
$headers = array(
- t('Q'),
- array('data' => t('responses'), 'colspan' => '10')
+ $single ? $component['name'] : t('Q'),
+ array('data' => $single ? ' ' : t('responses'), 'colspan' => '10')
);
- foreach ($node->webform['components'] as $component) {
+ foreach ($data as $cid => $row_data) {
$question_number++;
- // Do component specific call.
- if ($crows = webform_component_invoke($component['type'], 'analysis', $component, $sids)) {
- if (is_array($crows)) {
- $row[0] = array('data' => ''. $question_number .'', 'rowspan' => count($crows) + 1, 'valign' => 'top');
- $row[1] = array('data' => ''. $component['name'] .'', 'colspan' => '10');
- $rows = array_merge($rows, array_merge(array($row), $crows));
+ if (is_array($row_data)) {
+ $row = array();
+ if (!$single) {
+ $row[] = array('data' => ''. $question_number .'', 'rowspan' => count($row_data) + 1, 'valign' => 'top');
+ $row[] = array('data' => ''. check_plain($node->webform['components'][$cid]['name']) .'', 'colspan' => '10');
}
+ $rows = array_merge($rows, array_merge(array($row), $row_data));
}
}