updated webform, webform_localization, profile2, term_merge, search_api_saved_pages, rules, redirect, overide_node_options

This commit is contained in:
2019-05-13 18:47:27 +02:00
parent 58cd990c8c
commit 9adc940a67
281 changed files with 28658 additions and 7138 deletions

View File

@@ -1,37 +0,0 @@
Webform 2.x Changelog
---------------------
2.x to 3.0
----------
- Module directory structure moved around.
- Webform configuration moved to an entirely separate tab.
- E-mail templates are now editable by administrators.
- Conditional fields.
- Submissions may be saved as a draft and resumed later.
- Webform may now be attached to any content type.
- Public API for allowing other modules to provide components.
- Public API for interacting with submission save, insert, update, and delete.
- New rendering capabilities for HTML presentation of submissions and e-mails.
- Print module support.
- Basic Views module support.
- Popup calendar support on Date components (with Date Popup module).
- New Mollom module integration.
1.x to 2.0
----------
- Redirect POST option removed.
- Webform components moved to the "Form components" tab when editing.
- Webform node structure changed. All webform additions to the node are placed in $node->webform.
- Clone option added to components.
- Database storage improved to be more consistent and efficient.
- Additional e-mails may be sent by modifying the $node->webform['additional_emails'] variable in the Additional Validation field.
- The values of select and hidden fields may receive e-mails by using the option in Conditional e-mail recipients field.
- E-mail from name, from address, and from subject may be entered in a text field.
- The complete webform may be shown in the teaser view of a node.
- Submit button text may be changed.
- Theme function theme_webform_create_mailmessage() has been renamed to theme_webform_mail_message().
- $cid parameter added to theme_webform_mail_message() to create unique e-mails depending on a particular recipient or component.
- Theme function theme_webform_mail_headers added.
- Component descriptions are textareas rather than textfields.
- _webform_filtervalues() has been renamed to _webform_filter_values.

View File

@@ -1,14 +1,15 @@
Description
-----------
This module adds a webform content type to your Drupal site.
A webform can be a questionnaire, contact or request form. These can be used
A webform can be a questionnaire, contact or request form. These can be used
by visitor to make contact or to enable a more complex survey than polls
provide. Submissions from a webform are saved in a database table and
provide. Submissions from a webform are saved in a database table and
can optionally be mailed to e-mail addresses upon submission.
Requirements
------------
Drupal 7.x
See https://www.drupal.org/project/webform for additional requirements.
Installation
------------
@@ -23,19 +24,29 @@ Installation
Upgrading from previous versions
--------------------------------
Note that if you are upgrading from a Drupal 6 installation of Webform, you MUST
have been running Webform 3.x on your Drupal 6 site before upgrading to Drupal
7 and Webform 3.x. You cannot upgrade directly from Webform 6.x-2.x to Webform
7.x-3.x.
Note that you must be running the latest 3.x version of Webform (for either
Drupal 6 or Drupal 7) before upgrading to Webform 4.x.
1. Copy the entire webform directory the Drupal modules directory.
If you have contributed modules, custom modules, or theming on your Webforms,
please read over the documentation for upgrading your code for Webform 4.x at
https://drupal.org/node/1609324.
2. Login as the FIRST user or change the $access_check in update.php to FALSE
1. MAKE A DATABASE BACKUP. Upgrading to Webform 4.x makes a signficant number of
database changes. If you encounter an error and need to downgrade, you must
restore the previous database. You can make a database backup with your
hosting provider, using the Backup and Migrate module, or from the command
line.
3. Run update.php (at http://www.example.com/update.php)
2. Copy the entire webform directory the Drupal modules directory, replacing the
old copy of Webform. DO NOT KEEP THE OLD COPY in the same directory or
anywhere Drupal could possibily find it. Delete it from the server.
3. Login as an administrative user or change the $update_free_access in
update.php to TRUE.
4. Run update.php (at http://www.example.com/update.php).
Support
-------
Please use the issue queue for filing bugs with this module at
http://drupal.org/project/issues/webform

View File

@@ -1,9 +1,9 @@
Overview
--------
Webform supports theming similar to the CCK or Views modules. Any webform
may be themed on the server side, though doing so may require a reasonable
amount of knowledge about the Drupal Form API. More information about the Form
API may be found at http://api.drupal.org/api/file/developer/topics/forms_api.html
Webform supports theming similar to the CCK or Views modules. Any webform may be
themed on the server side, though doing so may require a reasonable amount of
knowledge about the Drupal Form API. More information about the Form API may be
found at: http://api.drupal.org/api/file/developer/topics/forms_api.html
Theme submission e-mails
-----------------------
@@ -63,10 +63,10 @@ the confirmation page of a single node or all webforms on your site.
</div>
<ul>
<li><a href="<?php print url('node/'. $node->nid . '/submission/'. $sid)?>">View your submission</a></li>
<li><a href="<?php print url('node/'. $node->nid . '/submission/'. $sid .'/edit')?>">Edit your submission</a></li>
<li><a href="<?php print url('node/' . $node->nid . '/submission/' . $sid)?>">View your submission</a></li>
<li><a href="<?php print url('node/' . $node->nid . '/submission/' . $sid . '/edit')?>">Edit your submission</a></li>
</ul>
<?php /* End sample webform confirmation page */ ?>
- You may edit the webform-confirmation.tpl.php file in your theme directory,
@@ -111,5 +111,4 @@ The template file for theming submissions is webform-submission.tpl.php. You can
use webform-submission-[node id here].tpl.php for individual nodes if desired.
Note that the contents of this template are used not only for display of
submissions in the Webform interface but also in e-mails when printing out
the %email_values token.
the [submission:values] token.

View File

@@ -15,16 +15,19 @@ function _webform_defaults_date() {
'pid' => 0,
'weight' => 0,
'value' => '',
'mandatory' => 0,
'required' => 0,
'extra' => array(
'timezone' => 'user',
'exclude' => array(),
'start_date' => '-2 years',
'end_date' => '+2 years',
'year_textfield' => 0,
'datepicker' => 1,
'title_display' => 0,
'description' => '',
'description_above' => FALSE,
'private' => FALSE,
'analysis' => FALSE,
),
);
}
@@ -67,12 +70,25 @@ function _webform_edit_date($component) {
'#type' => 'radios',
'#title' => t('Default value timezone'),
'#default_value' => empty($component['extra']['timezone']) ? 'user' : $component['extra']['timezone'],
'#description' => t('If using relative dates for a default value (e.g. "today") base the current day on this timezone.'),
'#description' => t('If using relative dates for a default value (for example, "today") base the current day on this timezone.'),
'#options' => array('user' => t('User timezone'), 'site' => t('Website timezone')),
'#weight' => 2,
'#access' => variable_get('configurable_timezones', 1),
);
$form['extra']['exclude'] = array(
'#type' => 'checkboxes',
'#title' => t('Hide'),
'#default_value' => $component['extra']['exclude'],
'#options' => array(
'day' => t('Day'),
'month' => t('Month'),
'year' => t('Year'),
),
'#description' => t('A hidden day or month will be set to 1. A hidden year will be set to the year of the default value.'),
'#weight' => 3,
);
$form['display']['datepicker'] = array(
'#type' => 'checkbox',
'#title' => t('Enable popup calendar'),
@@ -110,27 +126,63 @@ function _webform_edit_date($component) {
'#parents' => array('extra', 'end_date'),
);
$form['#validate'] = array('_webform_edit_date_validate');
return $form;
}
/**
* Implements hook_form_id_validate().
*
* Warns user about hiding all the date fields and not using the date picker.
*/
function _webform_edit_date_validate($form, &$form_state) {
// Reduce checkbox values to simple non-associative array of values.
form_set_value($form['extra']['exclude'], array_filter(array_values($form_state['values']['extra']['exclude'])), $form_state);
// Note that Drupal 7 doesn't support setting errors no checkboxes due to
// browser issues. See: https://www.drupal.org/node/222380
if (count($form_state['values']['extra']['exclude']) == 3) {
form_set_error('extra][exclude', 'The day, month and year can\'t all be hidden.');
}
if ($form_state['values']['extra']['exclude'] == array('month')) {
form_set_error('extra][exclude', 'You cannot hide just the month.');
}
// Validate that the start and end dates are valid. Don't validate the default
// date because with token substitution, it might not be valid at component
// definition time. Also note that validation was introduced in 7.x-4.8 and
// previously-defined webform may have invalid start and end dates.
foreach (array('start_date', 'end_date') as $field) {
if (trim($form_state['values']['extra'][$field]) && !($date[$field] = webform_strtodate('c', $form_state['values']['extra'][$field]))) {
form_set_error("extra][$field", t('The @field could not be interpreted in <a href="http://www.gnu.org/software/tar/manual/html_chapter/Date-input-formats.html">GNU Date Input Format</a>.',
array('@field' => $form['validation'][$field]['#title'])));
}
}
if (!empty($date['start_date']) && !empty($date['end_date']) && $date['end_date'] < $date['start_date']) {
form_set_error('extra][end_date', t('The End date must be on or after the Start date.'));
}
}
/**
* Implements _webform_render_component().
*/
function _webform_render_date($component, $value = NULL, $filter = TRUE) {
function _webform_render_date($component, $value = NULL, $filter = TRUE, $submission = NULL) {
$node = isset($component['nid']) ? node_load($component['nid']) : NULL;
$element = array(
'#type' => 'date',
'#title' => $filter ? _webform_filter_xss($component['name']) : $component['name'],
'#title' => $filter ? webform_filter_xss($component['name']) : $component['name'],
'#title_display' => $component['extra']['title_display'] ? $component['extra']['title_display'] : 'before',
'#weight' => $component['weight'],
'#description' => $filter ? _webform_filter_descriptions($component['extra']['description'], $node) : $component['extra']['description'],
'#required' => $component['mandatory'],
'#description' => $filter ? webform_filter_descriptions($component['extra']['description'], $node) : $component['extra']['description'],
'#required' => $component['required'],
'#start_date' => trim($component['extra']['start_date']),
'#end_date' => trim($component['extra']['end_date']),
'#reference_timestamp' => $submission && $submission->completed ? $submission->completed : NULL,
'#year_textfield' => $component['extra']['year_textfield'],
'#default_value' => $filter ? _webform_filter_values($component['value'], $node, NULL, NULL, FALSE) : $component['value'],
'#default_value' => $filter ? webform_replace_tokens($component['value'], $node) : $component['value'],
'#timezone' => $component['extra']['timezone'],
'#exclude' => $component['extra']['exclude'],
'#process' => array('webform_expand_date'),
'#theme' => 'webform_date',
'#theme_wrappers' => array('webform_element'),
@@ -160,9 +212,10 @@ function _webform_render_date($component, $value = NULL, $filter = TRUE) {
* Form API #process function for Webform date fields.
*/
function webform_expand_date($element) {
$timezone = $element['#timezone'] != 'user' ? NULL : 'user';
// Accept a string or array value for #default_value.
if (!empty($element['#default_value']) && is_string($element['#default_value'])) {
$timezone = $element['#timezone'] != 'user' ? NULL : 'user';
$timestring = webform_strtodate('c', $element['#default_value'], $timezone);
$element['#default_value'] = webform_date_array($timestring, 'date');
}
@@ -171,7 +224,7 @@ function webform_expand_date($element) {
unset($element['#value']);
}
// Set defaults according to existing #default_value (set by Form API)
// Set defaults according to existing #default_value (set by Form API).
if (isset($element['#default_value']['month']) || isset($element['#default_value']['day']) || isset($element['#default_value']['year'])) {
$default_values = array(
'month' => $element['#default_value']['month'],
@@ -190,31 +243,100 @@ function webform_expand_date($element) {
// Let Drupal do it's normal expansion.
$element = form_process_date($element);
// Convert relative dates to absolute and calculate the year, month and day.
$timezone = $element['#timezone'] != 'user' ? NULL : 'user';
foreach (array('start', 'end') as $start_end) {
$element_field = &$element["#{$start_end}_date"];
$element_field = $element_field ? webform_strtodate('Y-m-d', $element_field, $timezone, $element['#reference_timestamp']) : '';
if ($element_field) {
$parts = explode('-', $element_field);
}
else {
$parts = $start_end == 'start' ? array(webform_strtodate('Y', '-2 years'), 1, 1) : array(webform_strtodate('Y', '+2 years'), 12, 31);
$element_field = '';
}
// Drop PHP reference.
unset($element_field);
$parts[3] = $parts[0] . '-' . $parts[1] . '-' . $parts[2];
$range[$start_end] = array_combine(array('year', 'month', 'day', 'date'), $parts);
}
// The start date is not guaranteed to be early than the end date for
// historical reasons.
if ($range['start']['date'] > $range['end']['date']) {
$temp = $range['start'];
$range['start'] = $range['end'];
$range['end'] = $temp;
}
// Restrict the months and days when not all options are valid choices.
if ($element['#start_date'] && $element['#end_date']) {
$delta_months = ($range['end']['year'] * 12 + $range['end']['month']) - ($range['start']['year'] * 12 + $range['start']['month']);
if ($delta_months < 11) {
// There are 10 or fewer months between the start and end date. If there
// were 11, then every month would be possible, and the menu select
// should not be pruned.
$month_options = &$element['month']['#options'];
if ($range['start']['month'] <= $range['end']['month']) {
$month_options = array_intersect_key($month_options, array_flip(range($range['start']['month'], $range['end']['month'])));
}
else {
$month_options = array_intersect_key($month_options, array_flip(range($range['start']['month'], 12))) +
array_intersect_key($month_options, array_flip(range(1, $range['end']['month'])));
}
// Drop PHP reference.
unset($month_options);
if ($delta_months <= 1) {
// The start and end date are either on the same month or consecutive
// months. See if the days should be pruned.
$day_options = &$element['day']['#options'];
if ($range['start']['month'] == $range['end']['month']) {
// Range is within the same month. The days are a simple range from
// start day to end day.
$day_options = array_intersect_key($day_options, array_flip(range($range['start']['day'], $range['end']['day'])));
}
elseif ($range['start']['day'] > $range['end']['day'] + 1) {
// Range spans two months and at least one day would be omitted.
$days_in_month = date('t', mktime(0, 0, 0, $range['start']['month'], 1, $range['start']['year']));
$day_options = array_intersect_key($day_options, array_flip(range($range['start']['day'], $days_in_month))) +
array_intersect_key($day_options, array_flip(range(1, $range['end']['day'])));
}
// Drop PHP reference.
unset($day_options);
}
}
}
// Set default values.
foreach ($default_values as $type => $value) {
switch ($type) {
case 'month':
$none = t('Month');
$hidden_default = 1;
break;
case 'day':
$none = t('Day');
$hidden_default = 1;
break;
case 'year':
$none = t('Year');
$hidden_default = !empty($element['#default_value']['year']) ? $element['#default_value']['year'] : webform_strtodate('Y', 'today', $timezone);
break;
}
unset($element[$type]['#value']);
$element[$type]['#title'] = $none;
$element[$type]['#title_display'] = 'invisible';
$element[$type]['#default_value'] = isset($default_values[$type]) ? $default_values[$type] : NULL;
$element[$type]['#default_value'] = $default_values[$type];
$element[$type]['#options'] = array('' => $none) + $element[$type]['#options'];
}
// Convert relative dates to absolute ones.
foreach (array('start_date', 'end_date') as $start_end) {
$timezone = $element['#timezone'] != 'user' ? NULL : 'user';
$date = webform_strtodate('Y-m-d', $element['#' . $start_end], $timezone);
$element['#' . $start_end] = $date ? $date : '';
if (in_array($type, $element['#exclude'])) {
$element[$type] += array(
'#prefix' => '<div class="webform-date-field-wrapper element-invisible">',
'#suffix' => '</div>',
);
$element[$type]['#default_value'] = $hidden_default;
}
}
// Tweak the year field.
@@ -225,9 +347,7 @@ function webform_expand_date($element) {
unset($element['year']['#options']);
}
elseif ($element['#start_date'] || $element['#end_date']) {
$start_year = $element['#start_date'] ? webform_strtodate('Y', $element['#start_date']) : webform_strtodate('Y', '-2 years');
$end_year = $element['#end_date'] ? webform_strtodate('Y', $element['#end_date']) : webform_strtodate('Y', '+2 years');
$element['year']['#options'] = array('' => t('Year')) + drupal_map_assoc(range($start_year, $end_year));
$element['year']['#options'] = array('' => t('Year')) + drupal_map_assoc(range($range['start']['year'], $range['end']['year']));
}
return $element;
@@ -250,6 +370,13 @@ function theme_webform_date($variables) {
$element['day']['#attributes']['class'][] = 'error';
}
// Add HTML5 required attribute, if needed.
if ($element['#required']) {
$element['year']['#attributes']['required'] = 'required';
$element['month']['#attributes']['required'] = 'required';
$element['day']['#attributes']['required'] = 'required';
}
$class = array('webform-container-inline');
// Add the JavaScript calendar if available (provided by Date module package).
@@ -262,7 +389,7 @@ function theme_webform_date($variables) {
if ($element['#end_date']) {
$calendar_class[] = 'webform-calendar-end-' . $element['#end_date'];
}
$calendar_class[] ='webform-calendar-day-' . variable_get('date_first_day', 0);
$calendar_class[] = 'webform-calendar-day-' . variable_get('date_first_day', 0);
$calendar = theme('webform_calendar', array('component' => $element['#webform_component'], 'calendar_classes' => $calendar_class));
}
@@ -280,17 +407,35 @@ function theme_webform_date($variables) {
* Element validation for Webform date fields.
*/
function webform_validate_date($element, $form_state) {
// Check if the user filled the required fields.
foreach (array('day', 'month', 'year') as $field_type) {
if (!is_numeric($element[$field_type]['#value']) && $element['#required']) {
form_error($element, t('!name field is required.', array('!name' => $element['#title'])));
return;
$field_types = array('day', 'month', 'year');
// Determine if the user has specified a date. Hidden parts of the date will
// be submitted automatically.
foreach ($field_types as $field_type) {
if (!in_array($field_type, $element['#exclude']) && $element[$field_type]['#value'] !== '') {
$field_found = TRUE;
}
}
if ($element['month']['#value'] !== '' || $element['day']['#value'] !== '' || $element['year']['#value'] !== '') {
if (isset($field_found)) {
// Check that each part of the date has been filled in.
foreach ($field_types as $field_type) {
if (empty($element[$field_type]['#value'])) {
form_error($element[$field_type], t('!part in !name is missing.', array('!name' => $element['#title'], '!part' => $element[$field_type]['#title'])));
$missing_fields = TRUE;
}
}
if (isset($missing_fields)) {
return;
}
// Ensure date is made up of integers.
foreach (array('year', 'month', 'day') as $date_part) {
$element[$date_part]['#value'] = (int) $element[$date_part]['#value'];
}
// Check for a valid date.
if (!checkdate((int) $element['month']['#value'], (int) $element['day']['#value'], (int) $element['year']['#value'])) {
if (!checkdate($element['month']['#value'], $element['day']['#value'], $element['year']['#value'])) {
form_error($element, t('Entered !name is not a valid date.', array('!name' => $element['#title'])));
return;
}
@@ -299,7 +444,8 @@ function webform_validate_date($element, $form_state) {
$timestamp = strtotime($element['year']['#value'] . '-' . $element['month']['#value'] . '-' . $element['day']['#value']);
$format = webform_date_format('short');
// Flip start and end if needed.
// Flip start and end if needed. Prior to 7.x-4.8, it was possible to save
// a date component with the end date earlier than the start date.
$date1 = strtotime($element['#start_date']);
$date2 = strtotime($element['#end_date']);
if ($date1 !== FALSE && $date2 !== FALSE) {
@@ -312,19 +458,18 @@ function webform_validate_date($element, $form_state) {
}
// Check that the date is after the start date.
if ($start_date !== FALSE) {
if ($timestamp < $start_date) {
form_error($element, t('The entered date must be @start_date or later.', array('@start_date' => date($format, $start_date))));
}
if ($start_date !== FALSE && $timestamp < $start_date) {
form_error($element, t('The entered date must be @start_date or later.', array('@start_date' => date($format, $start_date))));
}
// Check that the date is before the end date.
if ($end_date !== FALSE) {
if ($timestamp > $end_date) {
form_error($element, t('The entered date must be @end_date or earlier.', array('@end_date' => date($format, $end_date))));
}
if ($end_date !== FALSE && $timestamp > $end_date) {
form_error($element, t('The entered date must be @end_date or earlier.', array('@end_date' => date($format, $end_date))));
}
}
elseif ($element['#required']) {
form_error($element, t('!name field is required.', array('!name' => $element['#title'])));
}
}
/**
@@ -338,15 +483,18 @@ function _webform_submit_date($component, $value) {
/**
* Implements _webform_display_component().
*/
function _webform_display_date($component, $value, $format = 'html') {
function _webform_display_date($component, $value, $format = 'html', $submission = array()) {
$value = webform_date_array(isset($value[0]) ? $value[0] : '', 'date');
return array(
'#title' => $component['name'],
'#title_display' => $component['extra']['title_display'] ? $component['extra']['title_display'] : 'before',
'#weight' => $component['weight'],
'#theme' => 'webform_display_date',
'#theme_wrappers' => $format == 'html' ? array('webform_element') : array('webform_element_text'),
'#format' => $format,
'#exclude' => $component['extra']['exclude'],
'#value' => $value,
'#translatable' => array('title'),
);
@@ -360,7 +508,7 @@ function theme_webform_display_date($variables) {
$output = ' ';
if ($element['#value']['year'] && $element['#value']['month'] && $element['#value']['day']) {
$timestamp = webform_strtotime($element['#value']['month'] . '/' . $element['#value']['day'] . '/' . $element['#value']['year']);
$format = webform_date_format('medium');
$format = webform_date_format(NULL, $element['#exclude']);
$output = format_date($timestamp, 'custom', $format, 'UTC');
}
@@ -370,15 +518,19 @@ function theme_webform_display_date($variables) {
/**
* Implements _webform_analysis_component().
*/
function _webform_analysis_date($component, $sids = array()) {
function _webform_analysis_date($component, $sids = array(), $single = FALSE, $join = NULL) {
$query = db_select('webform_submitted_data', 'wsd', array('fetch' => PDO::FETCH_ASSOC))
->fields('wsd', array('no', 'data'))
->condition('nid', $component['nid'])
->condition('cid', $component['cid'])
->orderBy('sid');
->condition('wsd.nid', $component['nid'])
->condition('wsd.cid', $component['cid'])
->orderBy('wsd.sid');
if (count($sids)) {
$query->condition('sid', $sids, 'IN');
$query->condition('wsd.sid', $sids, 'IN');
}
if ($join) {
$query->innerJoin($join, 'ws2_', 'wsd.sid = ws2_.sid');
}
$result = $query->execute();
@@ -396,7 +548,10 @@ function _webform_analysis_date($component, $sids = array()) {
$nonblanks = count($dates);
$rows[0] = array(t('Left Blank'), ($submissions - $nonblanks));
$rows[1] = array(t('User entered value'), $nonblanks);
return $rows;
return array(
'table_rows' => $rows,
);
}
/**
@@ -405,7 +560,7 @@ function _webform_analysis_date($component, $sids = array()) {
function _webform_table_date($component, $value) {
if ($value[0]) {
$timestamp = webform_strtotime($value[0]);
$format = webform_date_format('short');
$format = webform_date_format('short', $component['extra']['exclude']);
return format_date($timestamp, 'custom', $format, 'UTC');
}
else {
@@ -420,7 +575,7 @@ function _webform_csv_headers_date($component, $export_options) {
$header = array();
$header[0] = '';
$header[1] = '';
$header[2] = $component['name'];
$header[2] = $export_options['header_keys'] ? $component['form_key'] : $component['name'];
return $header;
}
@@ -430,7 +585,13 @@ function _webform_csv_headers_date($component, $export_options) {
function _webform_csv_data_date($component, $export_options, $value) {
if ($value[0]) {
$timestamp = webform_strtotime($value[0]);
$format = webform_date_format('short');
if (!empty($export_options['iso8601_date'])) {
// ISO 8601 date: 2004-02-12.
$format = 'Y-m-d';
}
else {
$format = webform_date_format('short');
}
return format_date($timestamp, 'custom', $format, 'UTC');
}
else {

View File

@@ -15,15 +15,20 @@ function _webform_defaults_email() {
'pid' => 0,
'weight' => 0,
'value' => '',
'mandatory' => 0,
'required' => 0,
'extra' => array(
'multiple' => 0,
'format' => 'short',
'width' => '',
'unique' => 0,
'disabled' => 0,
'title_display' => 0,
'description' => '',
'description_above' => FALSE,
'placeholder' => '',
'attributes' => array(),
'private' => FALSE,
'analysis' => FALSE,
),
);
}
@@ -52,22 +57,41 @@ function _webform_edit_email($component) {
'#type' => 'textfield',
'#title' => t('Default value'),
'#default_value' => $component['value'],
'#description' => t('The default value of the field.') . theme('webform_token_help'),
'#description' => t('The default value of the field.') . ' ' . theme('webform_token_help'),
'#size' => 60,
'#maxlength' => 127,
'#weight' => 0,
'#attributes' => ($component['value'] == '%useremail' && count(form_get_errors()) == 0) ? array('disabled' => TRUE) : array(),
'#attributes' => ($component['value'] == '[current-user:mail]' && count(form_get_errors()) == 0) ? array('disabled' => TRUE) : array(),
'#id' => 'email-value',
);
$form['user_email'] = array(
'#type' => 'checkbox',
'#title' => t('User email as default'),
'#default_value' => $component['value'] == '%useremail' ? 1 : 0,
'#default_value' => $component['value'] == '[current-user:mail]' ? 1 : 0,
'#description' => t('Set the default value of this field to the user email, if he/she is logged in.'),
'#attributes' => array('onclick' => 'getElementById("email-value").value = (this.checked ? "%useremail" : ""); getElementById("email-value").disabled = this.checked;'),
'#attributes' => array('onclick' => 'getElementById("email-value").value = (this.checked ? "[current-user:mail]" : ""); getElementById("email-value").disabled = this.checked;'),
'#weight' => 0,
'#element_validate' => array('_webform_edit_email_validate'),
);
$form['extra']['multiple'] = array(
'#type' => 'checkbox',
'#title' => t('Multiple'),
'#default_value' => $component['extra']['multiple'],
'#description' => t('Allow multiple e-mail addresses, separated by commas.'),
'#weight' => 0,
);
if (webform_variable_get('webform_email_address_format') == 'long') {
$form['extra']['format'] = array(
'#type' => 'radios',
'#title' => t('Format'),
'#options' => array(
'long' => t('Allow long format: "Example Name" &lt;name@example.com&gt;'),
'short' => t('Short format only: name@example.com'),
),
'#default_value' => $component['extra']['format'],
'#description' => t('Not all servers support the "long" format.'),
);
}
$form['display']['width'] = array(
'#type' => 'textfield',
'#title' => t('Width'),
@@ -77,11 +101,18 @@ function _webform_edit_email($component) {
'#maxlength' => 10,
'#parents' => array('extra', 'width'),
);
$form['display']['placeholder'] = array(
'#type' => 'textfield',
'#title' => t('Placeholder'),
'#default_value' => $component['extra']['placeholder'],
'#description' => t('The placeholder will be shown in the field until the user starts entering a value.') . ' ' . t('Often used for example values, such as "john@example.com".'),
'#parents' => array('extra', 'placeholder'),
);
$form['display']['disabled'] = array(
'#type' => 'checkbox',
'#title' => t('Disabled'),
'#return_value' => 1,
'#description' => t('Make this field non-editable. Useful for setting an unchangeable default value.'),
'#description' => t('Make this field non-editable. Useful for displaying default value. Changeable via JavaScript or developer tools.'),
'#weight' => 11,
'#default_value' => $component['extra']['disabled'],
'#parents' => array('extra', 'disabled'),
@@ -103,31 +134,34 @@ function _webform_edit_email($component) {
*/
function _webform_edit_email_validate($element, &$form_state) {
if ($form_state['values']['user_email']) {
$form_state['values']['value'] = '%useremail';
$form_state['values']['value'] = '[current-user:mail]';
}
}
/**
* Implements _webform_render_component().
*/
function _webform_render_email($component, $value = NULL, $filter = TRUE) {
global $user;
function _webform_render_email($component, $value = NULL, $filter = TRUE, $submission = NULL) {
$node = isset($component['nid']) ? node_load($component['nid']) : NULL;
$element = array(
'#type' => 'webform_email',
'#title' => $filter ? _webform_filter_xss($component['name']) : $component['name'],
'#title' => $filter ? webform_filter_xss($component['name']) : $component['name'],
'#title_display' => $component['extra']['title_display'] ? $component['extra']['title_display'] : 'before',
'#default_value' => $filter ? _webform_filter_values($component['value'], $node) : $component['value'],
'#required' => $component['mandatory'],
'#default_value' => $filter ? webform_replace_tokens($component['value'], $node) : $component['value'],
'#required' => $component['required'],
'#weight' => $component['weight'],
'#description' => $filter ? _webform_filter_descriptions($component['extra']['description'], $node) : $component['extra']['description'],
'#description' => $filter ? webform_filter_descriptions($component['extra']['description'], $node) : $component['extra']['description'],
'#attributes' => $component['extra']['attributes'],
'#element_validate' => array('_webform_validate_email'),
'#theme_wrappers' => array('webform_element'),
'#translatable' => array('title', 'description'),
);
if ($component['required']) {
$element['#attributes']['required'] = 'required';
}
// Add an e-mail class for identifying the difference from normal textfields.
$element['#attributes']['class'][] = 'email';
@@ -136,10 +170,35 @@ function _webform_render_email($component, $value = NULL, $filter = TRUE) {
$element['#element_validate'][] = 'webform_validate_unique';
}
if (isset($value)) {
if ($component['extra']['format'] == 'long') {
// html5 email elements enforce short-form email validation in addition to
// pattern validation. This means that long format email addresses must be
// rendered as text.
$element['#attributes']['type'] = 'text';
// html5 patterns have implied delimiters and start and end patterns.
// The are also case sensitive, not global, and not multi-line.
// See https://developer.mozilla.org/en-US/docs/Web/Guide/HTML/HTML5/Constraint_validation
// See http://stackoverflow.com/questions/19605773/html5-email-validation
$address = '[a-zA-Z0-9.!#$%&*+/=?^_`{|}~-]+@[a-zA-Z0-9-]+(?:\.[a-zA-Z0-9-]+)*';
$name = '("[^<>"]*?"|[^<>",]*?)';
$short_long = "($name *<$address>|$address)";
$element['#attributes']['pattern'] = $component['extra']['multiple']
? "$short_long(, *$short_long)*"
: $short_long;
}
elseif ($component['extra']['multiple']) {
$element['#attributes']['multiple'] = 'multiple';
}
if (isset($value[0])) {
$element['#default_value'] = $value[0];
}
if ($component['extra']['placeholder']) {
$element['#attributes']['placeholder'] = $component['extra']['placeholder'];
}
if ($component['extra']['disabled']) {
if ($filter) {
$element['#attributes']['readonly'] = 'readonly';
@@ -181,33 +240,36 @@ function theme_webform_email($variables) {
}
/**
* A Drupal Form API Validation function. Validates the entered values from
* email components on the client-side form.
* A Drupal Form API Validation function.
*
* @param $form_element
* Validates the entered values from email components on the client-side form.
* Calls a form_set_error if the e-mail is not valid.
*
* @param array $form_element
* The e-mail form element.
* @param $form_state
* @param array $form_state
* The full form state for the webform.
* @return
* None. Calls a form_set_error if the e-mail is not valid.
*/
function _webform_validate_email($form_element, &$form_state) {
$component = $form_element['#webform_component'];
$value = trim($form_element['#value']);
if ($value !== '' && !valid_email_address($value)) {
form_error($form_element, t('%value is not a valid email address.', array('%value' => $value)));
}
else {
form_set_value($form_element, $value, $form_state);
}
$format = webform_variable_get('webform_email_address_format') == 'long' ? $component['extra']['format'] : 'short';
webform_email_validate($form_element['#value'],
implode('][', $form_element['#parents']),
// Required validation is done elsewhere.
TRUE,
$component['extra']['multiple'],
// No tokens are allowed in user input.
FALSE,
$format);
}
/**
* Implements _webform_display_component().
*/
function _webform_display_email($component, $value, $format = 'html') {
function _webform_display_email($component, $value, $format = 'html', $submission = array()) {
return array(
'#title' => $component['name'],
'#title_display' => $component['extra']['title_display'] ? $component['extra']['title_display'] : 'before',
'#weight' => $component['weight'],
'#theme' => 'webform_display_email',
'#theme_wrappers' => $format == 'html' ? array('webform_element') : array('webform_element_text'),
@@ -229,14 +291,18 @@ function theme_webform_display_email($variables) {
/**
* Implements _webform_analysis_component().
*/
function _webform_analysis_email($component, $sids = array()) {
function _webform_analysis_email($component, $sids = array(), $single = FALSE, $join = NULL) {
$query = db_select('webform_submitted_data', 'wsd', array('fetch' => PDO::FETCH_ASSOC))
->fields('wsd', array('no', 'data'))
->condition('nid', $component['nid'])
->condition('cid', $component['cid']);
->condition('wsd.nid', $component['nid'])
->condition('wsd.cid', $component['cid']);
if (count($sids)) {
$query->condition('sid', $sids, 'IN');
$query->condition('wsd.sid', $sids, 'IN');
}
if ($join) {
$query->innerJoin($join, 'ws2_', 'wsd.sid = ws2_.sid');
}
$nonblanks = 0;
@@ -254,8 +320,16 @@ function _webform_analysis_email($component, $sids = array()) {
$rows[0] = array(t('Left Blank'), ($submissions - $nonblanks));
$rows[1] = array(t('User entered value'), $nonblanks);
$rows[2] = array(t('Average submission length in words (ex blanks)'), ($nonblanks != 0 ? number_format($wordcount/$nonblanks, 2) : '0'));
return $rows;
$other[0] = array(
t('Average submission length in words (ex blanks)'),
($nonblanks != 0 ? number_format($wordcount / $nonblanks, 2) : '0'),
);
return array(
'table_rows' => $rows,
'other_data' => $other,
);
}
/**
@@ -265,6 +339,13 @@ function _webform_table_email($component, $value) {
return check_plain(empty($value[0]) ? '' : $value[0]);
}
/**
* Implements _webform_action_set_component().
*/
function _webform_action_set_email($component, &$element, &$form_state, $value) {
$element['#value'] = $value;
form_set_value($element, $value, $form_state);
}
/**
* Implements _webform_csv_headers_component().
@@ -273,7 +354,7 @@ function _webform_csv_headers_email($component, $export_options) {
$header = array();
$header[0] = '';
$header[1] = '';
$header[2] = $component['name'];
$header[2] = $export_options['header_keys'] ? $component['form_key'] : $component['name'];
return $header;
}

View File

@@ -19,6 +19,7 @@ function _webform_defaults_fieldset() {
'collapsible' => 0,
'collapsed' => 0,
'description' => '',
'description_above' => FALSE,
'private' => FALSE,
),
);
@@ -51,15 +52,15 @@ function _webform_edit_fieldset($component) {
/**
* Implements _webform_render_component().
*/
function _webform_render_fieldset($component, $value = NULL, $filter = TRUE) {
function _webform_render_fieldset($component, $value = NULL, $filter = TRUE, $submission = NULL) {
$node = isset($component['nid']) ? node_load($component['nid']) : NULL;
$element = array(
'#type' => 'fieldset',
'#title' => $filter ? _webform_filter_xss($component['name']) : $component['name'],
'#title' => $filter ? webform_filter_xss($component['name']) : $component['name'],
'#title_display' => $component['extra']['title_display'] ? $component['extra']['title_display'] : NULL,
'#weight' => $component['weight'],
'#description' => $filter ? _webform_filter_descriptions($component['extra']['description'], $node) : $component['extra']['description'],
'#description' => $filter ? webform_filter_descriptions($component['extra']['description'], $node) : $component['extra']['description'],
'#collapsible' => $component['extra']['collapsible'],
'#collapsed' => $component['extra']['collapsed'],
'#attributes' => array('class' => array('webform-component-fieldset')),
@@ -71,17 +72,18 @@ function _webform_render_fieldset($component, $value = NULL, $filter = TRUE) {
}
/**
* Pre-render function to set a fieldset ID.
* Pre-render function to set a unique fieldset class name.
*/
function webform_fieldset_prerender($element) {
$element['#id'] = 'webform-component-' . str_replace('_', '-', implode('--', array_slice($element['#parents'], 1)));
$element['#id'] = NULL;
$element['#attributes']['class'][] = 'webform-component--' . str_replace('_', '-', implode('--', array_slice($element['#parents'], 1)));
return $element;
}
/**
* Implements _webform_display_component().
*/
function _webform_display_fieldset($component, $value, $format = 'html') {
function _webform_display_fieldset($component, $value, $format = 'html', $submission = array()) {
if ($format == 'text') {
$element = array(
'#title' => $component['name'],

View File

@@ -9,10 +9,15 @@
* Implements _webform_defaults_component().
*/
function _webform_defaults_file() {
// If private file storage is enabled, make it the default for security
// reasons. See: https://www.drupal.org/psa-2016-003
$available_schemes = file_get_stream_wrappers(STREAM_WRAPPERS_WRITE_VISIBLE);
$scheme = isset($available_schemes['private']) ? 'private' : 'public';
return array(
'name' => '',
'form_key' => NULL,
'mandatory' => 0,
'required' => 0,
'pid' => 0,
'weight' => 0,
'extra' => array(
@@ -21,13 +26,16 @@ function _webform_defaults_file() {
'addextensions' => '',
'size' => '2 MB',
),
'scheme' => 'public',
'rename' => '',
'scheme' => $scheme,
'directory' => '',
'progress_indicator' => 'throbber',
'title_display' => 0,
'description' => '',
'description_above' => FALSE,
'attributes' => array(),
'private' => FALSE,
'analysis' => FALSE,
),
);
}
@@ -45,6 +53,10 @@ function _webform_theme_file() {
'render element' => 'element',
'file' => 'components/file.inc',
),
'webform_managed_file' => array(
'render element' => 'element',
'file' => 'components/file.inc',
),
);
}
@@ -52,137 +64,148 @@ function _webform_theme_file() {
* Implements _webform_edit_component().
*/
function _webform_edit_file($component) {
$form = array();
$form['#element_validate'] = array('_webform_edit_file_check_directory');
$form['#after_build'] = array('_webform_edit_file_check_directory');
$form = array();
$form['#element_validate'] = array('_webform_edit_file_check_directory');
$form['#after_build'] = array('_webform_edit_file_check_directory');
$form['validation']['size'] = array(
'#type' => 'textfield',
'#title' => t('Max upload size'),
'#default_value' => $component['extra']['filtering']['size'],
'#description' => t('Enter the max file size a user may upload such as 2 MB or 800 KB. Your server has a max upload size of @size.', array('@size' => format_size(file_upload_max_size()))),
'#size' => 10,
'#parents' => array('extra', 'filtering', 'size'),
'#element_validate' => array('_webform_edit_file_size_validate'),
'#weight' => 1,
);
$form['validation']['size'] = array(
'#type' => 'textfield',
'#title' => t('Max upload size'),
'#default_value' => $component['extra']['filtering']['size'],
'#description' => t('Enter the max file size a user may upload such as 2 MB or 800 KB. Your server has a max upload size of @size.', array('@size' => format_size(file_upload_max_size()))),
'#size' => 10,
'#parents' => array('extra', 'filtering', 'size'),
'#element_validate' => array('_webform_edit_file_size_validate'),
'#weight' => 1,
);
$form['validation']['extensions'] = array(
'#element_validate' => array('_webform_edit_file_extensions_validate'),
'#parents' => array('extra', 'filtering'),
'#theme' => 'webform_edit_file_extensions',
'#theme_wrappers' => array('form_element'),
'#title' => t('Allowed file extensions'),
'#attached' => array(
'js' => array(drupal_get_path('module', 'webform') . '/js/webform-admin.js'),
'css' => array(drupal_get_path('module', 'webform') . '/css/webform-admin.css'),
),
'#weight' => 2,
);
$form['validation']['extensions'] = array(
'#element_validate' => array('_webform_edit_file_extensions_validate'),
'#parents' => array('extra', 'filtering'),
'#theme' => 'webform_edit_file_extensions',
'#theme_wrappers' => array('form_element'),
'#title' => t('Allowed file extensions'),
'#attached' => array(
'js' => array(drupal_get_path('module', 'webform') . '/js/webform-admin.js'),
'css' => array(drupal_get_path('module', 'webform') . '/css/webform-admin.css'),
),
'#type' => 'webform_file_extensions',
'#weight' => 2,
);
// Find the list of all currently valid extensions.
$current_types = isset($component['extra']['filtering']['types']) ? $component['extra']['filtering']['types'] : array();
// Find the list of all currently valid extensions.
$current_types = isset($component['extra']['filtering']['types']) ? $component['extra']['filtering']['types'] : array();
$types = array('gif', 'jpg', 'png');
$form['validation']['extensions']['types']['webimages'] = array(
'#type' => 'checkboxes',
'#title' => t('Web images'),
'#options' => drupal_map_assoc($types),
'#default_value' => array_intersect($current_types, $types),
);
$types = array('gif', 'jpg', 'png');
$form['validation']['extensions']['types']['webimages'] = array(
'#type' => 'checkboxes',
'#title' => t('Web images'),
'#options' => drupal_map_assoc($types),
'#default_value' => array_intersect($current_types, $types),
);
$types = array('bmp', 'eps', 'tif', 'pict', 'psd');
$form['validation']['extensions']['types']['desktopimages'] = array(
'#type' => 'checkboxes',
'#title' => t('Desktop images'),
'#options' => drupal_map_assoc($types),
'#default_value' => array_intersect($current_types, $types),
);
$types = array('bmp', 'eps', 'tif', 'pict', 'psd');
$form['validation']['extensions']['types']['desktopimages'] = array(
'#type' => 'checkboxes',
'#title' => t('Desktop images'),
'#options' => drupal_map_assoc($types),
'#default_value' => array_intersect($current_types, $types),
);
$types = array('txt', 'rtf', 'html', 'odf', 'pdf', 'doc', 'docx', 'ppt', 'pptx', 'xls', 'xlsx', 'xml');
$form['validation']['extensions']['types']['documents'] = array(
'#type' => 'checkboxes',
'#title' => t('Documents'),
'#options' => drupal_map_assoc($types),
'#default_value' => array_intersect($current_types, $types),
);
$types = array('txt', 'rtf', 'html', 'pdf', 'doc', 'docx', 'odt', 'ppt', 'pptx', 'odp', 'xls', 'xlsx', 'ods', 'xml');
$form['validation']['extensions']['types']['documents'] = array(
'#type' => 'checkboxes',
'#title' => t('Documents'),
'#options' => drupal_map_assoc($types),
'#default_value' => array_intersect($current_types, $types),
);
$types = array('avi', 'mov', 'mp3', 'ogg', 'wav');
$form['validation']['extensions']['types']['media'] = array(
'#type' => 'checkboxes',
'#title' => t('Media'),
'#options' => drupal_map_assoc($types),
'#default_value' => array_intersect($current_types, $types),
);
$types = array('avi', 'mov', 'mp3', 'ogg', 'wav');
$form['validation']['extensions']['types']['media'] = array(
'#type' => 'checkboxes',
'#title' => t('Media'),
'#options' => drupal_map_assoc($types),
'#default_value' => array_intersect($current_types, $types),
);
$types = array('bz2', 'dmg', 'gz', 'jar', 'rar', 'sit', 'tar', 'zip');
$form['validation']['extensions']['types']['archives'] = array(
'#type' => 'checkboxes',
'#title' => t('Archives'),
'#options' => drupal_map_assoc($types),
'#default_value' => array_intersect($current_types, $types),
);
$types = array('bz2', 'dmg', 'gz', 'jar', 'rar', 'sit', 'tar', 'zip');
$form['validation']['extensions']['types']['archives'] = array(
'#type' => 'checkboxes',
'#title' => t('Archives'),
'#options' => drupal_map_assoc($types),
'#default_value' => array_intersect($current_types, $types),
);
$form['validation']['extensions']['addextensions'] = array(
'#type' => 'textfield',
'#title' => t('Additional extensions'),
'#default_value' => $component['extra']['filtering']['addextensions'],
'#description' => t('Enter a list of additional file extensions for this upload field, separated by commas.<br /> Entered extensions will be appended to checked items above.'),
'#size' => 20,
'#weight' => 3,
);
$form['validation']['extensions']['addextensions'] = array(
'#type' => 'textfield',
'#title' => t('Additional extensions'),
'#default_value' => $component['extra']['filtering']['addextensions'],
'#description' => t('Enter a list of additional file extensions for this upload field, separated by commas.<br /> Entered extensions will be appended to checked items above.'),
'#size' => 20,
'#weight' => 3,
);
$scheme_options = array();
foreach (file_get_stream_wrappers(STREAM_WRAPPERS_WRITE_VISIBLE) as $scheme => $stream_wrapper) {
$scheme_options[$scheme] = $stream_wrapper['name'];
}
$form['extra']['scheme'] = array(
'#type' => 'radios',
'#title' => t('Upload destination'),
'#options' => $scheme_options,
'#default_value' => $component['extra']['scheme'],
'#description' => t('Private file storage has significantly more overhead than public files, but restricts file access to users who can view submissions.'),
'#weight' => 4,
'#access' => count($scheme_options) > 1,
);
$form['extra']['directory'] = array(
'#type' => 'textfield',
'#title' => t('Upload directory'),
'#default_value' => $component['extra']['directory'],
'#description' => t('You may optionally specify a sub-directory to store your files.'),
'#weight' => 5,
'#field_prefix' => 'webform/',
);
$scheme_options = array();
foreach (file_get_stream_wrappers(STREAM_WRAPPERS_WRITE_VISIBLE) as $scheme => $stream_wrapper) {
$scheme_options[$scheme] = $stream_wrapper['name'];
}
$form['extra']['scheme'] = array(
'#type' => 'radios',
'#title' => t('Upload destination'),
'#options' => $scheme_options,
'#default_value' => $component['extra']['scheme'],
'#description' => t('Public files upload destination is dangerous for forms that are available to anonymous and/or untrusted users. For more information, see <a href="@psa">DRUPAL-PSA-2016-003</a>.', array('@psa' => 'https://www.drupal.org/psa-2016-003')),
'#weight' => 4,
'#access' => count($scheme_options) > 1,
);
$form['extra']['directory'] = array(
'#type' => 'textfield',
'#title' => t('Upload directory'),
'#default_value' => $component['extra']['directory'],
'#description' => t('You may optionally specify a sub-directory to store your files.') . ' ' . theme('webform_token_help'),
'#weight' => 5,
'#field_prefix' => 'webform/',
);
$form['display']['progress_indicator'] = array(
'#type' => 'radios',
'#title' => t('Progress indicator'),
'#options' => array(
'throbber' => t('Throbber'),
'bar' => t('Bar with progress meter'),
),
'#default_value' => $component['extra']['progress_indicator'],
'#description' => t('The throbber display does not show the status of uploads but takes up less space. The progress bar is helpful for monitoring progress on large uploads.'),
'#weight' => 16,
'#access' => file_progress_implementation(),
'#parents' => array('extra', 'progress_indicator'),
);
$form['extra']['rename'] = array(
'#type' => 'textfield',
'#title' => t('Rename files'),
'#default_value' => $component['extra']['rename'],
'#description' => t('You may optionally use tokens to create a pattern used to rename files upon submission. Omit the extension; it will be added automatically.') . ' ' . theme('webform_token_help', array('groups' => array('node', 'submission'))),
'#weight' => 6,
'#element_validate' => array('_webform_edit_file_rename_validate'),
'#access' => webform_variable_get('webform_token_access'),
);
// TODO: Make managed_file respect the "size" parameter.
/*
$form['display']['width'] = array(
'#type' => 'textfield',
'#title' => t('Width'),
'#default_value' => $component['extra']['width'],
'#description' => t('Width of the file field.') . ' ' . t('Leaving blank will use the default size.'),
'#size' => 5,
'#maxlength' => 10,
'#weight' => 4,
'#parents' => array('extra', 'width')
);
*/
$form['display']['progress_indicator'] = array(
'#type' => 'radios',
'#title' => t('Progress indicator'),
'#options' => array(
'throbber' => t('Throbber'),
'bar' => t('Bar with progress meter'),
),
'#default_value' => $component['extra']['progress_indicator'],
'#description' => t('The throbber display does not show the status of uploads but takes up less space. The progress bar is helpful for monitoring progress on large uploads.'),
'#weight' => 16,
'#access' => file_progress_implementation(),
'#parents' => array('extra', 'progress_indicator'),
);
return $form;
return $form;
}
/**
* Form API validator ensures rename string is empty or contains one token.
*
* A Form API element validate function to ensure that the rename string is
* either empty or contains at least one token.
*/
function _webform_edit_file_rename_validate($element, &$form_state, $form) {
$rename = trim($form_state['values']['extra']['rename']);
form_set_value($element, $rename, $form_state);
if (strlen($rename) && !count(token_scan($rename))) {
form_error($element, t('To create unique file names, use at least one token in the file name pattern.'));
}
}
/**
@@ -192,12 +215,12 @@ function _webform_edit_file_size_validate($element) {
if (!empty($element['#value'])) {
$set_filesize = parse_size($element['#value']);
if ($set_filesize == FALSE) {
form_error($element, t('File size @value is not a valid filesize. Use a value such as 2 MB or 800 KB.', array('@value' => $element['#value'])));
form_error($element, t('File size @value is not a valid file size. Use a value such as 2 MB or 800 KB.', array('@value' => $element['#value'])));
}
else {
$max_filesize = parse_size(file_upload_max_size());
if ($max_filesize < $set_filesize) {
form_error($element, t('An upload size of @value is too large, you are allow to upload files @max or less.', array('@value' => $element['#value'], '@max' => format_size($max_filesize))));
form_error($element, t('An upload size of @value is too large. You are allowed to upload files that are @max or less.', array('@value' => $element['#value'], '@max' => format_size($max_filesize))));
}
}
}
@@ -213,15 +236,15 @@ function _webform_edit_file_check_directory($element) {
$directory = $element['extra']['directory']['#value'];
$destination_dir = file_stream_wrapper_uri_normalize($scheme . '://webform/' . $directory);
$tokenized_dir = drupal_strtolower(webform_replace_tokens($destination_dir, $element['#node']));
// Sanity check input to prevent use parent (../) directories.
if (preg_match('/\.\.[\/\\\]/', $destination_dir . '/')) {
form_error($element['extra']['directory'], t('The save directory %directory is not valid.', array('%directory' => $directory)));
if (preg_match('/\.\.[\/\\\]/', $tokenized_dir . '/')) {
form_error($element['extra']['directory'], t('The save directory %directory is not valid.', array('%directory' => $tokenized_dir)));
}
else {
$destination_success = file_prepare_directory($destination_dir, FILE_CREATE_DIRECTORY);
if (!$destination_success) {
form_error($element['extra']['directory'], t('The save directory %directory could not be created. Check that the webform files directory is writable.', array('%directory' => $directory)));
if (!file_prepare_directory($tokenized_dir, FILE_CREATE_DIRECTORY)) {
form_error($element['extra']['directory'], t('The save directory %directory could not be created. Check that the webform files directory is writable.', array('%directory' => $tokenized_dir)));
}
}
@@ -241,6 +264,10 @@ function _webform_edit_file_extensions_validate($element, &$form_state) {
foreach (array_keys($element['types'][$category]['#value']) as $extension) {
if ($element['types'][$category][$extension]['#value']) {
$extensions[] = $extension;
// "jpeg" is an exception. It is allowed anytime "jpg" is allowed.
if ($extension == 'jpg') {
$extensions[] = 'jpeg';
}
}
}
}
@@ -267,7 +294,6 @@ function theme_webform_edit_file_extensions($variables) {
$rows = array();
foreach (element_children($element['types']) as $filtergroup) {
$row = array();
$first_row = count($rows);
if ($element['types'][$filtergroup]['#type'] == 'checkboxes') {
$select_link = ' <a href="#" class="webform-select-link webform-select-link-' . $filtergroup . '">(' . t('select') . ')</a>';
$row[] = $element['types'][$filtergroup]['#title'];
@@ -279,12 +305,14 @@ function theme_webform_edit_file_extensions($variables) {
}
// Add the row for additional types.
$row = array();
$title = $element['addextensions']['#title'];
$element['addextensions']['#title'] = NULL;
$row[] = array('data' => $title, 'colspan' => 2);
$row[] = drupal_render($element['addextensions']);
$rows[] = $row;
if (!isset($element['addextensions']['#access']) || $element['addextensions']['#access']) {
$row = array();
$title = $element['addextensions']['#title'];
$element['addextensions']['#title'] = NULL;
$row[] = array('data' => $title, 'colspan' => 2);
$row[] = drupal_render($element['addextensions']);
$rows[] = $row;
}
$header = array(array('data' => t('Category'), 'colspan' => '2'), array('data' => t('Types')));
@@ -302,7 +330,7 @@ function theme_webform_edit_file_extensions($variables) {
/**
* Implements _webform_render_component().
*/
function _webform_render_file($component, $value = NULL, $filter = TRUE) {
function _webform_render_file($component, $value = NULL, $filter = TRUE, $submission = NULL) {
$node = isset($component['nid']) ? node_load($component['nid']) : NULL;
// Cap the upload size according to the PHP limit.
@@ -314,9 +342,10 @@ function _webform_render_file($component, $value = NULL, $filter = TRUE) {
$element = array(
'#type' => 'managed_file',
'#title' => $filter ? _webform_filter_xss($component['name']) : $component['name'],
'#theme' => 'webform_managed_file',
'#title' => $filter ? webform_filter_xss($component['name']) : $component['name'],
'#title_display' => $component['extra']['title_display'] ? $component['extra']['title_display'] : 'before',
'#required' => $component['mandatory'],
'#required' => $component['required'],
'#default_value' => isset($value[0]) ? $value[0] : NULL,
'#attributes' => $component['extra']['attributes'],
'#upload_validators' => array(
@@ -324,27 +353,75 @@ function _webform_render_file($component, $value = NULL, $filter = TRUE) {
'file_validate_extensions' => array(implode(' ', $component['extra']['filtering']['types'])),
),
'#pre_render' => array_merge(element_info_property('managed_file', '#pre_render'), array('webform_file_allow_access')),
'#upload_location' => $component['extra']['scheme'] . '://webform/' . $component['extra']['directory'],
'#upload_location' => $component['extra']['scheme'] . '://webform/' . ($filter
? drupal_strtolower(webform_replace_tokens($component['extra']['directory'], $node))
: $component['extra']['directory']),
'#progress_indicator' => $component['extra']['progress_indicator'],
'#description' => $filter ? _webform_filter_descriptions($component['extra']['description'], $node) : $component['extra']['description'],
'#description' => $filter ? webform_filter_descriptions($component['extra']['description'], $node) : $component['extra']['description'],
'#weight' => $component['weight'],
'#theme_wrappers' => array('webform_element'),
'#translatable' => array('title', 'description'),
);
if ($filter) {
$element['#description'] = theme('file_upload_help', array('description' => $element['#description'], 'upload_validators' => $element['#upload_validators']));
}
return $element;
}
/**
* Returns HTML for a webform managed file element.
*
* See #2495821 and #2497909. The core theme_file_managed_file creates a
* wrapper around the element with the element's id, thereby creating 2 elements
* with the same id.
*
* @param array $variables
* An associative array containing:
* - element: A render element representing the file.
*
* @return string
* The HTML.
*/
function theme_webform_managed_file($variables) {
$element = $variables['element'];
$attributes = array();
// For webform use, do not add the id to the wrapper.
// @code
// if (isset($element['#id'])) {
// $attributes['id'] = $element['#id'];
// }
// @endcode
if (!empty($element['#attributes']['class'])) {
$attributes['class'] = (array) $element['#attributes']['class'];
}
$attributes['class'][] = 'form-managed-file';
// This wrapper is required to apply JS behaviors and CSS styling.
$output = '';
$output .= '<div' . drupal_attributes($attributes) . '>';
$output .= drupal_render_children($element);
$output .= '</div>';
return $output;
}
/**
* Implements _webform_submit_component().
*/
function _webform_submit_file($component, $value) {
if (is_array($value)) {
return !empty($value['fid']) ? $value['fid'] : '';
}
else {
return !empty($value) ? $value : '';
$fid = is_array($value)
? (!empty($value['fid']) ? $value['fid'] : '')
: (!empty($value) ? $value : '');
// Extend access to this file, even if the submission has not been saved yet.
// This may happen when previewing a private file which was selected but not
// explicitly uploaded, and then previewed.
if ($fid) {
$_SESSION['webform_files'][$fid] = $fid;
}
return $fid;
}
/**
@@ -369,10 +446,11 @@ function webform_file_allow_access($element) {
/**
* Implements _webform_display_component().
*/
function _webform_display_file($component, $value, $format = 'html') {
function _webform_display_file($component, $value, $format = 'html', $submission = array()) {
$fid = isset($value[0]) ? $value[0] : NULL;
return array(
'#title' => $component['name'],
'#title_display' => $component['extra']['title_display'] ? $component['extra']['title_display'] : 'before',
'#value' => $fid ? webform_get_file($fid) : NULL,
'#weight' => $component['weight'],
'#theme' => 'webform_display_file',
@@ -383,7 +461,7 @@ function _webform_display_file($component, $value, $format = 'html') {
}
/**
* Format the output of text data for this component
* Format the output of text data for this component.
*/
function theme_webform_display_file($variables) {
$element = $variables['element'];
@@ -409,22 +487,25 @@ function _webform_delete_file($component, $value) {
*/
function _webform_attachments_file($component, $value) {
$file = (array) webform_get_file($value[0]);
//This is necessary until the next release of mimemail is out, see [#1388786]
$file['filepath'] = $file['uri'];
$files = array($file);
return $files;
}
/**
* Implements _webform_analysis_component().
*/
function _webform_analysis_file($component, $sids = array()) {
function _webform_analysis_file($component, $sids = array(), $single = FALSE, $join = NULL) {
$query = db_select('webform_submitted_data', 'wsd', array('fetch' => PDO::FETCH_ASSOC))
->fields('wsd', array('no', 'data'))
->condition('nid', $component['nid'])
->condition('cid', $component['cid']);
->condition('wsd.nid', $component['nid'])
->condition('wsd.cid', $component['cid']);
if (count($sids)) {
$query->condition('sid', $sids, 'IN');
$query->condition('wsd.sid', $sids, 'IN');
}
if ($join) {
$query->innerJoin($join, 'ws2_', 'wsd.sid = ws2_.sid');
}
$nonblanks = 0;
@@ -443,8 +524,11 @@ function _webform_analysis_file($component, $sids = array()) {
$rows[0] = array(t('Left Blank'), ($submissions - $nonblanks));
$rows[1] = array(t('User uploaded file'), $nonblanks);
$rows[2] = array(t('Average uploaded file size'), ($sizetotal != 0 ? (int) (($sizetotal/$nonblanks)/1024) . ' KB' : '0'));
return $rows;
$other[0] = array(t('Average uploaded file size'), ($sizetotal != 0 ? (int) (($sizetotal / $nonblanks) / 1024) . ' KB' : '0'));
return array(
'table_rows' => $rows,
'other_data' => $other,
);
}
/**
@@ -455,7 +539,7 @@ function _webform_table_file($component, $value) {
$file = webform_get_file($value[0]);
if (!empty($file->fid)) {
$output = '<a href="' . webform_file_url($file->uri) . '">' . check_plain(webform_file_name($file->uri)) . '</a>';
$output .= ' (' . (int) ($file->filesize/1024) . ' KB)';
$output .= ' (' . (int) ($file->filesize / 1024) . ' KB)';
}
return $output;
}
@@ -467,7 +551,7 @@ function _webform_csv_headers_file($component, $export_options) {
$header = array();
// Two columns in header.
$header[0] = array('', '');
$header[1] = array($component['name'], '');
$header[1] = array($export_options['header_keys'] ? $component['form_key'] : $component['name'], '');
$header[2] = array(t('Name'), t('Filesize (KB)'));
return $header;
}
@@ -477,7 +561,7 @@ function _webform_csv_headers_file($component, $export_options) {
*/
function _webform_csv_data_file($component, $export_options, $value) {
$file = webform_get_file($value[0]);
return empty($file->filename) ? array('', '') : array(webform_file_url($file->uri), (int) ($file->filesize/1024));
return empty($file->filename) ? array('', '') : array(webform_file_url($file->uri), (int) ($file->filesize / 1024));
}
/**
@@ -530,3 +614,59 @@ function webform_file_usage_adjust($submission) {
}
}
/**
* Rename any files which are eligible for renaming.
*
* Renames if this submission is being submitted for the first time.
*/
function webform_file_rename($node, $submission) {
if (isset($submission->file_usage)) {
foreach ($submission->file_usage['renameable'] as $cid => $fids) {
foreach ($fids as $fid) {
webform_file_process_rename($node, $submission, $node->webform['components'][$cid], $fid);
}
}
}
}
/**
* Renames the uploaded file name using tokens.
*
* @param object $node
* The webform node object.
* @param object $submission
* The webform submission object.
* @param array $component
* Component settings array for which fid is going to be processed.
* @param int $fid
* A file id to be processed.
*/
function webform_file_process_rename($node, $submission, $component, $fid) {
$file = webform_get_file($fid);
if ($file) {
// Get the destination uri.
$destination_dir = $component['extra']['scheme'] . '://webform/' . drupal_strtolower(webform_replace_tokens($component['extra']['directory'], $node));
$destination_dir = file_stream_wrapper_uri_normalize($destination_dir);
// Get the file extension.
$info = pathinfo($file->uri);
$extension = $info['extension'];
// Prepare new file name without extension.
$new_file_name = webform_replace_tokens($component['extra']['rename'], $node, $submission, NULL, TRUE);
$new_file_name = trim($new_file_name);
$new_file_name = _webform_transliterate($new_file_name);
$new_file_name = str_replace('/', '_', $new_file_name);
$new_file_name = preg_replace('/[^a-zA-Z0-9_\- ]/', '', $new_file_name);
if (strlen($new_file_name)) {
// Prepare the new uri with new filename.
$destination = "$destination_dir/$new_file_name.$extension";
// Compare the uri and Rename the file name.
if ($file->uri != $destination) {
file_move($file, $destination, FILE_EXISTS_RENAME);
}
}
}
}

View File

@@ -15,24 +15,28 @@ function _webform_defaults_grid() {
return array(
'name' => '',
'form_key' => NULL,
'mandatory' => 0,
'required' => 0,
'pid' => 0,
'weight' => 0,
'value' => '',
'extra' => array(
'options' => '',
'questions' => '',
'optrand' => 0,
'qrand' => 0,
'unique' => 0,
'title_display' => 0,
'custom_option_keys' => 0,
'custom_question_keys' => 0,
'sticky' => TRUE,
'description' => '',
'description_above' => FALSE,
'private' => FALSE,
'analysis' => TRUE,
),
);
}
/**
* Implements _webform_theme_component().
*/
@@ -55,34 +59,58 @@ function _webform_theme_grid() {
function _webform_edit_grid($component) {
$form = array();
$form['help'] = array(
'#type' => 'fieldset',
'#collapsible' => TRUE,
'#collapsed' => !empty($component['cid']),
'#title' => t('About options and questions&hellip;'),
'#description' => t('Options and questions may be configured here, in additional nested Select Options components, or even both.'),
'#weight' => -4,
'pros_and_cons' => array(
'#theme' => 'table',
'#header' => array('', t('Options and questions configured <strong>here</strong>'), t('Configured in additional <strong>nested</strong> components'), t('Both')),
'#rows' => array(
array(t('Questions'), t('Enter the questions below.'), t('Configure and save this grid, then add additional Select Options components nested (indented) below this grid.'), t('Additional questions from nested components will be displayed below any questions configured here.')),
array(t('Options'), t('Enter options below.'), t('May be different for each question. Initially the same as defined below.'), t('Options from additional nested components will be merged with any options configured here.')),
array(t('Checkboxes'), t('No. Radio buttons only.'), t('Yes. Some or all questions may be multiple choice with check boxes.'), ''),
array(t('Default'), t('Yes. Must be same for all questions.'), t('Yes. May all be the same or different.'), ''),
array(t('Pre-built option lists'), t('No.'), t('Yes.'), ''),
array(t('Required'), t('Yes. Must be same for all questions.'), t('Yes. May all be the same or different.'), ''),
array(t('Question conditionals'), t('No.'), t('Yes. Individual questions may be used in conditional rules and/or actions.'), t('The whole grid may be conditionally shown or required.')),
array(t('Other types of nested components'), t('No.'), t('Yes. Other component types may also be included in the grid. They will be displayed where the options would normally be.'), ''),
),
),
);
if (module_exists('options_element')) {
$form['options'] = array(
'#type' => 'fieldset',
'#title' => t('Options'),
'#collapsible' => TRUE,
'#description' => t('Options to select across the top. Usually these are ratings such as "poor" through "excellent" or "strongly disagree" through "strongly agree".'),
'#attributes' => array('class' => array('webform-options-element')),
'#element_validate' => array('_webform_edit_validate_options'),
'#weight' => -3,
);
$form['options']['options'] = array(
'#type' => 'options',
'#options' => _webform_select_options_from_text($component['extra']['options'], TRUE),
'#optgroups' => FALSE,
'#default_value' => FALSE,
'#default_value_allowed' => FALSE,
'#default_value' => $component['value'],
'#default_value_allowed' => TRUE,
'#optgroups' => FALSE,
'#key_type' => 'mixed',
'#key_type_toggle' => t('Customize option keys (Advanced)'),
'#key_type_toggled' => $component['extra']['custom_option_keys'],
'#default_value_pattern' => '^%.+\[.+\]$',
'#description' => t('<strong>Options to select across the top</strong>, such as "Poor" through "Excellent". Indicate the default to the left of the desired item. Use of only alphanumeric characters and underscores is recommended in keys.') . ' ' . theme('webform_token_help'),
);
$form['questions'] = array(
'#type' => 'fieldset',
'#title' => t('Questions'),
'#collapsible' => TRUE,
'#description' => t('Questions list down the side of the grid.'),
'#attributes' => array('class' => array('webform-options-element')),
'#element_validate' => array('_webform_edit_validate_options'),
'#weight' => -2,
);
$form['questions']['options'] = array(
'#type' => 'options',
@@ -90,10 +118,10 @@ function _webform_edit_grid($component) {
'#optgroups' => FALSE,
'#default_value' => FALSE,
'#default_value_allowed' => FALSE,
'#optgroups' => FALSE,
'#key_type' => 'mixed',
'#key_type_toggle' => t('Customize question keys (Advanced)'),
'#key_type_toggled' => $component['extra']['custom_question_keys'],
'#description' => t('<strong>Questions list down the side of the grid.</strong> For a heading column on the right, append "|" and the right-side heading. Use of only alphanumeric characters and underscores is recommended in keys.') . ' ' . theme('webform_token_help'),
);
}
else {
@@ -101,11 +129,11 @@ function _webform_edit_grid($component) {
'#type' => 'textarea',
'#title' => t('Options'),
'#default_value' => $component['extra']['options'],
'#description' => t('Options to select across the top. One option per line. <strong>Key-value pairs MUST be specified as "safe_key|Some readable option"</strong>. Use of only alphanumeric characters and underscores is recommended in keys.') . theme('webform_token_help'),
'#description' => t('Options to select across the top, such as "Poor" through "Excellent" or "Stronly Disagree" through "Strongly Agree".') .
'<p>' . t('One key-value option per line. <strong>Key-value pairs MUST be specified as "safe_key|Some readable option"</strong>. Use of only alphanumeric characters and underscores is recommended in keys.') . '</p>' . theme('webform_token_help'),
'#cols' => 60,
'#rows' => 5,
'#weight' => -3,
'#required' => TRUE,
'#wysiwyg' => FALSE,
'#element_validate' => array('_webform_edit_validate_select'),
);
@@ -113,14 +141,23 @@ function _webform_edit_grid($component) {
'#type' => 'textarea',
'#title' => t('Questions'),
'#default_value' => $component['extra']['questions'],
'#description' => t('Questions list down the side of the grid. One question per line. <strong>Key-value pairs MUST be specified as "safe_key|Some readable option"</strong>. Use of only alphanumeric characters and underscores is recommended in keys.') . theme('webform_token_help'),
'#description' => t('Questions list down the side of the grid. One question per line. <strong>Key-value pairs MUST be specified as "safe_key|Some readable question"</strong>. For a heading column on the right, append "|" and the right-side heading. Use of only alphanumeric characters and underscores is recommended in keys.') . ' ' . theme('webform_token_help') . ' ' .
'<p>' . t('<strong>Or for more control</strong> over the appearance and configuration, create additional additional Select Options or other type components nested under this grid. They will operate as separate components, but be displayed within this grid.') . '</p>',
'#cols' => 60,
'#rows' => 5,
'#weight' => -2,
'#required' => TRUE,
'#wysiwyg' => FALSE,
'#element_validate' => array('_webform_edit_validate_select'),
);
$form['value'] = array(
'#type' => 'textfield',
'#title' => t('Default value'),
'#default_value' => $component['value'],
'#description' => t('The default option of the grid identified by its key.') . ' ' . theme('webform_token_help'),
'#size' => 60,
'#maxlength' => 1024,
'#weight' => 1,
);
}
$form['display']['optrand'] = array(
@@ -128,43 +165,73 @@ function _webform_edit_grid($component) {
'#title' => t('Randomize Options'),
'#default_value' => $component['extra']['optrand'],
'#description' => t('Randomizes the order of options on the top when they are displayed in the form.'),
'#parents' => array('extra', 'optrand')
'#parents' => array('extra', 'optrand'),
);
$form['display']['qrand'] = array(
'#type' => 'checkbox',
'#title' => t('Randomize Questions'),
'#default_value' => $component['extra']['qrand'],
'#description' => t('Randomize the order of the questions on the side when they are displayed in the form.'),
'#parents' => array('extra', 'qrand')
'#parents' => array('extra', 'qrand'),
);
$form['display']['sticky'] = array(
'#type' => 'checkbox',
'#title' => t('Sticky table header'),
'#default_value' => $component['extra']['sticky'],
'#description' => t('Use a sticky (non-scrolling) table header.'),
'#parents' => array('extra', 'sticky'),
);
$form['validation']['unique'] = array(
'#type' => 'checkbox',
'#title' => t('Unique'),
'#return_value' => 1,
'#description' => t('Check that all entered values for this field are unique. The same value is not allowed to be used twice.'),
'#weight' => 1,
'#default_value' => $component['extra']['unique'],
'#parents' => array('extra', 'unique'),
);
return $form;
}
/**
* Implements _webform_render_component().
*/
function _webform_render_grid($component, $value = NULL, $filter = TRUE) {
function _webform_render_grid($component, $value = NULL, $filter = TRUE, $submission = NULL) {
$node = isset($component['nid']) ? node_load($component['nid']) : NULL;
$questions = _webform_select_options_from_text($component['extra']['questions'], TRUE);
$options = _webform_select_options_from_text($component['extra']['options'], TRUE);
if ($filter) {
$questions = _webform_select_replace_tokens($questions, $node);
$options = _webform_select_replace_tokens($options, $node);
}
$element = array(
'#type' => 'webform_grid',
'#title' => $filter ? _webform_filter_xss($component['name']) : $component['name'],
'#title' => $filter ? webform_filter_xss($component['name']) : $component['name'],
'#title_display' => $component['extra']['title_display'] ? $component['extra']['title_display'] : 'before',
'#required' => $component['mandatory'],
'#required' => $component['required'],
'#weight' => $component['weight'],
'#description' => $filter ? _webform_filter_descriptions($component['extra']['description'], $node) : $component['extra']['description'],
'#grid_questions' => _webform_select_options_from_text($component['extra']['questions'], TRUE),
'#grid_options' => _webform_select_options_from_text($component['extra']['options'], TRUE),
'#description' => $filter ? webform_filter_descriptions($component['extra']['description'], $node) : $component['extra']['description'],
'#grid_questions' => $questions,
'#grid_options' => $options,
'#default_value' => isset($value) || !strlen($component['value']) ? $value : array_fill_keys(array_keys($questions), $component['value']),
'#grid_default' => $component['value'],
'#optrand' => $component['extra']['optrand'],
'#qrand' => $component['extra']['qrand'],
'#sticky' => $component['extra']['sticky'],
'#theme' => 'webform_grid',
'#theme_wrappers' => array('webform_element'),
'#process' => array('webform_expand_grid'),
'#element_validate' => array('webform_validate_grid'),
'#translatable' => array('title', 'description', 'grid_options', 'grid_questions'),
);
if ($value) {
$element['#default_value'] = $value;
// Enforce uniqueness.
if ($component['extra']['unique']) {
$element['#element_validate'][] = '_webform_edit_grid_unique_validate';
}
return $element;
@@ -176,58 +243,160 @@ function _webform_render_grid($component, $value = NULL, $filter = TRUE) {
function webform_expand_grid($element) {
$options = $element['#grid_options'];
$questions = $element['#grid_questions'];
$weights = array();
if (!empty($element['#optrand'])) {
_webform_shuffle_options($options);
}
if (!empty($element['#qrand'])) {
_webform_shuffle_options($questions);
// Process questions and options from nested components.
foreach (element_children($element) as $key) {
$question = $element[$key];
// Both forms and grid displays have #webform_component.
if (isset($question['#webform_component']) &&
$question['#webform_component']['type'] == 'select' &&
!$question['#webform_component']['extra']['aslist'] &&
!$question['#webform_component']['extra']['other_option']) {
$options = webform_grid_merge_options($options, $question['#options']);
$weights[$key] = $question['#weight'];
}
}
// Add the internal grid questions.
$weight = -1000;
$value = isset($element['#default_value']) ? $element['#default_value'] : array();
foreach ($questions as $key => $question) {
if ($question != '') {
$question_value = isset($value[$key]) && $value[$key] !== '' ? $value[$key] : NULL;
$element[$key] = array(
'#grid_question' => TRUE,
'#title' => $question,
'#required' => $element['#required'],
'#options' => $options,
'#options' => $element['#grid_options'],
'#type' => 'radios',
'#default_value' => $question_value,
'#value' => $question_value,
'#process' => array('form_process_radios', 'webform_expand_select_ids'),
// Webform handles validation manually.
'#validated' => TRUE,
'#webform_validated' => FALSE,
'#translatable' => array('title'),
'#weight' => $weight,
);
// Add HTML5 required attribute, if needed.
if ($element['#required']) {
$element[$key]['#attributes']['required'] = 'required';
}
$weights[$key] = $weight;
$weight++;
}
}
$value = isset($element['#default_value']) ? $element['#default_value'] : array();
foreach (element_children($element) as $key) {
if (isset($value[$key])) {
$element[$key]['#default_value'] = ($value[$key] !== '') ? $value[$key] : NULL;
}
else {
$element[$key]['#default_value'] = NULL;
}
if (!empty($element['#optrand'])) {
_webform_shuffle_options($options);
}
$element['#grid_options'] = $options;
asort($weights);
if (!empty($element['#qrand'])) {
_webform_shuffle_options($weights);
}
if ($weights) {
$weight = min($weights);
}
foreach ($weights as $key => $old_weight) {
$element[$key]['#options'] = webform_grid_remove_options($options, $element[$key]['#options']);
$element[$key]['#weight'] = $weight++;
$element['#grid_questions'][$key] = $element[$key]['#title'];
}
return $element;
}
/**
* Helper. Merge select component options in order.
*
* @param array $existing
* An array of existing values into which any values from $new that aren't in
* $existing are inserted.
* @param array $new
* Values to be inserted into $existing.
*
* @return array
* The merged array.
*/
function webform_grid_merge_options(array $existing, array $new) {
$insert = NULL;
$queue = array();
foreach ($new as $key => $value) {
if (isset($existing[$key])) {
// Insert the queue before the found item.
$insert = array_search($key, array_keys($existing));
if ($queue) {
$existing = array_slice($existing, 0, $insert, TRUE) +
$queue +
array_slice($existing, $insert, NULL, TRUE);
$insert += count($queue);
$queue = array();
}
$insert++;
}
elseif (is_null($insert)) {
// It is not yet clear yet where to put this item. Add it to the queue.
$queue[$key] = $value;
}
else {
// PHP array_splice does not preserved the keys of the inserted array,
// but array_slice does (if the preserve keys parameter is TRUE).
$existing = array_slice($existing, 0, $insert, TRUE) +
array($key => $value) +
array_slice($existing, $insert, NULL, TRUE);
$insert++;
}
}
// Append any left over queued items.
$existing += $queue;
return $existing;
}
/**
* Helper. Replace missing options with empty values.
*
* @param array $header
* An array of options to be used at the grid table header.
* @param array $row_options
* An array of options to be used for this row.
*
* @return array
* The $row_options with any missing options replaced with empty values.
*/
function webform_grid_remove_options(array $header, array $row_options) {
foreach ($header as $key => $value) {
if (!isset($row_options[$key])) {
$header[$key] = '';
}
}
return $header;
}
/**
* Implements _webform_display_component().
*/
function _webform_display_grid($component, $value, $format = 'html') {
function _webform_display_grid($component, $value, $format = 'html', $submission = array()) {
$node = node_load($component['nid']);
$questions = _webform_select_options_from_text($component['extra']['questions'], TRUE);
$questions = _webform_select_replace_tokens($questions, $node);
$options = _webform_select_options_from_text($component['extra']['options'], TRUE);
$options = _webform_select_replace_tokens($options, $node);
$element = array(
'#title' => $component['name'],
'#title_display' => $component['extra']['title_display'] ? $component['extra']['title_display'] : 'before',
'#weight' => $component['weight'],
'#format' => $format,
'#grid_questions' => $questions,
'#grid_options' => $options,
'#default_value' => $value,
'#sticky' => $component['extra']['sticky'],
'#theme' => 'webform_display_grid',
'#theme_wrappers' => $format == 'html' ? array('webform_element') : array('webform_element_text'),
'#sorted' => TRUE,
@@ -247,42 +416,88 @@ function _webform_display_grid($component, $value, $format = 'html') {
return $element;
}
/**
* Preprocess function for displaying a grid component.
*/
function template_preprocess_webform_display_grid(&$variables) {
$element =& $variables['element'];
// Expand the grid, suppressing randomization. This builds the grid
// questions and options.
$element['#qrand'] = FALSE;
$element['#optrand'] = FALSE;
$element['#required'] = FALSE;
$element = webform_expand_grid($element);
}
/**
* Format the text output for this component.
*/
function theme_webform_display_grid($variables) {
$element = $variables['element'];
$component = $element['#webform_component'];
$format = $element['#format'];
if ($format == 'html') {
$right_titles = _webform_grid_right_titles($element);
$rows = array();
$header = array(array('data' => '', 'class' => array('webform-grid-question')));
foreach ($element['#grid_options'] as $option) {
$header[] = array('data' => _webform_filter_xss($option), 'class' => array('checkbox', 'webform-grid-option'));
}
foreach ($element['#grid_questions'] as $question_key => $question) {
// Set the header for the table.
$header = _webform_grid_header($element, $right_titles);
foreach (element_children($element) as $question_key) {
$question_element = $element[$question_key];
$row = array();
$row[] = array('data' => _webform_filter_xss($question), 'class' => array('webform-grid-question'));
foreach ($element['#grid_options'] as $option_value => $option_label) {
if (strcmp($element[$question_key]['#value'], $option_value) == 0) {
$row[] = array('data' => '<strong>X</strong>', 'class' => array('checkbox', 'webform-grid-option'));
}
else {
$row[] = array('data' => '&nbsp;', 'class' => array('checkbox', 'webform-grid-option'));
$questions = explode('|', $question_element['#title'], 2);
$values = $question_element['#value'];
$values = is_array($values) ? $values : array($values);
$row[] = array('data' => webform_filter_xss($questions[0]), 'class' => array('webform-grid-question'));
if (isset($element['#grid_questions'][$question_key])) {
foreach ($element['#grid_options'] as $option_value => $option_label) {
if (in_array($option_value, $values)) {
$row[] = array('data' => '<strong>X</strong>', 'class' => array('checkbox', 'webform-grid-option'));
}
else {
$row[] = array('data' => '&nbsp;', 'class' => array('checkbox', 'webform-grid-option'));
}
}
}
else {
$question_element['#title_display'] = 'none';
$row[] = array(
'data' => drupal_render($question_element),
'colspan' => count($element['#grid_options']),
);
}
if ($right_titles) {
$row[] = array('data' => isset($questions[1]) ? webform_filter_xss($questions[1]) : '', 'class' => array('webform-grid-question'));
}
$rows[] = $row;
}
$option_count = count($header) - 1;
$output = theme('table', array('header' => $header, 'rows' => $rows, 'attributes' => array('class' => array('webform-grid', 'webform-grid-' . $option_count))));
$output = theme('table', array('header' => $header, 'rows' => $rows, 'sticky' => $element['#sticky'], 'attributes' => array('class' => array('webform-grid', 'webform-grid-' . $option_count))));
}
else {
$items = array();
foreach (element_children($element) as $key) {
$items[] = ' - ' . $element[$key]['#title'] . ': ' . (isset($element['#grid_options'][$element[$key]['#value']]) ? $element['#grid_options'][$element[$key]['#value']] : '');
foreach (element_children($element) as $question_key) {
$question_element = $element[$question_key];
if (isset($element['#grid_questions'][$question_key])) {
$values = $question_element['#value'];
$values = is_array($values) ? $values : array($values);
foreach ($values as $value_key => $value) {
if (isset($element['#grid_options'][$value])) {
$values[$value_key] = $element['#grid_options'][$value];
}
else {
unset($values[$value_key]);
}
}
$value = implode(', ', $values);
}
else {
$element[$question_key]['#title'] = '';
$value = drupal_render($element[$question_key]);
}
$items[] = ' - ' . _webform_grid_question_header($question_element['#title']) . ': ' . $value;
}
$output = implode("\n", $items);
}
@@ -293,23 +508,30 @@ function theme_webform_display_grid($variables) {
/**
* Implements _webform_analysis_component().
*/
function _webform_analysis_grid($component, $sids = array()) {
function _webform_analysis_grid($component, $sids = array(), $single = FALSE, $join = NULL) {
// Generate the list of options and questions.
$options = _webform_select_options_from_text($component['extra']['options'], TRUE);
$node = node_load($component['nid']);
$questions = _webform_select_options_from_text($component['extra']['questions'], TRUE);
$questions = _webform_select_replace_tokens($questions, $node);
$options = _webform_select_options_from_text($component['extra']['options'], TRUE);
$options = _webform_select_replace_tokens($options, $node);
// Generate a lookup table of results.
$query = db_select('webform_submitted_data', 'wsd')
->fields('wsd', array('no', 'data'))
->condition('nid', $component['nid'])
->condition('cid', $component['cid'])
->condition('data', '', '<>')
->groupBy('no')
->groupBy('data');
$query->addExpression('COUNT(sid)', 'datacount');
->condition('wsd.nid', $component['nid'])
->condition('wsd.cid', $component['cid'])
->condition('wsd.data', '', '<>')
->groupBy('wsd.no')
->groupBy('wsd.data');
$query->addExpression('COUNT(wsd.sid)', 'datacount');
if (count($sids)) {
$query->condition('sid', $sids, 'IN');
$query->condition('wsd.sid', $sids, 'IN');
}
if ($join) {
$query->innerJoin($join, 'ws2_', 'wsd.sid = ws2_.sid');
}
$result = $query->execute();
@@ -324,49 +546,62 @@ function _webform_analysis_grid($component, $sids = array()) {
// Add options as a header row.
foreach ($options as $option) {
$header[] = _webform_filter_xss($option);
$header[] = webform_filter_xss($option);
}
// Add questions as each row.
foreach ($questions as $qkey => $question) {
$row = array(_webform_filter_xss($question));
$row = array(webform_filter_xss($question));
foreach ($options as $okey => $option) {
$row[] = !empty($counts[$qkey][$okey]) ? $counts[$qkey][$okey] : 0;
}
$rows[] = $row;
}
$output = theme('table', array('header' => $header, 'rows' => $rows, 'attributes' => array('class' => array('webform-grid'))));
return array(array(array('data' => $output, 'colspan' => 2)));
// Return return the table unless there are no internal questions in the grid.
if ($rows) {
return array(
'table_header' => $header,
'table_rows' => $rows,
);
}
}
/**
* Implements _webform_table_component().
*/
function _webform_table_grid($component, $value) {
$node = node_load($component['nid']);
$questions = _webform_select_options_from_text($component['extra']['questions'], TRUE);
$questions = _webform_select_replace_tokens($questions, $node);
$options = _webform_select_options_from_text($component['extra']['options'], TRUE);
$options = _webform_select_replace_tokens($options, $node);
$output = '';
// Set the value as a single string.
foreach ($questions as $key => $label) {
if (isset($value[$key]) && isset($options[$value[$key]])) {
$output .= _webform_filter_xss($label) . ': ' . _webform_filter_xss($options[$value[$key]]) . '<br />';
$output .= webform_filter_xss(_webform_grid_question_header($label)) . ': ' . webform_filter_xss($options[$value[$key]]) . '<br />';
}
}
return $output;
// Return output if the grid contains internal questions.
if (count($questions)) {
return $output;
}
}
/**
* Implements _webform_csv_headers_component().
*/
function _webform_csv_headers_grid($component, $export_options) {
$node = node_load($component['nid']);
$items = _webform_select_options_from_text($component['extra']['questions'], TRUE);
$items = _webform_select_replace_tokens($items, $node);
$header = array();
$header[0] = array('');
$header[1] = array($component['name']);
$items = _webform_select_options_from_text($component['extra']['questions'], TRUE);
$header[1] = array($export_options['header_keys'] ? $component['form_key'] : $component['name']);
$count = 0;
foreach ($items as $key => $item) {
// Empty column per sub-field in main header.
@@ -375,7 +610,7 @@ function _webform_csv_headers_grid($component, $export_options) {
$header[1][] = '';
}
// The value for this option.
$header[2][] = $item;
$header[2][] = $export_options['header_keys'] ? $key : _webform_grid_question_header($item);
$count++;
}
@@ -386,8 +621,12 @@ function _webform_csv_headers_grid($component, $export_options) {
* Implements _webform_csv_data_component().
*/
function _webform_csv_data_grid($component, $export_options, $value) {
$node = node_load($component['nid']);
$questions = _webform_select_options_from_text($component['extra']['questions'], TRUE);
$questions = _webform_select_replace_tokens($questions, $node);
$options = _webform_select_options_from_text($component['extra']['options'], TRUE);
$options = _webform_select_replace_tokens($options, $node);
$return = array();
foreach ($questions as $key => $question) {
if (isset($value[$key]) && isset($options[$value[$key]])) {
@@ -400,32 +639,216 @@ function _webform_csv_data_grid($component, $export_options, $value) {
return $return;
}
/**
* A Form API element validate function to check that all choices are unique.
*/
function _webform_edit_grid_unique_validate($element) {
// Grids may contain nested multiple value select components.
// Create a flat array of values.
$values = array();
$element['#value'] = (array) $element['#value'];
array_walk_recursive($element['#value'], function ($a) use (&$values) {
$values[] = $a;
});
$nr_unique = count(array_unique($values));
$nr_values = count($values);
$nr_possible = count($element['#grid_options']);
if (strlen($element['#grid_default']) && isset($element['#grid_options'][$element['#grid_default']])) {
// A default is defined and is one of the options. Don't count default values
// toward uniqueness.
$nr_defaults = count(array_keys($element['#value'], $element['#grid_default']));
if ($nr_defaults) {
$nr_values -= $nr_defaults;
$nr_unique--;
}
}
if ($nr_unique < $nr_values && $nr_unique < $nr_possible) {
// Fewer unique values than values means that at least one value is duplicated.
// Fewer unique values than possible values means that there is a possible choice
// that wasn't used.
form_error($element, t('!title is not allowed to have the same answer for more than one question.', array('!title' => $element['#title'])));
}
}
/**
* Theme function to render a grid component.
*/
function theme_webform_grid($variables) {
$element = $variables['element'];
$right_titles = _webform_grid_right_titles($element);
$rows = array();
$header = array(array('data' => '', 'class' => array('webform-grid-question')));
// Set the header for the table.
foreach ($element['#grid_options'] as $option) {
$header[] = array('data' => _webform_filter_xss($option), 'class' => array('checkbox', 'webform-grid-option'));
}
$header = _webform_grid_header($element, $right_titles);
foreach (element_children($element) as $key) {
$question_element = $element[$key];
$title_element =& $question_element;
if ($question_element['#type'] == 'select_or_other') {
$title_element =& $question_element['select'];
}
$question_titles = explode('|', $title_element['#title'], 2);
// Create a row with the question title.
$row = array(array('data' => _webform_filter_xss($question_element['#title']), 'class' => array('webform-grid-question')));
$required = !empty($question_element['#required']) ? theme('form_required_marker', array('element' => $question_element)) : '';
$row = array(array('data' => t('!title !required', array('!title' => webform_filter_xss($question_titles[0]), '!required' => $required)), 'class' => array('webform-grid-question')));
// Render each radio button in the row.
$radios = form_process_radios($question_element);
foreach (element_children($radios) as $key) {
$radios[$key]['#title'] = $question_element['#title'] . ' - ' . $radios[$key]['#title'];
$radios[$key]['#title_display'] = 'invisible';
$row[] = array('data' => drupal_render($radios[$key]), 'class' => array('checkbox', 'webform-grid-option'));
if ($question_element['#type'] == 'radios' || $question_element['#type'] == 'checkboxes') {
$radios = form_process_radios($question_element);
foreach (element_children($radios) as $key) {
$radio_title = $radios[$key]['#title'];
if (!strlen($radio_title)) {
$row[] = '&nbsp;';
}
else {
$radios[$key]['#title'] = $question_element['#title'] . ' - ' . $radio_title;
$radios[$key]['#title_display'] = 'invisible';
$row[] = array('data' => drupal_render($radios[$key]), 'class' => array('checkbox', 'webform-grid-option'), 'data-label' => array($radio_title));
}
}
}
$rows[] = $row;
else {
$title_element['#title_display'] = 'none';
$row[] = array(
'data' => drupal_render($question_element),
'colspan' => count($element['#grid_options']),
);
}
if ($right_titles) {
$row[] = array('data' => isset($question_titles[1]) ? webform_filter_xss($question_titles[1]) : '', 'class' => array('webform-grid-question'));
}
// Convert the parents array into a string, excluding the "submitted" wrapper.
$nested_level = $question_element['#parents'][0] == 'submitted' ? 1 : 0;
$parents = str_replace('_', '-', implode('--', array_slice($question_element['#parents'], $nested_level)));
$rows[] = array(
'data' => $row,
'class' => empty($question_element['#grid_question'])
? array(
'webform-component',
'webform-component-' . str_replace('_', '-', $question_element['#type']),
'webform-component--' . $parents,
)
: array(),
);
}
$option_count = count($header) - 1;
return theme('table', array('header' => $header, 'rows' => $rows, 'attributes' => array('class' => array('webform-grid', 'webform-grid-' . $option_count))));
return theme('table', array(
'header' => $header,
'rows' => $rows,
'sticky' => $element['#sticky'],
'attributes' => array(
'class' => array(
'webform-grid',
'webform-grid-' . $option_count,
),
),
));
}
/**
* Generate a table header suitable for form or html display.
*
* @param array $element
* The element array.
* @param bool $right_titles
* If TRUE, display a right-side title column.
*
* @return array
* An array of headers.
*/
function _webform_grid_header(array $element, $right_titles) {
$titles = explode('|', $element['#title'], 2);
$header = array(
array(
'data' => _webform_grid_header_title($element, $titles[0]),
'class' => array('webform-grid-question'),
),
);
foreach ($element['#grid_options'] as $option) {
$header[] = array(
'data' => webform_filter_xss($option),
'class' => array('checkbox', 'webform-grid-option'),
'scope' => 'col',
);
}
if ($right_titles) {
$header[] = array(
'data' => _webform_grid_header_title($element, isset($titles[1]) ? $titles[1] : ''),
'class' => array('webform-grid-question'),
);
}
return $header;
}
/**
* Create internal component title for table header, if any.
*/
function _webform_grid_header_title($element, $title) {
$header_title = '';
if ($element['#title_display'] == 'internal') {
$variables = array('element' => $element);
$variables['element']['#title_display'] = 'before';
$variables['element']['#title'] = $title;
$header_title = theme('form_element_label', $variables);
}
return $header_title;
}
/**
* Determine if a right-side title column has been specified.
*/
function _webform_grid_right_titles($element) {
if ($element['#title_display'] == 'internal' && substr_count($element['#title'], '|')) {
return TRUE;
}
foreach ($element['#grid_questions'] as $question_key => $question) {
if (substr_count($question, '|')) {
return TRUE;
}
}
return FALSE;
}
/**
* Create a question header for left, right or left/right question headers.
*/
function _webform_grid_question_header($text) {
return implode('/', array_filter(explode('|', $text)));
}
/**
* Element validation for Webform grid fields.
*
* Requires a component implementation because the default required validation
* passes when at least one value is supplied, rather than every value. This
* makes the server validation match the browser validation.
*/
function webform_validate_grid($element, $form_state) {
if ($element['#required']) {
$values = $form_state['input'];
foreach ($element['#parents'] as $key) {
$values = isset($values[$key]) ? $values[$key] : $values;
}
// Remove any values that aren't grid question (i.e. nested components).
$grid_questions = $element['#grid_questions'];
$values = array_intersect_key($values, $grid_questions);
// Remove any unanswered grid questions.
$answers = array_filter($values, function ($item) {
return !is_null($item);
});
// Give required errors for any questions that aren't answered.
foreach (array_diff_key($grid_questions, $answers) as $question_key => $question) {
// If the question is still required (e.g not modified by an after_build
// function), give the required error.
if (!empty($element[$question_key]['#required'])) {
form_error($element[$question_key], t('!question field within !name is required.', array('!question' => $question, '!name' => $element['#title'])));
}
}
}
}

View File

@@ -18,6 +18,7 @@ function _webform_defaults_hidden() {
'extra' => array(
'private' => FALSE,
'hidden_type' => 'value',
'analysis' => FALSE,
),
);
}
@@ -43,7 +44,7 @@ function _webform_edit_hidden($component) {
'#type' => 'textarea',
'#title' => t('Default value'),
'#default_value' => $component['value'],
'#description' => t('The default value of the field.') . theme('webform_token_help'),
'#description' => t('The default value of the field.') . ' ' . theme('webform_token_help'),
'#cols' => 60,
'#rows' => 5,
'#weight' => 0,
@@ -67,20 +68,16 @@ function _webform_edit_hidden($component) {
/**
* Implements _webform_render_component().
*/
function _webform_render_hidden($component, $value = NULL, $filter = TRUE) {
function _webform_render_hidden($component, $value = NULL, $filter = TRUE, $submission = NULL) {
$node = isset($component['nid']) ? node_load($component['nid']) : NULL;
// Set filtering options for "value" types, which are not displayed to the
// end user so they do not need to be sanitized.
$strict = $component['extra']['hidden_type'] != 'value';
$allow_anonymous = $component['extra']['hidden_type'] == 'value';
$default_value = $filter ? _webform_filter_values($component['value'], $node, NULL, NULL, $strict, $allow_anonymous) : $component['value'];
$default_value = $filter ? webform_replace_tokens($component['value'], $node) : $component['value'];
if (isset($value[0])) {
$default_value = $value[0];
}
$element = array(
'#title' => $filter ? _webform_filter_xss($component['name']) : $component['name'],
'#title' => $filter ? NULL : $component['name'],
'#weight' => $component['weight'],
'#translatable' => array('title'),
);
@@ -92,6 +89,12 @@ function _webform_render_hidden($component, $value = NULL, $filter = TRUE) {
else {
$element['#type'] = 'hidden';
$element['#default_value'] = $default_value;
// Same-page conditionals depend on the wrapper around elements for getting
// values. Wrap, but hide, the wrapper around hidden elements.
$element['#theme_wrappers'] = array('webform_element');
$element['#wrapper_attributes']['class'] = array();
$element['#wrapper_attributes']['style'] = array('display: none');
}
return $element;
@@ -100,7 +103,7 @@ function _webform_render_hidden($component, $value = NULL, $filter = TRUE) {
/**
* Implements _webform_display_component().
*/
function _webform_display_hidden($component, $value, $format = 'html') {
function _webform_display_hidden($component, $value, $format = 'html', $submission = array()) {
$element = array(
'#title' => $component['name'],
'#markup' => isset($value[0]) ? $value[0] : NULL,
@@ -114,6 +117,9 @@ function _webform_display_hidden($component, $value, $format = 'html') {
return $element;
}
/**
* Theme callback.
*/
function theme_webform_display_hidden($variables) {
$element = $variables['element'];
@@ -123,14 +129,18 @@ function theme_webform_display_hidden($variables) {
/**
* Implements _webform_analysis_component().
*/
function _webform_analysis_hidden($component, $sids = array()) {
function _webform_analysis_hidden($component, $sids = array(), $single = FALSE, $join = NULL) {
$query = db_select('webform_submitted_data', 'wsd', array('fetch' => PDO::FETCH_ASSOC))
->fields('wsd', array('no', 'data'))
->condition('nid', $component['nid'])
->condition('cid', $component['cid']);
->condition('wsd.nid', $component['nid'])
->condition('wsd.cid', $component['cid']);
if (count($sids)) {
$query->condition('sid', $sids, 'IN');
$query->condition('wsd.sid', $sids, 'IN');
}
if ($join) {
$query->innerJoin($join, 'ws2_', 'wsd.sid = ws2_.sid');
}
$nonblanks = 0;
@@ -146,11 +156,17 @@ function _webform_analysis_hidden($component, $sids = array()) {
$submissions++;
}
$rows[0] = array( t('Empty'), ($submissions - $nonblanks));
$rows[1] = array( t('Non-empty'), $nonblanks);
$rows[2] = array( t('Average submission length in words (ex blanks)'),
($nonblanks !=0 ? number_format($wordcount/$nonblanks, 2) : '0'));
return $rows;
$rows[0] = array(t('Empty'), ($submissions - $nonblanks));
$rows[1] = array(t('Non-empty'), $nonblanks);
$other[0] = array(
t('Average submission length in words (ex blanks)'),
$nonblanks != 0 ? number_format($wordcount / $nonblanks, 2) : '0',
);
return array(
'table_rows' => $rows,
'other_data' => $other,
);
}
/**
@@ -161,13 +177,21 @@ function _webform_table_hidden($component, $value) {
}
/**
* Implements _webform_csv_data_component().
* Implements _webform_action_set_component().
*/
function _webform_action_set_hidden($component, &$element, &$form_state, $value) {
$element['#value'] = $value;
form_set_value($element, $value, $form_state);
}
/**
* Implements _webform_csv_headers_component().
*/
function _webform_csv_headers_hidden($component, $export_options) {
$header = array();
$header[0] = '';
$header[1] = '';
$header[2] = $component['name'];
$header[2] = $export_options['header_keys'] ? $component['form_key'] : $component['name'];
return $header;
}

View File

@@ -18,6 +18,19 @@ function _webform_defaults_markup() {
'extra' => array(
'format' => NULL,
'private' => FALSE,
'display_on' => 'form',
),
);
}
/**
* Implements _webform_theme_component().
*/
function _webform_theme_markup() {
return array(
'webform_display_markup' => array(
'render element' => 'element',
'file' => 'components/markup.inc',
),
);
}
@@ -31,12 +44,25 @@ function _webform_edit_markup($component) {
'#type' => 'text_format',
'#title' => t('Value'),
'#default_value' => $component['value'],
'#description' => t('Markup allows you to enter custom HTML or PHP logic into your form.') . theme('webform_token_help'),
'#description' => t('Markup allows you to enter custom HTML into your form.') . ' ' . theme('webform_token_help', array('groups' => array('node', 'submission'))),
'#weight' => -1,
'#format' => $component['extra']['format'],
'#element_validate' => array('_webform_edit_markup_validate'),
);
$form['display']['display_on'] = array(
'#type' => 'select',
'#title' => t('Display on'),
'#default_value' => $component['extra']['display_on'],
'#options' => array(
'form' => t('form only'),
'display' => t('viewed submission only'),
'both' => t('both form and viewed submission'),
),
'#weight' => 1,
'#parents' => array('extra', 'display_on'),
);
return $form;
}
@@ -53,25 +79,98 @@ function _webform_edit_markup_validate($form, &$form_state) {
/**
* Implements _webform_render_component().
*/
function _webform_render_markup($component, $value = NULL, $filter = TRUE) {
$node = isset($component['nid']) ? node_load($component['nid']) : NULL;
function _webform_render_markup($component, $value = NULL, $filter = TRUE, $submission = NULL) {
$element = array(
'#type' => 'markup',
'#title' => $filter ? NULL : $component['name'],
'#weight' => $component['weight'],
'#markup' => $filter ? _webform_filter_values(check_markup($component['value'], $component['extra']['format'], '', TRUE), $node, NULL, NULL, FALSE) : $component['value'],
'#markup' => $component['value'],
'#format' => $component['extra']['format'],
'#theme_wrappers' => array('webform_element'),
'#translatable' => array('title', 'markup'),
'#access' => $component['extra']['display_on'] != 'display',
'#webform_nid' => isset($component['nid']) ? $component['nid'] : NULL,
'#webform_submission' => $submission,
'#webform_format' => $component['extra']['format'],
);
if ($filter) {
$element['#after_build'] = array('_webform_render_markup_after_build');
}
return $element;
}
/**
* Helper function to replace tokens in markup component.
*
* Required to have access to current form values from $form_state.
*/
function _webform_render_markup_after_build($form_element, &$form_state) {
global $user;
$node = node_load($form_element['#webform_nid']);
$submission = NULL;
// Convert existing submission data to an in-progress submission.
$form_state_for_submission = $form_state;
$form_state_for_submission['values']['submitted'] = $form_state['#conditional_values'];
module_load_include('inc', 'webform', 'includes/webform.submissions');
$submission = webform_submission_create($node, $user, $form_state_for_submission, TRUE, $form_element['#webform_submission']);
// Replace tokens using the current or generated submission.
$value = webform_replace_tokens($form_element['#markup'], $node, $submission, NULL, $form_element['#webform_format']);
// If the markup value has been set by a conditional, display that value.
$component = $form_element['#webform_component'];
if ($node) {
$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(isset($submission) ? $submission->data : array());
}
$conditional_value = $sorter->componentMarkup($component['cid'], $component['page_num']);
if (isset($conditional_value)) {
// Provide original value, should conditional logic no longer set the
// value.
$form_element['#wrapper_attributes']['data-webform-markup'] = $value;
if (is_string($conditional_value)) {
$value = check_markup($conditional_value, $component['extra']['format']);
}
}
}
$form_element['#markup'] = $value;
return $form_element;
}
/**
* Implements _webform_display_component().
*/
function _webform_display_markup($component, $value, $format = 'html') {
return array();
function _webform_display_markup($component, $value, $format = 'html', $submission = array()) {
$node = isset($component['nid']) ? node_load($component['nid']) : NULL;
$value = webform_replace_tokens($component['value'], $node, $submission, NULL, $component['extra']['format']);
// If the markup value has been set by a conditional, display that value.
if ($node && is_string($conditional_value = webform_get_conditional_sorter($node)->componentMarkup($component['cid'], $component['page_num']))) {
$value = check_markup($conditional_value, $component['extra']['format']);
}
return array(
'#weight' => $component['weight'],
'#theme' => 'webform_display_markup',
'#format' => $format,
'#value' => $value,
'#translatable' => array('title'),
'#access' => $component['extra']['display_on'] != 'form',
);
}
/**
* Format the output of data for this component.
*/
function theme_webform_display_markup($variables) {
$element = $variables['element'];
return $element['#access'] ? $element['#value'] : '';
}

View File

@@ -15,7 +15,7 @@ function _webform_defaults_number() {
'pid' => 0,
'weight' => 0,
'value' => '',
'mandatory' => 0,
'required' => 0,
'extra' => array(
'type' => 'textfield',
'field_prefix' => '',
@@ -24,8 +24,11 @@ function _webform_defaults_number() {
'unique' => 0,
'title_display' => 0,
'description' => '',
'description_above' => FALSE,
'placeholder' => '',
'attributes' => array(),
'private' => FALSE,
'analysis' => FALSE,
'min' => '',
'max' => '',
'step' => '',
@@ -54,6 +57,16 @@ function _webform_theme_number() {
);
}
/**
* Fix the view field(s) that are automatically generated for number components.
*/
function _webform_view_field_number($component, $fields) {
foreach ($fields as &$field) {
$field['webform_datatype'] = 'number';
}
return $fields;
}
/**
* Implements _webform_edit_component().
*/
@@ -63,7 +76,7 @@ function _webform_edit_number($component) {
'#type' => 'textfield',
'#title' => t('Default value'),
'#default_value' => $component['value'],
'#description' => t('The default value of the field.') . theme('webform_token_help'),
'#description' => t('The default value of the field.') . ' ' . theme('webform_token_help'),
'#size' => 60,
'#maxlength' => 1024,
'#weight' => 0,
@@ -80,6 +93,14 @@ function _webform_edit_number($component) {
'#weight' => -1,
'#parents' => array('extra', 'type'),
);
$form['display']['placeholder'] = array(
'#type' => 'textfield',
'#title' => t('Placeholder'),
'#default_value' => $component['extra']['placeholder'],
'#description' => t('The placeholder will be shown in the field until the user starts entering a value.'),
'#weight' => 1,
'#parents' => array('extra', 'placeholder'),
);
$form['display']['field_prefix'] = array(
'#type' => 'textfield',
'#title' => t('Prefix text placed to the left of the field'),
@@ -104,7 +125,7 @@ function _webform_edit_number($component) {
'#type' => 'checkbox',
'#title' => t('Disabled'),
'#return_value' => 1,
'#description' => t('Make this field non-editable. Useful for setting an unchangeable default value.'),
'#description' => t('Make this field non-editable. Useful for displaying default value. Changeable via JavaScript or developer tools.'),
'#weight' => 11,
'#default_value' => $component['extra']['disabled'],
'#parents' => array('extra', 'disabled'),
@@ -158,7 +179,7 @@ function _webform_edit_number($component) {
'#type' => 'checkbox',
'#title' => t('Integer'),
'#return_value' => 1,
'#description' => t('Permit only integer values as input. e.g. 12.34 would be invalid.'),
'#description' => t('Permit only integer values as input. For example, 12.34 would be invalid.'),
'#weight' => 1.5,
'#default_value' => $component['extra']['integer'],
'#parents' => array('extra', 'integer'),
@@ -167,7 +188,7 @@ function _webform_edit_number($component) {
'#type' => 'textfield',
'#title' => t('Minimum'),
'#default_value' => $component['extra']['min'],
'#description' => t('Minimum numeric value. e.g. 0 would ensure positive numbers.'),
'#description' => t('Minimum numeric value. For example, 0 would ensure positive numbers.'),
'#size' => 5,
'#maxlength' => 10,
'#weight' => 2.1,
@@ -189,7 +210,7 @@ function _webform_edit_number($component) {
'#type' => 'textfield',
'#title' => t('Step'),
'#default_value' => $component['extra']['step'],
'#description' => t('Limit options to a specific increment. e.g. a step of "5" would allow values 5, 10, 15, etc.'),
'#description' => t('Limit options to a specific increment. For example, a step of "5" would allow values 5, 10, 15, etc.'),
'#size' => 5,
'#maxlength' => 10,
'#weight' => 3,
@@ -225,11 +246,16 @@ function theme_webform_number($variables) {
// This IF statement is mostly in place to allow our tests to set type="text"
// because SimpleTest does not support type="number".
if (!isset($element['#attributes']['type'])) {
$element['#attributes']['type'] = 'number';
// HTML5 number fields are no long used pending better browser support.
// See issues #2290029, #2202905.
// @code
// $element['#attributes']['type'] = 'number';
// @endcode
$element['#attributes']['type'] = 'text';
}
// Step property *must* be a full number with 0 prefix if a decimal.
if (!empty($element['#step']) && !is_int($element['#step'] * 1)) {
if (!empty($element['#step']) && filter_var((float) $element['#step'], FILTER_VALIDATE_INT) === FALSE) {
$decimals = strlen($element['#step']) - strrpos($element['#step'], '.') - 1;
$element['#step'] = sprintf('%1.' . $decimals . 'F', $element['#step']);
}
@@ -257,18 +283,18 @@ function theme_webform_number($variables) {
/**
* Implements _webform_render_component().
*/
function _webform_render_number($component, $value = NULL, $filter = TRUE) {
function _webform_render_number($component, $value = NULL, $filter = TRUE, $submission = NULL) {
$node = isset($component['nid']) ? node_load($component['nid']) : NULL;
$element = array(
'#title' => $filter ? _webform_filter_xss($component['name']) : $component['name'],
'#title' => $filter ? webform_filter_xss($component['name']) : $component['name'],
'#title_display' => $component['extra']['title_display'] ? $component['extra']['title_display'] : 'before',
'#default_value' => $filter ? _webform_filter_values($component['value'], $node, NULL, NULL, FALSE) : $component['value'],
'#required' => $component['mandatory'],
'#default_value' => $filter ? webform_replace_tokens($component['value'], $node) : $component['value'],
'#required' => $component['required'],
'#weight' => $component['weight'],
'#field_prefix' => empty($component['extra']['field_prefix']) ? NULL : ($filter ? _webform_filter_xss($component['extra']['field_prefix']) : $component['extra']['field_prefix']),
'#field_suffix' => empty($component['extra']['field_suffix']) ? NULL : ($filter ? _webform_filter_xss($component['extra']['field_suffix']) : $component['extra']['field_suffix']),
'#description' => $filter ? _webform_filter_descriptions($component['extra']['description'], $node) : $component['extra']['description'],
'#field_prefix' => empty($component['extra']['field_prefix']) ? NULL : ($filter ? webform_filter_xss($component['extra']['field_prefix']) : $component['extra']['field_prefix']),
'#field_suffix' => empty($component['extra']['field_suffix']) ? NULL : ($filter ? webform_filter_xss($component['extra']['field_suffix']) : $component['extra']['field_suffix']),
'#description' => $filter ? webform_filter_descriptions($component['extra']['description'], $node) : $component['extra']['description'],
'#attributes' => $component['extra']['attributes'],
'#element_validate' => array('_webform_validate_number'),
'#theme_wrappers' => array('webform_element'),
@@ -282,6 +308,14 @@ function _webform_render_number($component, $value = NULL, $filter = TRUE) {
'#translatable' => array('title', 'description', 'field_prefix', 'field_suffix'),
);
if ($component['required']) {
$element['#attributes']['required'] = 'required';
}
if ($component['extra']['placeholder']) {
$element['#attributes']['placeholder'] = $component['extra']['placeholder'];
}
// Set the decimal count to zero for integers.
if ($element['#integer'] && $element['#decimals'] === '') {
$element['#decimals'] = 0;
@@ -295,7 +329,7 @@ function _webform_render_number($component, $value = NULL, $filter = TRUE) {
}
// Ensure #step starts with a zero if a decimal.
if (!is_int($element['#step'] * 1)) {
if (filter_var((float) $element['#step'], FILTER_VALIDATE_INT) === FALSE) {
$decimals = strlen($element['#step']) - strrpos($element['#step'], '.') - 1;
$element['#step'] = sprintf('%1.' . $decimals . 'F', $element['#step']);
}
@@ -307,7 +341,7 @@ function _webform_render_number($component, $value = NULL, $filter = TRUE) {
// Set the size property based on #max, to ensure consistent behavior for
// browsers that do not support type = number.
if ($element['#max']) {
$element['#size'] = strlen($element['#max']) + 1;
$element['#size'] = strlen($element['#max']) + 1;
}
}
else {
@@ -358,10 +392,11 @@ function _webform_render_number($component, $value = NULL, $filter = TRUE) {
/**
* Implements _webform_display_component().
*/
function _webform_display_number($component, $value, $format = 'html') {
function _webform_display_number($component, $value, $format = 'html', $submission = array()) {
$empty = !isset($value[0]) || $value[0] === '';
return array(
'#title' => $component['name'],
'#title_display' => $component['extra']['title_display'] ? $component['extra']['title_display'] : 'before',
'#weight' => $component['weight'],
'#theme' => 'webform_display_number',
'#theme_wrappers' => $format == 'html' ? array('webform_element') : array('webform_element_text'),
@@ -387,16 +422,20 @@ function theme_webform_display_number($variables) {
/**
* Implements _webform_analysis_component().
*/
function _webform_analysis_number($component, $sids = array(), $single = FALSE) {
function _webform_analysis_number($component, $sids = array(), $single = FALSE, $join = NULL) {
$advanced_stats = $single;
$query = db_select('webform_submitted_data', 'wsd', array('fetch' => PDO::FETCH_ASSOC))
->fields('wsd', array('data'))
->condition('nid', $component['nid'])
->condition('cid', $component['cid']);
->condition('wsd.nid', $component['nid'])
->condition('wsd.cid', $component['cid']);
if (count($sids)) {
$query->condition('sid', $sids, 'IN');
$query->condition('wsd.sid', $sids, 'IN');
}
if ($join) {
$query->innerJoin($join, 'ws2_', 'wsd.sid = ws2_.sid');
}
$population = array();
@@ -408,9 +447,9 @@ function _webform_analysis_number($component, $sids = array(), $single = FALSE)
$result = $query->execute();
foreach ($result as $data) {
$value = trim($data['data']);
$number = (float)$value;
$non_empty += (integer)($value !== '');
$non_zero += (integer)($number != 0.0);
$number = (float) $value;
$non_empty += (integer) ($value !== '');
$non_zero += (integer) ($number != 0.0);
$sum += $number;
$population[] = $number;
$submissions++;
@@ -439,20 +478,21 @@ function _webform_analysis_number($component, $sids = array(), $single = FALSE)
$average = _webform_number_format($component, $average);
$sum = _webform_number_format($component, $sum);
$rows[0] = array(t('Zero/blank'), ($submissions - $non_zero));
$rows[1] = array(t('User entered value'), $non_empty);
$rows[2] = array(t('Sum') . ($advanced_stats ? ' (&Sigma;)' : ''), $sum);
$rows[3] = array($average_title, $average);
$rows[] = array(t('Zero/blank'), ($submissions - $non_zero));
$rows[] = array(t('User entered value'), $non_empty);
$other[] = array(t('Sum') . ($advanced_stats ? ' (&Sigma;)' : ''), $sum);
$other[] = array($average_title, $average);
if (!$advanced_stats && $sum != 0) {
$rows[4] = array('', l(t('More stats »'), 'node/' . $component['nid'] . '/webform-results/analysis/' . $component['cid']));
$other[] = l(t('More stats »'), 'node/' . $component['nid'] . '/webform-results/analysis/' . $component['cid']);
}
// Normal distribution information.
if ($advanced_stats && $population_count && $sum != 0) {
// Standard deviation.
$stddev = 0;
foreach($population as $value) {
foreach ($population as $value) {
// Obtain the total of squared variances.
$stddev += pow(($value - $average), 2);
}
@@ -465,7 +505,10 @@ function _webform_analysis_number($component, $sids = array(), $single = FALSE)
// Skip the rest of the distribution rows if standard deviation is 0.
if (empty($stddev)) {
return $rows;
return array(
'table_rows' => $rows,
'other_data' => $other,
);
}
// Build normal distribution table rows.
@@ -494,21 +537,21 @@ function _webform_analysis_number($component, $sids = array(), $single = FALSE)
$stddev = _webform_number_format($component, $stddev);
$low = _webform_number_format($component, $population[0]);
$high = _webform_number_format($component, end($population));
foreach($limit as $key => $value) {
foreach ($limit as $key => $value) {
$limit[$key] = _webform_number_format($component, $value);
}
// Column headings (override potential theme uppercase, e.g. Seven in D7).
// Column headings (override potential theme uppercase, for example, Seven in D7).
$header = array(
t('Normal Distribution'),
array('data' => '-4' . $sigma, 'style' => 'text-transform: lowercase;',),
array('data' => '-3' . $sigma, 'style' => 'text-transform: lowercase;',),
array('data' => '-2' . $sigma, 'style' => 'text-transform: lowercase;',),
array('data' => '-1' . $sigma, 'style' => 'text-transform: lowercase;',),
array('data' => '+1' . $sigma, 'style' => 'text-transform: lowercase;',),
array('data' => '+2' . $sigma, 'style' => 'text-transform: lowercase;',),
array('data' => '+3' . $sigma, 'style' => 'text-transform: lowercase;',),
array('data' => '+4' . $sigma, 'style' => 'text-transform: lowercase;',),
array('data' => '-4' . $sigma, 'style' => 'text-transform: lowercase;'),
array('data' => '-3' . $sigma, 'style' => 'text-transform: lowercase;'),
array('data' => '-2' . $sigma, 'style' => 'text-transform: lowercase;'),
array('data' => '-1' . $sigma, 'style' => 'text-transform: lowercase;'),
array('data' => '+1' . $sigma, 'style' => 'text-transform: lowercase;'),
array('data' => '+2' . $sigma, 'style' => 'text-transform: lowercase;'),
array('data' => '+3' . $sigma, 'style' => 'text-transform: lowercase;'),
array('data' => '+4' . $sigma, 'style' => 'text-transform: lowercase;'),
);
// Insert row labels.
@@ -516,14 +559,17 @@ function _webform_analysis_number($component, $sids = array(), $single = FALSE)
array_unshift($count, t('Count'));
array_unshift($percent, t('% of !description', array('!description' => $description)));
$output = theme('table', array('header' => $header, 'rows' => array($limit, $count, $percent)));
$normal_distribution = theme('table', array('header' => $header, 'rows' => array($limit, $count, $percent), 'sticky' => FALSE));
$rows[4] = array(t('Range'), t('!low to !high', array('!low' => $low, '!high' => $high)));
$rows[5] = array(t('Standard deviation (!sigma)', array('!sigma' => $sigma)), $stddev);
$rows[6] = array(array('data' => $output, 'colspan' => 2));
$other[] = array(t('Range'), t('!low to !high', array('!low' => $low, '!high' => $high)));
$other[] = array(t('Standard deviation (!sigma)', array('!sigma' => $sigma)), $stddev);
$other[] = $normal_distribution;
}
return $rows;
return array(
'table_rows' => $rows,
'other_data' => $other,
);
}
/**
@@ -533,6 +579,14 @@ function _webform_table_number($component, $value) {
return isset($value[0]) ? _webform_number_format($component, $value[0]) : '';
}
/**
* Implements _webform_action_set_component().
*/
function _webform_action_set_number($component, &$element, &$form_state, $value) {
$element['#value'] = $value;
form_set_value($element, $value, $form_state);
}
/**
* Implements _webform_csv_headers_component().
*/
@@ -540,7 +594,7 @@ function _webform_csv_headers_number($component, $export_options) {
$header = array();
$header[0] = '';
$header[1] = '';
$header[2] = $component['name'];
$header[2] = $export_options['header_keys'] ? $component['form_key'] : $component['name'];
return $header;
}
@@ -555,15 +609,14 @@ function _webform_csv_data_number($component, $export_options, $value) {
}
/**
* A Drupal Form API Validation function. Validates the entered values from
* number components on the client-side form.
* A Drupal Form API Validation function.
*
* @param $element
* Validates the entered values from number components on the client-side form.
*
* @param array $element
* The form element. May either be a select or a webform_number element.
* @param $form_state
* @param array $form_state
* The full form state for the webform.
* @return
* None. Calls a form_set_error if the number is not valid.
*/
function _webform_validate_number($element, &$form_state) {
// Trim spaces for basic cleanup.
@@ -573,7 +626,7 @@ function _webform_validate_number($element, &$form_state) {
if ($value != '') {
// First check that the entered value matches the expected value.
if (!webform_number_format_match($value, $element['#point'], $element['#separator'])) {
form_error($element, t('%name field value must format numbers as "@example".', array('%name' => $element['#title'], '@example' => webform_number_format(12345.6789, $element['#decimals'], $element['#point'], $element['#separator']))));
form_error($element, t('!name field value must format numbers as "@example".', array('!name' => $element['#title'], '@example' => webform_number_format(12345.6789, $element['#decimals'], $element['#point'], $element['#separator']))));
return;
}
@@ -592,24 +645,24 @@ function _webform_validate_number($element, &$form_state) {
$max = $element['#min'];
}
if ($numeric_value > $max || $numeric_value < $min) {
form_error($element, t('%name field value of @value should be in the range @min to @max.', array('%name' => $element['#title'], '@value' => $value, '@min' => $element['#min'], '@max' => $element['#max'])));
form_error($element, t('!name field value of @value should be in the range @min to @max.', array('!name' => $element['#title'], '@value' => $value, '@min' => $element['#min'], '@max' => $element['#max'])));
}
}
elseif ($element['#max'] != '' && $numeric_value > $element['#max']) {
form_error($element, t('%name field value must be less than @max.', array('%name' => $element['#title'], '@max' => $element['#max'])));
form_error($element, t('!name field value must be less than @max.', array('!name' => $element['#title'], '@max' => $element['#max'])));
}
elseif ($element['#min'] != '' && $numeric_value < $element['#min']) {
form_error($element, t('%name field value must be greater than @min.', array('%name' => $element['#title'], '@min' => $element['#min'])));
form_error($element, t('!name field value must be greater than @min.', array('!name' => $element['#title'], '@min' => $element['#min'])));
}
// Integer test.
if ($element['#integer'] && !is_int($numeric_value * 1)) {
form_error($element, t('%name field value of @value must be an integer.', array('%name' => $element['#title'], '@value' => $value)));
if ($element['#integer'] && filter_var((float) $numeric_value, FILTER_VALIDATE_INT) === FALSE) {
form_error($element, t('!name field value of @value must be an integer.', array('!name' => $element['#title'], '@value' => $value)));
}
// Step test.
$starting_number = $element['#min'] ? $element['#min'] : 0;
if ($element['#step'] != 0 && webform_modulo($numeric_value - $starting_number, $element['#step']) != 0) {
if ($element['#step'] != 0 && webform_modulo($numeric_value - $starting_number, $element['#step']) != 0.0) {
$samples = array(
$starting_number,
$starting_number + ($element['#step'] * 1),
@@ -617,15 +670,15 @@ function _webform_validate_number($element, &$form_state) {
$starting_number + ($element['#step'] * 3),
);
if ($starting_number) {
form_error($element, t('%name field value must be @start plus a multiple of @step. i.e. @samples, etc.', array('%name' => $element['#title'], '@start' => $element['#min'], '@step' => $element['#step'], '@samples' => implode(', ', $samples))));
form_error($element, t('!name field value must be @start plus a multiple of @step. i.e. @samples, etc.', array('!name' => $element['#title'], '@start' => $element['#min'], '@step' => $element['#step'], '@samples' => implode(', ', $samples))));
}
else {
form_error($element, t('%name field value must be a multiple of @step. i.e. @samples, etc.', array('%name' => $element['#title'], '@step' => $element['#step'], '@samples' => implode(', ', $samples))));
form_error($element, t('!name field value must be a multiple of @step. i.e. @samples, etc.', array('!name' => $element['#title'], '@step' => $element['#step'], '@samples' => implode(', ', $samples))));
}
}
}
else {
form_error($element, t('%name field value of @value must be numeric.', array('%name' => $element['#title'], '@value' => $value)));
form_error($element, t('!name field value of @value must be numeric.', array('!name' => $element['#title'], '@value' => $value)));
}
}
}
@@ -667,11 +720,12 @@ function _webform_edit_number_validate($element, &$form_state) {
if (!is_numeric($values['min'])) {
form_error($element, t('Minimum must be numeric.'));
}
if ($values['integer'] && !is_int($values['min'] * 1)) {
if ($values['integer'] && filter_var((float) $values['min'], FILTER_VALIDATE_INT) === FALSE) {
form_error($element, t('Minimum must have an integer value.'));
}
}
break;
case 'max':
if ($values['max'] == '') {
if (isset($values['type']) && $values['type'] === 'select') {
@@ -682,18 +736,19 @@ function _webform_edit_number_validate($element, &$form_state) {
if (!is_numeric($values['max'])) {
form_error($element, t('Maximum must be numeric.'));
}
if ($values['integer'] && !is_int($values['max'] * 1)) {
if ($values['integer'] && filter_var((float) $values['max'], FILTER_VALIDATE_INT) === FALSE) {
form_error($element, t('Maximum must have an integer value.'));
}
}
break;
case 'step':
if ($values['step'] !== '') {
if (!is_numeric($values['step'])) {
form_error($element, t('Step must be numeric.'));
}
else {
if ($values['integer'] && !is_int($values['step'] * 1)) {
if ($values['integer'] && filter_var((float) $values['step'], FILTER_VALIDATE_INT) === FALSE) {
form_error($element, t('Step must have an integer value.'));
}
}
@@ -727,13 +782,14 @@ function _webform_number_select_options($component) {
$options[$f . ''] = $f . '';
}
// TODO: HTML5 browsers apparently do not include the max value if it does
// @todo: HTML5 browsers apparently do not include the max value if it does
// not line up with step. Restore this if needed in the future.
// Add end limit if it's been skipped due to step.
//if (end($options) != $max) {
// $options[$f] = $max;
//}
// @code
// if (end($options) != $max) {
// $options[$f] = $max;
// }
// @endcode
if ($flipped) {
$options = array_reverse($options, TRUE);
}
@@ -759,10 +815,38 @@ function _webform_number_format($component, $value) {
* This function allows the thousands separator to be optional, but decimal
* points must be in the right location.
*
* A valid number is:
* 1. optional minus sign.
* 2. optional space.
* 3. the rest of the string can't be just a decimal or blank.
* 4. optional integer portion, with thousands separators.
* 5. optional decimal portion, starting is a decimal separator.
* Don't use preg_quote because a space is a valid thousands separator and
* needs quoting for the 'x' option to preg_match.
*
* Based on http://stackoverflow.com/questions/5917082/regular-expression-to-match-numbers-with-or-without-commas-and-decimals-in-text.
*/
function webform_number_format_match($value, $point, $separator) {
return preg_match('/^(-? ?[1-9](?:\d{0,2})(?:' . ($separator ? (preg_quote($separator, '/') . '?') : '') . '\d{3})*(?:' . preg_quote($point, '/') . '\d*[0-9])?|0?' . preg_quote($point, '/') . '\d*[1-9]|0)$/', $value);
$thousands = $separator ? "\\$separator?" : '';
$decimal = "\\$point";
return preg_match("/
^ # Start of string
-? # Optional minus sign
\ ? # Optional space
(?!\.?$) # Assert looking ahead, not just a decimal or nothing
(?: # Interger portion (non-grouping)
\d{1,3} # 1 to 3 digits
(?: # Thousands group(s)
$thousands # Optional thousands separator
\d{2,3} # 2 or 3 digits. Some countries use groups of 2 sometimes
)* # 0 or more of these thousands groups
)? # End of optional integer portion
(?: # Decimal portion (non-grouping)
$decimal # Decimal point
\d* # 0 or more digits
)? # End of optional decimal portion
$
/x", $value);
}
/**
@@ -780,7 +864,7 @@ function webform_number_format($value, $decimals = NULL, $point = '.', $separato
// If no decimal places are specified, do a best guess length of decimals.
if (is_null($decimals) || $decimals === '') {
// If it's an integer, no decimals needed.
if (is_int(($value . '') * 1)) {
if (filter_var((float) $value, FILTER_VALIDATE_INT) !== FALSE) {
$decimals = 0;
}
else {
@@ -799,8 +883,11 @@ function webform_number_format($value, $decimals = NULL, $point = '.', $separato
*
* @param string $value
* The string value to be standardized into a numeric string.
* @param $point
* @param string $point
* The point separator between the whole number and the decimals.
*
* @return string
* The converted number.
*/
function webform_number_standardize($value, $point) {
// For simplicity, strip everything that's not the decimal point.
@@ -816,5 +903,58 @@ function webform_number_standardize($value, $point) {
* See https://drupal.org/node/1601968.
*/
function webform_modulo($a, $b) {
return $a - $b * (($b < 0) ? ceil($a / $b) : floor($a / $b));
}
$modulo = $a - $b * (($b < 0) ? ceil($a / $b) : floor($a / $b));
if (webform_compare_floats($modulo, 0.0) == 0 || webform_compare_floats($modulo, $b) == 0) {
$modulo = 0.0;
}
return $modulo;
}
/**
* Compare two floats.
*
* See @link http://php.net/manual/en/language.types.float.php @endlink.
*
* Comparison of floating point numbers for equality is surprisingly difficult,
* as evidenced by the references below. The simple test in this function works
* for numbers that are relatively close to 1E1. For very small numbers, it will
* show false equality. For very large numbers, it will show false inequality.
* Better implementations are hidered by the absense of PHP platform-specific
* floating point constants to properly set the minimum absolute and relative
* error in PHP.
*
* The use case for webform conditionals excludes very small or very large
* numeric comparisons.
*
* See @link http://www.cygnus-software.com/papers/comparingfloats/comparingfloats.htm @endlink
* See @link http://floating-point-gui.de/errors/comparison/ @endlink
* See @link http://en.wikipedia.org/wiki/IEEE_754-1985#Denormalized_numbers @endlink
*
* @param float $number_1
* The first number.
* @param float $number_2
* The second number.
*
* @return int|null
* < 0 if number_1 is less than number_2; > 0 if number_1 is greater than
* number_2, 0 if they are equal, and NULL if either is not numeric.
*/
function webform_compare_floats($number_1, $number_2) {
if (!is_numeric($number_1) || !is_numeric($number_2)) {
return NULL;
}
$number_1 = (float) $number_1;
$number_2 = (float) $number_2;
$epsilon = 0.000001;
if (abs($number_1 - $number_2) < $epsilon) {
return 0;
}
elseif ($number_1 > $number_2) {
return 1;
}
else {
return -1;
}
}

View File

@@ -45,8 +45,6 @@ function _webform_edit_pagebreak($component) {
'#value' => '0',
);
$form['display'] = array('#type' => 'markup'); // Hide the display options.
$form['extra']['next_page_label'] = array(
'#type' => 'textfield',
'#title' => t('Next page button label'),
@@ -56,8 +54,8 @@ function _webform_edit_pagebreak($component) {
);
$form['extra']['prev_page_label'] = array(
'#type' => 'textfield',
'#title' => t('Prev page button label'),
'#description' => t('This is used for the <em>Prev Page</em> button on the page after this page break. Default: <em>&lt; Prev Page</em>'),
'#title' => t('Previous page button label'),
'#description' => t('This is used for the <em>Previous Page</em> button on the page after this page break. Default: <em>&lt; Prev Page</em>'),
'#default_value' => $component['extra']['prev_page_label'],
'#size' => 30,
);
@@ -68,7 +66,7 @@ function _webform_edit_pagebreak($component) {
/**
* Implements _webform_render_component().
*/
function _webform_render_pagebreak($component, $value = NULL, $filter = TRUE) {
function _webform_render_pagebreak($component, $value = NULL, $filter = TRUE, $submission = NULL) {
$element = array(
'#type' => 'hidden',
'#value' => $component['name'],
@@ -78,9 +76,9 @@ function _webform_render_pagebreak($component, $value = NULL, $filter = TRUE) {
}
/**
* Implements _webform_render_component().
* Implements _webform_display_component().
*/
function _webform_display_pagebreak($component, $value = NULL, $format = 'html') {
function _webform_display_pagebreak($component, $value = NULL, $format = 'html', $submission = array()) {
$element = array(
'#theme' => 'webform_display_pagebreak',
'#title' => $component['name'],
@@ -97,5 +95,5 @@ function _webform_display_pagebreak($component, $value = NULL, $format = 'html')
function theme_webform_display_pagebreak($variables) {
$element = $variables['element'];
return $element['#format'] == 'html' ? '<h2 class="webform-page">' . check_plain($element['#title']) . '</h2>' : "--" . $element['#title'] . "--\n";
return $element['#format'] == 'html' ? '<h2 class="webform-page">' . check_plain($element['#title']) . '</h2>' : '==' . $element['#title'] . "==\n";
}

View File

@@ -12,7 +12,7 @@ function _webform_defaults_select() {
return array(
'name' => '',
'form_key' => NULL,
'mandatory' => 0,
'required' => 0,
'pid' => 0,
'weight' => 0,
'value' => '',
@@ -20,14 +20,17 @@ function _webform_defaults_select() {
'items' => '',
'multiple' => NULL,
'aslist' => NULL,
'empty_option' => '',
'optrand' => 0,
'other_option' => NULL,
'other_text' => t('Other...'),
'title_display' => 0,
'description' => '',
'description_above' => FALSE,
'custom_keys' => FALSE,
'options_source' => '',
'private' => FALSE,
'analysis' => TRUE,
),
);
}
@@ -57,6 +60,15 @@ function _webform_edit_select($component) {
),
);
// Default component if nested under a grid.
if (!isset($component['cid']) && $component['pid'] &&
($node = node_load($component['nid'])) && ($parent = $node->webform['components'][$component['pid']]) &&
$parent['type'] == 'grid') {
$component['value'] = $parent['value'];
$component['extra']['items'] = $parent['extra']['options'];
$component['required'] = $parent['required'];
}
$other = array();
if ($info = _webform_select_options_info()) {
$options = array('' => t('None'));
@@ -69,7 +81,6 @@ function _webform_edit_select($component) {
'#type' => 'select',
'#options' => $options,
'#default_value' => $component['extra']['options_source'],
'#weight' => 1,
'#description' => t('Use a pre-built list of options rather than entering options manually. Options will not be editable if using pre-built list.'),
'#parents' => array('extra', 'options_source'),
'#weight' => 5,
@@ -83,6 +94,7 @@ function _webform_edit_select($component) {
'#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.'),
'#parents' => array('extra', 'other_option'),
'#attributes' => array('class' => array('other-option-checkbox')),
'#weight' => 2,
);
$other['other_text'] = array(
@@ -92,6 +104,11 @@ function _webform_edit_select($component) {
'#description' => t('If allowing other options, enter text to be used for other-enabling option.'),
'#parents' => array('extra', 'other_text'),
'#weight' => 3,
'#states' => array(
'visible' => array(
':input.other-option-checkbox' => array('checked' => TRUE),
),
),
);
}
@@ -110,7 +127,7 @@ function _webform_edit_select($component) {
$form['items']['options'] = array(
'#type' => 'options',
'#limit' => 500,
'#optgroups' => $component['extra']['aslist'],
'#optgroups' => TRUE,
'#multiple' => $component['extra']['multiple'],
'#multiple_toggle' => t('Multiple'),
'#default_value' => $component['value'],
@@ -130,7 +147,7 @@ function _webform_edit_select($component) {
'#type' => 'textarea',
'#title' => t('Options'),
'#default_value' => $component['extra']['items'],
'#description' => t('<strong>Key-value pairs MUST be specified as "safe_key|Some readable option"</strong>. Use of only alphanumeric characters and underscores is recommended in keys. One option per line. Option groups may be specified with &lt;Group Name&gt;. &lt;&gt; can be used to insert items at the root of the menu after specifying a group.') . theme('webform_token_help'),
'#description' => t('<strong>Key-value pairs MUST be specified as "safe_key|Some readable option"</strong>. Use of only alphanumeric characters and underscores is recommended in keys. One option per line. Option groups may be specified with &lt;Group Name&gt;. &lt;&gt; can be used to insert items at the root of the menu after specifying a group.') . ' ' . theme('webform_token_help'),
'#cols' => 60,
'#rows' => 5,
'#weight' => 0,
@@ -148,7 +165,7 @@ function _webform_edit_select($component) {
'#type' => 'textfield',
'#title' => t('Default value'),
'#default_value' => $component['value'],
'#description' => t('The default value of the field identified by its key. For multiple selects use commas to separate multiple defaults.') . theme('webform_token_help'),
'#description' => t('The default value of the field identified by its key. For multiple selects use commas to separate multiple defaults.') . ' ' . theme('webform_token_help'),
'#size' => 60,
'#maxlength' => 1024,
'#weight' => 0,
@@ -169,6 +186,22 @@ function _webform_edit_select($component) {
'#description' => t('Check this option if you want the select component to be displayed as a select list box instead of radio buttons or checkboxes. Option groups (nested options) are only supported with listbox components.'),
'#parents' => array('extra', 'aslist'),
);
$form['display']['empty_option'] = array(
'#type' => 'textfield',
'#title' => t('Empty option'),
'#default_value' => $component['extra']['empty_option'],
'#size' => 60,
'#maxlength' => 255,
'#description' => t('The list item to show when no default is provided. Leave blank for "- None -" or "- Select -".'),
'#parents' => array('extra', 'empty_option'),
'#states' => array(
'visible' => array(
':input[name="extra[aslist]"]' => array('checked' => TRUE),
':input[name="extra[multiple]"]' => array('checked' => FALSE),
':input[name="value"]' => array('filled' => FALSE),
),
),
);
$form['display']['optrand'] = array(
'#type' => 'checkbox',
'#title' => t('Randomize options'),
@@ -198,7 +231,8 @@ function _webform_edit_validate_select($element, &$form_state) {
$line = trim($line);
if (preg_match('/^\<([^>]*)\>$/', $line, $matches)) {
$group = $matches[1];
$key = NULL; // No need to store group names.
// No need to store group names.
$key = NULL;
}
elseif (preg_match('/^([^|]*)\|(.*)$/', $line, $matches)) {
$key = $matches[1];
@@ -264,6 +298,11 @@ function _webform_edit_validate_options($element, &$form_state) {
// Options saved for grid components.
else {
$form_state['values']['extra']['custom_' . rtrim($key, 's') . '_keys'] = $element_options['custom_keys'];
// There is only one 'value', but grids have two options widgets, one for questions and one for options.
// Only options have a default 'value'. Note that multiple selection is now allowed for grid options.
if ($key == 'options') {
$form_state['values']['value'] = $element_options['default_value'];
}
}
}
@@ -285,32 +324,49 @@ function _webform_edit_validate_set_aslist($options, &$form_state) {
/**
* Implements _webform_render_component().
*/
function _webform_render_select($component, $value = NULL, $filter = TRUE) {
function _webform_render_select($component, $value = NULL, $filter = TRUE, $submission = NULL) {
$node = isset($component['nid']) ? node_load($component['nid']) : NULL;
$element = array(
'#title' => $filter ? _webform_filter_xss($component['name']) : $component['name'],
'#title' => $filter ? webform_filter_xss($component['name']) : $component['name'],
'#title_display' => $component['extra']['title_display'] ? $component['extra']['title_display'] : 'before',
'#required' => $component['mandatory'],
'#required' => $component['required'],
'#weight' => $component['weight'],
'#description' => $filter ? _webform_filter_descriptions($component['extra']['description'], $node) : $component['extra']['description'],
'#description' => $filter ? webform_filter_descriptions($component['extra']['description'], $node) : $component['extra']['description'],
'#theme_wrappers' => array('webform_element'),
'#pre_render' => array(), // Needed to disable double-wrapping of radios and checkboxes.
'#translatable' => array('title', 'description', 'options'),
);
// Prevent double-wrapping of radios and checkboxes.
if (!$component['extra']['aslist']) {
$element['#pre_render'] = array();
}
// Convert the user-entered options list into an array.
$default_value = $filter ? _webform_filter_values($component['value'], $node, NULL, NULL, FALSE) : $component['value'];
$default_value = $filter ? webform_replace_tokens($component['value'], $node) : $component['value'];
$options = _webform_select_options($component, !$component['extra']['aslist'], $filter);
if (empty($options)) {
// Make element inaccessible if there are no options as there is no point in showing it.
$element['#access'] = FALSE;
}
if ($component['extra']['optrand']) {
_webform_shuffle_options($options);
}
// Add HTML5 required attribute, if needed and possible (not working on more than one checkboxes).
if ($component['required'] && ($component['extra']['aslist'] || !$component['extra']['multiple'] || count($options) == 1)) {
$element['#attributes']['required'] = 'required';
}
// Add default options if using a select list with no default. This trigger's
// Drupal 7's adding of the option for us. See @form_process_select().
if ($component['extra']['aslist'] && !$component['extra']['multiple'] && $default_value === '') {
$element['#empty_value'] = '';
if (strlen($component['extra']['empty_option'])) {
$element['#empty_option'] = $component['extra']['empty_option'];
$element['#translatable'][] = 'empty_option';
}
}
// Set the component options.
@@ -322,8 +378,8 @@ function _webform_render_select($component, $value = NULL, $filter = TRUE) {
$value = $component['extra']['multiple'] ? array_filter(array_map('trim', explode(',', $default_value)), 'strlen') : $default_value;
}
// Convert all values into an array; component may now be single but was previously multiple, or vice-versa
$value = (array)$value;
// Convert all values into an array; component may now be single but was previously multiple, or vice-versa.
$value = (array) $value;
// Set the default value. Note: "No choice" is stored as an empty string,
// which will match a 0 key for radios; NULL is used to avoid unintentional
@@ -352,6 +408,9 @@ function _webform_render_select($component, $value = NULL, $filter = TRUE) {
$element['#other_delimiter'] = ', ';
// Merge in Webform's #process function for Select or other.
$element['#process'] = array_merge(element_info_property('select_or_other', '#process'), array('webform_expand_select_or_other'));
// Inherit select_or_other settings or set defaults.
$element['#disabled'] = isset($component['extra']['#disabled']) ? $component['extra']['#disabled'] : element_info_property('select_or_other', 'disabled');
$element['#size'] = isset($component['extra']['#size']) ? $component['extra']['#size'] : element_info_property('select_or_other', 'size');
if ($component['extra']['multiple']) {
$element['#multiple'] = TRUE;
@@ -435,7 +494,7 @@ function webform_expand_select_ids($element) {
$element[$key]['#id'] = $id . '-' . $delta;
// Prevent scripts or CSS in the labels for each checkbox or radio.
$element[$key]['#title'] = _webform_filter_xss($element[$key]['#title']);
$element[$key]['#title'] = isset($element[$key]['#title']) ? webform_filter_xss($element[$key]['#title']) : '';
}
return $element;
}
@@ -443,16 +502,21 @@ function webform_expand_select_ids($element) {
/**
* Implements _webform_display_component().
*/
function _webform_display_select($component, $value, $format = 'html') {
function _webform_display_select($component, $value, $format = 'html', $submission = array()) {
// Sort values by numeric key. These may be in alphabetic order from the database query,
// which is not numeric order for keys '10' and higher.
$value = (array) $value;
ksort($value, SORT_NUMERIC);
return array(
'#title' => $component['name'],
'#title_display' => $component['extra']['title_display'] ? $component['extra']['title_display'] : 'before',
'#weight' => $component['weight'],
'#multiple' => $component['extra']['multiple'],
'#theme' => 'webform_display_select',
'#theme_wrappers' => $format == 'html' ? array('webform_element') : array('webform_element_text'),
'#format' => $format,
'#options' => _webform_select_options($component, !$component['extra']['aslist']),
'#value' => (array) $value,
'#value' => $value,
'#translatable' => array('title', 'options'),
);
}
@@ -460,9 +524,21 @@ function _webform_display_select($component, $value, $format = 'html') {
/**
* Implements _webform_submit_component().
*
* Convert FAPI 0/1 values into something saveable.
* Handle select or other... modifications and convert FAPI 0/1 values into
* something saveable.
*/
function _webform_submit_select($component, $value) {
if (module_exists('select_or_other') && $component['extra']['other_option'] && is_array($value) && count($value) == 2 && isset($value['select'])) {
if (is_array($value['select']) && isset($value['select']['select_or_other'])) {
unset($value['select']['select_or_other']);
$value['select'][] = $value['other'];
}
elseif ($value['select'] == 'select_or_other') {
$value['select'] = $value['other'];
}
$value = $value['select'];
}
// Build a list of all valid keys expected to be submitted.
$options = _webform_select_options($component, TRUE);
@@ -475,7 +551,7 @@ function _webform_submit_select($component, $value) {
// Checkboxes submit an integer value of 0 when unchecked. A checkbox
// with a value of '0' is valid, so we can't use empty() here.
if ($option_value === 0 && !$component['extra']['aslist'] && $component['extra']['multiple']) {
unset($value[$option_value]);
// Don't save unchecked values.
}
else {
$return[] = $option_value;
@@ -487,6 +563,11 @@ function _webform_submit_select($component, $value) {
$return[] = $option_value;
}
}
// If no elements are selected, then save an empty string to indicate that this components should not be defaulted again.
if (!$return) {
$return = array('');
}
}
elseif (is_string($value)) {
$return = $value;
@@ -521,7 +602,7 @@ function theme_webform_display_select($variables) {
if ($option_value !== '') {
// Administer provided values.
if (isset($options[$option_value])) {
$items[] = $element['#format'] == 'html' ? _webform_filter_xss($options[$option_value]) : $options[$option_value];
$items[] = $element['#format'] == 'html' ? webform_filter_xss($options[$option_value]) : $options[$option_value];
}
// User-specified in the "other" field.
else {
@@ -534,7 +615,7 @@ function theme_webform_display_select($variables) {
if (isset($element['#value'][0]) && $element['#value'][0] !== '') {
// Administer provided values.
if (isset($options[$element['#value'][0]])) {
$items[] = $element['#format'] == 'html' ? _webform_filter_xss($options[$element['#value'][0]]) : $options[$element['#value'][0]];
$items[] = $element['#format'] == 'html' ? webform_filter_xss($options[$element['#value'][0]]) : $options[$element['#value'][0]];
}
// User-specified in the "other" field.
else {
@@ -564,65 +645,89 @@ function theme_webform_display_select($variables) {
/**
* Implements _webform_analysis_component().
*/
function _webform_analysis_select($component, $sids = array(), $single = FALSE) {
function _webform_analysis_select($component, $sids = array(), $single = FALSE, $join = NULL) {
$options = _webform_select_options($component, TRUE);
$show_other_results = $single;
$sid_placeholders = count($sids) ? array_fill(0, count($sids), "'%s'") : array();
$sid_filter = count($sids) ? " AND sid IN (" . implode(",", $sid_placeholders) . ")" : "";
$option_operator = $show_other_results ? 'NOT IN' : 'IN';
// Create a generic query for the component.
$query = db_select('webform_submitted_data', 'wsd', array('fetch' => PDO::FETCH_ASSOC))
->fields('wsd', array('data'))
->condition('nid', $component['nid'])
->condition('cid', $component['cid'])
->condition('data', '', '<>')
->condition('data', array_keys($options), $option_operator)
->groupBy('data');
$query->addExpression('COUNT(data)', 'datacount');
->condition('wsd.nid', $component['nid'])
->condition('wsd.cid', $component['cid'])
->condition('wsd.data', '', '<>');
if (count($sids)) {
$query->condition('sid', $sids, 'IN');
if ($sids) {
$query->condition('wsd.sid', $sids, 'IN');
}
$count_query = db_select('webform_submitted_data', 'wsd', array('fetch' => PDO::FETCH_ASSOC))
->condition('nid', $component['nid'])
->condition('cid', $component['cid'])
->condition('data', '', '<>');
$count_query->addExpression('COUNT(*)', 'datacount');
if (count($sids)) {
$count_query->condition('sid', $sids, 'IN');
if ($join) {
$query->innerJoin($join, 'ws2_', 'wsd.sid = ws2_.sid');
}
// Clone the query for later use, if needed.
if ($component['extra']['other_option']) {
$count_query = clone $query;
if ($single) {
$other_query = clone $query;
}
}
$result = $query->execute();
$rows = array();
$other = array();
$normal_count = 0;
foreach ($result as $data) {
$display_option = $single ? $data['data'] : $options[$data['data']];
$rows[$data['data']] = array(_webform_filter_xss($display_option), $data['datacount']);
$normal_count += $data['datacount'];
}
if (!$show_other_results) {
if ($options) {
// Gather the normal results first (not "other" options).
$query->addExpression('COUNT(wsd.data)', 'datacount');
$result = $query
->condition('wsd.data', array_keys($options), 'IN')
->fields('wsd', array('data'))
->groupBy('wsd.data')
->execute();
foreach ($result as $data) {
$display_option = isset($options[$data['data']]) ? $options[$data['data']] : $data['data'];
$rows[$data['data']] = array(webform_filter_xss($display_option), $data['datacount']);
$normal_count += $data['datacount'];
}
// 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']) {
$full_count = $count_query->execute()->fetchField();
$other_count = $full_count - $normal_count;
$display_option = !empty($component['extra']['other_text']) ? check_plain($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($display_option, $other_text);
}
$rows = $ordered_rows;
}
return $rows;
// Add a row for displaying the total unknown or user-entered values.
if ($component['extra']['other_option']) {
$count_query->addExpression('COUNT(*)', 'datacount');
$full_count = $count_query->execute()->fetchField();
$other_count = $full_count - $normal_count;
$display_option = !empty($component['extra']['other_text']) ? check_plain($component['extra']['other_text']) : t('Other...');
$other_text = ($other_count && !$single) ? $other_count . ' (' . l(t('view'), 'node/' . $component['nid'] . '/webform-results/analysis/' . $component['cid']) . ')' : $other_count;
$rows[] = array($display_option, $other_text);
// If showing all results, execute the "other" query and append their rows.
if ($single) {
$other_query->addExpression('COUNT(wsd.data)', 'datacount');
$other_query
->fields('wsd', array('data'))
->groupBy('wsd.data');
if ($options) {
$other_query->condition('wsd.data', array_keys($options), 'NOT IN');
}
$other_result = $other_query->execute();
foreach ($other_result as $data) {
$other[] = array(check_plain($data['data']), $data['datacount']);
}
if ($other) {
array_unshift($other, '<strong>' . t('Other responses') . '</strong>');
}
}
}
return array(
'table_rows' => $rows,
'other_data' => $other,
);
}
/**
@@ -633,12 +738,13 @@ function _webform_table_select($component, $value) {
$options = _webform_select_options($component, TRUE);
$value = (array) $value;
ksort($value, SORT_NUMERIC);
$items = array();
// Set the value as a single string.
foreach ($value as $option_value) {
if ($option_value !== '') {
if (isset($options[$option_value])) {
$items[] = _webform_filter_xss($options[$option_value]);
$items[] = webform_filter_xss($options[$option_value]);
}
else {
$items[] = check_plain($option_value);
@@ -649,6 +755,24 @@ function _webform_table_select($component, $value) {
return implode('<br />', $items);
}
/**
* Implements _webform_action_set_component().
*/
function _webform_action_set_select($component, &$element, &$form_state, $value) {
// Set the value as an array for multiple select or single value otherwise.
if ($element['#type'] == 'checkboxes') {
$checkbox_values = $element['#options'];
array_walk($checkbox_values, function (&$option_value, $key) use ($value) {
$option_value = (int) (strval($key) === $value);
});
}
else {
$value = $component['extra']['multiple'] ? array($value) : $value;
}
$element['#value'] = $value;
form_set_value($element, $value, $form_state);
}
/**
* Implements _webform_csv_headers_component().
*/
@@ -661,10 +785,15 @@ function _webform_csv_headers_select($component, $export_options) {
if ($component['extra']['multiple'] && $export_options['select_format'] == 'separate') {
$headers[0][] = '';
$headers[1][] = $component['name'];
$items = _webform_select_options($component, TRUE, FALSE);
$headers[1][] = $export_options['header_keys'] ? $component['form_key'] : $component['name'];
$items = _webform_select_options($component, TRUE);
if ($component['extra']['other_option']) {
$other_label = !empty($component['extra']['other_text']) ? check_plain($component['extra']['other_text']) : t('Other...');
if ($export_options['header_keys']) {
$other_label = $component['form_key'] . '_other';
}
else {
$other_label = !empty($component['extra']['other_text']) ? check_plain($component['extra']['other_text']) : t('Other...');
}
$items[$other_label] = $other_label;
}
$count = 0;
@@ -686,7 +815,7 @@ function _webform_csv_headers_select($component, $export_options) {
else {
$headers[0][] = '';
$headers[1][] = '';
$headers[2][] = $component['name'];
$headers[2][] = $export_options['header_keys'] ? $component['form_key'] : $component['name'];
}
return $headers;
}
@@ -695,12 +824,14 @@ function _webform_csv_headers_select($component, $export_options) {
* Implements _webform_csv_data_component().
*/
function _webform_csv_data_select($component, $export_options, $value) {
$options = _webform_select_options($component, TRUE, FALSE);
$options = _webform_select_options($component, TRUE);
$return = array();
if ($component['extra']['multiple']) {
foreach ($options as $key => $item) {
$index = array_search($key, (array) $value);
// Strict search is needed to avoid a key of 0 from matching an empty
// value.
$index = array_search((string) $key, (array) $value, TRUE);
if ($index !== FALSE) {
if ($export_options['select_format'] == 'separate') {
$return[] = 'X';
@@ -737,6 +868,23 @@ function _webform_csv_data_select($component, $export_options, $value) {
return $return;
}
/**
* Implements _webform_options_component().
*
* This function is confusingly an alias of _webform_select_options(). However
* this version is intended to be accessed publicly via
* webform_component_invoke(), since it is a Webform "hook", rather than an
* internal, private function. To get the list of select list options for
* a component, use:
*
* @code
* $options = webform_component_invoke($component['type'], 'options', $component);
* @endcode
*/
function _webform_options_select($component, $flat = FALSE) {
return _webform_select_options($component, $flat);
}
/**
* Menu callback; Return a predefined list of select options as JSON.
*/
@@ -745,7 +893,7 @@ function webform_select_options_ajax($source_name = '') {
$component['extra']['options_source'] = $source_name;
if ($source_name && isset($info[$source_name])) {
$options = _webform_select_options_to_text(_webform_select_options($component, !$component['extra']['aslist'], FALSE));
$options = _webform_select_options_to_text(_webform_select_options($component, FALSE, FALSE));
}
else {
$options = '';
@@ -764,15 +912,36 @@ function webform_select_options_ajax($source_name = '') {
*/
function _webform_select_options($component, $flat = FALSE, $filter = TRUE) {
if ($component['extra']['options_source']) {
$options = _webform_select_options_callback($component['extra']['options_source'], $component, $flat, $filter);
$options = _webform_select_options_callback($component['extra']['options_source'], $component, $flat);
}
else {
$options = _webform_select_options_from_text($component['extra']['items'], $flat, $filter);
$options = _webform_select_options_from_text($component['extra']['items'], $flat);
}
// Replace tokens if needed in the options.
if ($filter) {
$node = node_load($component['nid']);
$options = _webform_select_replace_tokens($options, $node);
}
return isset($options) ? $options : array();
}
/**
* Replace tokens in the values of a list of select options.
*/
function _webform_select_replace_tokens($options, $node) {
foreach ($options as $key => $option) {
if (is_array($option)) {
$options[$key] = _webform_select_replace_tokens($option, $node);
}
else {
$options[$key] = webform_replace_tokens($option, $node);
}
}
return $options;
}
/**
* Load Webform select option info from 3rd party modules.
*/
@@ -796,16 +965,14 @@ function _webform_select_options_info() {
/**
* Execute a select option callback.
*
* @param $name
* @param string $name
* The name of the options group.
* @param $component
* @param array $component
* The full Webform component.
* @param $flat
* @param bool $flat
* Whether the information returned should exclude any nested groups.
* @param $filter
* Whether information returned should be sanitized. Defaults to TRUE.
*/
function _webform_select_options_callback($name, $component, $flat = FALSE, $filter = TRUE) {
function _webform_select_options_callback($name, $component, $flat = FALSE) {
$info = _webform_select_options_info();
// Include any necessary files.
@@ -816,7 +983,7 @@ function _webform_select_options_callback($name, $component, $flat = FALSE, $fil
}
// Execute the callback function.
if (isset($info[$name]['options callback']) && function_exists($info[$name]['options callback'])) {
if (isset($info[$name]['options callback']) && is_callable($info[$name]['options callback'])) {
$function = $info[$name]['options callback'];
$arguments = array();
@@ -824,22 +991,24 @@ function _webform_select_options_callback($name, $component, $flat = FALSE, $fil
$arguments = $info[$name]['options arguments'];
}
return $function($component, $flat, $filter, $arguments);
return $function($component, $flat, $arguments);
}
}
/**
* Utility function to split user-entered values from new-line separated
* text into an array of options.
* Splits user values from new-line separated text into an array of options.
*
* @param $text
* @param string $text
* Text to be converted into a select option array.
* @param $flat
* @param bool $flat
* Optional. If specified, return the option array and exclude any optgroups.
* @param $filter
* Optional. Whether or not to filter returned values.
*
* @return array
* An array of options suitable for use as a #options property. Note that
* values are not filtered and may contain tokens. Individual values should be
* run through webform_replace_tokens() if displaying to an end-user.
*/
function _webform_select_options_from_text($text, $flat = FALSE, $filter = TRUE) {
function _webform_select_options_from_text($text, $flat = FALSE) {
static $option_cache = array();
// Keep each processed option block in an array indexed by the MD5 hash of
@@ -853,7 +1022,7 @@ function _webform_select_options_from_text($text, $flat = FALSE, $filter = TRUE)
$group = NULL;
foreach ($rows as $option) {
$option = trim($option);
/**
/*
* If the Key of the option is within < >, treat as an optgroup
*
* <Group 1>
@@ -867,17 +1036,16 @@ function _webform_select_options_from_text($text, $flat = FALSE, $filter = TRUE)
unset($group);
}
elseif (!$flat) {
$group = $filter ? _webform_filter_values($matches[1], NULL, NULL, NULL, FALSE) : $matches[1];
$group = $matches[1];
}
}
elseif (preg_match('/^([^|]+)\|(.*)$/', $option, $matches)) {
$key = $filter ? _webform_filter_values($matches[1], NULL, NULL, NULL, FALSE) : $matches[1];
$value = $filter ? _webform_filter_values($matches[2], NULL, NULL, NULL, FALSE) : $matches[2];
$key = $matches[1];
$value = $matches[2];
isset($group) ? $options[$group][$key] = $value : $options[$key] = $value;
}
else {
$filtered_option = $filter ? _webform_filter_values($option, NULL, NULL, NULL, FALSE) : $option;
isset($group) ? $options[$group][$filtered_option] = $filtered_option : $options[$filtered_option] = $filtered_option;
isset($group) ? $options[$group][$option] = $option : $options[$option] = $option;
}
}

View File

@@ -15,7 +15,7 @@ function _webform_defaults_textarea() {
'pid' => 0,
'weight' => 0,
'value' => '',
'mandatory' => 0,
'required' => 0,
'extra' => array(
'cols' => '',
'rows' => '',
@@ -23,13 +23,15 @@ function _webform_defaults_textarea() {
'resizable' => 1,
'disabled' => 0,
'description' => '',
'description_above' => FALSE,
'placeholder' => '',
'attributes' => array(),
'private' => FALSE,
'analysis' => FALSE,
),
);
}
/**
* Implements _webform_theme_component().
*/
@@ -51,7 +53,7 @@ function _webform_edit_textarea($component) {
'#type' => 'textarea',
'#title' => t('Default value'),
'#default_value' => $component['value'],
'#description' => t('The default value of the field.') . theme('webform_token_help'),
'#description' => t('The default value of the field.') . ' ' . theme('webform_token_help'),
'#cols' => 60,
'#rows' => 5,
'#weight' => 0,
@@ -82,11 +84,18 @@ function _webform_edit_textarea($component) {
'#default_value' => $component['extra']['resizable'],
'#parents' => array('extra', 'resizable'),
);
$form['display']['placeholder'] = array(
'#type' => 'textfield',
'#title' => t('Placeholder'),
'#default_value' => $component['extra']['placeholder'],
'#description' => t('The placeholder will be shown in the field until the user starts entering a value.'),
'#parents' => array('extra', 'placeholder'),
);
$form['display']['disabled'] = array(
'#type' => 'checkbox',
'#title' => t('Disabled'),
'#return_value' => 1,
'#description' => t('Make this field non-editable. Useful for setting an unchangeable default value.'),
'#description' => t('Make this field non-editable. Useful for displaying default value. Changeable via JavaScript or developer tools.'),
'#weight' => 11,
'#default_value' => $component['extra']['disabled'],
'#parents' => array('extra', 'disabled'),
@@ -97,25 +106,34 @@ function _webform_edit_textarea($component) {
/**
* Implements _webform_render_component().
*/
function _webform_render_textarea($component, $value = NULL, $filter = TRUE) {
function _webform_render_textarea($component, $value = NULL, $filter = TRUE, $submission = NULL) {
$node = isset($component['nid']) ? node_load($component['nid']) : NULL;
$element = array(
'#type' => 'textarea',
'#title' => $filter ? _webform_filter_xss($component['name']) : $component['name'],
'#title' => $filter ? webform_filter_xss($component['name']) : $component['name'],
'#title_display' => $component['extra']['title_display'] ? $component['extra']['title_display'] : 'before',
'#default_value' => $filter ? _webform_filter_values($component['value'], $node) : $component['value'],
'#required' => $component['mandatory'],
'#default_value' => $filter ? webform_replace_tokens($component['value'], $node) : $component['value'],
'#required' => $component['required'],
'#weight' => $component['weight'],
'#description' => $filter ? _webform_filter_descriptions($component['extra']['description'], $node) : $component['extra']['description'],
'#description' => $filter ? webform_filter_descriptions($component['extra']['description'], $node) : $component['extra']['description'],
'#rows' => !empty($component['extra']['rows']) ? $component['extra']['rows'] : 5,
'#cols' => !empty($component['extra']['cols']) ? $component['extra']['cols'] : 60,
'#attributes' => $component['extra']['attributes'],
'#resizable' => (bool) $component['extra']['resizable'], // MUST be FALSE to disable.
// MUST be FALSE to disable.
'#resizable' => (bool) $component['extra']['resizable'],
'#theme_wrappers' => array('webform_element'),
'#translatable' => array('title', 'description'),
);
if ($component['required']) {
$element['#attributes']['required'] = 'required';
}
if ($component['extra']['placeholder']) {
$element['#attributes']['placeholder'] = $component['extra']['placeholder'];
}
if ($component['extra']['disabled']) {
if ($filter) {
$element['#attributes']['readonly'] = 'readonly';
@@ -125,7 +143,7 @@ function _webform_render_textarea($component, $value = NULL, $filter = TRUE) {
}
}
if (isset($value)) {
if (isset($value[0])) {
$element['#default_value'] = $value[0];
}
@@ -135,9 +153,10 @@ function _webform_render_textarea($component, $value = NULL, $filter = TRUE) {
/**
* Implements _webform_display_component().
*/
function _webform_display_textarea($component, $value, $format = 'html') {
function _webform_display_textarea($component, $value, $format = 'html', $submission = array()) {
return array(
'#title' => $component['name'],
'#title_display' => $component['extra']['title_display'] ? $component['extra']['title_display'] : 'before',
'#weight' => $component['weight'],
'#theme' => 'webform_display_textarea',
'#theme_wrappers' => $format == 'html' ? array('webform_element') : array('webform_element_text'),
@@ -162,14 +181,18 @@ function theme_webform_display_textarea($variables) {
/**
* Implements _webform_analysis_component().
*/
function _webform_analysis_textarea($component, $sids = array()) {
function _webform_analysis_textarea($component, $sids = array(), $single = FALSE, $join = NULL) {
$query = db_select('webform_submitted_data', 'wsd', array('fetch' => PDO::FETCH_ASSOC))
->fields('wsd', array('no', 'data'))
->condition('nid', $component['nid'])
->condition('cid', $component['cid']);
->condition('wsd.nid', $component['nid'])
->condition('wsd.cid', $component['cid']);
if (count($sids)) {
$query->condition('sid', $sids, 'IN');
$query->condition('wsd.sid', $sids, 'IN');
}
if ($join) {
$query->innerJoin($join, 'ws2_', 'wsd.sid = ws2_.sid');
}
$nonblanks = 0;
@@ -187,8 +210,16 @@ function _webform_analysis_textarea($component, $sids = array()) {
$rows[0] = array(t('Left Blank'), ($submissions - $nonblanks));
$rows[1] = array(t('User entered value'), $nonblanks);
$rows[2] = array(t('Average submission length in words (ex blanks)'), ($nonblanks != 0 ? number_format($wordcount/$nonblanks, 2) : '0'));
return $rows;
$other[] = array(
t('Average submission length in words (ex blanks)'),
$nonblanks != 0 ? number_format($wordcount / $nonblanks, 2) : '0',
);
return array(
'table_rows' => $rows,
'other_data' => $other,
);
}
/**
@@ -198,6 +229,14 @@ function _webform_table_textarea($component, $value) {
return empty($value[0]) ? '' : check_plain($value[0]);
}
/**
* Implements _webform_action_set_component().
*/
function _webform_action_set_textarea($component, &$element, &$form_state, $value) {
$element['#value'] = $value;
form_set_value($element, $value, $form_state);
}
/**
* Implements _webform_csv_headers_component().
*/
@@ -205,7 +244,7 @@ function _webform_csv_headers_textarea($component, $export_options) {
$header = array();
$header[0] = '';
$header[1] = '';
$header[2] = $component['name'];
$header[2] = $export_options['header_keys'] ? $component['form_key'] : $component['name'];
return $header;
}

View File

@@ -15,18 +15,22 @@ function _webform_defaults_textfield() {
'pid' => 0,
'weight' => 0,
'value' => '',
'mandatory' => 0,
'required' => 0,
'extra' => array(
'width' => '',
'maxlength' => '',
'minlength' => '',
'field_prefix' => '',
'field_suffix' => '',
'disabled' => 0,
'unique' => 0,
'title_display' => 0,
'description' => '',
'description_above' => FALSE,
'placeholder' => '',
'attributes' => array(),
'private' => FALSE,
'analysis' => FALSE,
),
);
}
@@ -52,7 +56,7 @@ function _webform_edit_textfield($component) {
'#type' => 'textfield',
'#title' => t('Default value'),
'#default_value' => $component['value'],
'#description' => t('The default value of the field.') . theme('webform_token_help'),
'#description' => t('The default value of the field.') . ' ' . theme('webform_token_help'),
'#size' => 60,
'#maxlength' => 1024,
'#weight' => 0,
@@ -67,6 +71,14 @@ function _webform_edit_textfield($component) {
'#weight' => 0,
'#parents' => array('extra', 'width'),
);
$form['display']['placeholder'] = array(
'#type' => 'textfield',
'#title' => t('Placeholder'),
'#default_value' => $component['extra']['placeholder'],
'#description' => t('The placeholder will be shown in the field until the user starts entering a value.'),
'#weight' => 1,
'#parents' => array('extra', 'placeholder'),
);
$form['display']['field_prefix'] = array(
'#type' => 'textfield',
'#title' => t('Prefix text placed to the left of the textfield'),
@@ -74,7 +86,7 @@ function _webform_edit_textfield($component) {
'#description' => t('Examples: $, #, -.'),
'#size' => 20,
'#maxlength' => 127,
'#weight' => 1.1,
'#weight' => 2.1,
'#parents' => array('extra', 'field_prefix'),
);
$form['display']['field_suffix'] = array(
@@ -84,14 +96,14 @@ function _webform_edit_textfield($component) {
'#description' => t('Examples: lb, kg, %.'),
'#size' => 20,
'#maxlength' => 127,
'#weight' => 1.2,
'#weight' => 2.2,
'#parents' => array('extra', 'field_suffix'),
);
$form['display']['disabled'] = array(
'#type' => 'checkbox',
'#title' => t('Disabled'),
'#return_value' => 1,
'#description' => t('Make this field non-editable. Useful for setting an unchangeable default value.'),
'#description' => t('Make this field non-editable. Useful for displaying default value. Changeable via JavaScript or developer tools.'),
'#weight' => 11,
'#default_value' => $component['extra']['disabled'],
'#parents' => array('extra', 'disabled'),
@@ -115,30 +127,53 @@ function _webform_edit_textfield($component) {
'#weight' => 2,
'#parents' => array('extra', 'maxlength'),
);
$form['validation']['minlength'] = array(
'#type' => 'textfield',
'#title' => t('Minlength'),
'#default_value' => $component['extra']['minlength'],
'#description' => t('Minimum length of the textfield value. The component may still be empty unless it is set as Required.'),
'#size' => 5,
'#maxlength' => 10,
'#weight' => 3,
'#parents' => array('extra', 'minlength'),
);
return $form;
}
/**
* Implements _webform_render_component().
*/
function _webform_render_textfield($component, $value = NULL, $filter = TRUE) {
function _webform_render_textfield($component, $value = NULL, $filter = TRUE, $submission = NULL) {
$node = isset($component['nid']) ? node_load($component['nid']) : NULL;
$element = array(
'#type' => 'textfield',
'#title' => $filter ? _webform_filter_xss($component['name']) : $component['name'],
'#title' => $filter ? webform_filter_xss($component['name']) : $component['name'],
'#title_display' => $component['extra']['title_display'] ? $component['extra']['title_display'] : 'before',
'#default_value' => $filter ? _webform_filter_values($component['value'], $node, NULL, NULL, FALSE) : $component['value'],
'#required' => $component['mandatory'],
'#default_value' => $filter ? webform_replace_tokens($component['value'], $node) : $component['value'],
'#required' => $component['required'],
'#weight' => $component['weight'],
'#field_prefix' => empty($component['extra']['field_prefix']) ? NULL : ($filter ? _webform_filter_xss($component['extra']['field_prefix']) : $component['extra']['field_prefix']),
'#field_suffix' => empty($component['extra']['field_suffix']) ? NULL : ($filter ? _webform_filter_xss($component['extra']['field_suffix']) : $component['extra']['field_suffix']),
'#description' => $filter ? _webform_filter_descriptions($component['extra']['description'], $node) : $component['extra']['description'],
'#field_prefix' => empty($component['extra']['field_prefix']) ? NULL : ($filter ? webform_filter_xss($component['extra']['field_prefix']) : $component['extra']['field_prefix']),
'#field_suffix' => empty($component['extra']['field_suffix']) ? NULL : ($filter ? webform_filter_xss($component['extra']['field_suffix']) : $component['extra']['field_suffix']),
'#description' => $filter ? webform_filter_descriptions($component['extra']['description'], $node) : $component['extra']['description'],
'#attributes' => $component['extra']['attributes'],
'#theme_wrappers' => array('webform_element'),
'#translatable' => array('title', 'description', 'field_prefix', 'field_suffix'),
'#translatable' => array(
'title',
'description',
'field_prefix',
'field_suffix',
),
);
if ($component['required']) {
$element['#attributes']['required'] = 'required';
}
if ($component['extra']['placeholder']) {
$element['#attributes']['placeholder'] = $component['extra']['placeholder'];
}
if ($component['extra']['disabled']) {
if ($filter) {
$element['#attributes']['readonly'] = 'readonly';
@@ -160,8 +195,11 @@ function _webform_render_textfield($component, $value = NULL, $filter = TRUE) {
if ($component['extra']['maxlength'] > 0) {
$element['#maxlength'] = $component['extra']['maxlength'];
}
if ($component['extra']['minlength'] > 0) {
$element['#minlength'] = $component['extra']['minlength'];
}
if (isset($value)) {
if (isset($value[0])) {
$element['#default_value'] = $value[0];
}
@@ -171,9 +209,10 @@ function _webform_render_textfield($component, $value = NULL, $filter = TRUE) {
/**
* Implements _webform_display_component().
*/
function _webform_display_textfield($component, $value, $format = 'html') {
function _webform_display_textfield($component, $value, $format = 'html', $submission = array()) {
return array(
'#title' => $component['name'],
'#title_display' => $component['extra']['title_display'] ? $component['extra']['title_display'] : 'before',
'#weight' => $component['weight'],
'#theme' => 'webform_display_textfield',
'#theme_wrappers' => $format == 'html' ? array('webform_element') : array('webform_element_text'),
@@ -199,14 +238,18 @@ function theme_webform_display_textfield($variables) {
/**
* Implements _webform_analysis_component().
*/
function _webform_analysis_textfield($component, $sids = array()) {
function _webform_analysis_textfield($component, $sids = array(), $single = FALSE, $join = NULL) {
$query = db_select('webform_submitted_data', 'wsd', array('fetch' => PDO::FETCH_ASSOC))
->fields('wsd', array('data'))
->condition('nid', $component['nid'])
->condition('cid', $component['cid']);
->condition('wsd.nid', $component['nid'])
->condition('wsd.cid', $component['cid']);
if (count($sids)) {
$query->condition('sid', $sids, 'IN');
$query->condition('wsd.sid', $sids, 'IN');
}
if ($join) {
$query->innerJoin($join, 'ws2_', 'wsd.sid = ws2_.sid');
}
$nonblanks = 0;
@@ -224,8 +267,16 @@ function _webform_analysis_textfield($component, $sids = array()) {
$rows[0] = array(t('Left Blank'), ($submissions - $nonblanks));
$rows[1] = array(t('User entered value'), $nonblanks);
$rows[2] = array(t('Average submission length in words (ex blanks)'), ($nonblanks != 0 ? number_format($wordcount/$nonblanks, 2) : '0'));
return $rows;
$other[] = array(
t('Average submission length in words (ex blanks)'),
$nonblanks != 0 ? number_format($wordcount / $nonblanks, 2) : '0',
);
return array(
'table_rows' => $rows,
'other_data' => $other,
);
}
/**
@@ -235,6 +286,14 @@ function _webform_table_textfield($component, $value) {
return check_plain(empty($value[0]) ? '' : $value[0]);
}
/**
* Implements _webform_action_set_component().
*/
function _webform_action_set_textfield($component, &$element, &$form_state, $value) {
$element['#value'] = $value;
form_set_value($element, $value, $form_state);
}
/**
* Implements _webform_csv_headers_component().
*/
@@ -242,7 +301,7 @@ function _webform_csv_headers_textfield($component, $export_options) {
$header = array();
$header[0] = '';
$header[1] = '';
$header[2] = $component['name'];
$header[2] = $export_options['header_keys'] ? $component['form_key'] : $component['name'];
return $header;
}

View File

@@ -18,14 +18,18 @@ function _webform_defaults_time() {
'pid' => 0,
'weight' => 0,
'value' => '',
'mandatory' => 0,
'required' => 0,
'extra' => array(
'timezone' => 'user',
'start_time' => '',
'end_time' => '',
'hourformat' => '12-hour',
'minuteincrements' => 1,
'title_display' => 0,
'description' => '',
'description_above' => FALSE,
'private' => FALSE,
'analysis' => FALSE,
),
);
}
@@ -60,11 +64,29 @@ function _webform_edit_time($component) {
'#maxlength' => 127,
'#weight' => 0,
);
$form['validation']['start_time'] = array(
'#type' => 'textfield',
'#title' => t('Start time'),
'#default_value' => $component['extra']['start_time'],
'#description' => t('The earliest time that may be entered into the field.'),
'#size' => 10,
'#weight' => 3,
'#parents' => array('extra', 'start_time'),
);
$form['validation']['end_time'] = array(
'#type' => 'textfield',
'#title' => t('End time'),
'#default_value' => $component['extra']['end_time'],
'#description' => t('The latest time that may be entered into the field.'),
'#size' => 10,
'#weight' => 4,
'#parents' => array('extra', 'end_time'),
);
$form['extra']['timezone'] = array(
'#type' => 'radios',
'#title' => t('Default value timezone'),
'#default_value' => $component['extra']['timezone'],
'#description' => t('If using relative dates for a default value (e.g. "now") base the current time on this timezone.'),
'#description' => t('If using relative dates for a default value (for example, "now") base the current time on this timezone.'),
'#options' => array('user' => t('User timezone'), 'site' => t('Website timezone')),
'#weight' => 2,
'#access' => variable_get('configurable_timezones', 1),
@@ -91,26 +113,47 @@ function _webform_edit_time($component) {
'#weight' => 3,
'#parents' => array('extra', 'minuteincrements'),
);
$form['#validate'] = array('_webform_edit_time_validate');
return $form;
}
/**
* Implements hook_form_id_validate().
*
* Validate start and end times.
*/
function _webform_edit_time_validate($form, &$form_state) {
// Validate that the start and end times are valid. Don't validate the default
// time because with token substitution, it might not be valid at component
// definition time. The end time may be before the start time to facilitate
// time ranges spanning midnight.
foreach (array('start_time', 'end_time') as $field) {
$time[$field] = FALSE;
if (trim($form_state['values']['extra'][$field]) && ($time[$field] = strtotime('1-1-1970 UTC ' . $form_state['values']['extra'][$field])) === FALSE) {
form_set_error("extra][$field", t("The @field isn't a valid time.", array('@field' => $form['validation'][$field]['#title'])));
}
}
}
/**
* Implements _webform_render_component().
*/
function _webform_render_time($component, $value = NULL, $filter = TRUE) {
function _webform_render_time($component, $value = NULL, $filter = TRUE, $submission = NULL) {
$node = isset($component['nid']) ? node_load($component['nid']) : NULL;
$element = array(
'#type' => 'webform_time',
'#title' => $filter ? _webform_filter_xss($component['name']) : $component['name'],
'#title' => $filter ? webform_filter_xss($component['name']) : $component['name'],
'#title_display' => $component['extra']['title_display'] ? $component['extra']['title_display'] : 'before',
'#required' => $component['mandatory'],
'#required' => $component['required'],
'#weight' => $component['weight'],
'#description' => $filter ? _webform_filter_descriptions($component['extra']['description'], $node) : $component['extra']['description'],
'#description' => $filter ? webform_filter_descriptions($component['extra']['description'], $node) : $component['extra']['description'],
'#element_validate' => array('webform_validate_time'),
'#start_time' => trim($component['extra']['start_time']),
'#end_time' => trim($component['extra']['end_time']),
'#hourformat' => $component['extra']['hourformat'],
'#minuteincrements' => $component['extra']['minuteincrements'],
'#default_value' => $filter ? _webform_filter_values($component['value'], $node, NULL, NULL, FALSE) : $component['value'],
'#default_value' => $filter ? webform_replace_tokens($component['value'], $node) : $component['value'],
'#timezone' => $component['extra']['timezone'],
'#process' => array('webform_expand_time'),
'#theme' => 'webform_time',
@@ -149,54 +192,58 @@ function webform_expand_time($element) {
'second' => '',
);
}
$start_hour = $element['#start_time'] ? date('G', strtotime('1-1-1970 ' . $element['#start_time'])) : FALSE;
$end_hour = $element['#end_time'] ? date('G', strtotime('1-1-1970 ' . $element['#end_time'])) : FALSE;
$reduced_range = ($start_hour !== FALSE && $start_hour > 0) || ($end_hour !== FALSE && $end_hour < 23);
$format_12_hour = $element['#hourformat'] == '12-hour';
$first_hour = 0;
$last_hour = 23;
if ($element['#hourformat'] == '12-hour') {
$first_hour = 1;
$last_hour = 12;
// Generate the choices for the hour drop-down select.
$hours = $format_12_hour && !$reduced_range ? array_slice(range(0, 12), 1, 12, TRUE) : range(0, 23);
if ($format_12_hour && $reduced_range) {
$hours = array_map(function ($hour) {
return (1 + ($hour + 11) % 12) . ($hour < 12 ? ' am' : ' pm');
}, $hours);
}
// Prune the hours to the allowed range.
if ($reduced_range) {
// $start_hour of FALSE type-juggles nicely to 0.
$end_hour = $end_hour === FALSE ? 23 : $end_hour;
if ($start_hour <= $end_hour) {
$hours = array_intersect_key($hours, array_flip(range($start_hour, $end_hour)));
}
else {
$hours = array_intersect_key($hours, array_flip(range($start_hour, 23))) +
array_intersect_key($hours, array_flip(range(0, $end_hour)));
}
}
// Generate the choices for the minute drop-down select.
$minutes = range(0, 59, $element['#minuteincrements']);
$minutes = array_combine($minutes, array_map(function ($minute) {
return substr('00' . $minute, -2);
}, $minutes));
// Add the labels to the drop-down selects.
$hours = array('' => t('Hour')) + $hours;
$minutes = array('' => t('Minute')) + $minutes;
// Adjust the default for minutes if needed, rounding down if needed.
// Rounding down eliminate the problem of rounding up going to the next hour.
// Worse, rounding 23:59 up would actually be the next day, which can't be
// represented because time components aren't linked to date components.
if (!isset($minutes[$default_values['minute']])) {
$default_values['minute'] -= $default_values['minute'] % $element['#minuteincrements'];
}
// Set the overall default value.
if ($default_values['hour'] !== '') {
$element['#default_value'] = webform_date_string($default_values);
}
// Convert default to 12-hour if needed.
if ($format_12_hour && !$reduced_range) {
$default_values = webform_time_convert($default_values, '12-hour');
$default_values['ampm'] = $default_values['ampm'] ? $default_values['ampm'] : 'am';
}
// Generate the choices for drop-down selects.
$hours[''] = t('hour');
$minutes[''] = t('minute');
for ($i = $first_hour; $i <= $last_hour; $i++) {
$hours[$i] = $i;
}
for ($i = 0; $i <= 59; $i += $element['#minuteincrements']) {
$minutes[$i] = $i < 10 ? "0$i" : $i;
}
$ampms = array('am' => t('am'), 'pm' => t('pm'));
// Adjust the default for minutes if needed, rounding up to the closest value.
if (!isset($minutes[$default_values['minute']])) {
foreach ($minutes as $minute => $padded_minute) {
if ($minute > $default_values['minute']) {
$default_values['minute'] = $minute;
break;
}
}
}
// If the above loop didn't set a value, it's because rounding up would go to
// the next hour. This gets quite a bit more complicated, since we need to
// deal with looping around on hours, as well as flipping am/pm.
if (!isset($minutes[$default_values['minute']])) {
$default_values['minute'] = 0;
$default_values['hour']++;
// If the hour rolls over also, set hour to the first hour in the list.
if (!isset($hours[$default_values['hour']])) {
$default_values['hour'] = $element['#hourformat'] == '12-hour' ? 1 : 0;
}
// If the hour has been incremented to 12:00 in 12-hour format, flip am/pm.
// Note that technically midnight and noon are neither am or pm, but common
// convention (and US standard) is to represent 12:00am as midnight.
// See http://en.wikipedia.org/wiki/Midnight#Start_and_end_of_day.
if ($element['#hourformat'] == '12-hour' && $default_values['hour'] == 12) {
$default_values['ampm'] = $default_values['ampm'] == 'am' ? 'pm' : 'am';
}
}
$element['hour'] = array(
@@ -215,19 +262,14 @@ function webform_expand_time($element) {
'#default_value' => $default_values['minute'],
'#options' => $minutes,
);
if (strcmp($element['#hourformat'], '12-hour') == 0) {
if ($format_12_hour && !$reduced_range) {
$element['ampm'] = array(
'#type' => 'radios',
'#default_value' => $default_values['ampm'],
'#options' => $ampms,
'#default_value' => $default_values['ampm'] ? $default_values['ampm'] : 'am',
'#options' => array('am' => t('am'), 'pm' => t('pm')),
);
}
// Set the overall default value.
if ($default_values['hour'] !== '') {
$element['#default_value'] = webform_date_string($default_values);
}
return $element;
}
@@ -246,29 +288,66 @@ function theme_webform_time($variables) {
$element['minute']['#attributes']['class'][] = 'error';
}
// Add HTML5 required attribute, if needed.
if ($element['#required']) {
$element['hour']['#attributes']['required'] = 'required';
$element['minute']['#attributes']['required'] = 'required';
if (!empty($element['ampm'])) {
$element['ampm']['am']['#attributes']['required'] = 'required';
$element['ampm']['pm']['#attributes']['required'] = 'required';
}
}
$output = '<div class="webform-container-inline">' . drupal_render($element['hour']) . drupal_render($element['minute']) . drupal_render($element['ampm']) . '</div>';
return $output;
}
/**
* Validate that the time data is valid, calling form_error() if not.
*/
function webform_validate_time($element, $form_state) {
$form_key = $element['#webform_component']['form_key'];
$name = $element['#webform_component']['name'];
// Check if the user filled the required fields.
foreach ($element['#hourformat'] == '12-hour' ? array('hour', 'minute', 'ampm') : array('hour', 'minute') as $field_type) {
if ($element[$field_type]['#value'] === '' && $element['#required']) {
form_error($element, t('%field field is required.', array('%field' => $name)));
return;
if ($element['#required']) {
foreach (array('hour', 'minute', 'ampm') as $field_type) {
if (isset($element[$field_type]) && $element[$field_type]['#value'] === '') {
form_error($element, t('!name field is required.', array('!name' => $element['#title'])));
return;
}
}
}
// Check for a valid time.
if ($element['hour']['#value'] !== '' || $element['minute']['#value'] !== '') {
// Check for a valid time. Allow a minute with no hour as "no time set".
if ($element['hour']['#value'] !== '') {
if (!is_numeric($element['hour']['#value']) || !is_numeric($element['minute']['#value']) || (isset($element['ampm']) && $element['ampm']['#value'] === '')) {
form_error($element, t('Entered %name is not a valid time.', array('%name' => $name)));
form_error($element, t('Entered !name is not a valid time.', array('!name' => $element['#title'])));
return;
}
// Enforce the start and end times, if any.
$timestamp = strtotime($element['hour']['#value'] . ':' . $element['minute']['#value'] . ' ' .
(isset($element['ampm']) ? $element['ampm']['#value'] : ''));
$start_time = strtotime($element['#start_time']);
$end_time = strtotime($element['#end_time']);
$subs = array(
'@start_time' => $element['#start_time'],
'@end_time' => $element['#end_time'],
);
if ($start_time !== FALSE && $end_time !== FALSE && $start_time > $end_time) {
// Validate as "over midnight" date range.
if ($end_time < $timestamp && $timestamp < $start_time) {
form_error($element, t('The entered time must be from @start_time to midnight to @end_time.', $subs));
}
}
else {
// Validate the start and end times are a regular (over noon) time range.
if ($start_time !== FALSE && $timestamp < $start_time) {
form_error($element, t('The entered time must be no earlier than @start_time.', $subs));
}
if ($end_time !== FALSE && $timestamp > $end_time) {
form_error($element, t('The entered time must be no later than @end_time.', $subs));
}
}
}
}
@@ -288,7 +367,7 @@ function _webform_submit_time($component, $value) {
/**
* Implements _webform_display_component().
*/
function _webform_display_time($component, $value, $format = 'html') {
function _webform_display_time($component, $value, $format = 'html', $submission = array()) {
$value = webform_date_array(isset($value[0]) ? $value[0] : '', 'time');
if ($component['extra']['hourformat'] == '12-hour') {
$value = webform_time_convert($value, '12-hour');
@@ -296,6 +375,7 @@ function _webform_display_time($component, $value, $format = 'html') {
return array(
'#title' => $component['name'],
'#title_display' => $component['extra']['title_display'] ? $component['extra']['title_display'] : 'before',
'#weight' => $component['weight'],
'#theme' => 'webform_display_time',
'#theme_wrappers' => $format == 'html' ? array('webform_element') : array('webform_element_text'),
@@ -326,15 +406,19 @@ function theme_webform_display_time($variables) {
/**
* Implements _webform_analysis_component().
*/
function _webform_analysis_time($component, $sids = array()) {
function _webform_analysis_time($component, $sids = array(), $single = FALSE, $join = NULL) {
$query = db_select('webform_submitted_data', 'wsd', array('fetch' => PDO::FETCH_ASSOC))
->fields('wsd', array('no', 'data'))
->condition('nid', $component['nid'])
->condition('cid', $component['cid'])
->orderBy('sid');
->condition('wsd.nid', $component['nid'])
->condition('wsd.cid', $component['cid'])
->orderBy('wsd.sid');
if (count($sids)) {
$query->condition('sid', $sids, 'IN');
$query->condition('wsd.sid', $sids, 'IN');
}
if ($join) {
$query->innerJoin($join, 'ws2_', 'wsd.sid = ws2_.sid');
}
$result = $query->execute();
@@ -352,7 +436,10 @@ function _webform_analysis_time($component, $sids = array()) {
$nonblanks = count($times);
$rows[0] = array(t('Left Blank'), ($submissions - $nonblanks));
$rows[1] = array(t('User entered value'), $nonblanks);
return $rows;
return array(
'table_rows' => $rows,
);
}
/**
@@ -381,7 +468,7 @@ function _webform_csv_headers_time($component, $export_options) {
$header = array();
$header[0] = '';
$header[1] = '';
$header[2] = $component['name'];
$header[2] = $export_options['header_keys'] ? $component['form_key'] : $component['name'];
return $header;
}
@@ -391,7 +478,8 @@ function _webform_csv_headers_time($component, $export_options) {
function _webform_csv_data_time($component, $export_options, $value) {
if ($value[0]) {
$time = webform_date_array($value[0], 'time');
if ($component['extra']['hourformat'] == '24-hour') {
// An ISO 8601 time is the same as 24-hour time.
if (!empty($export_options['iso8601_time']) || $component['extra']['hourformat'] == '24-hour') {
return sprintf('%02d', $time['hour']) . ':' . sprintf('%02d', $time['minute']);
}
else {
@@ -407,11 +495,12 @@ function _webform_csv_data_time($component, $export_options, $value) {
/**
* Convert a time between a 24-hour and a 12-hour value.
*
* @param $array
* @param array $array
* An array of hour, minute, second, and optionally ampm.
* @param $format
* @param string $format
* Either 12-hour or 24-hour.
* @return
*
* @return array
* An array with hour, minute, second, and ampm (if using "12-hour").
*/
function webform_time_convert($array, $format) {

View File

@@ -24,6 +24,46 @@
margin-left: 20px;
}
/* Component table */
.webform-component-formkey,
.webform-component-value {
max-width: 7em;
word-wrap: break-word;
}
/* Analysis pages */
.webform-analysis-component {
width: 33.33%;
margin: 0 -2px;
padding: 0;
display: inline-block;
vertical-align: top;
}
@media all and (max-width: 1024px) {
.webform-analysis-component {
width: 50%;
}
}
@media all and (max-width: 600px) {
.webform-analysis-component {
width: 100%;
}
}
.webform-analysis-component-grid {
width: 100%;
clear: both;
}
.webform-analysis-component-inner {
padding: 12px;
overflow: auto;
}
.webform-analysis-component table {
width: 100%;
}
.webform-analysis-component table td {
white-space: normal;
}
/* Element for selecting components, i.e. included components for e-mails. */
.webform-component-select-wrapper {
max-height: 300px;
@@ -45,6 +85,9 @@
height: 12px;
margin: 0 2px 2px;
}
.webform-component-select-suffix {
margin-top: 10px;
}
.webform-select-list-format table {
border: 1px solid;
width: auto;
@@ -61,20 +104,6 @@ td.webform-pagebreak {
font-weight: bold;
}
/* Special theming for the options element widget (if installed) */
.webform-options-element thead {
display: none;
}
.webform-options-element fieldset {
border: none;
background: none;
margin: 0;
padding: 0;
}
.webform-options-element fieldset legend {
display: none;
}
/* Checkboxes for allowed file extensions */
table.webform-file-extensions td {
vertical-align: top;
@@ -99,9 +128,6 @@ table.webform-file-extensions input.form-text {
.webform-container-inline div.form-item {
display: inline;
}
.webform-default-value {
color: #999;
}
.webform-results-per-page a.selected {
font-weight: bold;
}
@@ -114,6 +140,100 @@ html.js div.webform-position {
tr.webform-add-form .tabledrag-changed {
display: none;
}
#webform-emails tr.webform-add-form,
#webform-components tr.webform-add-form {
background-color: inherit;
}
/* E-mail configuration */
html.js .webform-email-mapping {
display: none;
}
td.webform-email-option {
text-align: right;
}
/* Conditionals */
.webform-conditional,
.webform-conditional-new {
display: block;
position: relative;
padding-left: 1em;
padding-right: 9em;
margin-left: 3em;
max-width: 750px;
}
.webform-conditional-new {
text-align: right;
margin-left: 12em;
padding-right: 0;
}
.webform-conditional-if {
position: absolute;
left: -.5em;
margin-top: .2em;
}
.webform-conditional-rule {
margin: .5em 0;
}
.webform-conditional-condition {
display: inline;
}
.webform-conditional-operations {
position: absolute;
right: 0;
margin-top: .1em;
}
.webform-andor {
padding-left: 0.5em;
}
.webform-subconditional {
font-size: 1.5em;
line-height: 0;
}
.webform-indentation {
float: left;
width: 2em;
}
.webform-conditional-new input[disabled] {
visibility: hidden;
}
.webform-conditional-new input.form-submit,
.webform-conditional-operations input.form-submit {
margin: 0 2px;
padding: 0 6px 2px 6px;
}
#webform-conditionals-table input.progress-disabled,
.webform-conditional-operations input.progress-disabled {
float: none;
}
#webform-conditionals-table .ahah-progress-throbber {
float: none;
display: inline;
}
#webform-conditionals-table .ahah-progress-throbber .throbber {
float: none;
display: inline;
padding-right: 12px;
}
.webform-conditional-andor {
display: inline;
}
.webform-conditional-andor .form-item {
margin: 0;
padding: 0;
}

View File

@@ -28,3 +28,53 @@ html.js input.webform-calendar {
.webform-container-inline div.ajax-progress-bar div {
display: inherit;
}
.webform-container-inline.webform-component-textarea label {
vertical-align: top;
}
.webform-container-inline.webform-component-textarea .form-textarea-wrapper {
display: inline-block;
}
.webform-component-textarea .grippie {
display: block;
}
.webform-progressbar {
width: 90%;
margin: 0 auto;
text-align: center;
}
.webform-progressbar-inner {
height: 1em;
background-color: #74c421;
height: 3px;
}
.webform-progressbar-outer {
position: relative;
border: 1px solid #356900;
width: 100%;
height: 3px;
margin: 0.35em -1px 2em;
background-color: white;
}
.webform-progressbar-page {
position: absolute;
width: 7px;
height: 7px;
margin: -6px -4px;
border: 1px solid #356900;
background-color: white;
border-radius: 5px;
}
.webform-progressbar-page.completed {
background-color: #74c421;
}
.webform-progressbar-page.current {
background-color: #74c421;
}
.webform-progressbar-page .webform-progressbar-page-number {
display: none;
}
.webform-progressbar-page .webform-progressbar-page-label {
position: relative;
top: 10px;
margin: 0 -10em;
}

View File

@@ -0,0 +1,103 @@
<?php
/**
* Base class defining the common methods available to exporters.
*/
class webform_exporter {
public $options = array();
public $export_wordrap;
/**
* Constructor for webform_exporter classes.
*
* @param $options
* The array of export options as provided by the user-interface.
*/
public function __construct($options) {
$this->options = $options;
$this->export_wordwrap = webform_variable_get('webform_export_wordwrap');
}
/**
* Determines whether a cell is eligible for word-wrapping.
*
* This is based upon position in file and the contents of cell.
*
* Return true when the global word-wrapping option is enabled and the cell
* is anything other than the first column in either of the first two rows.
* By default, these rows are long and are intended to overlap the columns
* to the right. Also returns true when the cell contains a return character.
*
* @param int $row
* Row number, counting from 0.
* @param int $column
* Column number, counting from 0.
* @param string $value
* The value of the cell.
*
* @return bool
* Whether the cell position is eligible for wordwrapping.
*/
public function wrappable($row, $column, $value) {
return strpos($value, "\n") !== FALSE ||
$this->export_wordwrap && ($row > 2 || $column > 0 || $this->options['header_keys'] < 0);
}
/**
* Add a single row to the export file.
*
* @param $file_handle
* A PHP file handle to the export file.
* @param array $data
* An array of formatted data for this row. One cell per item.
* @param int $row_count
* The current number of rows in the export file.
*/
public function add_row(&$file_handle, array $data, $row_count) {
}
/**
* Provide headers to the page when an export file is being downloaded.
*
* @param string $filename
* The name of the file being downloaded, for example, export.xls.
*/
public function set_headers($filename) {
drupal_add_http_header('Content-Type', 'application/force-download');
drupal_add_http_header('Pragma', 'public');
drupal_add_http_header('Cache-Control', 'max-age=0');
}
/**
* Write the start of the export file.
*
* @param $file_handle
* A PHP file handle to the export file.
*/
public function bof(&$file_handle) {
}
/**
* Write the end of the export file.
*
* @param $file_handle
* A PHP file handle to the export file.
* @param int $row_count
* @param int $col_count
*/
public function eof(&$file_handle, $row_count, $col_count) {
}
/**
* Allow final processing of the results.
*
* @param $results
* An array of result data, including:
* - node: The node whose results are being downloaded.
* - file_name: The full file path of the generated file.
* - row_count: The final number of rows in the generated file.
*/
public function post_process(&$results) {
}
}

View File

@@ -0,0 +1,60 @@
<?php
/**
* Webform exporter for creating CSV/TSV delimited files.
*/
class webform_exporter_delimited extends webform_exporter {
public $line_ending;
public $delimiter;
/**
* {@inheritdoc}
*/
public function __construct($options) {
$this->line_ending = webform_variable_get('webform_csv_line_ending');
$this->delimiter = isset($options['delimiter']) ? $options['delimiter'] : ',';
// Convert tabs.
if ($this->delimiter == '\t') {
$this->delimiter = "\t";
}
$options['delimiter'] = $this->delimiter;
parent::__construct($options);
}
/**
* {@inheritdoc}
*/
public function add_row(&$file_handle, array $data, $row_count) {
foreach ($data as $key => $value) {
// Escape inner quotes and wrap all contents in new quotes.
$data[$key] = '"' . str_replace('"', '""', $data[$key]) . '"';
// Remove <script> tags, which mysteriously cause Excel not to import.
$data[$key] = preg_replace('!<(/?script.*?)>!', '[$1]', $data[$key]);
}
$row = implode($this->delimiter, $data) . $this->line_ending;
@fwrite($file_handle, $row);
}
/**
* {@inheritdoc}
*/
public function set_headers($filename) {
parent::set_headers($filename);
// Convert tabs.
if ($this->delimiter == "\t") {
$extension = 'tsv';
$content_type = 'text/tab-separated-values';
}
else {
$extension = 'csv';
$content_type = 'text/csv';
}
drupal_add_http_header('Content-Type', $content_type);
drupal_add_http_header('Content-Disposition', "attachment; filename=$filename.$extension");
}
}

View File

@@ -0,0 +1,60 @@
<?php
/**
* The Excel exporter currently is just a tab-delimited export.
*/
class webform_exporter_excel_delimited extends webform_exporter_delimited {
/**
* {@inheritdoc}
*/
public function __construct($options) {
$options['delimiter'] = '\t';
parent::__construct($options);
}
/**
* {@inheritdoc}
*/
public function bof(&$file_handle) {
$output = '';
// Include at BOM at the beginning of the file for Little Endian.
// This makes tab-separated imports work correctly in MS Excel.
if (function_exists('mb_convert_encoding')) {
$output = chr(255) . chr(254);
}
@fwrite($file_handle, $output);
}
/**
* {@inheritdoc}
*/
public function add_row(&$file_handle, array $data, $row_count) {
foreach ($data as $key => $value) {
// Escape inner quotes and wrap all contents in new quotes.
$data[$key] = '"' . str_replace('"', '""', $data[$key]) . '"';
// Remove <script> tags, which mysteriously cause Excel not to import.
$data[$key] = preg_replace('!<(/?script.*?)>!', '[$1]', $data[$key]);
}
$row = implode($this->delimiter, $data) . "\n";
if (function_exists('mb_convert_encoding')) {
$row = mb_convert_encoding($row, 'UTF-16LE', 'UTF-8');
}
@fwrite($file_handle, $row);
}
/**
* {@inheritdoc}
*/
public function set_headers($filename) {
drupal_add_http_header('Content-Type', 'application/x-msexcel');
drupal_add_http_header('Content-Disposition', "attachment; filename=$filename.xls");
drupal_add_http_header('Pragma', 'public');
drupal_add_http_header('Cache-Control', 'max-age=0');
}
}

View File

@@ -0,0 +1,267 @@
<?php
/**
* This exporter creates an XLSX file readable by newer versions of Excel.
*/
class webform_exporter_excel_xlsx extends webform_exporter {
/**
* Regular expression that checks for a valid ISO 8601 date/time.
*/
const DATE_REGEX_ANY = '/^((\d{4})(-(\d{2}))(-(\d{2})))?(([T \s]?(\d{2}))(:(\d{2}))(:(\d{2}))?)?$/';
const DATE_REGEX_DATE = '/^((\d{4})(-(\d{2}))(-(\d{2})))$/';
const DATE_REGEX_TIME = '/^(([T \s]?(\d{2}))(:(\d{2}))(:(\d{2}))?)?$/';
/**
* {@inheritdoc}
*/
public function add_row(&$file_handle, array $data, $row_count) {
$row = $row_count + 1;
$col = 'A';
$output = '<row>';
$utc_timezone = new DateTimeZone('UTC');
foreach ($data as $key => $value) {
// Strip UTF8 characters that are not legal in XML files.
// See http://www.w3.org/TR/xml/#charsets
// See http://stackoverflow.com/questions/3466035/how-to-skip-invalid-characters-in-xml-file-using-php
// @code
// Char ::= #x9 | #xA | #xD | [#x20-#xD7FF] | [#xE000-#xFFFD] | [#x10000-#x10FFFF]
// @endcode
$value = preg_replace('/[^\x{0009}\x{000a}\x{000d}\x{0020}-\x{D7FF}\x{E000}-\x{FFFD}]+/u', '', $value);
$cell_position = $col . $row;
if (strlen($value) === 0) {
// Skip empty cells.
}
elseif (preg_match(self::DATE_REGEX_ANY, $value)) {
// An Excel timestamp is the number of days since Jan 1, 1900, with
// the decimal portion indicating the time (ddddd.tttttt).
// To calculate, take the UNIX timestamp then add the number of days
// between 1900 and 1970 (25568).
$timestamp = date_timestamp_get(date_create($value, $utc_timezone));
$excel_timestamp = ($timestamp / 86400) + 25568;
// 1900 is treated as a leap year, but it is not. So all dates after
// Feb 28, 1900 have one extra day added. That is "59" should be
// March 1, 1900, but it's considered Feb 29, 1900, which didn't exist.
// So all dates greater than 59 have 1 extra day added.
// See http://www.cpearson.com/excel/datetime.htm.
if ($excel_timestamp >= 59) {
$excel_timestamp = $excel_timestamp + 1.0;
}
// Excel does not support dates prior to 0 (Jan 1, 1900). They have to
// be represented as plain-text instead.
if ($excel_timestamp <= 0) {
$output .= '<c r="' . $cell_position . '" t="inlineStr"><is><t>';
$output .= htmlspecialchars($value, ENT_QUOTES);
$output .= '</t></is></c>';
}
// Now after calculating the Excel "timestamp", save it as a decimal
// and point to a style formatter to make it appear as a date/time.
else {
// 1: Dates.
// 2: Times.
// 3: Date times.
// These are tied to style definitions in the styles.xml file
// generated by webform_exporter_excel_xlsx::xlsx_parts().
if (preg_match(self::DATE_REGEX_DATE, $value)) {
$style_format = 1;
}
elseif (preg_match(self::DATE_REGEX_TIME, $value)) {
// Only take the time portion of time values.
$excel_timestamp = $excel_timestamp - (int) $excel_timestamp;
$style_format = 2;
}
else {
$style_format = 3;
}
$output .= '<c r="' . $cell_position . '" s="' . $style_format . '"><v>';
$output .= $excel_timestamp;
$output .= '</v></c>';
}
}
else {
$output .= '<c r="' . $cell_position . '" t="inlineStr"' . ($this->wrappable($row_count, $key, $value) ? ' s="4"' : '') . '><is><t>';
$output .= htmlspecialchars($value, ENT_QUOTES, 'UTF-8');
$output .= '</t></is></c>';
}
// Unbelievably, in PHP you can increment on letters. "Z"++ becomes "AA"
// and "AA"++ becomes "AB", identical to Excel column names.
$col++;
}
$output .= '</row>';
$row++;
@fwrite($file_handle, $output);
}
/**
* Output space for the BOF.
*
* Our beginning of file needs to include unknown data (the number of columns
* and rows) at this point. Instead of writing the true BOF, we output enough
* empty space to fill in the BOF later. See
* webform_exporter_excel_xlsx::eof().
*/
public function bof(&$file_handle) {
$output = str_repeat(' ', 1024);
@fwrite($file_handle, $output . "\n");
}
/**
* Output the BOF and end the file.
*
* We output a chunk of empty data in webform_exporter_excel_xlsx::bof() to
* leave room for our real header, which includes the important <dimension>
* tag. This is required for proper importing into Google Docs.
*/
public function eof(&$file_handle, $row_count, $col_count) {
// Convert column count to letter representation.
$col = 'A';
for ($n = 1; $n < $col_count; $n++) {
$col++;
}
$bof = '';
$bof .= '<?xml version="1.0" encoding="UTF-8" standalone="yes"?>' . "\n";
$bof .= '<worksheet xmlns="http://schemas.openxmlformats.org/spreadsheetml/2006/main">';
$bof .= '<dimension ref="A1:' . $col . $row_count . '"/>';
$bof .= '<sheetData>';
@fseek($file_handle, 0);
@fwrite($file_handle, $bof);
$eof = '';
$eof .= '</sheetData>';
$eof .= '</worksheet>';
fseek($file_handle, 0, SEEK_END);
fwrite($file_handle, $eof);
}
/**
* {@inheritdoc}
*/
public function post_process(&$results) {
// Our download file is currently a single XML sheet file. We need to add
// the peripheral XML files to make this into a XLSX directory, then zip it.
$file_uri = $results['file_name'];
$zip_uri = _webform_export_tempname();
// ZipArchive does not support stream wrappers, convert to filesystem path.
$zip_filepath = drupal_realpath($zip_uri);
$file_filepath = drupal_realpath($file_uri);
$zip = new ZipArchive();
if ($zip->open($zip_filepath, ZipArchive::CREATE) === TRUE) {
// Create a bare-bones Office Open XML format directory structure. This is
// based on the sample simple XLSX file at
// http://blogs.msdn.com/b/chrisrae/archive/2011/08/18/creating-a-simple-xlsx-from-scratch-using-the-open-xml-sdk.aspx
$parts = $this->xlsx_parts();
foreach ($parts as $file_name => $file_contents) {
if (empty($file_contents)) {
$zip->addEmptyDir($file_name);
}
else {
$zip->addFromString($file_name, $file_contents);
}
}
// Add the actual export to the zip.
$zip->addEmptyDir('xl/worksheets');
$zip->addFile($file_filepath, 'xl/worksheets/sheet1.xml');
$zip->close();
// Switch the results file name to the new zip (xlsx) file.
unlink($file_uri);
if (!@rename($zip_uri, $file_uri)) {
// The file could not be renamed, probably due to different stream
// wrappers during drush wfx execution.
copy($zip_uri, $file_uri);
unlink($zip_uri);
}
}
}
/**
* {@inheritdoc}
*/
public function set_headers($filename) {
drupal_add_http_header('Content-Type', 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet');
drupal_add_http_header('Content-Disposition', "attachment; filename=$filename.xlsx");
drupal_add_http_header('Pragma', 'public');
drupal_add_http_header('Cache-Control', 'max-age=0');
}
/**
* Return all the parts needed to assemble a bare-bones XLSX package.
*/
public function xlsx_parts() {
$parts['_rels'] = '';
$parts['_rels/.rels'] = <<<EOL
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<Relationships xmlns="http://schemas.openxmlformats.org/package/2006/relationships">
<Relationship Id="rId1" Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/officeDocument" Target="xl/workbook.xml"/>
</Relationships>
EOL;
$parts['xl'] = '';
$parts['xl/_rels'] = '';
$parts['xl/_rels/workbook.xml.rels'] = <<<EOL
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<Relationships xmlns="http://schemas.openxmlformats.org/package/2006/relationships">
<Relationship Id="rId1" Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/worksheet" Target="worksheets/sheet1.xml"/>
<Relationship Id="rId2" Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/styles" Target="styles.xml"/>
</Relationships>
EOL;
$parts['xl/styles.xml'] = <<<EOL
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<styleSheet xmlns="http://schemas.openxmlformats.org/spreadsheetml/2006/main">
<fonts count="1"><font><name val="Verdana"/></font></fonts>
<fills count="1"><fill><patternFill patternType="none"/></fill></fills>
<borders count="1"><border><left/><right/><top/><bottom/><diagonal/></border></borders>
<cellStyleXfs count="1"><xf numFmtId="0" fontId="0" fillId="0" borderId="0"/></cellStyleXfs>
<cellXfs count="5">
<xf numFmtId="0" fontId="0" fillId="0" borderId="0" xfId="0"/>
<xf numFmtId="14" fontId="0" fillId="0" borderId="0" xfId="0" applyNumberFormat="1"/>
<xf numFmtId="18" fontId="0" fillId="0" borderId="0" xfId="0" applyNumberFormat="1"/>
<xf numFmtId="22" fontId="0" fillId="0" borderId="0" xfId="0" applyNumberFormat="1"/>
<xf numFmtId="0" fontId="0" fillId="0" borderId="0" xfId="0" applyAlignment="1">
<alignment wrapText="1"/>
</xf>
</cellXfs>
<cellStyles count="1"><cellStyle name="Normal" xfId="0" builtinId="0"/></cellStyles>
<dxfs count="0"/>
<tableStyles count="0"/>
</styleSheet>
EOL;
$parts['xl/workbook.xml'] = <<<EOL
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<workbook xmlns="http://schemas.openxmlformats.org/spreadsheetml/2006/main" xmlns:r="http://schemas.openxmlformats.org/officeDocument/2006/relationships">
<workbookPr />
<sheets>
<sheet name="Sheet1" sheetId="1" r:id="rId1"/>
</sheets>
<calcPr calcId="0"/>
<fileRecoveryPr repairLoad="1"/>
</workbook>
EOL;
$parts['[Content_Types].xml'] = <<<EOL
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<Types xmlns="http://schemas.openxmlformats.org/package/2006/content-types">
<Default Extension="rels" ContentType="application/vnd.openxmlformats-package.relationships+xml"/>
<Default Extension="xml" ContentType="application/xml"/>
<Override PartName="/xl/workbook.xml" ContentType="application/vnd.openxmlformats-officedocument.spreadsheetml.sheet.main+xml"/>
<Override PartName="/xl/worksheets/sheet1.xml" ContentType="application/vnd.openxmlformats-officedocument.spreadsheetml.worksheet+xml"/>
<Override PartName="/xl/styles.xml" ContentType="application/vnd.openxmlformats-officedocument.spreadsheetml.styles+xml"/>
</Types>
EOL;
return $parts;
}
}

View File

@@ -11,15 +11,6 @@
function webform_admin_settings() {
module_load_include('inc', 'webform', 'includes/webform.export');
$node_types = node_type_get_names();
$form['node_types'] = array(
'#type' => 'checkboxes',
'#title' => t('Webform-enabled content types'),
'#description' => t('Webform allows you to enable the webform components for any content type. Choose the types on which you would like to associate webform components.'),
'#options' => $node_types,
'#default_value' => webform_variable_get('webform_node_types'),
);
$form['components'] = array(
'#type' => 'fieldset',
'#title' => t('Available components'),
@@ -48,49 +39,103 @@ function webform_admin_settings() {
'#collapsed' => FALSE,
);
$form['email']['webform_default_from_address'] = array(
$form['email']['webform_default_from_address'] = array(
'#type' => 'textfield',
'#title' => t('From address'),
'#default_value' => variable_get('webform_default_from_address', variable_get('site_mail', ini_get('sendmail_from'))),
'#default_value' => webform_variable_get('webform_default_from_address'),
'#description' => t('The default sender address for emailed webform results; often the e-mail address of the maintainer of your forms.'),
);
$form['email']['webform_default_from_name'] = array(
$form['email']['webform_default_from_name'] = array(
'#type' => 'textfield',
'#title' => t('From name'),
'#default_value' => variable_get('webform_default_from_name', variable_get('site_name', '')),
'#default_value' => webform_variable_get('webform_default_from_name'),
'#description' => t('The default sender name which is used along with the default from address.'),
);
$form['email']['webform_default_subject'] = array(
$form['email']['webform_default_subject'] = array(
'#type' => 'textfield',
'#title' => t('Default subject'),
'#default_value' => variable_get('webform_default_subject', t('Form submission from: %title')),
'#default_value' => webform_variable_get('webform_default_subject'),
'#description' => t('The default subject line of any e-mailed results.'),
);
$form['email']['webform_default_format'] = array(
$form['email']['webform_email_replyto'] = array(
'#type' => 'checkbox',
'#title' => t('Use Reply-To header'),
'#default_value' => webform_variable_get('webform_email_replyto'),
'#description' => t('If the default from name and address are set above, send all e-mail from the default address set the "Reply-To" header to the actual sender. This helps prevent e-mail from being flagged as spam.'),
);
$form['email']['webform_email_html_capable'] = array(
'#type' => 'checkbox',
'#title' => t('HTML mail system'),
'#default_value' => webform_variable_get('webform_email_html_capable'),
'#description' => t('Whether the mail system configured for webform is capable of sending mail in HTML format.'),
);
$form['email']['webform_default_format'] = array(
'#type' => 'radios',
'#title' => t('Format'),
'#options' => array(
0 => t('Plain text'),
1 => t('HTML'),
),
'#default_value' => variable_get('webform_default_format', 0),
'#default_value' => webform_variable_get('webform_default_format'),
'#description' => t('The default format for new e-mail settings. Webform e-mail options take precedence over the settings for system-wide e-mails configured in MIME mail.'),
'#access' => webform_email_html_capable(),
'#states' => array(
'visible' => array(
':input[name="webform_email_html_capable"]' => array('checked' => TRUE),
),
),
);
$form['email']['webform_format_override'] = array(
$form['email']['webform_format_override'] = array(
'#type' => 'radios',
'#title' => t('Format override'),
'#options' => array(
0 => t('Per-webform configuration of e-mail format'),
1 => t('Send all e-mails in the default format'),
),
'#default_value' => variable_get('webform_format_override', 0),
'#default_value' => webform_variable_get('webform_format_override'),
'#description' => t('Force all webform e-mails to be sent in the default format.'),
'#access' => webform_email_html_capable(),
'#states' => array(
'visible' => array(
':input[name="webform_email_html_capable"]' => array('checked' => TRUE),
),
),
);
$form['progressbar'] = array(
'#type' => 'fieldset',
'#title' => t('Progress bar'),
'#collapsible' => TRUE,
'#collapsed' => TRUE,
);
$form['progressbar']['webform_progressbar_style'] = array(
'#type' => 'checkboxes',
'#title' => t('Progress bar style'),
'#options' => array(
'progressbar_bar' => t('Show progress bar'),
'progressbar_page_number' => t('Show page number as number of completed (i.e. Page 1 of 10)'),
'progressbar_percent' => t('Show percentage completed (i.e. 10%)'),
'progressbar_pagebreak_labels' => t('Show page labels from page break components'),
'progressbar_include_confirmation' => t('Include confirmation page in progress bar'),
),
'#default_value' => webform_variable_get('webform_progressbar_style'),
'#description' => t('Choose how the progress bar should be displayed for multi-page forms.'),
);
$form['progressbar']['webform_progressbar_label_first'] = array(
'#type' => 'textfield',
'#title' => t('First page label'),
'#default_value' => webform_variable_get('webform_progressbar_label_first'),
'#maxlength' => 255,
);
$form['progressbar']['webform_progressbar_label_confirmation'] = array(
'#type' => 'textfield',
'#title' => t('Confirmation page label'),
'#default_value' => webform_variable_get('webform_progressbar_label_confirmation'),
'#maxlength' => 255,
);
$form['advanced'] = array(
@@ -98,23 +143,19 @@ function webform_admin_settings() {
'#title' => t('Advanced options'),
'#collapsible' => TRUE,
'#collapsed' => TRUE,
'#weight' => 20,
);
$form['advanced']['webform_use_cookies'] = array(
'#type' => 'checkbox',
'#checked_value' => 1,
'#title' => t('Allow cookies for tracking submissions'),
'#default_value' => variable_get('webform_use_cookies', 0),
'#description' => t('<a href="http://www.wikipedia.org/wiki/HTTP_cookie">Cookies</a> can be used to help prevent the same user from repeatedly submitting a webform. This feature is not needed for limiting submissions per user, though it can increase accuracy in some situations. Besides cookies, Webform also uses IP addresses and site usernames to prevent repeated submissions.'),
);
$form['advanced']['webform_search_index'] = array(
'#type' => 'checkbox',
'#checked_value' => 1,
'#title' => t('Include webform forms in search index'),
'#default_value' => variable_get('webform_search_index', 1),
'#description' => t('When selected, all Webform nodes will have their form components indexed by the search engine.'),
'#access' => module_exists('search'),
$form['advanced']['webform_tracking_mode'] = array(
'#type' => 'radios',
'#title' => t('Track anonymous users by:'),
'#options' => array(
'cookie' => t('Cookie only (least strict)'),
'ip_address' => t('IP address only'),
'strict' => t('Both cookie and IP address (most strict)'),
),
'#default_value' => webform_variable_get('webform_tracking_mode'),
'#description' => t('<a href="http://www.wikipedia.org/wiki/HTTP_cookie">Cookies</a> can be used to help prevent the same user from repeatedly submitting a webform. Limiting by IP address is more effective against repeated submissions, but may result in unintentional blocking of users sharing the same address. Confidential submissions are tracked by cookie only. Logged-in users are always tracked by their user ID and are not affected by this option.'),
);
$form['advanced']['webform_email_address_format'] = array(
@@ -124,22 +165,45 @@ function webform_admin_settings() {
'long' => t('Long format: "Example Name" &lt;name@example.com&gt;'),
'short' => t('Short format: name@example.com'),
),
'#default_value' => variable_get('webform_email_address_format', 'long'),
'#default_value' => webform_variable_get('webform_email_address_format'),
'#description' => t('Most servers support the "long" format which will allow for more friendly From addresses in e-mails sent. However many Windows-based servers are unable to send in the long format. Change this option if experiencing problems sending e-mails with Webform.'),
);
$form['advanced']['webform_email_address_individual'] = array(
'#type' => 'radios',
'#title' => t('E-mailing multiple recipients'),
'#options' => array(
'0' => t('Send a single e-mail to all recipients'),
'1' => t('Send individual e-mails to each recipient'),
),
'#default_value' => webform_variable_get('webform_email_address_individual'),
'#description' => t('Individual e-mails increases privacy by not revealing the addresses of other recipients. A single e-mail to all recipients lets them use "Reply All" to communicate.'),
);
$date_format_options = array();
foreach (system_get_date_types() as $type => $type_info) {
$date_format_options[$type] = t('@title — @sample', array('@title' => $type_info['title'], '@sample' => format_date(REQUEST_TIME, 'custom', webform_date_format($type))));
}
$form['advanced']['webform_date_type'] = array(
'#type' => 'select',
'#title' => t('Date format'),
'#options' => $date_format_options,
'#default_value' => webform_variable_get('webform_date_type'),
'#description' => t('Choose the format for the display of date components. Only the date portion of the format is used. Reporting and export use the short format.'),
);
$form['advanced']['webform_export_format'] = array(
'#type' => 'radios',
'#title' => t('Default export format'),
'#options' => webform_export_list(),
'#default_value' => variable_get('webform_export_format', 'delimited'),
'#default_value' => webform_variable_get('webform_export_format'),
);
$form['advanced']['webform_csv_delimiter'] = array(
$form['advanced']['webform_csv_delimiter'] = array(
'#type' => 'select',
'#title' => t('Default export delimiter'),
'#description' => t('This is the delimiter used in the CSV/TSV file when downloading Webform results. Using tabs in the export is the most reliable method for preserving non-latin characters. You may want to change this to another character depending on the program with which you anticipate importing results.'),
'#default_value' => variable_get('webform_csv_delimiter', '\t'),
'#default_value' => webform_variable_get('webform_csv_delimiter'),
'#options' => array(
',' => t('Comma (,)'),
'\t' => t('Tab (\t)'),
@@ -151,17 +215,46 @@ function webform_admin_settings() {
),
);
$form['advanced']['webform_submission_access_control'] = array(
$form['advanced']['webform_export_wordwrap'] = array(
'#type' => 'radios',
'#title' => t('Export word-wrap'),
'#options' => array(
'0' => t('Only text containing return characters'),
'1' => t('All text'),
),
'#default_value' => webform_variable_get('webform_export_wordwrap'),
'#description' => t('Some export formats, such as Microsoft Excel, support word-wrapped text cells.'),
);
$form['advanced']['webform_submission_access_control'] = array(
'#type' => 'radios',
'#title' => t('Submission access control'),
'#options' => array(
'1' => t('Select the user roles that may submit each individual webform'),
'0' => t('Disable Webform submission access control'),
),
'#default_value' => variable_get('webform_submission_access_control', 1),
'#default_value' => webform_variable_get('webform_submission_access_control'),
'#description' => t('By default, the configuration form for each webform allows the administrator to choose which roles may submit the form. You may want to allow users to always submit the form if you are using a separate node access module to control access to webform nodes themselves.'),
);
$form['advanced']['webform_token_access'] = array(
'#type' => 'radios',
'#title' => t('Token access'),
'#options' => array(
'1' => t('Allow tokens to be used in Webforms.'),
'0' => t('Disable tokens in Webforms'),
),
'#default_value' => webform_variable_get('webform_token_access'),
'#description' => t('Tokens can be used to reveal sensitive information. Allow tokens if Webform creators are trusted.'),
);
$form['advanced']['webform_email_select_max'] = array(
'#type' => 'textfield',
'#title' => t("Select email mapping limit"),
'#default_value' => webform_variable_get('webform_email_select_max'),
'#description' => t('When mapping emails addresses to a select component, limit the choice to components with less than the amount of options indicated. This is to avoid flooding the email settings form.'),
);
$form = system_settings_form($form);
$form['#theme'] = 'webform_admin_settings';
array_unshift($form['#submit'], 'webform_admin_settings_submit');
@@ -183,9 +276,8 @@ function webform_admin_settings_submit($form, &$form_state) {
$form_state['values']['webform_disabled_components'] = $disabled_components;
unset($form_state['values']['components']);
// Change the name of the node type variable and clean it up.
$form_state['values']['webform_node_types'] = array_keys(array_filter($form_state['values']['node_types']));
unset($form_state['values']['node_types']);
// Trim out empty options in the progress bar options.
$form_state['values']['webform_progressbar_style'] = array_keys(array_filter($form_state['values']['webform_progressbar_style']));
}
/**
@@ -220,6 +312,13 @@ function theme_webform_admin_settings($variables) {
* Menu callback for admin/content/webform. Displays all webforms on the site.
*/
function webform_admin_content() {
// Determine whether views or hard-coded tables should be used for the
// webforms table.
if (!webform_variable_get('webform_table')) {
$view = views_get_view('webform_webforms');
return $view->preview('default');
}
$query = db_select('webform', 'w');
$query->join('node', 'n', 'w.nid = n.nid');
$query->fields('n');
@@ -231,11 +330,11 @@ function webform_admin_content() {
* Create a comma-separate list of content types that are webform enabled.
*/
function webform_admin_type_list() {
$webform_types = webform_variable_get('webform_node_types');
$webform_types = webform_node_types();
$webform_type_list = '';
$webform_type_count = count($webform_types);
foreach ($webform_types as $n => $type) {
$webform_type_list .= l(node_type_get_name($type), 'node/add/' . $type);
$webform_type_list .= l(node_type_get_name($type), 'node/add/' . str_replace('_', '-', $type));
if ($n + 1 < $webform_type_count) {
$webform_type_list .= $webform_type_count == 2 ? ' ' : ', ';
}
@@ -248,14 +347,14 @@ function webform_admin_type_list() {
}
/**
* Generate a list of all webforms avaliable on this site.
* Generate a list of all webforms available on this site.
*/
function theme_webform_admin_content($variables) {
$nodes = $variables['nodes'];
$header = array(
t('Title'),
array('data' => t('View'), 'colspan' => '4'),
array('data' => t('Operations'), 'colspan' => '3')
array('data' => t('Operations'), 'colspan' => '3'),
);
$rows = array();
@@ -273,7 +372,7 @@ function theme_webform_admin_content($variables) {
}
if (empty($rows)) {
$webform_types = webform_variable_get('webform_node_types');
$webform_types = webform_node_types();
if (empty($webform_types)) {
$message = t('Webform is currently not enabled on any content types.') . ' ' . t('Visit the <a href="!url">Webform settings</a> page and enable Webform on at least one content type.', array('!url' => url('admin/config/content/webform')));
}

View File

@@ -31,7 +31,6 @@ function webform_components_page($node, $page_number = 1) {
* Builder module is available.
*/
function theme_webform_components_page($variables) {
$node = $variables['node'];
$form = $variables['form'];
return drupal_render($form);
@@ -69,10 +68,10 @@ function webform_components_form($form, $form_state, $node) {
'#title' => t('Weight'),
'#default_value' => $component['weight'],
);
$form['components'][$cid]['mandatory'] = array(
$form['components'][$cid]['required'] = array(
'#type' => 'checkbox',
'#title' => t('Mandatory'),
'#default_value' => $component['mandatory'],
'#title' => t('Required'),
'#default_value' => $component['required'],
'#access' => webform_component_feature($component['type'], 'required'),
);
if (!isset($max_weight) || $component['weight'] > $max_weight) {
@@ -82,8 +81,8 @@ function webform_components_form($form, $form_state, $node) {
$form['add']['name'] = array(
'#type' => 'textfield',
'#size' => 24,
'#maxlength' => 255,
'#size' => 30,
'#maxlength' => NULL,
);
$form['add']['type'] = array(
@@ -92,7 +91,7 @@ function webform_components_form($form, $form_state, $node) {
'#weight' => 3,
'#default_value' => (isset($_GET['cid']) && isset($node->webform['components'][$_GET['cid']])) ? $node->webform['components'][$_GET['cid']]['type'] : 'textfield',
);
$form['add']['mandatory'] = array(
$form['add']['required'] = array(
'#type' => 'checkbox',
);
$form['add']['cid'] = array(
@@ -135,127 +134,74 @@ function webform_components_form($form, $form_state, $node) {
'#submit' => array('webform_components_form_add_submit'),
);
$form['actions'] = array('#type' => 'actions');
$form['actions'] = array(
'#type' => 'actions',
'#weight' => 45,
);
$form['actions']['submit'] = array(
'#type' => 'submit',
'#value' => t('Save'),
'#weight' => 45,
'#access' => count($node->webform['components']) > 0,
);
$form['warning'] = array(
'#weight' => -1,
);
webform_input_vars_check($form, $form_state, 'components', 'warning');
return $form;
}
/**
* Theme the node components form. Use a table to organize the components.
*
* @param $form
* The form array.
* @return
* Formatted HTML form, ready for display.
* Preprocess variables for theming the webform components form.
*/
function theme_webform_components_form($variables) {
function template_preprocess_webform_components_form(&$variables) {
$form = $variables['form'];
$form['components']['#attached']['library'][] = array('webform', 'admin');
// TODO: Attach these. See http://drupal.org/node/732022.
// @todo: Attach these. See http://drupal.org/node/732022.
drupal_add_tabledrag('webform-components', 'order', 'sibling', 'webform-weight');
drupal_add_tabledrag('webform-components', 'match', 'parent', 'webform-pid', 'webform-pid', 'webform-cid');
$node = $form['#node'];
$header = array(t('Label'), t('Type'), t('Value'), t('Mandatory'), t('Weight'), array('data' => t('Operations'), 'colspan' => 3));
$header = array(t('Label'), t('Form key'), t('Type'), t('Value'), t('Required'), t('Weight'), array('data' => t('Operations'), 'colspan' => 3));
$rows = array();
// Add a row containing form elements for a new item.
unset($form['add']['name']['#title'], $form['add_type']['#description']);
$form['add']['name']['#attributes']['rel'] = t('New component name');
$form['add']['name']['#attributes']['class'] = array('webform-default-value');
$form['add']['cid']['#attributes']['class'] = array('webform-cid');
$form['add']['pid']['#attributes']['class'] = array('webform-pid');
$form['add']['weight']['#attributes']['class'] = array('webform-weight');
$form['add']['name']['#attributes']['placeholder'] = t('New component name');
$form['add']['cid']['#attributes']['class'][] = 'webform-cid';
$form['add']['pid']['#attributes']['class'][] = 'webform-pid';
$form['add']['weight']['#attributes']['class'][] = 'webform-weight';
$row_data = array(
drupal_render($form['add']['name']),
drupal_render($form['add']['type']),
'',
drupal_render($form['add']['mandatory']),
drupal_render($form['add']['cid']) . drupal_render($form['add']['pid']) . drupal_render($form['add']['weight']),
array('colspan' => 3, 'data' => drupal_render($form['add']['add'])),
array('data' => drupal_render($form['add']['name']), 'class' => array('webform-component-name'), 'colspan' => 2),
array('data' => drupal_render($form['add']['type']), 'class' => array('webform-component-type')),
array('data' => '', 'class' => array('webform-component-value')),
array('data' => drupal_render($form['add']['required']), 'class' => array('webform-component-required', 'checkbox')),
array('data' => drupal_render($form['add']['cid']) . drupal_render($form['add']['pid']) . drupal_render($form['add']['weight'])),
array('colspan' => 3, 'data' => drupal_render($form['add']['add']), 'class' => array('webform-component-add')),
);
$add_form = array('data' => $row_data, 'class' => array('draggable', 'webform-add-form'));
$form_rendered = FALSE;
$add_form = array('data' => $row_data, 'class' => array('draggable', 'webform-add-form', 'tabledrag-leaf'));
if (!empty($node->webform['components'])) {
$component_tree = array();
$page_count = 1;
_webform_components_tree_build($node->webform['components'], $component_tree, 0, $page_count);
$component_tree = _webform_components_tree_sort($component_tree);
// Build the table rows.
function _webform_components_form_rows($node, $cid, $component, $level, &$form, &$rows, &$add_form) {
// Create presentable values.
if (drupal_strlen($component['value']) > 30) {
$component['value'] = drupal_substr($component['value'], 0, 30);
$component['value'] .= '...';
}
$component['value'] = check_plain($component['value']);
// Remove individual titles from the mandatory and weight fields.
unset($form['components'][$cid]['mandatory']['#title']);
unset($form['components'][$cid]['pid']['#title']);
unset($form['components'][$cid]['weight']['#title']);
// Add special classes for weight and parent fields.
$form['components'][$cid]['cid']['#attributes']['class'] = array('webform-cid');
$form['components'][$cid]['pid']['#attributes']['class'] = array('webform-pid');
$form['components'][$cid]['weight']['#attributes']['class'] = array('webform-weight');
// Build indentation for this row.
$indents = '';
for ($n = 1; $n <= $level; $n++) {
$indents .= '<div class="indentation">&nbsp;</div>';
}
// Add each component to a table row.
$row_data = array(
$indents . filter_xss($component['name']),
$form['add']['type']['#options'][$component['type']],
($component['value'] == '') ? '-' : $component['value'],
drupal_render($form['components'][$cid]['mandatory']),
drupal_render($form['components'][$cid]['cid']) . drupal_render($form['components'][$cid]['pid']) . drupal_render($form['components'][$cid]['weight']),
l(t('Edit'), 'node/' . $node->nid . '/webform/components/' . $cid, array('query' => drupal_get_destination())),
l(t('Clone'), 'node/' . $node->nid . '/webform/components/' . $cid . '/clone', array('query' => drupal_get_destination())),
l(t('Delete'), 'node/' . $node->nid . '/webform/components/' . $cid . '/delete', array('query' => drupal_get_destination())),
);
$row_class = array('draggable');
if (!webform_component_feature($component['type'], 'group')) {
$row_class[] = 'tabledrag-leaf';
}
if ($component['type'] == 'pagebreak') {
$row_class[] = 'tabledrag-root';
$row_class[] = 'webform-pagebreak';
$row_data[0] = array('class' => array('webform-pagebreak'), 'data' => $row_data[0]);
}
$rows[] = array('data' => $row_data, 'class' => $row_class);
if (isset($component['children']) && is_array($component['children'])) {
foreach ($component['children'] as $cid => $component) {
_webform_components_form_rows($node, $cid, $component, $level + 1, $form, $rows, $add_form);
}
}
// Add the add form if this was the last edited component.
if (isset($_GET['cid']) && $component['cid'] == $_GET['cid'] && $add_form) {
$add_form['data'][0] = $indents . $add_form['data'][0];
$rows[] = $add_form;
$add_form = FALSE;
}
}
// Build the table rows recursively.
foreach ($component_tree['children'] as $cid => $component) {
_webform_components_form_rows($node, $cid, $component, 0, $form, $rows, $add_form);
}
}
else {
$rows[] = array(array('data' => t('No Components, add a component below.'), 'colspan' => 9));
// When there are no components, tabledrag.js will look to the add component
// row to locate the weight column. Because of the name/form_key colspan
// it will mis-count the columns and locate the Required column instead of
// the Weight column.
unset($add_form['data'][0]['colspan']);
array_splice($add_form['data'], 1, 0, '&nbsp;');
}
// Append the add form if not already printed.
@@ -263,9 +209,86 @@ function theme_webform_components_form($variables) {
$rows[] = $add_form;
}
$variables['rows'] = $rows;
$variables['header'] = $header;
$variables['form'] = $form;
}
/**
* Recursive function for nesting components into a table.
*
* @see preprocess_webform_components_form()
*/
function _webform_components_form_rows($node, $cid, $component, $level, &$form, &$rows, &$add_form) {
// Create presentable values.
$form_key = truncate_utf8($component['form_key'], 30, FALSE, TRUE);
$value = truncate_utf8($component['value'], 30, TRUE, TRUE, 20);
// Remove individual titles from the required and weight fields.
unset($form['components'][$cid]['required']['#title']);
unset($form['components'][$cid]['pid']['#title']);
unset($form['components'][$cid]['weight']['#title']);
// Add special classes for weight and parent fields.
$form['components'][$cid]['cid']['#attributes']['class'] = array('webform-cid');
$form['components'][$cid]['pid']['#attributes']['class'] = array('webform-pid');
$form['components'][$cid]['weight']['#attributes']['class'] = array('webform-weight');
// Build indentation for this row.
$indents = '';
for ($n = 1; $n <= $level; $n++) {
$indents .= '<div class="indentation">&nbsp;</div>';
}
// Add each component to a table row.
$row_data = array(
array('data' => $indents . filter_xss($component['name']), 'class' => array('webform-component-name')),
array('data' => check_plain($form_key), 'class' => array('webform-component-formkey')) +
((string) $component['form_key'] === (string) $form_key ? array() : array('title' => $component['form_key'])),
array('data' => $form['add']['type']['#options'][$component['type']], 'class' => array('webform-component-type')),
array('data' => ($value == '') ? '-' : check_plain($value), 'class' => array('webform-component-value')) +
($component['value'] == $value ? array() : array('title' => $component['value'])),
array('data' => drupal_render($form['components'][$cid]['required']), 'class' => array('webform-component-required', 'checkbox')),
array('data' => drupal_render($form['components'][$cid]['cid']) . drupal_render($form['components'][$cid]['pid']) . drupal_render($form['components'][$cid]['weight'])),
array('data' => l(t('Edit'), 'node/' . $node->nid . '/webform/components/' . $cid, array('query' => drupal_get_destination())), 'class' => array('webform-component-edit')),
array('data' => l(t('Clone'), 'node/' . $node->nid . '/webform/components/' . $cid . '/clone', array('query' => drupal_get_destination())), 'class' => array('webform-component-clone')),
array('data' => l(t('Delete'), 'node/' . $node->nid . '/webform/components/' . $cid . '/delete', array('query' => drupal_get_destination())), 'class' => array('webform-component-delete')),
);
$row_class = array('draggable');
if (!webform_component_feature($component['type'], 'group')) {
$row_class[] = 'tabledrag-leaf';
}
if ($component['type'] == 'pagebreak') {
$row_class[] = 'tabledrag-root';
$row_class[] = 'webform-pagebreak';
$row_data[0]['class'][] = 'webform-pagebreak';
}
$rows[] = array('data' => $row_data, 'class' => $row_class, 'data-cid' => $cid);
if (isset($component['children']) && is_array($component['children'])) {
foreach ($component['children'] as $cid => $component) {
_webform_components_form_rows($node, $cid, $component, $level + 1, $form, $rows, $add_form);
}
}
// Add the add form if this was the last edited component.
if (isset($_GET['cid']) && $component['cid'] == $_GET['cid'] && $add_form) {
$add_form['data'][0]['data'] = $indents . $add_form['data'][0]['data'];
$rows[] = $add_form;
$add_form = FALSE;
}
}
/**
* Theme the node components form. Use a table to organize the components.
*
* @return string
* Formatted HTML form, ready for display.
*/
function theme_webform_components_form($variables) {
$output = '';
$output .= theme('table', array('header' => $header, 'rows' => $rows, 'attributes' => array('id' => 'webform-components')));
$output .= drupal_render_children($form);
$output .= drupal_render_children($variables['form']['warning']);
$output .= theme('table', array('header' => $variables['header'], 'rows' => $variables['rows'], 'attributes' => array('id' => 'webform-components')));
$output .= drupal_render_children($variables['form']);
return $output;
}
@@ -278,8 +301,8 @@ function webform_components_form_validate($form, &$form_state) {
$parents = array();
if (isset($form_state['values']['components'])) {
foreach ($form_state['values']['components'] as $cid => $component) {
$form_key = $form['#node']->webform['components'][$cid]['form_key'];
if (isset($parents[$component['pid']]) && ($existing = array_search($form_key, $parents[$component['pid']])) && $existing !== FALSE) {
$form_key = (string) $form['#node']->webform['components'][$cid]['form_key'];
if (isset($parents[$component['pid']]) && ($existing = array_search($form_key, $parents[$component['pid']], TRUE)) && $existing !== FALSE) {
if (!isset($duplicates[$form_key])) {
$duplicates[$form_key] = array($existing);
}
@@ -294,7 +317,7 @@ function webform_components_form_validate($form, &$form_state) {
$items = array();
foreach ($duplicates as $form_key => $cids) {
foreach ($cids as $cid) {
$items[] = _webform_filter_xss($form['#node']->webform['components'][$cid]['name']);
$items[] = webform_filter_xss($form['#node']->webform['components'][$cid]['name']);
}
}
@@ -318,13 +341,13 @@ function webform_components_form_add_validate($form, &$form_state) {
function webform_components_form_submit($form, &$form_state) {
$node = node_load($form_state['values']['nid']);
// Update all mandatory and weight values.
// Update all required and weight values.
$changes = FALSE;
foreach ($node->webform['components'] as $cid => $component) {
if ($component['pid'] != $form_state['values']['components'][$cid]['pid'] || $component['weight'] != $form_state['values']['components'][$cid]['weight'] || $component['mandatory'] != $form_state['values']['components'][$cid]['mandatory']) {
if ($component['pid'] != $form_state['values']['components'][$cid]['pid'] || $component['weight'] != $form_state['values']['components'][$cid]['weight'] || $component['required'] != $form_state['values']['components'][$cid]['required']) {
$changes = TRUE;
$node->webform['components'][$cid]['weight'] = $form_state['values']['components'][$cid]['weight'];
$node->webform['components'][$cid]['mandatory'] = $form_state['values']['components'][$cid]['mandatory'];
$node->webform['components'][$cid]['required'] = $form_state['values']['components'][$cid]['required'];
$node->webform['components'][$cid]['pid'] = $form_state['values']['components'][$cid]['pid'];
}
}
@@ -333,7 +356,7 @@ function webform_components_form_submit($form, &$form_state) {
node_save($node);
}
drupal_set_message(t('The component positions and mandatory values have been updated.'));
drupal_set_message(t('The component positions and required values have been updated.'));
}
/**
@@ -343,7 +366,22 @@ function webform_components_form_add_submit($form, &$form_state) {
$node = node_load($form_state['values']['nid']);
$component = $form_state['values']['add'];
$form_state['redirect'] = array('node/' . $node->nid . '/webform/components/new/' . $component['type'], array('query' => array('name' => $component['name'], 'mandatory' => $component['mandatory'], 'pid' => $component['pid'], 'weight' => $component['weight'])));
// Set the values in the query string for the add component page.
$query = array(
'name' => $component['name'],
'required' => $component['required'],
'pid' => $component['pid'],
'weight' => $component['weight'],
);
// Forward the "destination" query string value to the next form.
if (isset($_GET['destination'])) {
$query['destination'] = $_GET['destination'];
unset($_GET['destination']);
drupal_static_reset('drupal_get_destination');
}
$form_state['redirect'] = array('node/' . $node->nid . '/webform/components/new/' . $component['type'], array('query' => $query));
}
/**
@@ -352,7 +390,7 @@ function webform_components_form_add_submit($form, &$form_state) {
function webform_component_edit_form($form, $form_state, $node, $component, $clone = FALSE) {
drupal_set_title(t('Edit component: @name', array('@name' => $component['name'])), PASS_THROUGH);
$form['#attached']['library'][] = array('webform', 'admin');
$form['#node'] = $node;
$form['#tree'] = TRUE;
// Print the correct field type specification.
@@ -382,14 +420,14 @@ function webform_component_edit_form($form, $form_state, $node, $component, $clo
'#description' => t('This is used as a descriptive label when displaying this form element.'),
'#required' => TRUE,
'#weight' => -10,
'#maxlength' => 255,
'#maxlength' => NULL,
);
}
$form['form_key'] = array(
'#type' => 'textfield',
'#default_value' => empty($component['form_key']) ? _webform_safe_name($component['name']) : $component['form_key'],
'#title' => t('Field Key'),
'#title' => t('Form Key'),
'#description' => t('Enter a machine readable key for this form element. May contain only alphanumeric characters and underscores. This key will be used as the name attribute of the form element. This value has no effect on the way data is saved, but may be helpful if doing custom form processing.'),
'#required' => TRUE,
'#weight' => -9,
@@ -401,7 +439,7 @@ function webform_component_edit_form($form, $form_state, $node, $component, $clo
'#type' => 'textarea',
'#default_value' => isset($component['extra']['description']) ? $component['extra']['description'] : '',
'#title' => t('Description'),
'#description' => t('A short description of the field used as help for the user when he/she uses the form.') . theme('webform_token_help'),
'#description' => t('A short description of the field used as help for the user when he/she uses the form.') . ' ' . theme('webform_token_help'),
'#weight' => -1,
);
}
@@ -425,7 +463,7 @@ function webform_component_edit_form($form, $form_state, $node, $component, $clo
'inline' => t('Inline'),
'none' => t('None'),
),
'#description' => t('Determines the placement of the component\'s label.'),
'#description' => t("Determines the placement of the component's label."),
);
}
else {
@@ -441,6 +479,17 @@ function webform_component_edit_form($form, $form_state, $node, $component, $clo
$form['display']['title_display']['#parents'] = array('extra', 'title_display');
}
if (webform_component_feature($component['type'], 'description')) {
$form['display']['description_above'] = array(
'#type' => 'checkbox',
'#default_value' => !empty($component['extra']['description_above']),
'#title' => t('Description above field'),
'#description' => t('Place the description above &mdash; rather than below &mdash; the field.'),
'#weight' => 8.5,
'#parents' => array('extra', 'description_above'),
);
}
if (webform_component_feature($component['type'], 'private')) {
// May user mark fields as Private?
$form['display']['private'] = array(
@@ -454,6 +503,27 @@ function webform_component_edit_form($form, $form_state, $node, $component, $clo
);
}
if (webform_component_feature($component['type'], 'wrapper_classes')) {
$form['display']['wrapper_classes'] = array(
'#type' => 'textfield',
'#title' => t('Wrapper CSS classes'),
'#default_value' => isset($component['extra']['wrapper_classes']) ? $component['extra']['wrapper_classes'] : '',
'#description' => t('Apply a class to the wrapper around both the field and its label. Separate multiple by spaces.'),
'#weight' => 50,
'#parents' => array('extra', 'wrapper_classes'),
);
}
if (webform_component_feature($component['type'], 'css_classes')) {
$form['display']['css_classes'] = array(
'#type' => 'textfield',
'#title' => t('CSS classes'),
'#default_value' => isset($component['extra']['css_classes']) ? $component['extra']['css_classes'] : '',
'#description' => t('Apply a class to the field. Separate multiple by spaces.'),
'#weight' => 51,
'#parents' => array('extra', 'css_classes'),
);
}
// Validation settings.
$form['validation'] = array(
'#type' => 'fieldset',
@@ -463,13 +533,13 @@ function webform_component_edit_form($form, $form_state, $node, $component, $clo
'#weight' => 5,
);
if (webform_component_feature($component['type'], 'required')) {
$form['validation']['mandatory'] = array(
$form['validation']['required'] = array(
'#type' => 'checkbox',
'#title' => t('Mandatory'),
'#default_value' => ($component['mandatory'] == '1' ? TRUE : FALSE),
'#title' => t('Required'),
'#default_value' => ($component['required'] == '1' ? TRUE : FALSE),
'#description' => t('Check this option if the user must enter a value.'),
'#weight' => -1,
'#parents' => array('mandatory'),
'#parents' => array('required'),
);
}
@@ -508,71 +578,8 @@ function webform_component_edit_form($form, $form_state, $node, $component, $clo
'#weight' => 4,
);
// Add conditional fields.
$conditional_components = array();
$counter = 0;
$last_pagebreak_slice = 0;
foreach ($node->webform['components'] as $cid => $test_component) {
// Only components before the pagebreak can be considered.
if ($test_component['type'] == 'pagebreak') {
$last_pagebreak_slice = $counter;
}
if (isset($component['cid']) && $cid == $component['cid']) {
break;
}
if (webform_component_feature($test_component['type'], 'conditional')) {
$conditional_components[$cid] = $test_component;
$counter++;
}
}
if ($component['type'] != 'pagebreak') {
$fieldset_description = t('Create a rule to control whether or not to skip this page.');
}
else {
$fieldset_description = t('Create a rule to control whether or not to show this form element.');
}
$conditional_components = array_slice($conditional_components, 0, $last_pagebreak_slice, TRUE);
$form['conditional'] = array(
'#weight' => 10,
'#type' => 'fieldset',
'#title' => t('Conditional rules'),
'#collapsible' => TRUE,
'#collapsed' => TRUE,
'#description' => t('Create a rule to control whether or not to show this form element.'),
'#tree' => FALSE,
);
$form['conditional']['extra'] = array(
'#tree' => TRUE,
);
$form['conditional']['extra']['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.'),
'#default_value' => $component['extra']['conditional_component'],
);
$form['conditional']['extra']['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.'),
'#default_value' => $component['extra']['conditional_operator'],
);
$form['conditional']['extra']['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.'),
'#default_value' => $component['extra']['conditional_values'],
);
if (empty($conditional_components)) {
$form['conditional']['#access'] = FALSE;
}
// Add the fields specific to this component type:
$additional_form_elements = (array) webform_component_invoke($component['type'], 'edit', $component);
$additional_form_elements = (array) webform_component_invoke($component['type'], 'edit', $component, $form, $form_state);
if (empty($additional_form_elements)) {
drupal_set_message(t('The webform component of type @type does not have an edit function defined.', array('@type' => $component['type'])));
}
@@ -598,17 +605,23 @@ function webform_component_edit_form($form, $form_state, $node, $component, $clo
unset($form['validation']);
}
$form = array_merge($form, $additional_form_elements);
// Ensure that the webform admin library is attached, possibly in addition to
// component-specific attachments.
$form['#attached']['library'][] = array('webform', 'admin');
// Add the submit button.
$form['actions'] = array('#type' => 'actions');
$form['actions'] = array(
'#type' => 'actions',
'#weight' => 50,
);
$form['actions']['submit'] = array(
'#type' => 'submit',
'#value' => t('Save component'),
'#weight' => 50,
);
// Remove fieldsets without any child form controls.
foreach ($form as $group_key => $group) {
foreach (element_children($form) as $group_key) {
$group = $form[$group_key];
if (isset($group['#type']) && $group['#type'] === 'fieldset' && !element_children($group)) {
unset($form[$group_key]);
}
@@ -621,15 +634,15 @@ function webform_component_edit_form($form, $form_state, $node, $component, $clo
* Field name validation for the webform unique key. Must be alphanumeric.
*/
function webform_component_edit_form_validate($form, &$form_state) {
$node = node_load($form_state['values']['nid']);
$node = $form['#node'];;
if (!preg_match('/^[a-z0-9_]+$/i', $form_state['values']['form_key'])) {
form_set_error('form_key', t('The field key %field_key is invalid. Please include only lowercase alphanumeric characters and underscores.', array('%field_key' => $form_state['values']['form_key'])));
form_set_error('form_key', t('The form key %form_key is invalid. Please include only lowercase alphanumeric characters and underscores.', array('%form_key' => $form_state['values']['form_key'])));
}
foreach ($node->webform['components'] as $cid => $component) {
if (($component['cid'] != $form_state['values']['cid'] || $form_state['values']['clone']) && ($component['pid'] == $form_state['values']['pid']) && (strcasecmp($component['form_key'], $form_state['values']['form_key']) == 0)) {
form_set_error('form_key', t('The field key %field_key is already in use by the field labeled %existing_field. Please use a unique key.', array('%field_key' => $form_state['values']['form_key'], '%existing_field' => $component['name'])));
form_set_error('form_key', t('The form key %form_key is already in use by the field labeled %existing_field. Please use a unique key.', array('%form_key' => $form_state['values']['form_key'], '%existing_field' => $component['name'])));
}
}
}
@@ -639,13 +652,15 @@ function webform_component_edit_form_validate($form, &$form_state) {
*/
function webform_component_edit_form_submit($form, &$form_state) {
// Ensure a webform record exists.
$node = node_load($form_state['values']['nid']);
$node = $form['#node'];
webform_ensure_record($node);
// Remove empty extra values.
// Remove extra values that match the default.
if (isset($form_state['values']['extra'])) {
$default = array('type' => $form_state['values']['type'], 'extra' => array());
webform_component_defaults($default);
foreach ($form_state['values']['extra'] as $key => $value) {
if ($value === '' && !isset($form['display'][$key]['#options'][''])) {
if (isset($default['extra'][$key]) && $default['extra'][$key] === $value) {
unset($form_state['values']['extra'][$key]);
}
}
@@ -675,13 +690,12 @@ function webform_component_edit_form_submit($form, &$form_state) {
// Since Webform components have been updated but the node itself has not
// been saved, it is necessary to explicitly clear the cache to make sure
// the updated webform is visible to anonymous users.
// the updated webform is visible to anonymous users. This resets the page
// and block caches (only).
cache_clear_all();
// Clear the entity cache if Entity Cache module is installed.
if (module_exists('entitycache')) {
entity_get_controller('node')->resetCache(array($node->nid));
}
// Refresh the entity cache, should it be cached in persistent storage.
entity_get_controller('node')->resetCache(array($node->nid));
$form_state['redirect'] = array('node/' . $node->nid . '/webform/components', isset($cid) ? array('query' => array('cid' => $cid)) : array());
}
@@ -702,9 +716,15 @@ function webform_component_delete_form($form, $form_state, $node, $component) {
'#value' => $component,
);
if (webform_component_feature($node->webform['components'][$cid]['type'], 'group')) {
$component_type = $node->webform['components'][$cid]['type'];
if (webform_component_feature($component_type, 'group')) {
$question = t('Delete the %name fieldset?', array('%name' => $node->webform['components'][$cid]['name']));
$description = t('This will immediately delete the %name fieldset and all children elements within %name from the %webform webform. This cannot be undone.', array('%name' => $node->webform['components'][$cid]['name'], '%webform' => $node->title));
$description = t('This will immediately delete the %name @type component and all nested components within %name from the %webform webform. This cannot be undone.',
array(
'%name' => $node->webform['components'][$cid]['name'],
'@type' => webform_component_property($component_type, 'label'),
'%webform' => $node->title,
));
}
else {
$question = t('Delete the %name component?', array('%name' => $node->webform['components'][$cid]['name']));
@@ -730,13 +750,12 @@ function webform_component_delete_form_submit($form, &$form_state) {
// Since Webform components have been updated but the node itself has not
// been saved, it is necessary to explicitly clear the cache to make sure
// the updated webform is visible to anonymous users.
// the updated webform is visible to anonymous users. This resets the page
// and block caches (only).
cache_clear_all();
// Clear the entity cache if Entity Cache module is installed.
if (module_exists('entitycache')) {
entity_get_controller('node')->resetCache(array($node->nid));
}
// Refresh the entity cache, should it be cached in persistent storage.
entity_get_controller('node')->resetCache(array($node->nid));
$form_state['redirect'] = 'node/' . $node->nid . '/webform/components';
}
@@ -746,6 +765,10 @@ function webform_component_delete_form_submit($form, &$form_state) {
*
* @param $component
* A full component containing fields from the component form.
*
* @return false|int
* On success return identifier for the components within node.
* FALSE on failure
*/
function webform_component_insert(&$component) {
// Allow modules to modify the component before saving.
@@ -755,7 +778,7 @@ function webform_component_insert(&$component) {
}
$component['value'] = isset($component['value']) ? $component['value'] : NULL;
$component['mandatory'] = isset($component['mandatory']) ? $component['mandatory'] : 0;
$component['required'] = isset($component['required']) ? $component['required'] : 0;
$component['extra']['private'] = isset($component['extra']['private']) ? $component['extra']['private'] : 0;
if (!isset($component['cid'])) {
@@ -774,7 +797,7 @@ function webform_component_insert(&$component) {
}
}
$query = db_insert('webform_component')
db_insert('webform_component')
->fields(array(
'nid' => $component['nid'],
'cid' => $component['cid'],
@@ -784,7 +807,7 @@ function webform_component_insert(&$component) {
'type' => $component['type'],
'value' => (string) $component['value'],
'extra' => serialize($component['extra']),
'mandatory' => $component['mandatory'],
'required' => $component['required'],
'weight' => $component['weight'],
))
->execute();
@@ -810,7 +833,7 @@ function webform_component_update($component) {
}
$component['value'] = isset($component['value']) ? $component['value'] : NULL;
$component['mandatory'] = isset($component['mandatory']) ? $component['mandatory'] : 0;
$component['required'] = isset($component['required']) ? $component['required'] : 0;
$component['extra']['private'] = isset($component['extra']['private']) ? $component['extra']['private'] : 0;
db_update('webform_component')
->fields(array(
@@ -820,8 +843,8 @@ function webform_component_update($component) {
'type' => $component['type'],
'value' => isset($component['value']) ? $component['value'] : '',
'extra' => serialize($component['extra']),
'mandatory' => $component['mandatory'],
'weight' => $component['weight']
'required' => $component['required'],
'weight' => $component['weight'],
))
->condition('nid', $component['nid'])
->condition('cid', $component['cid'])
@@ -831,10 +854,12 @@ function webform_component_update($component) {
module_invoke_all('webform_component_update', $component);
}
/**
* Delete a Webform component.
*/
function webform_component_delete($node, $component) {
// Check if a delete function is available for this component. If so,
// load all submissions and allow the component to delete each one.
webform_component_include($component['type']);
$delete_function = '_webform_delete_' . $component['type'];
if (function_exists($delete_function)) {
@@ -842,7 +867,7 @@ function webform_component_delete($node, $component) {
$submissions = webform_get_submissions($node->nid);
foreach ($submissions as $submission) {
if (isset($submission->data[$component['cid']])) {
webform_component_invoke($component['type'], 'delete', $component, $submission->data[$component['cid']]['value']);
webform_component_invoke($component['type'], 'delete', $component, $submission->data[$component['cid']]);
}
}
}
@@ -857,6 +882,60 @@ function webform_component_delete($node, $component) {
->condition('cid', $component['cid'])
->execute();
// Delete any conditionals dependent on this component.
module_load_include('inc', 'webform', 'includes/webform.conditionals');
foreach ($node->webform['conditionals'] as $rgid => &$conditional) {
$specs = array(
array(
'field' => 'rules',
'table' => 'webform_conditional_rules',
'component' => 'source',
'component_type' => 'source_type',
'index' => 'rid',
),
array(
'field' => 'actions',
'table' => 'webform_conditional_actions',
'component' => 'target',
'component_type' => 'target_type',
'index' => 'aid',
),
);
foreach ($specs as $spec) {
$deleted = array();
$field = $spec['field'];
foreach ($conditional[$field] as $key => $thing) {
if ($thing[$spec['component_type']] === 'component' && $thing[$spec['component']] == $component['cid']) {
$deleted[$key] = $key;
unset($conditional[$field][$key]);
}
}
if ($spec['field'] == 'rules') {
// Rules deleted because of the source component being deleted may have left
// empty sub-conditionals. Delete them, and then the entire rule group if
// there aren't any rules left.
$deleted += webform_delete_empty_subconditionals($conditional);
}
// Delete the conditional if this component is the only source / target.
if (empty($conditional[$field])) {
webform_conditional_delete($node, $conditional);
// Also delete the conditional from the $node so it is not re-created
// later on in webform_node_update().
unset($node->webform['conditionals'][$conditional['rgid']]);
// Loop exit.
break;
}
// Remove the deleted rules / actions from the database.
foreach ($deleted as $key) {
db_delete($spec['table'])
->condition('nid', $node->nid)
->condition('rgid', $rgid)
->condition($spec['index'], $key)
->execute();
}
}
}
// Delete all elements under this element.
$result = db_select('webform_component', 'c')
->fields('c')
@@ -879,6 +958,10 @@ function webform_component_delete($node, $component) {
* The node object containing the current webform.
* @param $component
* A full component containing fields from the component form.
*
* @return false|int
* On success return identifier for the components within node.
* FALSE on failure
*/
function webform_component_clone(&$node, &$component) {
$original_cid = $component['cid'];
@@ -904,6 +987,7 @@ function webform_component_clone(&$node, &$component) {
function webform_component_feature($type, $feature) {
$components = webform_components();
$defaults = array(
'analysis' => TRUE,
'csv' => TRUE,
'default_value' => TRUE,
'description' => TRUE,
@@ -915,14 +999,32 @@ function webform_component_feature($type, $feature) {
'title_display' => TRUE,
'title_inline' => TRUE,
'conditional' => TRUE,
'conditional_action_set' => FALSE,
'spam_analysis' => FALSE,
'group' => FALSE,
'attachment' => FALSE,
'private' => TRUE,
'placeholder' => FALSE,
'wrapper_classes' => TRUE,
'css_classes' => TRUE,
'views_range' => FALSE,
);
return isset($components[$type]['features'][$feature]) ? $components[$type]['features'][$feature] : !empty($defaults[$feature]);
}
/**
* Get a component property from the component definition.
*
* @see hook_webform_component_info()
*/
function webform_component_property($type, $property) {
$components = webform_components();
$defaults = array(
'conditional_type' => 'string',
);
return isset($components[$type][$property]) ? $components[$type][$property] : $defaults[$property];
}
/**
* Create a list of components suitable for a select list.
*
@@ -932,32 +1034,52 @@ function webform_component_feature($type, $feature) {
* Either an array of components, or a string containing a feature name (csv,
* email, required, conditional) on which this list of components will be
* restricted.
* @param $indent
* Indent components placed under fieldsets with hyphens.
* @param $optgroups
* @param $prefix_group
* TRUE to indent with a hyphen, or 'path" to Prepend enclosing group (for example,
* fieldset) name(s)
* @param $pagebreak_groups
* Determine if pagebreaks should be converted to option groups in the
* returned list of options.
*
* @return array
* An array of options.
*/
function webform_component_list($node, $component_filter = NULL, $indent = TRUE, $optgroups = FALSE) {
function webform_component_list($node, $component_filter = NULL, $prepend_group = TRUE, $pagebreak_groups = FALSE) {
$options = array();
$page_names = array();
$parent_names = array();
$components = is_array($component_filter) ? $component_filter : $node->webform['components'];
$feature = is_string($component_filter) ? $component_filter : NULL;
foreach ($components as $cid => $component) {
if (!isset($feature) || webform_component_feature($component['type'], $feature) || ($indent && webform_component_feature($component['type'], 'group'))) {
$prefix = '';
$page_num = $component['page_num'];
$page_index = 'p' . $page_num;
if ($indent && ($parent_count = count(webform_component_parent_keys($node, $component)) - 1)) {
$prefix = str_repeat('-', $parent_count);
// If this component is a group (for example, fieldset), then remember its name, including any parents.
if ($prepend_group && webform_component_feature($component['type'], 'group')) {
$parent_names[$cid] = ($component['pid'] ? $parent_names[$component['pid']] : '') .
($prepend_group === 'path' ? $component['name'] . ': ' : '-');
}
$page_num = $component['page_num'];
// If this component is a pagebreak, then generate an option group, ensuring a unique name.
if ($pagebreak_groups && $component['type'] == 'pagebreak') {
$page_name = $component['name'];
// When a $page_name consists only of digits, append a space to ensure it
// is never the same as a $cid.
if ((string) $page_name === (string) (int) $page_name) {
$page_name .= ' ';
}
if ($optgroups && $component['type'] == 'pagebreak') {
$page_names[$page_index] = $component['name'];
$copy = 1;
while (in_array($page_name, $page_names)) {
$page_name = $component['name'] . '_' . ++$copy;
}
elseif ($optgroups && $page_num > 1) {
$options[$page_index][$cid] = $prefix . $component['name'];
$page_names[$page_num] = $page_name;
}
// If this component should be included in the options, add it with any prefix, in a page group, as needed.
if (!isset($feature) || webform_component_feature($component['type'], $feature) || $prepend_group === TRUE) {
$prefix = ($prepend_group && $component['pid']) ? $parent_names[$component['pid']] : '';
if ($pagebreak_groups && $page_num > 1) {
$options[$page_names[$page_num]][$cid] = $prefix . $component['name'];
}
else {
$options[$cid] = $prefix . $component['name'];
@@ -965,20 +1087,6 @@ function webform_component_list($node, $component_filter = NULL, $indent = TRUE,
}
}
// Convert page breaks into optgroups.
if ($optgroups) {
$grouped_options = $options;
$options = array();
foreach ($grouped_options as $key => $values) {
if (is_array($values) && isset($page_names[$key])) {
$options[$page_names[$key]] = $values;
}
else {
$options[$key] = $values;
}
}
}
return $options;
}
@@ -1007,6 +1115,7 @@ function webform_component_select($element) {
$element['#attached'] = array(
'library' => array(
array('webform', 'admin'),
array('system', 'drupal.collapse'),
),
'js' => array(
'misc/tableselect.js' => array(),
@@ -1028,9 +1137,11 @@ function theme_webform_component_select($variables) {
$header = array(array('class' => array('select-all'), 'data' => ' ' . t('Include all components')));
}
foreach (element_children($element) as $key) {
$rows[] = array(
theme('indentation', array('size' => $element[$key]['#indent'])) . drupal_render($element[$key]),
);
if ($key != 'suffix') {
$rows[] = array(
theme('indentation', array('size' => $element[$key]['#indent'])) . drupal_render($element[$key]),
);
}
}
$element['#type'] = 'fieldset';
@@ -1047,20 +1158,37 @@ function theme_webform_component_select($variables) {
$element['#children'] = t('No available components.');
}
else {
$element['#children'] = '<div class="webform-component-select-wrapper">' . theme('table', array('header' => $header, 'rows' => $rows)) . '</div>';
$element['#children'] = '<div class="webform-component-select-wrapper">' . theme('table', array('header' => $header, 'rows' => $rows, 'sticky' => FALSE)) . '</div>';
}
if (isset($element['suffix'])) {
$element['#children'] .= '<div class="webform-component-select-suffix">' . drupal_render($element['suffix']) . '</div>';
}
return theme('fieldset', array('element' => $element));
}
/**
* Find a components parents within a node.
* Find a component's parents within a node.
*
* @param object $node
* The webform node.
* @param array $component
* The component to start with.
* @param string|true $what_to_return
* If TRUE, return complete component arrays. Otherwise, return the property
* of each component named in this parametre.
*
* @return array
* An array with a value for each parent and for the start component, in order
* ending with start component. What the value is is controlled by
* $what_to_return.
*/
function webform_component_parent_keys($node, $component) {
$parents = array($component['form_key']);
function webform_component_parent_keys($node, array $component, $what_to_return = 'form_key') {
$parents = array(($what_to_return === TRUE) ? $component : $component[$what_to_return]);
$pid = $component['pid'];
while ($pid) {
$parents[] = $node->webform['components'][$pid]['form_key'];
$parents[] = ($what_to_return === TRUE) ? $node->webform['components'][$pid] : $node->webform['components'][$pid][$what_to_return];
$pid = $node->webform['components'][$pid]['pid'];
}
return array_reverse($parents);
@@ -1083,11 +1211,6 @@ function webform_component_defaults(&$component) {
$component['extra'][$extra] = $default;
}
}
$component['extra'] += array(
'conditional_component' => '',
'conditional_operator' => '=',
'conditional_values' => '',
);
}
}
@@ -1103,7 +1226,8 @@ function webform_validate_unique($element, $form_state) {
->condition('nid', $nid)
->condition('cid', $element['#webform_component']['cid'])
->condition('data', $element['#value'])
->range(0, 1); // More efficient than using countQuery() for data checks.
// More efficient than using countQuery() for data checks.
->range(0, 1);
if ($sid) {
$query->condition('sid', $sid, '<>');
}

File diff suppressed because it is too large Load Diff

View File

@@ -19,19 +19,13 @@ function webform_emails_form($form, $form_state, $node) {
$form['#node'] = $node;
$form['components'] = array();
$form['nid'] = array(
'#type' => 'value',
'#value' => $node->nid,
);
foreach ($node->webform['emails'] as $eid => $email) {
$email_addresses = array_filter(explode(',', $email['email']));
foreach ($email_addresses as $key => $email_address) {
$email_addresses[$key] = check_plain(webform_format_email_address($email_address, NULL, $node, NULL, FALSE));
}
$form['emails'][$eid]['status'] = array(
'#type' => 'checkbox',
'#default_value' => $email['status'],
);
$form['emails'][$eid]['email'] = array(
'#markup' => implode('<br />', $email_addresses),
'#markup' => nl2br(check_plain(implode("\n", webform_format_email_address($email['email'], NULL, $node, NULL, FALSE, FALSE)))),
);
$form['emails'][$eid]['subject'] = array(
'#markup' => check_plain(webform_format_email_subject($email['subject'], $node)),
@@ -46,6 +40,11 @@ function webform_emails_form($form, $form_state, $node) {
'#tree' => FALSE,
);
$form['add']['status'] = array(
'#type' => 'checkbox',
'#default_value' => TRUE,
);
$form['add']['email_option'] = array(
'#type' => 'radios',
'#options' => array(
@@ -71,13 +70,23 @@ function webform_emails_form($form, $form_state, $node) {
$form['add']['email_component']['#disabled'] = TRUE;
}
$form['add_button'] = array(
$form['add']['add'] = array(
'#type' => 'submit',
'#value' => t('Add'),
'#weight' => 45,
'#validate' => array('webform_email_address_validate'),
'#submit' => array('webform_emails_form_submit'),
);
$form['#validate'] = array('webform_email_address_validate');
$form['actions'] = array(
'#type' => 'actions',
'#weight' => 45,
);
$form['actions']['save'] = array(
'#type' => 'submit',
'#value' => t('Save'),
'#submit' => array('webform_emails_form_status_save'),
'#access' => count($node->webform['emails']) > 0,
);
return $form;
}
@@ -85,43 +94,50 @@ function webform_emails_form($form, $form_state, $node) {
/**
* Theme the node components form. Use a table to organize the components.
*
* @param $form
* The form array.
* @return
* @param array $variables
* Array with key "form" containing the form array.
*
* @return string
* Formatted HTML form, ready for display.
*
* @throws Exception
*/
function theme_webform_emails_form($variables) {
function theme_webform_emails_form(array $variables) {
$form = $variables['form'];
$node = $form['#node'];
$header = array(t('E-mail to'), t('Subject'), t('From'), array('data' => t('Operations'), 'colspan' => 2));
$header = array(t('Send'), t('E-mail to'), t('Subject'), t('From'), array('data' => t('Operations'), 'colspan' => 3));
$rows = array();
if (!empty($form['emails'])) {
foreach (element_children($form['emails']) as $eid) {
// Add each component to a table row.
$rows[] = array(
array('data' => drupal_render($form['emails'][$eid]['status']), 'class' => array('webform-email-status', 'checkbox')),
drupal_render($form['emails'][$eid]['email']),
drupal_render($form['emails'][$eid]['subject']),
drupal_render($form['emails'][$eid]['from']),
l(t('Edit'), 'node/' . $node->nid . '/webform/emails/' . $eid),
l(t('Clone'), 'node/' . $node->nid . '/webform/emails/' . $eid . '/clone'),
l(t('Delete'), 'node/' . $node->nid . '/webform/emails/' . $eid . '/delete'),
);
}
}
else {
$rows[] = array(array('data' => t('Currently not sending e-mails, add an e-mail recipient below.'), 'colspan' => 5));
$rows[] = array(array('data' => t('Currently not sending e-mails, add an e-mail recipient below.'), 'colspan' => 7));
}
// Add a row containing form elements for a new item.
$add_button = drupal_render($form['add']['add']);
$row_data = array(
array('data' => drupal_render($form['add']['status']), 'class' => array('webform-email-status', 'checkbox')),
array('colspan' => 3, 'data' => drupal_render($form['add'])),
array('colspan' => 2, 'data' => drupal_render($form['add_button'])),
array('colspan' => 3, 'data' => $add_button),
);
$rows[] = array('data' => $row_data, 'class' => array('webform-add-form'));
$output = '';
$output .= theme('table', array('header' => $header, 'rows' => $rows, 'attributes' => array('id' => 'webform-emails')));
$output .= theme('table', array('header' => $header, 'rows' => $rows, 'sticky' => FALSE, 'attributes' => array('id' => 'webform-emails')));
$output .= drupal_render_children($form);
return $output;
}
@@ -133,16 +149,17 @@ function theme_webform_email_add_form($variables) {
$form = $variables['form'];
// Add a default value to the custom e-mail textfield.
$form['email_custom']['#attributes']['rel'] = t('email@example.com');
$form['email_custom']['#attributes']['placeholder'] = t('email@example.com');
$form['email_custom']['#attributes']['class'][] = 'webform-set-active';
$form['email_custom']['#attributes']['class'][] = 'webform-default-value';
$form['email_custom']['#theme_wrappers'] = array();
$form['email_option']['custom']['#theme_wrappers'] = array('webform_inline_radio');
$form['email_option']['custom']['#inline_element'] = drupal_render($form['email_custom']);
$form['email_option']['custom']['#title'] = t('Address: !email', array('!email' => drupal_render($form['email_custom'])));
// Render the component value.
$form['email_component']['#theme_wrappers'] = array();
$form['email_component']['#attributes']['class'][] = 'webform-set-active';
$form['email_option']['component']['#theme_wrappers'] = array('webform_inline_radio');
$form['email_option']['component']['#inline_element'] = drupal_render($form['email_component']);
$form['email_option']['component']['#title'] = t('Component value: !component', array('!component' => drupal_render($form['email_component'])));
return drupal_render_children($form);
}
@@ -157,27 +174,45 @@ function webform_emails_form_submit($form, &$form_state) {
else {
$email = $form_state['values']['email_component'];
}
$form_state['redirect'] = array('node/' . $form['#node']->nid . '/webform/emails/new', array('query' => array('option' => $form_state['values']['email_option'], 'email' => trim($email))));
$form_state['redirect'] = array('node/' . $form['#node']->nid . '/webform/emails/new', array('query' => array('status' => $form_state['values']['status'], 'option' => $form_state['values']['email_option'], 'email' => trim($email))));
}
/**
* Submit handler for status update.
*/
function webform_emails_form_status_save($form, &$form_state) {
foreach ($form_state['values']['emails'] as $eid => $status) {
db_update('webform_emails')->fields(array(
'status' => $status['status'],
))
->condition('eid', $eid)
->condition('nid', $form['#node']->nid)
->execute();
}
// Refresh the entity cache, should it be cached in persistent storage.
entity_get_controller('node')->resetCache(array($form['#node']->nid));
}
/**
* Form for configuring an e-mail setting and template.
*/
function webform_email_edit_form($form, $form_state, $node, $email = array()) {
function webform_email_edit_form($form, $form_state, $node, $email = array(), $clone = FALSE) {
module_load_include('inc', 'webform', 'includes/webform.components');
$form['#attached']['library'][] = array('webform', 'admin');
$form['#attached']['js'][] = array('data' => array('webform' => array('revertConfirm' => t('Are you sure you want to revert any changes to your template back to the default?'))), 'type' => 'setting');
$form['#tree'] = TRUE;
$form['node'] = array(
'#type' => 'value',
'#value' => $node,
);
$form['#node'] = $node;
$form['eid'] = array(
'#type' => 'value',
'#value' => isset($email['eid']) ? $email['eid'] : NULL,
);
$form['clone'] = array(
'#type' => 'value',
'#value' => $clone,
);
// All these fields work essentially the same, with a radio button set,
// a textfield for custom values, and a select list for a component.
@@ -188,20 +223,23 @@ function webform_email_edit_form($form, $form_state, $node, $email = array()) {
$title = t('E-mail to address');
$description = t('Form submissions will be e-mailed to this address. Any email, select, or hidden form element may be selected as the recipient address. Multiple e-mail addresses may be separated by commas.');
break;
case 'subject':
$default_value = _webform_filter_values(webform_variable_get('webform_default_subject'), $node);
$default_value = webform_replace_tokens(webform_variable_get('webform_default_subject'), $node);
$title = t('E-mail subject');
$description = t('Any textfield, select, or hidden form element may be selected as the subject for e-mails.');
break;
case 'from_address':
$default_value = _webform_filter_values(webform_variable_get('webform_default_from_address'), $node);
$default_value = webform_replace_tokens(webform_variable_get('webform_default_from_address'), $node);
$title = t('E-mail from address');
$description = t('Any email, select, or hidden form element may be selected as the sender\'s e-mail address.');
$description = t("Any email, select, or hidden form element may be selected as the sender's e-mail address.");
break;
case 'from_name':
$default_value = _webform_filter_values(webform_variable_get('webform_default_from_name'), $node);
$default_value = webform_replace_tokens(webform_variable_get('webform_default_from_name'), $node);
$title = t('E-mail from name');
$description = t('Any textfield, select, or hidden form element may be selected as the sender\'s name for e-mails.');
$description = t("Any textfield, select, or hidden form element may be selected as the sender's name for e-mails.");
break;
}
@@ -221,25 +259,77 @@ function webform_email_edit_form($form, $form_state, $node, $email = array()) {
'#type' => 'textfield',
'#size' => 40,
'#default_value' => (!is_numeric($email[$field]) && $email[$field] != 'default') ? $email[$field] : NULL,
'#maxlength' => $field == 'email' ? 500 : 255,
'#maxlength' => 500,
);
$options = webform_component_list($node, $field == 'from_address' || $field == 'email' ? 'email_address' : 'email_name', FALSE);
$field_type = $field === 'from_address' || $field === 'email' ? 'email_address' : 'email_name';
$component_options = webform_component_list($node, $field_type, FALSE);
$form[$field . '_component'] = array(
'#type' => 'select',
'#default_value' => is_numeric($email[$field]) ? $email[$field] : NULL,
'#options' => empty($options) ? array('' => t('No available components')) : $options,
'#disabled' => empty($options) ? TRUE : FALSE,
'#default_value' => is_numeric($email[$field]) ? $email[$field] : NULL,
'#options' => empty($component_options) ? array('' => t('No available components')) : $component_options,
'#disabled' => empty($component_options) ? TRUE : FALSE,
'#weight' => 6,
);
// If this component is being used as an e-mail address and has multiple
// options (such as a select list or checkboxes), we provide text fields to
// map each option to a user-defined e-mail.
if ($field_type === 'email_address') {
foreach ($component_options as $cid => $component_label) {
$component = $node->webform['components'][$cid];
$options = webform_component_invoke($component['type'], 'options', $component, TRUE);
// For components that don't provide multiple options (hidden or email).
if (empty($options)) {
continue;
}
// To avoid flooding the form with hundreds of textfields, skip select
// lists that have huge numbers of options.
if (count($options) > webform_variable_get('webform_email_select_max')) {
unset($form[$field . '_component']['#options'][$cid]);
continue;
}
$form[$field . '_mapping'][$cid] = array(
'#title' => check_plain($component['name']),
'#theme' => 'webform_email_component_mapping',
'#attributes' => array('class' => array('webform-email-mapping')),
'#webform_allow_empty' => $field === 'email',
'#type' => 'container',
'#states' => array(
'visible' => array(
'input[name=' . $field . '_option' . ']' => array('value' => 'component'),
'select[name=' . $field . '_component' . ']' => array('value' => (string) $cid),
),
),
);
foreach ($options as $key => $label) {
$form[$field . '_mapping'][$cid][$key] = array(
'#type' => 'textfield',
'#title' => $label,
'#default_value' => is_numeric($email[$field]) && $email[$field] == $cid && is_array($email['extra']) && isset($email['extra'][$field . '_mapping'][$key]) ? $email['extra'][$field . '_mapping'][$key] : '',
'#attributes' => array('placeholder' => t('email@example.com')),
'#maxlength' => 500,
);
}
}
}
}
// Do not show the "E-mail from name" if using the short e-mail format.
if (variable_get('webform_email_address_format', 'long') == 'short') {
if (webform_variable_get('webform_email_address_format') == 'short') {
$form['from_name_option']['#access'] = FALSE;
$form['from_name_custom']['#access'] = FALSE;
$form['from_name_component']['#access'] = FALSE;
}
// Add the checkbox to disable the email for current recipients.
$form['status'] = array(
'#title' => t('Enable sending'),
'#description' => t('Uncheck to disable sending this email.'),
'#type' => 'checkbox',
'#default_value' => $email['status'],
);
// Add the template fieldset.
$form['template'] = array(
'#type' => 'fieldset',
@@ -267,25 +357,22 @@ function webform_email_edit_form($form, $form_state, $node, $email = array()) {
'#type' => 'textarea',
'#rows' => max(10, min(20, count(explode("\n", $template)))),
'#default_value' => $template,
'#wysiwyg' => webform_email_html_capable() ? NULL : FALSE,
'#wysiwyg' => webform_variable_get('webform_email_html_capable') ? NULL : FALSE,
'#description' => theme('webform_token_help', array('groups' => array('node', 'submission'))),
);
$form['template']['html'] = array(
'#type' => 'checkbox',
'#title' => t('Send e-mail as HTML'),
'#default_value' => $email['html'],
'#access' => webform_email_html_capable() && !variable_get('webform_format_override', 0),
'#access' => webform_variable_get('webform_email_html_capable') && !webform_variable_get('webform_format_override'),
);
$form['template']['attachments'] = array(
'#type' => 'checkbox',
'#title' => t('Include files as attachments'),
'#default_value' => $email['attachments'],
'#access' => webform_email_html_capable(),
);
$form['template']['tokens'] = array(
'#markup' => theme('webform_token_help', array('groups' => 'all')),
'#access' => webform_variable_get('webform_email_html_capable'),
);
$form['template']['components'] = array(
@@ -295,11 +382,17 @@ function webform_email_edit_form($form, $form_state, $node, $email = array()) {
'#default_value' => array_diff(array_keys($node->webform['components']), $email['excluded_components']),
'#multiple' => TRUE,
'#size' => 10,
'#description' => t('The selected components will be included in the %email_values token. Individual values may still be printed if explicitly specified as a %email[key] in the template.'),
'#description' => t('The selected components will be included in the [submission:values] token. Individual values may still be printed if explicitly specified as a [submission:values:?] in the template.'),
'#process' => array('webform_component_select'),
);
// TODO: Allow easy re-use of existing templates.
$form['template']['components']['suffix']['exclude_empty'] = array(
'#type' => 'checkbox',
'#title' => t('Exclude empty components'),
'#default_value' => $email['exclude_empty'],
);
// @todo: Allow easy re-use of existing templates.
$form['templates']['#tree'] = TRUE;
$form['templates']['default'] = array(
'#type' => 'textarea',
@@ -310,11 +403,13 @@ function webform_email_edit_form($form, $form_state, $node, $email = array()) {
);
// Add the submit button.
$form['actions'] = array('#type' => 'actions');
$form['actions'] = array(
'#type' => 'actions',
'#weight' => 20,
);
$form['actions']['submit'] = array(
'#type' => 'submit',
'#value' => t('Save e-mail settings'),
'#weight' => 20,
);
$form['#validate'] = array('webform_email_address_validate', 'webform_email_edit_form_validate');
@@ -332,10 +427,12 @@ function theme_webform_email_edit_form($variables) {
foreach (array('email', 'subject', 'from_address', 'from_name') as $field) {
foreach (array('custom', 'component') as $option) {
$form[$field . '_' . $option]['#attributes']['class'][] = 'webform-set-active';
$form[$field . '_' . $option]['#theme_wrappers'] = array();
$form[$field . '_option'][$option]['#theme_wrappers'] = array('webform_inline_radio');
$form[$field . '_option'][$option]['#inline_element'] = drupal_render($form[$field . '_' . $option]);
$form[$field . '_option'][$option]['#title'] = t('!title: !field', array('!title' => $form[$field . '_option'][$option]['#title'], '!field' => drupal_render($form[$field . '_' . $option])));
}
if (isset($form[$field . '_option']['#options']['default'])) {
$form[$field]['#theme_wrappers'] = array();
$form[$field . '_option']['default']['#theme_wrappers'] = array('webform_inline_radio');
}
}
@@ -343,7 +440,9 @@ function theme_webform_email_edit_form($variables) {
$details = '';
$details .= drupal_render($form['subject_option']);
$details .= drupal_render($form['from_address_option']);
$details .= drupal_render($form['from_address_mapping']);
$details .= drupal_render($form['from_name_option']);
$form['details'] = array(
'#type' => 'fieldset',
'#title' => t('E-mail header details'),
@@ -365,23 +464,52 @@ function theme_webform_email_edit_form($variables) {
return drupal_render_children($form, $children);
}
/**
* Theme the presentation of select list option to e-mail address mappings.
*/
function theme_webform_email_component_mapping($variables) {
$element = $variables['element'];
$header = array(t('Option'), t('E-mail address'));
$rows = array();
foreach (element_children($element) as $key) {
$element[$key]['#theme_wrappers'] = array();
$row = array();
$row[] = array(
'data' => theme('form_element_label', array('element' => $element[$key])),
'class' => array('webform-email-option'),
);
$row[] = drupal_render($element[$key]);
$rows[] = $row;
}
$empty = t('This component has no options defined yet.');
$table = theme('table', array('header' => $header, 'rows' => $rows, 'sticky' => FALSE, 'empty' => $empty));
$description = t('The selected component %name has multiple options. You may enter an e-mail address for each choice.', array('%name' => $element['#title']));
if ($element['#webform_allow_empty']) {
$description .= ' ' . t('When that choice is selected, an e-mail will be sent to the corresponding address. If a field is left blank, no e-mail will be sent for that option.');
}
else {
$description .= ' ' . t('When that choice is selected, an e-mail will be sent from the corresponding address.');
}
$wrapper_element = array(
'#title' => t('Component e-mail options'),
'#children' => $table,
'#description' => $description,
'#theme_wrappers' => array('form_element'),
'#id' => NULL,
);
return render($wrapper_element);
}
/**
* Validate handler for webform_email_edit_form() and webform_emails_form().
*/
function webform_email_address_validate($form, &$form_state) {
if ($form_state['values']['email_option'] == 'custom') {
$email = trim($form_state['values']['email_custom']);
if (empty($email)) {
form_set_error('email_custom', t('When adding a new custom e-mail, the e-mail field is required.'));
}
else {
$emails = array_filter(explode(',', $email));
foreach ($emails as $email) {
if (!valid_email_address(trim($email))) {
form_set_error('email_custom', t('The entered e-mail address "@email" does not appear valid.', array('@email' => $email)));
}
}
}
webform_email_validate($form_state['values']['email_custom'], 'email_custom', FALSE, TRUE, TRUE);
}
}
@@ -389,9 +517,24 @@ function webform_email_address_validate($form, &$form_state) {
* Validate handler for webform_email_edit_form().
*/
function webform_email_edit_form_validate($form, &$form_state) {
if ($form_state['values']['from_address_option'] == 'custom' && !valid_email_address($form_state['values']['from_address_custom'])) {
form_set_error('from_address_custom', t('The entered e-mail address "@email" does not appear valid.', array('@email' => $form_state['values']['from_address_custom'])));
if ($form_state['values']['from_address_option'] == 'custom') {
webform_email_validate($form_state['values']['from_address_custom'], 'from_address_custom', FALSE, FALSE, TRUE);
}
// Validate component-based values for the TO and FROM address.
foreach (array('email', 'from_address') as $field_name) {
if ($form_state['values'][$field_name . '_option'] == 'component') {
$cid = $form_state['values'][$field_name . '_component'];
if (isset($form_state['values'][$field_name . '_mapping'][$cid])) {
$empty_allowed = $field_name === 'email';
$multiple_allowed = $field_name === 'email';
foreach ($form_state['values'][$field_name . '_mapping'][$cid] as $key => &$value) {
webform_email_validate($value, "{$field_name}_mapping][$cid][$key", $empty_allowed, $multiple_allowed, TRUE);
}
}
}
}
}
/**
@@ -399,9 +542,12 @@ function webform_email_edit_form_validate($form, &$form_state) {
*/
function webform_email_edit_form_submit($form, &$form_state) {
// Ensure a webform record exists.
$node = $form_state['values']['node'];
$node = $form['#node'];
webform_ensure_record($node);
// Remove duplicate email To: addresses.
$form_state['values']['email_custom'] = implode(',', array_unique(array_map('trim', explode(',', $form_state['values']['email_custom']))));
// Merge the e-mail, name, address, and subject options into single values.
$email = array(
'eid' => $form_state['values']['eid'],
@@ -415,6 +561,12 @@ function webform_email_edit_form_submit($form, &$form_state) {
}
else {
$email[$field] = $form_state['values'][$field . '_' . $option];
// Merge the email mapping(s) into single value(s)
$cid = $form_state['values'][$field . '_' . $option];
if (is_numeric($cid) && isset($form_state['values'][$field . '_mapping'][$cid])) {
$email['extra'][$field . '_mapping'] = $form_state['values'][$field . '_mapping'][$cid];
}
}
}
@@ -423,7 +575,7 @@ function webform_email_edit_form_submit($form, &$form_state) {
$form_state['values']['templates']['default'] = str_replace(array("\r", "\n"), array('', "\n"), $form_state['values']['templates']['default']);
// Set the template value.
// TODO: Support reuse of templates.
// @todo: Support reuse of templates.
if (strcmp(trim($form_state['values']['templates']['default']), trim($form_state['values']['template'])) == 0) {
$email['template'] = 'default';
}
@@ -437,12 +589,20 @@ function webform_email_edit_form_submit($form, &$form_state) {
// Save the list of included components.
// We actually maintain an *exclusion* list, so any new components will
// default to being included in the %email_values token until unchecked.
// default to being included in the [submission:values] token until unchecked.
$included = array_keys(array_filter((array) $form_state['values']['components']));
$excluded = array_diff(array_keys($node->webform['components']), $included);
$email['excluded_components'] = $excluded;
if (empty($form_state['values']['eid'])) {
$email['exclude_empty'] = empty($form_state['values']['exclude_empty']) ? 0 : 1;
$email['status'] = empty($form_state['values']['status']) ? 0 : 1;
if ($form_state['values']['clone']) {
drupal_set_message(t('Email settings cloned.'));
$form_state['values']['eid'] = webform_email_clone($email);
}
elseif (empty($form_state['values']['eid'])) {
drupal_set_message(t('Email settings added.'));
$form_state['values']['eid'] = webform_email_insert($email);
}
@@ -451,10 +611,8 @@ function webform_email_edit_form_submit($form, &$form_state) {
webform_email_update($email);
}
// Clear the entity cache if Entity Cache module is installed.
if (module_exists('entitycache')) {
entity_get_controller('node')->resetCache(array($node->nid));
}
// Refresh the entity cache, should it be cached in persistent storage.
entity_get_controller('node')->resetCache(array($node->nid));
$form_state['redirect'] = array('node/' . $node->nid . '/webform/emails');
}
@@ -463,8 +621,6 @@ function webform_email_edit_form_submit($form, &$form_state) {
* Form for deleting an e-mail setting.
*/
function webform_email_delete_form($form, $form_state, $node, $email) {
$eid = $email['eid'];
$form['node'] = array(
'#type' => 'value',
'#value' => $node,
@@ -499,10 +655,8 @@ function webform_email_delete_form_submit($form, &$form_state) {
unset($node->webform['emails'][$email['eid']]);
webform_check_record($node);
// Clear the entity cache if Entity Cache module is installed.
if (module_exists('entitycache')) {
entity_get_controller('node')->resetCache(array($node->nid));
}
// Refresh the entity cache, should it be cached in persistent storage.
entity_get_controller('node')->resetCache(array($node->nid));
$form_state['redirect'] = 'node/' . $node->nid . '/webform/emails';
}
@@ -520,14 +674,17 @@ function webform_email_load($eid, $nid) {
'from_address' => 'default',
'template' => 'default',
'excluded_components' => array(),
'html' => variable_get('webform_default_format', 0),
'exclude_empty' => 0,
'html' => webform_variable_get('webform_default_format'),
'attachments' => 0,
'extra' => '',
'status' => 1,
);
}
else {
$email = isset($node->webform['emails'][$eid]) ? $node->webform['emails'][$eid] : FALSE;
if (variable_get('webform_format_override', 0)) {
$email['html'] = variable_get('webform_default_format', 0);
if ($email && webform_variable_get('webform_format_override')) {
$email['html'] = webform_variable_get('webform_default_format');
}
}
@@ -539,9 +696,12 @@ function webform_email_load($eid, $nid) {
*
* @param $email
* An array of settings for sending an e-mail.
*
* @return int|false
* The e-mail identifier for this row's settings on success else false.
*/
function webform_email_insert($email) {
// TODO: This is not race-condition safe. Switch to using transactions?
// @todo: This is not race-condition safe. Switch to using transactions?
if (!isset($email['eid'])) {
$next_id_query = db_select('webform_emails')->condition('nid', $email['nid']);
$next_id_query->addExpression('MAX(eid) + 1', 'eid');
@@ -552,20 +712,40 @@ function webform_email_insert($email) {
}
$email['excluded_components'] = implode(',', $email['excluded_components']);
$email['extra'] = empty($email['extra']) ? '' : serialize($email['extra']);
$success = drupal_write_record('webform_emails', $email);
return $success ? $email['eid'] : FALSE;
}
/**
* Clone an existing e-mail setting.
*
* @param $email
* An array of settings for sending an e-mail.
*
* @return false|int
* The e-mail identifier for this row's settings on success else false.
*/
function webform_email_clone($email) {
$email['eid'] = NULL;
return webform_email_insert($email);
}
/**
* Update an existing e-mail setting with new values.
*
* @param $email
* An array of settings for sending an e-mail containing a nid, eid, and all
* other fields from the e-mail form.
*
* @return false|int
* On success SAVED_NEW or SAVED_UPDATED, depending on the operation
* performed, FALSE on failure.
*/
function webform_email_update($email) {
$email['excluded_components'] = implode(',', $email['excluded_components']);
$email['extra'] = empty($email['extra']) ? '' : serialize($email['extra']);
return drupal_write_record('webform_emails', $email, array('nid', 'eid'));
}

View File

@@ -2,31 +2,54 @@
/**
* @file
* Provides several different handlers for exporting webform results.
* Provides several different handlers for exporting webform results.
*/
/**
* Implements hook_webform_exporters().
*
* Defines the exporters this module implements.
*
* @return
* An "array of arrays", keyed by content-types. The 'handler' slot
* should point to the PHP class implementing this flag.
*/
function webform_webform_exporters() {
return array(
$exporters = array(
'delimited' => array(
'title' => t('Delimited text'),
'description' => t('A plain text file delimited by commas, tabs, or other characters.'),
'handler' => 'webform_exporter_delimited',
'file' => drupal_get_path('module', 'webform') . '/includes/exporters/webform_exporter_delimited.inc',
'weight' => 10,
),
'excel' => array(
'title' => t('Microsoft Excel'),
'description' => t('A file readable by Microsoft Excel.'),
'handler' => 'webform_exporter_excel',
'handler' => 'webform_exporter_excel_xlsx',
'file' => drupal_get_path('module', 'webform') . '/includes/exporters/webform_exporter_excel_xlsx.inc',
'weight' => -1,
// Tells the options to use consistent dates instead of user-defined
// formats.
'options' => array(
'iso8601_time' => TRUE,
'iso8601_date' => TRUE,
),
),
'excel_legacy' => array(
'title' => t('Microsoft Excel (older versions)'),
'description' => t('A file readable by older versions of Microsoft Excel.'),
'handler' => 'webform_exporter_excel_delimited',
'file' => drupal_get_path('module', 'webform') . '/includes/exporters/webform_exporter_excel_delimited.inc',
'weight' => 0,
),
);
// The new Excel exporter is only available if ZipArchive is available.
if (!class_exists('ZipArchive')) {
drupal_set_message(t('@format downloads are not available because this install of PHP lacks Zip archive support.', array('@format' => $exporters['excel']['title'])), 'warning');
unset($exporters['excel']);
}
// By default the legacy Excel exporter is disabled.
if (!webform_variable_get('webform_excel_legacy_exporter')) {
unset($exporters['excel_legacy']);
}
return $exporters;
}
/**
@@ -48,6 +71,13 @@ function webform_export_fetch_definition($format = NULL) {
static $cache;
if (!isset($cache)) {
$cache = module_invoke_all('webform_exporters');
drupal_alter('webform_exporters', $cache);
foreach ($cache as $key => $exporter) {
$cache[$key] += array('weight' => 0);
// Used in sorting.
$cache[$key]['name'] = $key;
}
uasort($cache, '_webform_components_sort');
}
if (isset($format)) {
@@ -65,106 +95,16 @@ function webform_export_fetch_definition($format = NULL) {
*/
function webform_export_create_handler($format, $options) {
$definition = webform_export_fetch_definition($format);
if (isset($definition) && class_exists($definition['handler'])) {
if (isset($definition['file'])) {
include_once $definition['file'];
}
if (isset($definition)) {
$handler = new $definition['handler']($options);
}
else {
// TODO: Create a default broken exporter.
else {
// @todo: Create a default broken exporter.
$handler = new webform_exporter_broken($options);
}
return $handler;
}
class webform_exporter {
function add_row(&$file_handle, $data) {
}
function set_headers($filename) {
drupal_add_http_header('Content-Type', 'application/force-download');
drupal_add_http_header('Pragma', 'public');
drupal_add_http_header('Cache-Control', 'max-age=0');
}
function bof(&$file_handle) {
}
function eof(&$file_handle) {
}
}
class webform_exporter_delimited extends webform_exporter {
var $delimiter;
function webform_exporter_delimited($options) {
$this->delimiter = isset($options['delimiter']) ? $options['delimiter'] : ',';
// Convert tabs.
if ($this->delimiter == '\t') {
$this->delimiter = "\t";
}
}
function bof(&$file_handle) {
$output = '';
// Include at BOM at the beginning of the file for Little Endian.
// This makes tab-separated imports work correctly in MS Excel.
if (function_exists('mb_convert_encoding') && $this->delimiter == "\t") {
$output = chr(255) . chr(254);
}
@fwrite($file_handle, $output);
}
function add_row(&$file_handle, $data) {
foreach ($data as $key => $value) {
// Escape inner quotes and wrap all contents in new quotes.
$data[$key] = '"' . str_replace('"', '""', $data[$key]) . '"';
// Remove <script> tags, which mysteriously cause Excel not to import.
$data[$key] = preg_replace('!<(/?script.*?)>!', '[$1]', $data[$key]);
}
$row = implode($this->delimiter, $data) . "\n";
if (function_exists('mb_convert_encoding')) {
$row = mb_convert_encoding($row, 'UTF-16LE', 'UTF-8');
}
@fwrite($file_handle, $row);
}
function set_headers($filename) {
parent::set_headers($filename);
// Convert tabs.
if ($this->delimiter == "\t") {
$extension = 'tsv';
$content_type = 'text/tab-separated-values';
}
else {
$extension = 'csv';
$content_type = 'text/csv';
}
drupal_add_http_header('Content-Type', $content_type);
drupal_add_http_header('Content-Disposition', "attachment; filename=$filename.$extension");
}
}
/**
* The Excel exporter currently is just a tab-delimited export.
*/
class webform_exporter_excel extends webform_exporter_delimited {
var $delimiter;
function webform_exporter_excel($options) {
$options['delimiter'] = '\t';
parent::webform_exporter_delimited($options);
}
function set_headers($filename) {
drupal_add_http_header('Content-Type', 'application/x-msexcel');
drupal_add_http_header('Content-Disposition', "attachment; filename=$filename.xls");
drupal_add_http_header('Pragma', 'public');
drupal_add_http_header('Cache-Control', 'max-age=0');
}
}

View File

@@ -6,7 +6,7 @@
*/
/**
* Private implementation of hook_webform_select_options_info().
* Implements hook_webform_select_options_info().
*
* @see webform_webform_select_options_info()
*/
@@ -35,9 +35,11 @@ function _webform_options_info() {
}
/**
* Implements callback_webform_options().
*
* Option list containing the days of the week.
*/
function webform_options_days($component, $flat, $filter, $arguments) {
function webform_options_days($component, $flat, $arguments) {
$days = array(
'sunday' => t('Sunday'),
'monday' => t('Monday'),
@@ -58,17 +60,21 @@ function webform_options_days($component, $flat, $filter, $arguments) {
}
/**
* Implements callback_webform_options().
*
* Options list containing country names.
*/
function webform_options_countries($component, $flat, $filter, $arguments) {
function webform_options_countries($component, $flat, $arguments) {
include_once DRUPAL_ROOT . '/includes/locale.inc';
return country_get_list();
}
/**
* Implements callback_webform_options().
*
* Options list containing United States states and territories.
*/
function webform_options_united_states($component, $flat, $filter, $arguments) {
function webform_options_united_states($component, $flat, $arguments) {
return array(
'AL' => t('Alabama'),
'AK' => t('Alaska'),

View File

@@ -2,7 +2,6 @@
/**
* @file
*
* Menu callbacks and functions for configuring and editing webforms.
*/
@@ -10,8 +9,15 @@
* Main configuration form for editing a webform node.
*/
function webform_configure_form($form, &$form_state, $node) {
form_load_include($form_state, 'inc', 'webform', 'includes/webform.components');
$form['#attached']['library'][] = array('webform', 'admin');
// Refresh the entity cache to get the latest submission number.
$nid = $node->nid;
entity_get_controller('node')->resetCache(array($nid));
$node = node_load($nid);
$form['#node'] = $node;
$form['#submit'] = array(
@@ -36,7 +42,7 @@ function webform_configure_form($form, &$form_state, $node) {
$form['submission']['confirmation'] = array(
'#type' => 'text_format',
'#title' => t('Confirmation message'),
'#description' => t('Message to be shown upon successful submission. If the redirection location is set to <em>Confirmation page</em> it will be shown on its own page, otherwise this displays as a message.'),
'#description' => t('Message to be shown upon successful submission. If the redirection location is set to <em>Confirmation page</em> it will be shown on its own page, otherwise this displays as a message. Supports Webform token replacements.') . ' ' . theme('webform_token_help', array('groups' => array('node', 'submission'))),
'#default_value' => $node->webform['confirmation'],
'#cols' => 40,
'#rows' => 10,
@@ -64,9 +70,9 @@ function webform_configure_form($form, &$form_state, $node) {
'#type' => 'item',
'#title' => t('Redirection location'),
'#theme' => 'webform_advanced_redirection_form',
'#description' => t('Choose where to redirect the user upon successful submission.') . ' ' . t('The <em>Custom URL</em> option supports Webform token replacements.') . theme('webform_token_help', array('groups' => array('basic', 'node', 'special', 'submission'))),
'#description' => t('Choose where to redirect the user upon successful submission.') . ' ' . t('The <em>Custom URL</em> option supports Webform token replacements.') . ' ' . theme('webform_token_help', array('groups' => array('node', 'submission'))),
);
$form['submission']['redirection']['redirect']= array(
$form['submission']['redirection']['redirect'] = array(
'#type' => 'radios',
'#default_value' => $redirect,
'#options' => array(
@@ -74,13 +80,16 @@ function webform_configure_form($form, &$form_state, $node) {
'url' => t('Custom URL'),
'none' => t('No redirect (reload current page)'),
),
'#parents' => array('redirect'),
);
$form['submission']['redirection']['redirect_url'] = array(
'#type' => 'textfield',
'#title' => t('Redirect URL'),
'#description' => t('URL to redirect the user to upon successful submission.'),
'#default_value' => $redirect_url,
'#maxlength' => 255,
'#maxlength' => 2048,
'#parents' => array('redirect_url'),
'#attributes' => array('title' => t('Custom URL value')),
);
// Submission limit settings for all submissions.
@@ -103,14 +112,17 @@ function webform_configure_form($form, &$form_state, $node) {
'#default_value' => $node->webform['total_submit_limit'] != -1 ? $node->webform['total_submit_limit'] : '',
'#parents' => array('total_submit_limit'),
);
$interval_options = array(
'-1' => t('ever'),
'60' => t('every minute'),
'300' => t('every 5 minutes'),
'3600' => t('every hour'),
'86400' => t('every day'),
'604800' => t('every week'),
);
$form['submission']['total_submit_limit']['total_submit_interval'] = array(
'#type' => 'select',
'#options' => array(
'-1' => t('ever'),
'3600' => t('every hour'),
'86400' => t('every day'),
'604800' => t('every week'),
),
'#options' => $interval_options,
'#default_value' => $node->webform['total_submit_interval'],
'#parents' => array('total_submit_interval'),
);
@@ -137,12 +149,7 @@ function webform_configure_form($form, &$form_state, $node) {
);
$form['submission']['submit_limit']['submit_interval'] = array(
'#type' => 'select',
'#options' => array(
'-1' => t('ever'),
'3600' => t('every hour'),
'86400' => t('every day'),
'604800' => t('every week'),
),
'#options' => $interval_options,
'#default_value' => $node->webform['submit_interval'],
'#parents' => array('submit_interval'),
);
@@ -163,9 +170,14 @@ function webform_configure_form($form, &$form_state, $node) {
'#title' => t('Submission access'),
'#collapsible' => TRUE,
'#collapsed' => FALSE,
'#weight' => -3,
'#description' => t('These permissions affect which roles can submit this webform. It does not prevent access to the webform page. If needing to prevent access to the webform page entirely, use a content access module such as <a href="http://drupal.org/project/taxonomy_access">Taxonomy Access</a> or <a href="http://drupal.org/project/node_privacy_byrole">Node Privacy by Role</a>.'),
'#access' => variable_get('webform_submission_access_control', 1),
'#access' => webform_variable_get('webform_submission_access_control'),
'#weight' => 10,
'#states' => array(
'invisible' => array(
':input[name="confidential"]' => array('checked' => TRUE),
),
),
);
$user_roles = user_roles();
foreach ($user_roles as $rid => $rname) {
@@ -183,13 +195,135 @@ function webform_configure_form($form, &$form_state, $node) {
);
/* End per-role submission control */
/* Start progress bar settings form */
$form['progressbar'] = array(
'#type' => 'fieldset',
'#title' => t('Progress bar'),
'#collapsible' => TRUE,
'#collapsed' => TRUE,
'#weight' => 15,
);
$progress_bar_style = array_filter(array(
$node->webform['progressbar_page_number'] ? 'progressbar_page_number' : NULL,
$node->webform['progressbar_percent'] ? 'progressbar_percent' : NULL,
$node->webform['progressbar_bar'] ? 'progressbar_bar' : NULL,
$node->webform['progressbar_pagebreak_labels'] ? 'progressbar_pagebreak_labels' : NULL,
$node->webform['progressbar_include_confirmation'] ? 'progressbar_include_confirmation' : NULL,
));
$form['progressbar']['webform_progressbar_style'] = array(
'#type' => 'checkboxes',
'#title' => t('Progress bar style'),
'#options' => array(
'progressbar_bar' => t('Show progress bar'),
'progressbar_page_number' => t('Show page number as number of completed (i.e. Page 1 of 10)'),
'progressbar_percent' => t('Show percentage completed (i.e. 10%)'),
'progressbar_pagebreak_labels' => t('Show page labels from page break components'),
'progressbar_include_confirmation' => t('Include confirmation page in progress bar'),
),
'#default_value' => $progress_bar_style,
'#description' => t('Choose how the progress bar should be displayed for multi-page forms.'),
);
$form['progressbar']['progressbar_label_first'] = array(
'#type' => 'textfield',
'#title' => t('First page label'),
'#default_value' => $node->webform['progressbar_label_first'],
'#maxlength' => 255,
'#description' => t('The first page label in the progress bar. Subseqent pages are titled by their page break label.'),
'#states' => array(
'visible' => array(
':input[name="webform_progressbar_style[progressbar_pagebreak_labels]"]' => array('checked' => TRUE),
),
),
);
$form['progressbar']['progressbar_label_confirmation'] = array(
'#type' => 'textfield',
'#title' => t('Confirmation page label'),
'#default_value' => $node->webform['progressbar_label_confirmation'],
'#maxlength' => 255,
'#states' => array(
'visible' => array(
':input[name="webform_progressbar_style[progressbar_pagebreak_labels]"]' => array('checked' => TRUE),
':input[name="webform_progressbar_style[progressbar_include_confirmation]"]' => array('checked' => TRUE),
),
),
);
/* End progress bar settings form */
/* Preview page settings form */
$form['preview'] = array(
'#type' => 'fieldset',
'#title' => t('Preview page'),
'#collapsible' => TRUE,
'#collapsed' => TRUE,
'#weight' => 18,
'#id' => 'webform-preview-fieldset',
);
$form['preview']['preview'] = array(
'#type' => 'checkbox',
'#title' => t('Enable preview page'),
'#description' => t('Add a page for previewing the form before submitting.'),
'#default_value' => $node->webform['preview'],
'#id' => 'webform-preview-enable',
);
$form['preview']['settings'] = array(
'#type' => 'container',
'#states' => array(
'visible' => array(
'#webform-preview-enable' => array('checked' => TRUE),
),
),
'#id' => 'webform-preview-settings',
'#theme_wrappers' => array('container'),
);
$form['preview']['settings']['preview_title'] = array(
'#type' => 'textfield',
'#title' => t('Preview page title'),
'#default_value' => $node->webform['preview_title'],
'#description' => t('The page title will be used in the progress bar (if enabled). If left blank, the default title %preview will be used.', array('%preview' => t('Preview'))),
);
$form['preview']['settings']['preview_next_button_label'] = array(
'#type' => 'textfield',
'#title' => t('Preview button label'),
'#default_value' => $node->webform['preview_next_button_label'],
'#description' => t('The text for the button that will proceed to the preview page. If left blank, the default label %preview will be used.', array('%preview' => t('Preview'))),
'#size' => 20,
);
$form['preview']['settings']['preview_prev_button_label'] = array(
'#type' => 'textfield',
'#title' => t('Previous page button label'),
'#default_value' => $node->webform['preview_prev_button_label'],
'#description' => t('The text for the button to go backwards from the preview page. If left blank, the default label %previous will be used.', array('%previous' => t('< Previous'))),
'#size' => 20,
);
$submit_button_text = $node->webform['submit_text'] ? t($node->webform['submit_text']) : t('Submit');
$preview_default_message = t('Please review your submission. Your submission is not complete until you press the "@button" button!', array('@button' => $submit_button_text));
$form['preview']['settings']['preview_message'] = array(
'#type' => 'text_format',
'#title' => t('Preview message'),
'#default_value' => $node->webform['preview_message'],
'#format' => $node->webform['preview_message_format'],
'#description' => t('A message to be displayed on the preview page. If left blank, the message "!default" will be used. Supports Webform token replacements.', array('!default' => $preview_default_message)) . ' ' . theme('webform_token_help', array('groups' => array('node', 'submission'))),
);
$form['preview']['settings']['preview_components'] = array(
'#type' => 'select',
'#title' => t('Included preview values'),
'#options' => webform_component_list($node, 'email', TRUE),
'#default_value' => array_diff(array_keys($node->webform['components']), $node->webform['preview_excluded_components']),
'#multiple' => TRUE,
'#size' => 10,
'#description' => t('If you wish to include only parts of the submission in the preview, select the components that should be included.'),
'#process' => array('webform_component_select'),
);
/* End preview page settings form */
/* Start advanced settings form */
$form['advanced'] = array(
'#type' => 'fieldset',
'#title' => t('Advanced settings'),
'#collapsible' => TRUE,
'#collapsed' => TRUE,
'#weight' => -1,
'#weight' => 20,
);
$form['advanced']['block'] = array(
'#type' => 'checkbox',
@@ -198,12 +332,6 @@ function webform_configure_form($form, &$form_state, $node) {
'#description' => t('If enabled this webform will be available as a block.'),
'#access' => user_access('administer blocks') || user_access('administer site configuration') || user_access('use panels dashboard'),
);
$form['advanced']['teaser'] = array(
'#type' => 'checkbox',
'#title' => t('Show complete form in teaser'),
'#default_value' => $node->webform['teaser'],
'#description' => t('Display the entire form in the teaser display of this node.'),
);
$form['advanced']['allow_draft'] = array(
'#type' => 'checkbox',
'#title' => t('Show "Save draft" button'),
@@ -212,21 +340,36 @@ function webform_configure_form($form, &$form_state, $node) {
);
$form['advanced']['auto_save'] = array(
'#type' => 'checkbox',
'#title' => t('Automatically save as draft between pages'),
'#title' => t('Automatically save as draft between pages and when there are validation errors'),
'#default_value' => $node->webform['auto_save'],
'#description' => t('Automatically save partial submissions when users click the "Next" or "Previous" buttons in a multipage form.'),
'#description' => t('Automatically save partial submissions when users click the "Next" or "Previous" buttons in a multipage form or when validation errors prevent form submission.'),
);
$form['advanced']['submit_notice'] = array(
'#type' => 'checkbox',
'#title' => t('Show the notification about previous submissions.'),
'#title' => t('Show the notification about previous submissions'),
'#default_value' => $node->webform['submit_notice'],
'#description' => t('Show the previous submissions notification that appears when users have previously submitted this form.'),
);
$form['advanced']['confidential'] = array(
'#type' => 'checkbox',
'#title' => t('Confidential submissions'),
'#default_value' => $node->webform['confidential'],
'#description' => t('Confidential submissions have no recorded IP address and must be submitted while logged out.'),
);
$form['advanced']['submit_text'] = array(
'#type' => 'textfield',
'#title' => t('Submit button text'),
'#title' => t('Submit button label'),
'#default_value' => $node->webform['submit_text'],
'#description' => t('By default the submit button on this form will have the label <em>Submit</em>. Enter a new title here to override the default.'),
'#size' => 20,
);
$form['advanced']['next_serial'] = array(
'#type' => 'textfield',
'#title' => t('Next submission number'),
'#default_value' => $node->webform['next_serial'],
'#description' => t('The value of the next submission number. This is usually 1 when you start and will go up with each form submission.'),
'#size' => 8,
'#maxlength' => 8,
);
/* End Advanced Settings Form */
@@ -247,24 +390,13 @@ function webform_configure_form($form, &$form_state, $node) {
* Validate handler for webform_configure_form().
*/
function webform_configure_form_validate($form, &$form_state) {
// Ensure the entered e-mail addresses are valid.
if (!empty($form_state['values']['email'])) {
$emails = explode(',', $form_state['values']['email']);
foreach ($emails as $email) {
if (!valid_email_address(trim($email))) {
form_error($form['submission']['redirect_url'], t('The entered email address %address is not a valid address.', array('%address' => $email)));
break;
}
}
}
// Ensure the entered redirect URL is valid.
if ($form_state['values']['redirect'] == 'url') {
$redirect_url = trim($form_state['values']['redirect_url']);
if (empty($redirect_url)) {
form_error($form['submission']['redirection']['redirect_url'], t('A valid URL is required for custom redirection.'));
}
elseif (strpos($redirect_url, 'http') === 0 && !valid_url($redirect_url, TRUE)) {
elseif (strpos($redirect_url, 'http') === 0 && !valid_url(webform_replace_tokens($redirect_url, $form['#node']), TRUE)) {
form_error($form['submission']['redirection']['redirect_url'], t('The entered URL is not a valid address.'));
}
else {
@@ -277,6 +409,25 @@ function webform_configure_form_validate($form, &$form_state) {
else {
form_set_value($form['submission']['redirection']['redirect_url'], '<none>', $form_state);
}
// Ensure only positive integers are entered as submission limits.
foreach (array('total_submit_limit' => 'enforce_total_limit', 'submit_limit' => 'enforce_limit') as $field => $enforce_fk) {
$limit = $form['submission'][$field][$field]['#value'];
if ($form['submission'][$field][$enforce_fk]['#value'] !== 'no' && $limit !== '' && (int) $limit < 1 && (int) $limit !== -1) {
form_error($form['submission'][$field][$field], t('The submission limit must be at least 1.'));
}
}
// Prohibit the combination of confidential + per-user limit + ip-only
// submission tracking for anonymous users as it would not be enforceable.
if (webform_variable_get('webform_tracking_mode') == 'ip_address' &&
$form_state['values']['confidential'] &&
$form_state['values']['enforce_limit'] == 'yes') {
// Note that FAPI doesn't actually support error highlighting on radio or
// checkbox form elements.
form_error($form['advanced']['confidential'],
t('Choose a "Per user submission limit" or "Confidential submissions", but not both. Or ask the adminstrator to track anonymous users by cookie, rather than IP address only.'));
}
}
/**
@@ -290,7 +441,7 @@ function webform_configure_form_submit($form, &$form_state) {
$node->webform['confirmation'] = $form_state['values']['confirmation']['value'];
$node->webform['confirmation_format'] = $form_state['values']['confirmation']['format'];
// Save the redirect URL
// Save the redirect URL.
$node->webform['redirect_url'] = $form_state['values']['redirect_url'];
// Overall form status.
@@ -302,9 +453,6 @@ function webform_configure_form_submit($form, &$form_state) {
// Set the block option.
$node->webform['block'] = $form_state['values']['block'];
// Set the Show complete form in teaser setting.
$node->webform['teaser'] = $form_state['values']['teaser'];
// Set the draft option.
$node->webform['allow_draft'] = $form_state['values']['allow_draft'];
@@ -317,8 +465,8 @@ function webform_configure_form_submit($form, &$form_state) {
$node->webform['submit_interval'] = -1;
}
else {
$node->webform['submit_limit'] = $form_state['values']['submit_limit'];
$node->webform['submit_interval'] = $form_state['values']['submit_interval'];
$node->webform['submit_limit'] = (int) $form_state['values']['submit_limit'];
$node->webform['submit_interval'] = (int) $form_state['values']['submit_interval'];
}
// Set the total submit limit to -1 if set to unlimited.
@@ -327,15 +475,55 @@ function webform_configure_form_submit($form, &$form_state) {
$node->webform['total_submit_interval'] = -1;
}
else {
$node->webform['total_submit_limit'] = $form_state['values']['total_submit_limit'];
$node->webform['total_submit_interval'] = $form_state['values']['total_submit_interval'];
$node->webform['total_submit_limit'] = (int) $form_state['values']['total_submit_limit'];
$node->webform['total_submit_interval'] = (int) $form_state['values']['total_submit_interval'];
}
// Set the progress bar preferences.
$progress_bar_settings = array_filter($form_state['values']['webform_progressbar_style']);
$node->webform['progressbar_page_number'] = in_array('progressbar_page_number', $progress_bar_settings);
$node->webform['progressbar_percent'] = in_array('progressbar_percent', $progress_bar_settings);
$node->webform['progressbar_bar'] = in_array('progressbar_bar', $progress_bar_settings);
$node->webform['progressbar_pagebreak_labels'] = in_array('progressbar_pagebreak_labels', $progress_bar_settings);
$node->webform['progressbar_include_confirmation'] = in_array('progressbar_include_confirmation', $progress_bar_settings);
$node->webform['progressbar_label_first'] = $form_state['values']['progressbar_label_first'];
$node->webform['progressbar_label_confirmation'] = $form_state['values']['progressbar_label_confirmation'];
// Set the preview settings.
$node->webform['preview'] = (int) $form_state['values']['preview'];
$node->webform['preview_next_button_label'] = $form_state['values']['preview_next_button_label'];
$node->webform['preview_prev_button_label'] = $form_state['values']['preview_prev_button_label'];
$node->webform['preview_title'] = $form_state['values']['preview_title'];
$node->webform['preview_message'] = $form_state['values']['preview_message']['value'];
$node->webform['preview_message_format'] = $form_state['values']['preview_message']['format'];
// Save the list of included preview components.
// We actually maintain an *exclusion* list, so any new components will
// default to being included in the preview until unchecked.
$included = array_keys(array_filter((array) $form_state['values']['preview_components']));
$excluded = array_diff(array_keys($node->webform['components']), $included);
$node->webform['preview_excluded_components'] = $excluded;
// Set submit notice.
$node->webform['submit_notice'] = $form_state['values']['submit_notice'];
// Set confidential.
$node->webform['confidential'] = $form_state['values']['confidential'];
// Set submit button text.
$node->webform['submit_text'] = $form_state['values']['submit_text'];
// Set next serial number. It must be a positive integer greater than any
// existing serial number, which could have increased while this form was
// being edited.
$next_min = _webform_submission_serial_next_value_used($node->nid);
$next_serial = (int) $form_state['values']['next_serial'];
if ($next_serial < $next_min) {
drupal_set_message(t('The next submission number was increased to @min to make it higher than existing submissions.',
array('@min' => $next_min)));
$next_serial = $next_min;
}
$node->webform['next_serial'] = $next_serial;
}
/**
@@ -347,6 +535,29 @@ function webform_configure_form_submit($form, &$form_state) {
function webform_configure_form_submit_save($form, &$form_state) {
node_save($form['#node']);
drupal_set_message(t('The form settings have been updated.'));
$node = &$form['#node'];
if (!$node->webform['block'] && function_exists('block_load') &&
($block = block_load('webform', 'client-block-' . $node->nid)) &&
!empty($block->bid)) {
// An existing block for this not-currently-available block was already configured.
// See https://www.drupal.org/node/2365825
// The core block module contains a bug which causes it to not remove blocks
// which are no longer available in code. Therefore, to prevent the block from
// being processed, it must be removed manually here.
// Remove the block for all themes.
db_delete('block')
->condition('module', 'webform')
->condition('delta', $block->delta)
->execute();
// Remove any roles associated with the block.
db_delete('block_role')
->condition('module', 'webform')
->condition('delta', $block->delta)
->execute();
drupal_set_message(t('The block %info was deleted.', array('%info' => t('Webform: !title', array('!title' => $node->title)))));
cache_clear_all();
}
}
/**
@@ -357,15 +568,13 @@ function theme_webform_advanced_redirection_form($variables) {
// Add special class for setting the active radio button.
$form['redirect_url']['#attributes']['class'][] = 'webform-set-active';
// Remove title and description for Redirect URL field.
$form['redirect_url']['#title'] = NULL;
$form['redirect_url']['#description'] = NULL;
// Remove wrappers around the inline Redirect URL field.
$form['redirect_url']['#theme_wrappers'] = array();
$form['redirect']['confirmation']['#theme_wrappers'] = array('webform_inline_radio');
$form['redirect']['url']['#theme_wrappers'] = array('webform_inline_radio');
$form['redirect']['none']['#theme_wrappers'] = array('webform_inline_radio');
$form['redirect']['url']['#inline_element'] = $form['redirect']['url']['#title'] . ': ' . drupal_render($form['redirect_url']);
$form['redirect']['url']['#title'] = NULL;
$form['redirect']['url']['#title'] = t('Custom URL: !redirect_url', array('!redirect_url' => drupal_render($form['redirect_url'])));
return drupal_render_children($form);
}
@@ -380,15 +589,10 @@ function theme_webform_advanced_submit_limit_form($variables) {
// Remove div wrappers around limit options.
$form['submit_limit']['#theme_wrappers'] = array();
$form['submit_interval']['#theme_wrappers'] = array();
$replacements = array(
'!count' => drupal_render($form['submit_limit']),
'!timespan' => drupal_render($form['submit_interval']),
);
$form['enforce_limit']['no']['#theme_wrappers'] = array('webform_inline_radio');
$form['enforce_limit']['yes']['#title'] = NULL;
$form['enforce_limit']['yes']['#inline_element'] = t('Limit each user to !count submission(s) !timespan', $replacements);
$form['enforce_limit']['yes']['#theme_wrappers'] = array('webform_inline_radio');
$form['enforce_limit']['yes']['#title'] = t('Limit each user to !count submission(s) !timespan', array('!count' => drupal_render($form['submit_limit']), '!timespan' => drupal_render($form['submit_interval'])));
return drupal_render_children($form);
}
@@ -403,15 +607,10 @@ function theme_webform_advanced_total_submit_limit_form($variables) {
// Remove div wrappers around limit options.
$form['total_submit_limit']['#theme_wrappers'] = array();
$form['total_submit_interval']['#theme_wrappers'] = array();
$replacements = array(
'!count' => drupal_render($form['total_submit_limit']),
'!timespan' => drupal_render($form['total_submit_interval']),
);
$form['enforce_total_limit']['no']['#theme_wrappers'] = array('webform_inline_radio');
$form['enforce_total_limit']['yes']['#title'] = NULL;
$form['enforce_total_limit']['yes']['#inline_element'] = t('Limit to !count total submission(s) !timespan', $replacements);
$form['enforce_total_limit']['yes']['#theme_wrappers'] = array('webform_inline_radio');
$form['enforce_total_limit']['yes']['#title'] = t('Limit to !count total submission(s) !timespan', array('!count' => drupal_render($form['total_submit_limit']), '!timespan' => drupal_render($form['total_submit_interval'])));
return drupal_render_children($form);
}

View File

@@ -0,0 +1,637 @@
<?php
/**
* @file
* Conditional engine to process dependencies within the webform's conditionals.
*/
/**
* Performs analysis and topological sorting on the conditionals.
*/
class WebformConditionals {
/**
* Define constants.
*
* Define constants for the result of an analysis of the conditionals on a
* page for a given set of input values. Determines whether the component is
* always hidden, always shown, or may or may not be shown depending upon
* other values on the same page. In the last case, the component needs to be
* rendered on the page because at least one source component is on the same
* page. The field will be hidden with JavaScript.
*
* @var int
*/
const componentHidden = 0;
const componentShown = 1;
const componentDependent = 2;
protected static $conditionals = array();
protected $node;
protected $topologicalOrder;
protected $pageMap;
protected $childrenMap;
protected $visibilityMap;
protected $requiredMap;
protected $setMap;
protected $markupMap;
public $errors;
/**
* Creates and caches a WebformConditional for a given node.
*/
public static function factory($node) {
if (!isset(self::$conditionals[$node->nid])) {
self::$conditionals[$node->nid] = new WebformConditionals($node);
}
return self::$conditionals[$node->nid];
}
/**
* Constructs a WebformConditional.
*/
public function __construct($node) {
$this->node = $node;
}
/**
* Sorts the conditionals into topological order.
*
* The "nodes" of the list are the conditionals, not the components that
* they operate upon.
*
* The webform components must already be sorted into component tree order
* before calling this method.
*
* See http://en.wikipedia.org/wiki/Topological_sorting
*/
protected function topologicalSort() {
$components = $this->node->webform['components'];
$conditionals = $this->node->webform['conditionals'];
$errors = array();
// Generate a component to conditional map for conditional targets.
$cid_to_target_rgid = array();
$cid_hidden = array();
foreach ($conditionals as $rgid => $conditional) {
foreach ($conditional['actions'] as $aid => $action) {
$target_id = $action['target'];
$cid_to_target_rgid[$target_id][$rgid] = $rgid;
if ($action['action'] == 'show') {
$cid_hidden[$target_id] = isset($cid_hidden[$target_id]) ? $cid_hidden[$target_id] + 1 : 1;
if ($cid_hidden[$target_id] == 2) {
$component = $components[$target_id];
$errors[$component['page_num']][] = t('More than one conditional hides or shows component "@name".',
array('@name' => $component['name']));
}
}
}
}
// Generate T-Orders for each page.
$new_entry = array('in' => array(), 'out' => array(), 'rgid' => array());
$page_num = 0;
// If the first component is a page break, then no component is on page 1. Create empty arrays for page 1.
$sorted = array(1 => array());
$page_map = array(1 => array());
$component = reset($components);
while ($component) {
$cid = $component['cid'];
// Start a new page, if needed.
if ($component['page_num'] > $page_num) {
$page_num = $component['page_num'];
// Create an empty list that will contain the sorted elements.
// This list is known as L in the literature.
$sorted[$page_num] = array();
// Create an empty list of dependency nodes for this page.
$nodes = array();
}
// Create the pageMap as a side benefit of generating the t-sort.
$page_map[$page_num][$cid] = $cid;
// Process component by adding it's conditional data to a component-tree-traversal order an index of:
// - incoming dependencies = the source components for the conditions for this target component and
// - outgoing dependencies = components which depend upon the target component
// Note: Surprisingly, 0 is a valid rgid, as well as a valid rid. Use -1 as a semaphore.
if (isset($cid_to_target_rgid[$cid])) {
// The component is the target of conditional(s)
foreach ($cid_to_target_rgid[$cid] as $rgid) {
$conditional = $conditionals[$rgid];
if (!isset($nodes[$cid])) {
$nodes[$cid] = $new_entry;
}
$nodes[$cid]['rgid'][$rgid] = $rgid;
foreach ($conditional['rules'] as $rule) {
if ($rule['source_type'] == 'component') {
$source_id = $rule['source'];
if (!isset($nodes[$source_id])) {
$nodes[$source_id] = $new_entry;
}
$nodes[$cid]['in'][$source_id] = $source_id;
$nodes[$source_id]['out'][$cid] = $cid;
$source_component = $components[$source_id];
$source_pid = $source_component['pid'];
if ($source_pid) {
if (!isset($nodes[$source_pid])) {
$nodes[$source_pid] = $new_entry;
}
// The rule source is within a parent fieldset. Create a dependency on the parent.
$nodes[$source_pid]['out'][$source_id] = $source_id;
$nodes[$source_id]['in'][$source_pid] = $source_pid;
}
if ($source_component['page_num'] > $page_num) {
$errors[$page_num][] = t('A forward reference from page @from, %from to %to was found.',
array(
'%from' => $source_component['name'],
'@from' => $source_component['page_num'],
'%to' => $component['name'],
));
}
elseif ($source_component['page_num'] == $page_num && $component['type'] == 'pagebreak') {
$errors[$page_num][] = t("The page break %to can't be controlled by %from on the same page.",
array(
'%from' => $source_component['name'],
'%to' => $component['name'],
));
}
}
}
}
}
// Fetch the next component, if any.
$component = next($components);
// Finish any previous page already processed.
if (!$component || $component['page_num'] > $page_num) {
// Create a set of all components which have are not dependent upon anything.
// This list is known as S in the literature.
$start_nodes = array();
foreach ($nodes as $id => $n) {
if (!$n['in']) {
$start_nodes[] = $id;
}
}
// Process the start nodes, removing each one in turn from the queue.
while ($start_nodes) {
$id = array_shift($start_nodes);
// If the node represents an actual conditional, it can now be added
// to the end of the sorted order because anything it depends upon has
// already been calculated.
if ($nodes[$id]['rgid']) {
foreach ($nodes[$id]['rgid'] as $rgid) {
$sorted[$page_num][] = array(
'cid' => $id,
'rgid' => $rgid,
'name' => $components[$id]['name'],
);
}
}
// Any other nodes that depend upon this node may now have their dependency
// on this node removed, since it has now been calculated.
foreach ($nodes[$id]['out'] as $out_id) {
unset($nodes[$out_id]['in'][$id]);
if (!$nodes[$out_id]['in']) {
$start_nodes[] = $out_id;
}
}
// All out-going dependencies have been handled.
$nodes[$id]['out'] = array();
}
// Check for a cyclic graph (circular dependency).
foreach ($nodes as $id => $n) {
if ($n['in'] || $n['out']) {
$errors[$page_num][] = t('A circular reference involving %name was found.',
array('%name' => $components[$id]['name']));
}
}
} // End finishing previous page.
} // End component loop.
// Create an empty page map for the preview page.
$page_map[$page_num + 1] = array();
$this->topologicalOrder = $sorted;
$this->errors = $errors;
$this->pageMap = $page_map;
}
/**
* Returns the (possibly cached) topological sort order.
*/
public function getOrder() {
if (!$this->topologicalOrder) {
$this->topologicalSort();
}
return $this->topologicalOrder;
}
/**
* Returns an index of components by page number.
*/
public function getPageMap() {
if (!$this->pageMap) {
$this->topologicalSort();
}
return $this->pageMap;
}
/**
* Displays and error messages from the previously-generated sort order.
*
* User's who can't fix the webform are shown a single, simplified message.
*/
public function reportErrors() {
$this->getOrder();
if ($this->errors) {
if (webform_node_update_access($this->node)) {
foreach ($this->errors as $page_num => $page_errors) {
drupal_set_message(format_plural(count($page_errors),
'Conditional error on page @num:',
'Conditional errors on page @num:',
array('@num' => $page_num)) .
'<br /><ul><li>' . implode('</li><li>', $page_errors) . '</li></ul>', 'warning');
}
}
else {
drupal_set_message(t('This form is improperly configured. Contact the administrator.'), 'warning');
}
}
}
/**
* Creates and caches a map of the children of a each component.
*
* Called after the component tree has been made and then flattened again.
* Alas, the children data is removed when the tree is flattened. The
* components are indexed by cid but in tree order. Because cid's are
* numeric, they may not appear in IDE's or debuggers in their actual order.
*/
public function getChildrenMap() {
if (!$this->childrenMap) {
$map = array();
foreach ($this->node->webform['components'] as $cid => $component) {
$pid = $component['pid'];
if ($pid) {
$map[$pid][] = $cid;
}
}
$this->childrenMap = $map;
}
return $this->childrenMap;
}
/**
* Deletes the value of the given component, plus any descendants.
*/
protected function deleteFamily(&$input_values, $parent_id, &$page_visiblity_page) {
if (isset($input_values[$parent_id])) {
$input_values[$parent_id] = NULL;
}
if (isset($this->childrenMap[$parent_id])) {
foreach ($this->childrenMap[$parent_id] as $child_id) {
$page_visiblity_page[$child_id] = $page_visiblity_page[$parent_id];
$this->deleteFamily($input_values, $child_id, $page_visiblity_page);
}
}
}
protected $stackPointer;
protected $resultStack;
/**
* Initializes an execution stack for a conditional group's rules.
*
* Also initializes sub-conditional rules.
*/
public function executionStackInitialize($andor) {
$this->stackPointer = -1;
$this->resultStack = array();
$this->executionStackPush($andor);
}
/**
* Starts a new subconditional for the given and/or operator.
*/
public function executionStackPush($andor) {
$this->resultStack[++$this->stackPointer] = array(
'results' => array(),
'andor' => $andor,
);
}
/**
* Adds a rule's result to the current sub-conditional.
*/
public function executionStackAccumulate($result) {
$this->resultStack[$this->stackPointer]['results'][] = $result;
}
/**
* Finishes a sub-conditional and adds the result to the parent stack frame.
*/
public function executionStackPop() {
// Calculate the and/or result.
$stack_frame = $this->resultStack[$this->stackPointer];
// Pop stack and protect against stack underflow.
$this->stackPointer = max(0, $this->stackPointer - 1);
$conditional_results = $stack_frame['results'];
$filtered_results = array_filter($conditional_results);
return $stack_frame['andor'] === 'or'
? count($filtered_results) > 0
: count($filtered_results) === count($conditional_results);
}
/**
* Executes the conditionals on a submission.
*
* This removes any data which should be hidden.
*/
public function executeConditionals($input_values, $page_num = 0) {
$this->getOrder();
$this->getChildrenMap();
if (!$this->visibilityMap || $page_num == 0) {
// Create a new visibility map, with all components shown.
$this->visibilityMap = $this->pageMap;
array_walk_recursive($this->visibilityMap, function (&$status) {
$status = WebformConditionals::componentShown;
});
// Create empty required, set, and markup maps.
$this->requiredMap = array_fill(1, count($this->pageMap), array());
$this->setMap = $this->requiredMap;
$this->markupMap = $this->requiredMap;
}
else {
array_walk($this->visibilityMap[$page_num], function (&$status) {
$status = WebformConditionals::componentShown;
});
$this->requiredMap[$page_num] = array();
$this->setMap[$page_num] = array();
$this->markupMap[$page_num] = array();
}
module_load_include('inc', 'webform', 'includes/webform.conditionals');
$components = $this->node->webform['components'];
$conditionals = $this->node->webform['conditionals'];
$operators = webform_conditional_operators();
$targetLocked = array();
$first_page = $page_num ? $page_num : 1;
$last_page = $page_num ? $page_num : count($this->topologicalOrder);
for ($page = $first_page; $page <= $last_page; $page++) {
foreach ($this->topologicalOrder[$page] as $conditional_spec) {
$conditional = $conditionals[$conditional_spec['rgid']];
$source_page_nums = array();
// Execute each comparison callback.
$this->executionStackInitialize($conditional['andor']);
foreach ($conditional['rules'] as $rule) {
switch ($rule['source_type']) {
case 'component':
$source_component = $components[$rule['source']];
$source_cid = $source_component['cid'];
$source_values = array();
if (isset($input_values[$source_cid])) {
$component_value = $input_values[$source_cid];
// For select_or_other components, use only the select values because $source_values must not be a nested array.
// During preview, the array is already flattened.
if ($source_component['type'] === 'select' &&
!empty($source_component['extra']['other_option']) &&
isset($component_value['select'])) {
$component_value = $component_value['select'];
}
$source_values = is_array($component_value) ? $component_value : array($component_value);
}
// Determine the operator and callback.
$conditional_type = webform_component_property($source_component['type'], 'conditional_type');
$operator_info = $operators[$conditional_type];
// Perform the comparison callback and build the results for this group.
$comparison_callback = $operator_info[$rule['operator']]['comparison callback'];
// Contrib caching, such as entitycache, may have loaded the node
// without building it. It is possible that the component include file
// hasn't been included yet. See #2529246.
webform_component_include($source_component['type']);
// Load missing include files for conditional types.
// In the case of the 'string', 'date', and 'time' conditional types, it is
// not necessary to load their include files for conditional behavior
// because the required functions are already loaded
// in webform.conditionals.inc.
switch ($conditional_type) {
case 'numeric':
webform_component_include('number');
break;
case 'select':
webform_component_include($conditional_type);
break;
}
$this->executionStackAccumulate($comparison_callback($source_values, $rule['value'], $source_component));
// Record page number to later determine any intra-page dependency on this source.
$source_page_nums[$source_component['page_num']] = $source_component['page_num'];
break;
case 'conditional_start':
$this->executionStackPush($rule['operator']);
break;
case 'conditional_end':
$this->executionStackAccumulate($this->executionStackPop());
break;
}
}
$conditional_result = $this->executionStackPop();
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) {
$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]);
$targetLocked[$target] = TRUE;
}
break;
case 'require':
$this->requiredMap[$page_num][$target] = $action_result;
break;
case 'set':
if ($components[$target]['type'] == 'markup') {
$this->markupMap[$page_num][$target] = FALSE;
}
if ($action_result && empty($targetLocked[$target])) {
if ($components[$target]['type'] == 'markup') {
$this->markupMap[$page_num][$target] = $action['argument'];
}
else {
$input_values[$target] = isset($input_values[$target]) && is_array($input_values[$target])
? array($action['argument'])
: $action['argument'];
$this->setMap[$page_num][$target] = TRUE;
}
}
break;
}
}
} // End conditinal loop
} // End page loop
return $input_values;
}
/**
* Returns whether the conditionals have been executed yet.
*/
public function isExecuted() {
return (boolean) ($this->visibilityMap);
}
/**
* Returns the required status for a component.
*
* Returns whether a given component is always hidden, always shown, or might
* be shown depending upon other sources on the same page.
*
* Assumes that the conditionals have already been executed on the given page.
*
* @param int $cid
* The component id of the component whose visibility is being sought.
* @param int $page_num
* The page number that the component is on.
*
* @return int
* self::componentHidden, ...Shown, or ...Dependent.
*/
public function componentVisibility($cid, $page_num) {
if (!$this->visibilityMap) {
// The conditionals have not yet been executed on a submission.
$this->executeConditionals(array(), 0);
watchdog('webform', 'WebformConditionals::componentVisibility called prior to evaluating a submission.', array(), WATCHDOG_ERROR);
}
return isset($this->visibilityMap[$page_num][$cid]) ? $this->visibilityMap[$page_num][$cid] : self::componentShown;
}
/**
* Returns whether a given page should be displayed.
*
* This requires any conditional for the page itself to be shown, plus at
* least one component within the page must be shown too. The first and
* preview pages are always shown, however.
*
* @param int $page_num
* The page number that the component is on.
*
* @return int
* self::componentHidden or ...Shown.
*/
public function pageVisibility($page_num) {
$result = self::componentHidden;
if ($page_num == 1 || empty($this->visibilityMap[$page_num])) {
$result = self::componentShown;
}
elseif (($page_map = $this->pageMap[$page_num]) && $this->componentVisibility(reset($page_map), $page_num)) {
while ($cid = next($page_map)) {
if ($this->componentVisibility($cid, $page_num) != self::componentHidden) {
$result = self::componentShown;
break;
}
}
}
return $result;
}
/**
* Returns the required status for a component.
*
* Returns whether a given component is always required, always optional, or
* unchanged by conditional logic.
*
* Assumes that the conditionals have already been executed on the given page.
*
* @param int $cid
* The component id of the component whose required state is being sought.
* @param int $page_num
* The page number that the component is on.
*
* @return bool
* Whether the component is required based on conditionals.
*/
public function componentRequired($cid, $page_num) {
if (!$this->requiredMap) {
// The conditionals have not yet been executed on a submission.
$this->executeConditionals(array(), 0);
watchdog('webform', 'WebformConditionals::componentRequired called prior to evaluating a submission.', array(), WATCHDOG_ERROR);
}
return isset($this->requiredMap[$page_num][$cid]) ? $this->requiredMap[$page_num][$cid] : NULL;
}
/**
* Returns whether a given component has been set by conditional logic.
*
* Assumes that the conditionals have already been executed on the given page.
*
* @param int $cid
* The component id of the component whose set state is being sought.
* @param int $page_num
* The page number that the component is on.
*
* @return bool
* Whether the component was set based on conditionals.
*/
public function componentSet($cid, $page_num) {
if (!$this->setMap) {
// The conditionals have not yet been executed on a submission.
$this->executeConditionals(array(), 0);
watchdog('webform', 'WebformConditionals::componentSet called prior to evaluating a submission.', array(), WATCHDOG_ERROR);
}
return isset($this->setMap[$page_num][$cid]) ? $this->setMap[$page_num][$cid] : NULL;
}
/**
* Returns the calculated markup as set by conditional logic.
*
* Assumes that the conditionals have already been executed on the given page.
*
* @param int $cid
* The component id of the component whose set state is being sought.
* @param int $page_num
* The page number that the component is on.
*
* @return string
* The conditional markup, or NULL if none.
*/
public function componentMarkup($cid, $page_num) {
if (!$this->markupMap) {
// The conditionals have not yet been executed on a submission.
$this->executeConditionals(array(), 0);
watchdog('webform', 'WebformConditionals::componentMarkup called prior to evaluating a submission.', array(), WATCHDOG_ERROR);
}
return isset($this->markupMap[$page_num][$cid]) ? $this->markupMap[$page_num][$cid] : NULL;
}
}

View File

@@ -0,0 +1,27 @@
/**
* @file
* Enhancements for webform node type forms.
*/
(function ($) {
"use strict";
Drupal.behaviors.webformContentTypes = {
attach: function (context) {
// Provide the vertical tab summaries.
$('fieldset#edit-webform', context).drupalSetSummary(function (context) {
var vals = [];
$('input[type=checkbox]', context).each(function () {
if (this.checked && this.attributes['data-enabled-description']) {
vals.push(this.attributes['data-enabled-description'].value);
}
else if (!this.checked && this.attributes['data-disabled-description']) {
vals.push(this.attributes['data-disabled-description'].value);
}
});
return vals.join(', ');
});
}
};
})(jQuery);

View File

@@ -1,4 +1,3 @@
/**
* @file
* Enhancements for select list configuration options.
@@ -6,51 +5,63 @@
(function ($) {
Drupal.behaviors.webformSelectLoadOptions = {};
Drupal.behaviors.webformSelectLoadOptions.attach = function(context) {
settings = Drupal.settings;
"use strict";
$('#edit-extra-options-source', context).change(function() {
var url = settings.webform.selectOptionsUrl + '/' + this.value;
$.ajax({
url: url,
success: Drupal.webform.selectOptionsLoad,
dataType: 'json'
Drupal.behaviors.webformSelectLoadOptions = {};
Drupal.behaviors.webformSelectLoadOptions.attach = function (context) {
$('#edit-extra-options-source', context).change(function () {
var url = Drupal.settings.webform.selectOptionsUrl + '/' + this.value;
$.ajax({
url: url,
success: Drupal.webform.selectOptionsLoad,
dataType: 'json'
});
});
});
}
};
Drupal.webform = Drupal.webform || {};
Drupal.webform = Drupal.webform || {};
Drupal.webform.selectOptionsOriginal = false;
Drupal.webform.selectOptionsLoad = function(result) {
if (Drupal.optionsElement) {
if (result.options) {
// Save the current select options the first time a new list is chosen.
if (Drupal.webform.selectOptionsOriginal === false) {
Drupal.webform.selectOptionsOriginal = $(Drupal.optionElements[result.elementId].manualOptionsElement).val();
}
$(Drupal.optionElements[result.elementId].manualOptionsElement).val(result.options);
Drupal.optionElements[result.elementId].disable();
Drupal.optionElements[result.elementId].updateWidgetElements();
}
else {
Drupal.optionElements[result.elementId].enable();
if (Drupal.webform.selectOptionsOriginal) {
$(Drupal.optionElements[result.elementId].manualOptionsElement).val(Drupal.webform.selectOptionsOriginal);
Drupal.webform.selectOptionsOriginal = false;
Drupal.webform.selectOptionsLoad = function (result) {
if (Drupal.optionsElement) {
if (result.options) {
// Save the current select options the first time a new list is chosen.
if (Drupal.webform.selectOptionsOriginal === false) {
Drupal.webform.selectOptionsOriginal = $(Drupal.optionElements[result.elementId].manualOptionsElement).val();
}
$(Drupal.optionElements[result.elementId].manualOptionsElement).val(result.options);
Drupal.optionElements[result.elementId].disable();
Drupal.optionElements[result.elementId].updateWidgetElements();
Drupal.webform.selectOptionsOriginal = false;
}
else {
Drupal.optionElements[result.elementId].enable();
if (Drupal.webform.selectOptionsOriginal) {
$(Drupal.optionElements[result.elementId].manualOptionsElement).val(Drupal.webform.selectOptionsOriginal);
Drupal.optionElements[result.elementId].updateWidgetElements();
Drupal.webform.selectOptionsOriginal = false;
}
}
}
else {
var $element = $('#' + result.elementId);
$element.webformProp('readonly', result.options);
if (result.options) {
$element.val(result.options);
}
}
}
else {
if (result.options) {
$('#' + result.elementId).val(result.options).attr('readonly', 'readonly');
/**
* Make a prop shim for jQuery < 1.9.
*/
$.fn.webformProp = $.fn.webformProp || function (name, value) {
if (value) {
return $.fn.prop ? this.prop(name, true) : this.attr(name, true);
}
else {
$('#' + result.elementId).removeAttr('readonly');
return $.fn.prop ? this.prop(name, false) : this.removeAttr(name);
}
}
}
};
})(jQuery);

View File

@@ -1,119 +1,342 @@
(function ($) {
/**
* Webform node form interface enhancments.
* @file
* Webform node form interface enhancements.
*/
Drupal.behaviors.webformAdmin = {};
Drupal.behaviors.webformAdmin.attach = function(context) {
// Apply special behaviors to fields with default values.
Drupal.webform.defaultValues(context);
// On click or change, make a parent radio button selected.
Drupal.webform.setActive(context);
// Update the template select list upon changing a template.
Drupal.webform.updateTemplate(context);
// Select all link for file extensions.
Drupal.webform.selectCheckboxesLink(context);
// Enhance the normal tableselect.js file to support indentations.
Drupal.webform.tableSelectIndentation(context);
}
(function ($) {
Drupal.webform = Drupal.webform || {};
"use strict";
Drupal.webform.defaultValues = function(context) {
var $fields = $('.webform-default-value:not(.error)', context);
var $forms = $fields.parents('form:first');
$fields.each(function() {
this.defaultValue = $(this).attr('rel');
if (this.value != this.defaultValue) {
$(this).removeClass('webform-default-value');
}
$(this).focus(function() {
if (this.value == this.defaultValue) {
this.value = '';
$(this).removeClass('webform-default-value');
}
});
$(this).blur(function() {
if (this.value == '') {
$(this).addClass('webform-default-value');
this.value = this.defaultValue;
}
});
});
// Clear all the form elements before submission.
$forms.submit(function() {
$fields.focus();
});
};
Drupal.webform.setActive = function(context) {
var setActive = function(e) {
$('.form-radio', $(this).parent().parent()).attr('checked', true);
e.preventDefault();
Drupal.behaviors.webformAdmin = {};
Drupal.behaviors.webformAdmin.attach = function (context) {
// On click or change, make a parent radio button selected.
Drupal.webform.setActive(context);
Drupal.webform.updateTemplate(context);
// Update the template select list upon changing a template.
// Select all link for file extensions.
Drupal.webform.selectCheckboxesLink(context);
// Enhance the normal tableselect.js file to support indentations.
Drupal.webform.tableSelectIndentation(context);
// Automatically download exports if available.
Drupal.webform.downloadExport(context);
// Enhancements for the conditionals administrative page.
Drupal.webform.conditionalAdmin(context);
// Trigger radio/checkbox change when label click automatically selected by
// browser.
Drupal.webform.radioLabelAutoClick(context);
};
$('.webform-set-active', context).click(setActive).change(setActive);
};
Drupal.webform.updateTemplate = function(context) {
var defaultTemplate = $('#edit-templates-default').val();
var $templateSelect = $('#webform-template-fieldset select#edit-template-option', context);
var $templateTextarea = $('#webform-template-fieldset textarea:visible', context);
Drupal.webform = Drupal.webform || {};
var updateTemplateSelect = function() {
if ($(this).val() == defaultTemplate) {
$templateSelect.val('default');
Drupal.webform.setActive = function (context) {
$('.webform-inline-radio', context).click(function (e) {
$(this).closest('.form-type-radio').find('input[type=radio]').webformProp('checked', true);
});
$('.webform-set-active', context).change(function (e) {
if ($(this).val()) {
$(this).closest('.form-type-radio').find('input[type=radio]').webformProp('checked', true);
}
e.preventDefault();
});
// Firefox improperly selects the parent radio button when clicking inside
// a label that contains an input field. The only way of preventing this
// currently is to remove the "for" attribute on the label.
// See https://bugzilla.mozilla.org/show_bug.cgi?id=213519.
if (navigator.userAgent.match(/Firefox/)) {
$('.webform-inline-radio', context).removeAttr('for');
}
else {
$templateSelect.val('custom');
}
}
};
var updateTemplateText = function() {
if ($(this).val() == 'default' && $templateTextarea.val() != defaultTemplate) {
if (confirm(Drupal.settings.webform.revertConfirm)) {
$templateTextarea.val(defaultTemplate);
// Update e-mail templates between default and custom.
Drupal.webform.updateTemplate = function (context) {
var defaultTemplate = $('#edit-templates-default').val();
var $templateSelect = $('#webform-template-fieldset select#edit-template-option', context);
var $templateTextarea = $('#webform-template-fieldset textarea:visible', context);
var updateTemplateSelect = function () {
if ($(this).val() == defaultTemplate) {
$templateSelect.val('default');
}
else {
$(this).val('custom');
$templateSelect.val('custom');
}
};
var updateTemplateText = function () {
if ($(this).val() == 'default' && $templateTextarea.val() != defaultTemplate) {
if (confirm(Drupal.settings.webform.revertConfirm)) {
$templateTextarea.val(defaultTemplate);
}
else {
$(this).val('custom');
}
}
};
$templateTextarea.keyup(updateTemplateSelect);
$templateSelect.change(updateTemplateText);
};
Drupal.webform.selectCheckboxesLink = function (context) {
function selectCheckboxes() {
var group = this.className.replace(/.*?webform-select-link-([^ ]*).*/, '$1');
var $checkboxes = $('.webform-select-group-' + group + ' input[type=checkbox]');
var reverseCheck = !$checkboxes[0].checked;
$checkboxes.each(function () {
this.checked = reverseCheck;
});
$checkboxes.trigger('change');
return false;
}
}
$('a.webform-select-link', context).click(selectCheckboxes);
};
$templateTextarea.keyup(updateTemplateSelect);
$templateSelect.change(updateTemplateText);
}
Drupal.webform.selectCheckboxesLink = function(context) {
function selectCheckboxes() {
var group = this.className.replace(/.*?webform-select-link-([^ ]*).*/, '$1');
var $checkboxes = $('.webform-select-group-' + group + ' input[type=checkbox]');
var reverseCheck = !$checkboxes[0].checked;
$checkboxes.each(function() {
this.checked = reverseCheck;
Drupal.webform.tableSelectIndentation = function (context) {
var $tables = $('th.select-all', context).parents('table');
$tables.find('input.form-checkbox').change(function () {
var $rows = $(this).parents('table:first').find('tr');
var row = $(this).parents('tr:first').get(0);
var rowNumber = $rows.index(row);
var rowTotal = $rows.size();
var indentLevel = $(row).find('div.indentation').size();
for (var n = rowNumber + 1; n < rowTotal; n++) {
if ($rows.eq(n).find('div.indentation').size() <= indentLevel) {
break;
}
$rows.eq(n).find('input.form-checkbox').webformProp('checked', this.checked);
}
});
$checkboxes.trigger('change');
return false;
}
$('a.webform-select-link', context).click(selectCheckboxes);
}
};
Drupal.webform.tableSelectIndentation = function(context) {
var $tables = $('th.select-all', context).parents('table');
$tables.find('input.form-checkbox').change(function() {
var $rows = $(this).parents('table:first').find('tr');
var row = $(this).parents('tr:first').get(0);
var rowNumber = $rows.index(row);
var rowTotal = $rows.size();
var indentLevel = $(row).find('div.indentation').size();
for (var n = rowNumber + 1; n < rowTotal; n++) {
if ($rows.eq(n).find('div.indentation').size() <= indentLevel) {
break;
}
$rows.eq(n).find('input.form-checkbox').attr('checked', this.checked);
/**
* Attach behaviors for Webform results download page.
*/
Drupal.webform.downloadExport = function (context) {
if (context === document && Drupal.settings && Drupal.settings.webformExport && document.cookie.match(/webform_export_info=1/)) {
window.location = Drupal.settings.webformExport;
delete Drupal.settings.webformExport;
}
});
}
};
/**
* Attach behaviors for Webform conditional administration.
*/
Drupal.webform.conditionalAdmin = function (context) {
var $context = $(context);
// Bind to the entire form and allow events to bubble-up from elements. This
// saves a lot of processing when new conditions are added/removed.
$context.find('#webform-conditionals-ajax:not(.webform-conditional-processed)')
.addClass('webform-conditional-processed')
.bind('change', function (e) {
var $target = $(e.target);
if ($target.is('.webform-conditional-source select')) {
Drupal.webform.conditionalSourceChange.apply(e.target);
}
if ($target.is('.webform-conditional-operator select')) {
Drupal.webform.conditionalOperatorChange.apply(e.target);
}
if ($target.is('.webform-conditional-andor select')) {
Drupal.webform.conditionalAndOrChange.apply(e.target);
}
if ($target.is('.webform-conditional-action select')) {
Drupal.webform.conditionalActionChange.apply(e.target);
}
});
// Add event handlers to delete the entire row if the last rule or action is removed.
$context.find('.webform-conditional-rule-remove:not(.webform-conditional-processed)').bind('click', function () {
this.webformRemoveClass = '.webform-conditional-rule-remove';
window.setTimeout($.proxy(Drupal.webform.conditionalRemove, this), 100);
}).addClass('webform-conditional-processed');
$context.find('.webform-conditional-action-remove:not(.webform-conditional-processed)').bind('click', function () {
this.webformRemoveClass = '.webform-conditional-action-remove';
window.setTimeout($.proxy(Drupal.webform.conditionalRemove, this), 100);
}).addClass('webform-conditional-processed');
// Trigger default handlers on the source element, this in turn will trigger
// the operator handlers.
$context.find('.webform-conditional-source select').trigger('change');
// Trigger defaults handlers on the action element.
$context.find('.webform-conditional-action select').trigger('change');
// When adding a new table row, make it draggable and hide the weight column.
if ($context.is('tr.ajax-new-content') && $context.find('.webform-conditional').length === 1) {
Drupal.tableDrag['webform-conditionals-table'].makeDraggable($context[0]);
$context.find('.webform-conditional-weight').closest('td').addClass('tabledrag-hide');
if ($.cookie('Drupal.tableDrag.showWeight') !== '1') {
Drupal.tableDrag['webform-conditionals-table'].hideColumns();
}
$context.removeClass('ajax-new-content');
}
};
/**
* Event callback for the remove button next to an individual rule.
*/
Drupal.webform.conditionalRemove = function () {
// See if there are any remaining rules in this element.
var rowCount = $(this).parents('.webform-conditional:first').find(this.webformRemoveClass).length;
if (rowCount <= 1) {
var $tableRow = $(this).parents('tr:first');
var $table = $('#webform-conditionals-table');
if ($tableRow.length && $table.length) {
$tableRow.remove();
Drupal.webform.restripeTable($table[0]);
}
}
};
/**
* Event callback to update the list of operators after a source change.
*/
Drupal.webform.conditionalSourceChange = function () {
var source = $(this).val();
var dataType = Drupal.settings.webform.conditionalValues.sources[source]['data_type'];
var $operator = $(this).parents('.webform-conditional-rule:first').find('.webform-conditional-operator select');
// Store a the original list of all operators for all data types in the select
// list DOM element.
if (!$operator[0]['webformConditionalOriginal']) {
$operator[0]['webformConditionalOriginal'] = $operator[0].innerHTML;
}
// Reference the original list to create a new list matching the data type.
var $originalList = $($operator[0]['webformConditionalOriginal']);
var $newList = $originalList.filter('optgroup[label=' + dataType + ']');
var newHTML = $newList[0].innerHTML;
// Update the options and fire the change event handler on the list to update
// the value field, only if the options have changed. This avoids resetting
// existing selections.
if (newHTML != $operator.html()) {
$operator.html(newHTML);
}
// Trigger the change in case the source component changed from one select
// component to another.
$operator.trigger('change');
};
/**
* Event callback to update the value field after an operator change.
*/
Drupal.webform.conditionalOperatorChange = function () {
var source = $(this).parents('.webform-conditional-rule:first').find('.webform-conditional-source select').val();
var dataType = Drupal.settings.webform.conditionalValues.sources[source]['data_type'];
var operator = $(this).val();
var $value = $(this).parents('.webform-conditional-rule:first').find('.webform-conditional-value');
var name = $value.find('input, select, textarea').attr('name');
var originalValue = false;
// Given the dataType and operator, we can determine the form key.
var formKey = Drupal.settings.webform.conditionalValues.operators[dataType][operator]['form'];
var formSource = typeof Drupal.settings.webform.conditionalValues.forms[formKey] == 'undefined' ? false : source;
// On initial request, save the default field as printed on the original page.
if (!$value[0]['webformConditionalOriginal']) {
$value[0]['webformConditionalOriginal'] = $value[0].innerHTML;
originalValue = $value.find('input:first').val();
}
// On changes to an existing operator, check if the form key is different
// (and any per-source form, such as a select option list) before replacing
// the form with an identical version.
else if ($value[0]['webformConditionalFormKey'] == formKey && $value[0]['webformConditionalFormSource'] == formSource) {
return;
}
// Store the current form key for checking the next time the operator changes.
$value[0]['webformConditionalFormKey'] = formKey;
$value[0]['webformConditionalFormSource'] = formSource;
// If using the default (a textfield), restore the original field.
if (formKey === 'default') {
$value[0].innerHTML = $value[0]['webformConditionalOriginal'];
}
// If the operator does not need a source value (i.e. is empty), hide it.
else if (formKey === false) {
$value[0].innerHTML = '<input type="text" value="" style="display: none;" >';
}
// If there is a per-source form for this operator (e.g. option lists), use
// the specialized value form.
else if (typeof Drupal.settings.webform.conditionalValues.forms[formKey] == 'object') {
$value[0].innerHTML = Drupal.settings.webform.conditionalValues.forms[formKey][source];
}
// Otherwise all the sources use a generic field (e.g. a text field).
else {
$value[0].innerHTML = Drupal.settings.webform.conditionalValues.forms[formKey];
}
// Set the name attribute to match the original placeholder field.
var $firstElement = $value.find('input, select, textarea').filter(':first');
$firstElement.attr('name', name);
if (originalValue) {
$firstElement.val(originalValue);
}
};
/**
* Event callback to make sure all group and/or operators match.
*/
Drupal.webform.conditionalAndOrChange = function () {
var rid = this.getAttribute('data-rid');
var text = $(this).find('option:selected').text();
$(this).parents('.webform-conditional:first').find('.webform-conditional-andor div[data-rid="' + rid + '"]').text(text);
};
/**
* Event callback to show argument only for appropriate actions.
*/
Drupal.webform.conditionalActionChange = function () {
var action = $(this).val();
var $argument = $(this).parents('.webform-conditional-condition:first').find('.webform-conditional-argument input');
var isShown = $argument.is(':visible');
switch (action) {
case 'show':
case 'require':
if (isShown) {
$argument.hide();
}
break;
case 'set':
if (!isShown) {
$argument.show();
}
break;
}
};
/**
* Triggers a change event when a label receives a click.
*
* When the browser automatically selects a radio button when it's label is
* clicked, the FAPI states jQuery code doesn't receive an event. This function
* ensures that automatically-selected radio buttons keep in sync with the
* FAPI states.
*/
Drupal.webform.radioLabelAutoClick = function (context) {
$('label').once('webform-label').click(function () {
$(this).prev('input:radio').change();
});
};
/**
* Make a prop shim for jQuery < 1.9.
*/
$.fn.webformProp = $.fn.webformProp || function (name, value) {
if (value) {
return $.fn.prop ? this.prop(name, true) : this.attr(name, true);
}
else {
return $.fn.prop ? this.prop(name, false) : this.removeAttr(name);
}
};
})(jQuery);

View File

@@ -1,86 +1,720 @@
/**
* @file
* JavaScript behaviors for the front-end display of webforms.
*/
(function ($) {
Drupal.behaviors.webform = Drupal.behaviors.webform || {};
"use strict";
Drupal.behaviors.webform.attach = function(context) {
// Calendar datepicker behavior.
Drupal.webform.datepicker(context);
};
Drupal.behaviors.webform = Drupal.behaviors.webform || {};
Drupal.webform = Drupal.webform || {};
Drupal.behaviors.webform.attach = function (context) {
// Calendar datepicker behavior.
Drupal.webform.datepicker(context);
Drupal.webform.datepicker = function(context) {
$('div.webform-datepicker').each(function() {
var $webformDatepicker = $(this);
var $calendar = $webformDatepicker.find('input.webform-calendar');
// Conditional logic.
if (Drupal.settings.webform && Drupal.settings.webform.conditionals) {
Drupal.webform.conditional(context);
}
};
// Ensure the page we're on actually contains a datepicker.
if ($calendar.length == 0) {
return;
Drupal.webform = Drupal.webform || {};
Drupal.webform.datepicker = function (context) {
$('div.webform-datepicker').each(function () {
var $webformDatepicker = $(this);
var $calendar = $webformDatepicker.find('input.webform-calendar');
// Ensure the page we're on actually contains a datepicker.
if ($calendar.length == 0) {
return;
}
var startDate = $calendar[0].className.replace(/.*webform-calendar-start-(\d{4}-\d{2}-\d{2}).*/, '$1').split('-');
var endDate = $calendar[0].className.replace(/.*webform-calendar-end-(\d{4}-\d{2}-\d{2}).*/, '$1').split('-');
var firstDay = $calendar[0].className.replace(/.*webform-calendar-day-(\d).*/, '$1');
// Convert date strings into actual Date objects.
startDate = new Date(startDate[0], startDate[1] - 1, startDate[2]);
endDate = new Date(endDate[0], endDate[1] - 1, endDate[2]);
// Ensure that start comes before end for datepicker.
if (startDate > endDate) {
var laterDate = startDate;
startDate = endDate;
endDate = laterDate;
}
var startYear = startDate.getFullYear();
var endYear = endDate.getFullYear();
// Set up the jQuery datepicker element.
$calendar.datepicker({
dateFormat: 'yy-mm-dd',
yearRange: startYear + ':' + endYear,
firstDay: parseInt(firstDay),
minDate: startDate,
maxDate: endDate,
onSelect: function (dateText, inst) {
var date = dateText.split('-');
$webformDatepicker.find('select.year, input.year').val(+date[0]).trigger('change');
$webformDatepicker.find('select.month').val(+date[1]).trigger('change');
$webformDatepicker.find('select.day').val(+date[2]).trigger('change');
},
beforeShow: function (input, inst) {
// Get the select list values.
var year = $webformDatepicker.find('select.year, input.year').val();
var month = $webformDatepicker.find('select.month').val();
var day = $webformDatepicker.find('select.day').val();
// If empty, default to the current year/month/day in the popup.
var today = new Date();
year = year ? year : today.getFullYear();
month = month ? month : today.getMonth() + 1;
day = day ? day : today.getDate();
// Make sure that the default year fits in the available options.
year = (year < startYear || year > endYear) ? startYear : year;
// jQuery UI Datepicker will read the input field and base its date
// off of that, even though in our case the input field is a button.
$(input).val(year + '-' + month + '-' + day);
}
});
// Prevent the calendar button from submitting the form.
$calendar.click(function (event) {
$(this).focus();
event.preventDefault();
});
});
};
Drupal.webform.conditional = function (context) {
// Add the bindings to each webform on the page.
$.each(Drupal.settings.webform.conditionals, function (formKey, settings) {
var $form = $('.' + formKey + ':not(.webform-conditional-processed)');
$form.each(function (index, currentForm) {
var $currentForm = $(currentForm);
$currentForm.addClass('webform-conditional-processed');
$currentForm.bind('change', {'settings': settings}, Drupal.webform.conditionalCheck);
// Trigger all the elements that cause conditionals on this form.
Drupal.webform.doConditions($currentForm, settings);
});
});
};
/**
* Event handler to respond to field changes in a form.
*
* This event is bound to the entire form, not individual fields.
*/
Drupal.webform.conditionalCheck = function (e) {
var $triggerElement = $(e.target).closest('.webform-component');
var $form = $triggerElement.closest('form');
var triggerElementKey = $triggerElement.attr('class').match(/webform-component--[^ ]+/)[0];
var settings = e.data.settings;
if (settings.sourceMap[triggerElementKey]) {
Drupal.webform.doConditions($form, settings);
}
};
/**
* Processes all conditional.
*/
Drupal.webform.doConditions = function ($form, settings) {
var stackPointer;
var resultStack;
/**
* Initializes an execution stack for a conditional group's rules.
*
* Also initializes sub-conditional rules.
*/
function executionStackInitialize(andor) {
stackPointer = -1;
resultStack = [];
executionStackPush(andor);
}
var startDate = $calendar[0].className.replace(/.*webform-calendar-start-(\d{4}-\d{2}-\d{2}).*/, '$1').split('-');
var endDate = $calendar[0].className.replace(/.*webform-calendar-end-(\d{4}-\d{2}-\d{2}).*/, '$1').split('-');
var firstDay = $calendar[0].className.replace(/.*webform-calendar-day-(\d).*/, '$1');
// Convert date strings into actual Date objects.
startDate = new Date(startDate[0], startDate[1] - 1, startDate[2]);
endDate = new Date(endDate[0], endDate[1] - 1, endDate[2]);
// Ensure that start comes before end for datepicker.
if (startDate > endDate) {
var laterDate = startDate;
startDate = endDate;
endDate = laterDate;
/**
* Starts a new subconditional for the given and/or operator.
*/
function executionStackPush(andor) {
resultStack[++stackPointer] = {
results: [],
andor: andor,
};
}
var startYear = startDate.getFullYear();
var endYear = endDate.getFullYear();
/**
* Adds a rule's result to the current sub-conditional.
*/
function executionStackAccumulate(result) {
resultStack[stackPointer]['results'].push(result);
}
// Set up the jQuery datepicker element.
$calendar.datepicker({
dateFormat: 'yy-mm-dd',
yearRange: startYear + ':' + endYear,
firstDay: parseInt(firstDay),
minDate: startDate,
maxDate: endDate,
onSelect: function(dateText, inst) {
var date = dateText.split('-');
$webformDatepicker.find('select.year, input.year').val(+date[0]).trigger('change');
$webformDatepicker.find('select.month').val(+date[1]).trigger('change');
$webformDatepicker.find('select.day').val(+date[2]).trigger('change');
},
beforeShow: function(input, inst) {
// Get the select list values.
var year = $webformDatepicker.find('select.year, input.year').val();
var month = $webformDatepicker.find('select.month').val();
var day = $webformDatepicker.find('select.day').val();
/**
* Finishes a sub-conditional and adds the result to the parent stack frame.
*/
function executionStackPop() {
// Calculate the and/or result.
var stackFrame = resultStack[stackPointer];
// Pop stack and protect against stack underflow.
stackPointer = Math.max(0, stackPointer - 1);
var $conditionalResults = stackFrame['results'];
var filteredResults = $.map($conditionalResults, function (val) {
return val ? val : null;
});
return stackFrame['andor'] === 'or'
? filteredResults.length > 0
: filteredResults.length === $conditionalResults.length;
}
// If empty, default to the current year/month/day in the popup.
var today = new Date();
year = year ? year : today.getFullYear();
month = month ? month : today.getMonth() + 1;
day = day ? day : today.getDate();
// Track what has been set/hidden for each target component's elements.
// Hidden elements must be disabled because if they are required and don't
// have a value, they will prevent submission due to html5 validation.
// Each execution of the conditionals adds a temporary class
// webform-disabled-flag so that elements hidden or set can be disabled and
// also be prevented from being re-enabled by another conditional (such as a
// parent fieldset). After processing conditionals, this temporary class
// must be removed in preparation for the next execution of the
// conditionals.
$.each(settings.ruleGroups, function (rgid_key, rule_group) {
var ruleGroup = settings.ruleGroups[rgid_key];
// Make sure that the default year fits in the available options.
year = (year < startYear || year > endYear) ? startYear : year;
// Perform the comparison callback and build the results for this group.
executionStackInitialize(ruleGroup['andor']);
$.each(ruleGroup['rules'], function (m, rule) {
switch (rule['source_type']) {
case 'component':
var elementKey = rule['source'];
var element = $form.find('.' + elementKey)[0];
var existingValue = settings.values[elementKey] ? settings.values[elementKey] : null;
executionStackAccumulate(window['Drupal']['webform'][rule.callback](element, existingValue, rule['value']));
break;
// jQuery UI Datepicker will read the input field and base its date off
// of that, even though in our case the input field is a button.
$(input).val(year + '-' + month + '-' + day);
case 'conditional_start':
executionStackPush(rule['andor']);
break;
case 'conditional_end':
executionStackAccumulate(executionStackPop());
break;
}
});
var conditionalResult = executionStackPop();
$.each(ruleGroup['actions'], function (aid, action) {
var $target = $form.find('.' + action['target']);
var actionResult = action['invert'] ? !conditionalResult : conditionalResult;
switch (action['action']) {
case 'show':
var changed = actionResult != Drupal.webform.isVisible($target);
if (actionResult) {
$target.find('.webform-conditional-disabled:not(.webform-disabled-flag)')
.removeClass('webform-conditional-disabled')
.webformProp('disabled', false);
$target
.removeClass('webform-conditional-hidden')
.show();
$form.find('.chosen-disabled').prev().trigger('chosen:updated.chosen');
}
else {
$target
.hide()
.addClass('webform-conditional-hidden')
.find(':input')
.addClass('webform-conditional-disabled webform-disabled-flag')
.webformProp('disabled', true);
}
if (changed && $target.is('tr')) {
Drupal.webform.restripeTable($target.closest('table').first());
}
break;
case 'require':
var $requiredSpan = $target.find('.form-required, .form-optional').first();
if (actionResult != $requiredSpan.hasClass('form-required')) {
var $targetInputElements = $target.find("input:text,textarea,input[type='email'],select,input:radio,input:file");
// Rather than hide the required tag, remove it so that other
// jQuery can respond via Drupal behaviors.
Drupal.detachBehaviors($requiredSpan);
$targetInputElements
.webformProp('required', actionResult)
.toggleClass('required', actionResult);
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;
case 'set':
var $texts = $target.find("input:text,textarea,input[type='email']");
var $selects = $target.find('select,select option,input:radio,input:checkbox');
var $markups = $target.filter('.webform-component-markup');
if (actionResult) {
var multiple = $.map(action['argument'].split(','), $.trim);
$selects
.webformVal(multiple)
.webformProp('disabled', true)
.addClass('webform-disabled-flag');
$texts
.val([action['argument']])
.webformProp('readonly', true)
.addClass('webform-disabled-flag');
// A special case is made for markup. It is sanitized with
// filter_xss_admin on the server. otherwise text() should be used
// to avoid an XSS vulnerability. text() however would preclude
// the use of tags like <strong> or <a>.
$markups.html(action['argument']);
}
else {
$selects.not('.webform-disabled-flag')
.webformProp('disabled', false);
$texts.not('.webform-disabled-flag')
.webformProp('readonly', false);
// Markup not set? Then restore original markup as provided in
// the attribute data-webform-markup.
$markups.each(function () {
var $this = $(this);
var original = $this.data('webform-markup');
if (original !== undefined) {
$this.html(original);
}
});
}
break;
}
}); // End look on each action for one conditional.
}); // End loop on each conditional.
$form.find('.webform-disabled-flag').removeClass('webform-disabled-flag');
};
/**
* Event handler to prevent propagation of events.
*
* Typically click for disabling radio and checkboxes.
*/
Drupal.webform.stopEvent = function () {
return false;
};
Drupal.webform.conditionalOperatorStringEqual = function (element, existingValue, ruleValue) {
var returnValue = false;
var currentValue = Drupal.webform.stringValue(element, existingValue);
$.each(currentValue, function (n, value) {
if (value.toLowerCase() === ruleValue.toLowerCase()) {
returnValue = true;
return false; // break.
}
});
return returnValue;
};
// Prevent the calendar button from submitting the form.
$calendar.click(function(event) {
$(this).focus();
event.preventDefault();
Drupal.webform.conditionalOperatorStringNotEqual = function (element, existingValue, ruleValue) {
var found = false;
var currentValue = Drupal.webform.stringValue(element, existingValue);
$.each(currentValue, function (n, value) {
if (value.toLowerCase() === ruleValue.toLowerCase()) {
found = true;
}
});
});
}
return !found;
};
Drupal.webform.conditionalOperatorStringContains = function (element, existingValue, ruleValue) {
var returnValue = false;
var currentValue = Drupal.webform.stringValue(element, existingValue);
$.each(currentValue, function (n, value) {
if (value.toLowerCase().indexOf(ruleValue.toLowerCase()) > -1) {
returnValue = true;
return false; // break.
}
});
return returnValue;
};
Drupal.webform.conditionalOperatorStringDoesNotContain = function (element, existingValue, ruleValue) {
var found = false;
var currentValue = Drupal.webform.stringValue(element, existingValue);
$.each(currentValue, function (n, value) {
if (value.toLowerCase().indexOf(ruleValue.toLowerCase()) > -1) {
found = true;
}
});
return !found;
};
Drupal.webform.conditionalOperatorStringBeginsWith = function (element, existingValue, ruleValue) {
var returnValue = false;
var currentValue = Drupal.webform.stringValue(element, existingValue);
$.each(currentValue, function (n, value) {
if (value.toLowerCase().indexOf(ruleValue.toLowerCase()) === 0) {
returnValue = true;
return false; // break.
}
});
return returnValue;
};
Drupal.webform.conditionalOperatorStringEndsWith = function (element, existingValue, ruleValue) {
var returnValue = false;
var currentValue = Drupal.webform.stringValue(element, existingValue);
$.each(currentValue, function (n, value) {
if (value.toLowerCase().lastIndexOf(ruleValue.toLowerCase()) === value.length - ruleValue.length) {
returnValue = true;
return false; // break.
}
});
return returnValue;
};
Drupal.webform.conditionalOperatorStringEmpty = function (element, existingValue, ruleValue) {
var currentValue = Drupal.webform.stringValue(element, existingValue);
var returnValue = true;
$.each(currentValue, function (n, value) {
if (value !== '') {
returnValue = false;
return false; // break.
}
});
return returnValue;
};
Drupal.webform.conditionalOperatorStringNotEmpty = function (element, existingValue, ruleValue) {
return !Drupal.webform.conditionalOperatorStringEmpty(element, existingValue, ruleValue);
};
Drupal.webform.conditionalOperatorSelectGreaterThan = function (element, existingValue, ruleValue) {
var currentValue = Drupal.webform.stringValue(element, existingValue);
return Drupal.webform.compare_select(currentValue[0], ruleValue, element) > 0;
};
Drupal.webform.conditionalOperatorSelectGreaterThanEqual = function (element, existingValue, ruleValue) {
var currentValue = Drupal.webform.stringValue(element, existingValue);
var comparison = Drupal.webform.compare_select(currentValue[0], ruleValue, element);
return comparison > 0 || comparison === 0;
};
Drupal.webform.conditionalOperatorSelectLessThan = function (element, existingValue, ruleValue) {
var currentValue = Drupal.webform.stringValue(element, existingValue);
return Drupal.webform.compare_select(currentValue[0], ruleValue, element) < 0;
};
Drupal.webform.conditionalOperatorSelectLessThanEqual = function (element, existingValue, ruleValue) {
var currentValue = Drupal.webform.stringValue(element, existingValue);
var comparison = Drupal.webform.compare_select(currentValue[0], ruleValue, element);
return comparison < 0 || comparison === 0;
};
Drupal.webform.conditionalOperatorNumericEqual = function (element, existingValue, ruleValue) {
// See float comparison: http://php.net/manual/en/language.types.float.php
var currentValue = Drupal.webform.stringValue(element, existingValue);
var epsilon = 0.000001;
// An empty string does not match any number.
return currentValue[0] === '' ? false : (Math.abs(parseFloat(currentValue[0]) - parseFloat(ruleValue)) < epsilon);
};
Drupal.webform.conditionalOperatorNumericNotEqual = function (element, existingValue, ruleValue) {
// See float comparison: http://php.net/manual/en/language.types.float.php
var currentValue = Drupal.webform.stringValue(element, existingValue);
var epsilon = 0.000001;
// An empty string does not match any number.
return currentValue[0] === '' ? true : (Math.abs(parseFloat(currentValue[0]) - parseFloat(ruleValue)) >= epsilon);
};
Drupal.webform.conditionalOperatorNumericGreaterThan = function (element, existingValue, ruleValue) {
var currentValue = Drupal.webform.stringValue(element, existingValue);
return parseFloat(currentValue[0]) > parseFloat(ruleValue);
};
Drupal.webform.conditionalOperatorNumericGreaterThanEqual = function (element, existingValue, ruleValue) {
return Drupal.webform.conditionalOperatorNumericGreaterThan(element, existingValue, ruleValue) ||
Drupal.webform.conditionalOperatorNumericEqual(element, existingValue, ruleValue);
};
Drupal.webform.conditionalOperatorNumericLessThan = function (element, existingValue, ruleValue) {
var currentValue = Drupal.webform.stringValue(element, existingValue);
return parseFloat(currentValue[0]) < parseFloat(ruleValue);
};
Drupal.webform.conditionalOperatorNumericLessThanEqual = function (element, existingValue, ruleValue) {
return Drupal.webform.conditionalOperatorNumericLessThan(element, existingValue, ruleValue) ||
Drupal.webform.conditionalOperatorNumericEqual(element, existingValue, ruleValue);
};
Drupal.webform.conditionalOperatorDateEqual = function (element, existingValue, ruleValue) {
var currentValue = Drupal.webform.dateValue(element, existingValue);
return currentValue === ruleValue;
};
Drupal.webform.conditionalOperatorDateNotEqual = function (element, existingValue, ruleValue) {
return !Drupal.webform.conditionalOperatorDateEqual(element, existingValue, ruleValue);
};
Drupal.webform.conditionalOperatorDateBefore = function (element, existingValue, ruleValue) {
var currentValue = Drupal.webform.dateValue(element, existingValue);
return (currentValue !== false) && currentValue < ruleValue;
};
Drupal.webform.conditionalOperatorDateBeforeEqual = function (element, existingValue, ruleValue) {
var currentValue = Drupal.webform.dateValue(element, existingValue);
return (currentValue !== false) && (currentValue < ruleValue || currentValue === ruleValue);
};
Drupal.webform.conditionalOperatorDateAfter = function (element, existingValue, ruleValue) {
var currentValue = Drupal.webform.dateValue(element, existingValue);
return (currentValue !== false) && currentValue > ruleValue;
};
Drupal.webform.conditionalOperatorDateAfterEqual = function (element, existingValue, ruleValue) {
var currentValue = Drupal.webform.dateValue(element, existingValue);
return (currentValue !== false) && (currentValue > ruleValue || currentValue === ruleValue);
};
Drupal.webform.conditionalOperatorTimeEqual = function (element, existingValue, ruleValue) {
var currentValue = Drupal.webform.timeValue(element, existingValue);
return currentValue === ruleValue;
};
Drupal.webform.conditionalOperatorTimeNotEqual = function (element, existingValue, ruleValue) {
return !Drupal.webform.conditionalOperatorTimeEqual(element, existingValue, ruleValue);
};
Drupal.webform.conditionalOperatorTimeBefore = function (element, existingValue, ruleValue) {
// Date and time operators intentionally exclusive for "before".
var currentValue = Drupal.webform.timeValue(element, existingValue);
return (currentValue !== false) && (currentValue < ruleValue);
};
Drupal.webform.conditionalOperatorTimeBeforeEqual = function (element, existingValue, ruleValue) {
// Date and time operators intentionally exclusive for "before".
var currentValue = Drupal.webform.timeValue(element, existingValue);
return (currentValue !== false) && (currentValue < ruleValue || currentValue === ruleValue);
};
Drupal.webform.conditionalOperatorTimeAfter = function (element, existingValue, ruleValue) {
// Date and time operators intentionally inclusive for "after".
var currentValue = Drupal.webform.timeValue(element, existingValue);
return (currentValue !== false) && (currentValue > ruleValue);
};
Drupal.webform.conditionalOperatorTimeAfterEqual = function (element, existingValue, ruleValue) {
// Date and time operators intentionally inclusive for "after".
var currentValue = Drupal.webform.timeValue(element, existingValue);
return (currentValue !== false) && (currentValue > ruleValue || currentValue === ruleValue);
};
/**
* Utility function to compare values of a select component.
*
* @param string a
* First select option key to compare
* @param string b
* Second select option key to compare
* @param array options
* Associative array where the a and b are within the keys
*
* @return integer based upon position of $a and $b in $options
* -N if $a above (<) $b
* 0 if $a = $b
* +N if $a is below (>) $b
*/
Drupal.webform.compare_select = function (a, b, element) {
var optionList = [];
$('option,input:radio,input:checkbox', element).each(function () {
optionList.push($(this).val());
});
var a_position = optionList.indexOf(a);
var b_position = optionList.indexOf(b);
return (a_position < 0 || b_position < 0) ? null : a_position - b_position;
};
/**
* Utility to return current visibility.
*
* Uses actual visibility, except for hidden components which use the applied
* disabled class.
*/
Drupal.webform.isVisible = function ($element) {
return $element.hasClass('webform-component-hidden')
? !$element.find('input').first().hasClass('webform-conditional-disabled')
: $element.closest('.webform-conditional-hidden').length == 0;
};
/**
* Function to get a string value from a select/radios/text/etc. field.
*/
Drupal.webform.stringValue = function (element, existingValue) {
var value = [];
if (element) {
var $element = $(element);
if (Drupal.webform.isVisible($element)) {
// Checkboxes and radios.
$element.find('input[type=checkbox]:checked,input[type=radio]:checked').each(function () {
value.push(this.value);
});
// Select lists.
if (!value.length) {
var selectValue = $element.find('select').val();
if (selectValue) {
if ($.isArray(selectValue)) {
value = selectValue;
}
else {
value.push(selectValue);
}
}
}
// Simple text fields. This check is done last so that the select list
// in select-or-other fields comes before the "other" text field.
if (!value.length) {
$element.find('input:not([type=checkbox],[type=radio]),textarea').each(function () {
value.push(this.value);
});
}
}
}
else {
switch ($.type(existingValue)) {
case 'array':
value = existingValue;
break;
case 'string':
value.push(existingValue);
break;
}
}
return value;
};
/**
* Utility function to calculate a second-based timestamp from a time field.
*/
Drupal.webform.dateValue = function (element, existingValue) {
var value = false;
if (element) {
var $element = $(element);
if (Drupal.webform.isVisible($element)) {
var day = $element.find('[name*=day]').val();
var month = $element.find('[name*=month]').val();
var year = $element.find('[name*=year]').val();
// Months are 0 indexed in JavaScript.
if (month) {
month--;
}
if (year !== '' && month !== '' && day !== '') {
value = Date.UTC(year, month, day) / 1000;
}
}
}
else {
if ($.type(existingValue) === 'array' && existingValue.length) {
existingValue = existingValue[0];
}
if ($.type(existingValue) === 'string') {
existingValue = existingValue.split('-');
}
if (existingValue.length === 3) {
value = Date.UTC(existingValue[0], existingValue[1], existingValue[2]) / 1000;
}
}
return value;
};
/**
* Utility function to calculate a millisecond timestamp from a time field.
*/
Drupal.webform.timeValue = function (element, existingValue) {
var value = false;
if (element) {
var $element = $(element);
if (Drupal.webform.isVisible($element)) {
var hour = $element.find('[name*=hour]').val();
var minute = $element.find('[name*=minute]').val();
var ampm = $element.find('[name*=ampm]:checked').val();
// Convert to integers if set.
hour = (hour === '') ? hour : parseInt(hour);
minute = (minute === '') ? minute : parseInt(minute);
if (hour !== '') {
hour = (hour < 12 && ampm == 'pm') ? hour + 12 : hour;
hour = (hour === 12 && ampm == 'am') ? 0 : hour;
}
if (hour !== '' && minute !== '') {
value = Date.UTC(1970, 0, 1, hour, minute) / 1000;
}
}
}
else {
if ($.type(existingValue) === 'array' && existingValue.length) {
existingValue = existingValue[0];
}
if ($.type(existingValue) === 'string') {
existingValue = existingValue.split(':');
}
if (existingValue.length >= 2) {
value = Date.UTC(1970, 0, 1, existingValue[0], existingValue[1]) / 1000;
}
}
return value;
};
/**
* Make a prop shim for jQuery < 1.9.
*/
$.fn.webformProp = $.fn.webformProp || function (name, value) {
if (value) {
return $.fn.prop ? this.prop(name, true) : this.attr(name, true);
}
else {
return $.fn.prop ? this.prop(name, false) : this.removeAttr(name);
}
};
/**
* Make a multi-valued val() function.
*
* This is for setting checkboxes, radios, and select elements.
*/
$.fn.webformVal = function (values) {
this.each(function () {
var $this = $(this);
var value = $this.val();
var on = $.inArray($this.val(), values) != -1;
if (this.nodeName == 'OPTION') {
$this.webformProp('selected', on ? value : false);
}
else {
$this.val(on ? [value] : false);
}
});
return this;
};
/**
* Given a table's DOM element, restripe the odd/even classes.
*/
Drupal.webform.restripeTable = function (table) {
// :even and :odd are reversed because jQuery counts from 0 and
// we count from 1, so we're out of sync.
// Match immediate children of the parent element to allow nesting.
$('> tbody > tr, > tr', table)
.filter(':visible:odd').filter('.odd')
.removeClass('odd').addClass('even')
.end().end()
.filter(':visible:even').filter('.even')
.removeClass('even').addClass('odd');
};
})(jQuery);

View File

@@ -0,0 +1,22 @@
<?php
/**
* @file
* Template for rendering an individual component's analysis data.
*
* Available variables:
* - $component: The component whose analysis is being rendered.
* - $component_analysis: A renderable containing this components analysis.
* - $data: An array of array containing the analysis data. Contains the keys:
* - table_header: If this table has more than a single column, an array
* of header labels.
* - table_rows: If this component has a table that should be rendered, an
* array of values.
*/
?>
<div class="<?php print $classes; ?>">
<div class="webform-analysis-component-inner">
<h3><?php print check_plain($component['name']); ?></h3>
<?php print drupal_render_children($component_analysis); ?>
</div>
</div>

View File

@@ -0,0 +1,31 @@
<?php
/**
* @file
* Template for printing out the contents of the "Analysis" tab on a Webform.
*
* Available variables:
* - $node: The node object for this webform.
* - $component: If a single components analysis is being printed, this will
* contain a Webform component. Otherwise all components are having their
* analysis printed on the same page.
* - $analysis: A renderable object containing the following children:
* - 'exposed_filter': The output of any exposed filter created by the
* webform_analysis, webform_analysis_CONTENTTYPE, or webform_analysis_NID
* view.
* - 'form': A form for selecting which components should be included in the
* analysis.
* - 'data': An render array of analysis results for each component enabled.
*/
?>
<div class="webform-analysis">
<?php print drupal_render($analysis['form']['help']); ?>
<?php print drupal_render($analysis['exposed_filter']); ?>
<div class="webform-analysis-data">
<?php print drupal_render($analysis['data']); ?>
</div>
<?php print drupal_render($analysis['form']); ?>
<?php /* Print out any remaining part of the renderable. */ ?>
<?php print drupal_render_children($analysis); ?>
</div>

View File

@@ -5,4 +5,4 @@
* Theme the button for the date component date popup.
*/
?>
<input type="image" src="<?php print base_path() . drupal_get_path('module', 'webform') . '/images/calendar.png'; ?>" class="<?php print implode(' ', $calendar_classes); ?>" alt="<?php print t('Open popup calendar'); ?>" title="<?php print t('Open popup calendar'); ?>" />
<input type="image" aria-hidden="true" role="presentation" src="<?php print base_path() . drupal_get_path('module', 'webform') . '/images/calendar.png'; ?>" class="<?php print implode(' ', $calendar_classes); ?>" alt="<?php print t('Open popup calendar'); ?>" title="<?php print t('Open popup calendar'); ?>" />

View File

@@ -11,10 +11,15 @@
*
* Available variables:
* - $node: The node object for this webform.
* - $confirmation_message: The confirmation message input by the webform author.
* - $progressbar: The progress bar 100% filled (if configured). This may not
* print out anything if a progress bar is not enabled for this node.
* - $confirmation_message: The confirmation message input by the webform
* author.
* - $sid: The unique submission ID of this submission.
* - $url: The URL of the form (or for in-block confirmations, the same page).
*/
?>
<?php print $progressbar; ?>
<div class="webform-confirmation">
<?php if ($confirmation_message): ?>
@@ -25,5 +30,5 @@
</div>
<div class="links">
<a href="<?php print url('node/'. $node->nid) ?>"><?php print t('Go back to the form') ?></a>
<a href="<?php print $url; ?>"><?php print t('Go back to the form'); ?></a>
</div>

View File

@@ -15,13 +15,27 @@
* The $form array contains two main pieces:
* - $form['submitted']: The main content of the user-created form.
* - $form['details']: Internal information stored by Webform.
*
* If a preview is enabled, these keys will be available on the preview page:
* - $form['preview_message']: The preview message renderable.
* - $form['preview']: A renderable representing the entire submission preview.
*/
?>
<?php
// Print out the progress bar at the top of the page.
print drupal_render($form['progressbar']);
// Print out the preview message if on the preview page.
if (isset($form['preview_message'])) {
print '<div class="messages warning">';
print drupal_render($form['preview_message']);
print '</div>';
}
// Print out the main part of the form.
// Feel free to break this up and move the pieces within the array.
print drupal_render($form['submitted']);
// Always print out the entire $form. This renders the remaining pieces of the
// form that haven't yet been rendered above.
// form that haven't yet been rendered above (buttons, hidden elements, etc).
print drupal_render_children($form);

View File

@@ -12,25 +12,27 @@
* - $node: The node object for this webform.
* - $submission: The webform submission.
* - $email: The entire e-mail configuration settings.
* - $user: The current user submitting the form.
* - $ip_address: The IP address of the user submitting the form.
* - $user: The current user submitting the form. Always the Anonymous user
* (uid 0) for confidential submissions.
* - $ip_address: The IP address of the user submitting the form or '(unknown)'
* for confidential submissions.
*
* The $email['email'] variable can be used to send different e-mails to different users
* when using the "default" e-mail template.
* The $email['email'] variable can be used to send different e-mails to
* different users when using the "default" e-mail template.
*/
?>
<?php print ($email['html'] ? '<p>' : '') . t('Submitted on %date'). ($email['html'] ? '</p>' : ''); ?>
<?php print ($email['html'] ? '<p>' : '') . t('Submitted on [submission:date:long]') . ($email['html'] ? '</p>' : ''); ?>
<?php if ($user->uid): ?>
<?php print ($email['html'] ? '<p>' : '') . t('Submitted by user: %username') . ($email['html'] ? '</p>' : ''); ?>
<?php print ($email['html'] ? '<p>' : '') . t('Submitted by user: [submission:user]') . ($email['html'] ? '</p>' : ''); ?>
<?php else: ?>
<?php print ($email['html'] ? '<p>' : '') . t('Submitted by anonymous user: [%ip_address]') . ($email['html'] ? '</p>' : ''); ?>
<?php print ($email['html'] ? '<p>' : '') . t('Submitted by anonymous user: [submission:ip-address]') . ($email['html'] ? '</p>' : ''); ?>
<?php endif; ?>
<?php print ($email['html'] ? '<p>' : '') . t('Submitted values are') . ':' . ($email['html'] ? '</p>' : ''); ?>
%email_values
[submission:values]
<?php print ($email['html'] ? '<p>' : '') . t('The results of this submission may be viewed at:') . ($email['html'] ? '</p>' : '') ?>
<?php print ($email['html'] ? '<p>' : '') . t('The results of this submission may be viewed at:') . ($email['html'] ? '</p>' : ''); ?>
<?php print ($email['html'] ? '<p>' : ''); ?>%submission_url<?php print ($email['html'] ? '</p>' : ''); ?>
<?php print ($email['html'] ? '<p>' : ''); ?>[submission:url]<?php print ($email['html'] ? '</p>' : ''); ?>

View File

@@ -0,0 +1,65 @@
<?php
/**
* @file
* Display the progress bar for multipage forms.
*
* Available variables:
* - $node: The webform node.
* - $progressbar_page_number: TRUE if the actual page number should be
* displayed.
* - $progressbar_percent: TRUE if the percentage complete should be displayed.
* - $progressbar_bar: TRUE if the bar should be displayed.
* - $progressbar_pagebreak_labels: TRUE if the page break labels should be
* displayed.
*
* - $page_num: The current page number.
* - $page_count: The total number of pages in this form.
* - $page_labels: The labels for the pages. This typically includes a label for
* the starting page (index 0), each page in the form based on page break
* labels, and then the confirmation page (index number of pages + 1).
* - $percent: The percentage complete.
*/
?>
<div class="webform-progressbar">
<?php if ($progressbar_bar): ?>
<div class="webform-progressbar-outer">
<div class="webform-progressbar-inner" style="width: <?php print number_format($percent, 0); ?>%">&nbsp;</div>
<?php for ($n = 1; $n <= $page_count; $n++): ?>
<span class="webform-progressbar-page<?php
if ($n < $page_num):
print ' completed';
endif;
if ($n == $page_num):
print ' current';
endif;
?>" style="<?php print ($GLOBALS['language']->direction == 0) ? 'left' : 'right'; ?>: <?php print number_format(($n - 1) / ($page_count - 1), 4) * 100; ?>%">
<span class="webform-progressbar-page-number"><?php print $n; ?></span>
<?php if ($progressbar_pagebreak_labels): ?>
<span class="webform-progressbar-page-label">
<?php print check_plain($page_labels[$n - 1]); ?>
</span>
<?php endif; ?>
</span>
<?php endfor; ?>
</div>
<?php endif; ?>
<?php if ($progressbar_page_number): ?>
<div class="webform-progressbar-number">
<?php print t('Page @start of @end', array('@start' => $page_num, '@end' => $page_count)); ?>
<?php if ($progressbar_percent): ?>
<span class="webform-progressbar-number">
(<?php print number_format($percent, 0); ?>%)
</span>
<?php endif; ?>
</div>
<?php endif; ?>
<?php if (!$progressbar_page_number && $progressbar_percent): ?>
<div class="webform-progressbar-number">
<?php print number_format($percent, 0); ?>%
</div>
<?php endif; ?>
</div>

View File

@@ -1,5 +1,4 @@
<?php
// $Id:
/**
* @file
@@ -14,7 +13,8 @@
* - $table: The table[] array consists of three keys:
* - $table['#header']: Table header.
* - $table['#rows']: Table rows.
* - $table['#operation_total']: Maximum number of operations in the operation column.
* - $table['#operation_total']: Maximum number of operations in the operation
* column.
*/
?>

View File

@@ -18,7 +18,7 @@
<div class="webform-submission-info-text">
<div><?php print t('Form: !form', array('!form' => l($node->title, 'node/' . $node->nid))); ?></div>
<div><?php print t('Submitted by !name', array('!name' => theme('username', array('account' => $account)))); ?></div>
<div><?php print format_date($submission->submitted, 'long'); ?></div>
<div><?php print $submission->remote_addr; ?></div>
<div><?php print check_plain(format_date($submission->submitted, webform_variable_get('webform_date_type'))); ?></div>
<div><?php print check_plain($submission->remote_addr); ?></div>
</div>
</fieldset>

View File

@@ -0,0 +1,194 @@
<?php
/**
* Webform module component tests.
*/
class WebformComponentsTestCase extends WebformTestCase {
/**
* {@inheritdoc}
*/
public static function getInfo() {
return array(
'name' => t('Webform components'),
'description' => t('Add and remove components from a webform.'),
'group' => t('Webform'),
);
}
/**
* Webform module component tests.
*/
public function testWebformComponents() {
// Test webform_component_list().
// Create a form consisting of three textfields separated by pagebreaks.
$test_components = $this->webformComponents();
$textfield = $test_components['textfield']['component'];
// Page 1 textfield.
$textfield['page_num'] = 1;
$textfield['name'] = $this->randomName();
$textfield['form_key'] = $this->randomName();
$components[1] = $textfield;
// Page 2 break.
$components[2] = array(
'type' => 'pagebreak',
'form_key' => 'pagebreak_' . $this->randomName(),
'pid' => 0,
'name' => $this->randomName(),
'page_num' => 2,
);
// Page 2 textfield.
$textfield['name'] = $this->randomName();
$textfield['form_key'] = $this->randomName();
$textfield['page_num'] = 2;
$components[3] = $textfield;
// Page 3 break.
$components[4] = array(
'type' => 'pagebreak',
'form_key' => 'pagebreak_' . $this->randomName(),
'pid' => 0,
// Name is the cid of another component. Should get a space added when it
// is used as a key in the value returned from webform_component_list().
'name' => '1',
'page_num' => 3,
);
// Page 3 textfield.
$textfield['name'] = $this->randomName();
$textfield['form_key'] = $this->randomName();
$textfield['page_num'] = 3;
$components[5] = $textfield;
// Page 4 break.
$components[6] = array(
'type' => 'pagebreak',
'form_key' => 'pagebreak_' . $this->randomName(),
'pid' => 0,
// Name is the same as Page 3 break. Tests that name is made unique.
'name' => '1',
'page_num' => 4,
);
// Page 4 textfield.
$textfield['name'] = $this->randomName();
$textfield['form_key'] = $this->randomName();
$textfield['page_num'] = 4;
$components[7] = $textfield;
// Generate a component list.
$node = new stdClass();
$node->webform['components'] = $components;
$webform_component_list = webform_component_list($node, NULL, TRUE, TRUE);
// Test return value.
$test_list = array(
1 => $components[1]['name'],
$components[2]['name'] => array(2 => $components[2]['name'], 3 => $components[3]['name']),
$components[4]['name'] . ' ' => array(4 => $components[4]['name'], 5 => $components[5]['name']),
$components[6]['name'] . '_2' => array(6 => $components[6]['name'], 7 => $components[7]['name']),
);
$this->assertIdentical($webform_component_list, $test_list, 'webform_component_list() returns expected value.');
// Test webform_component_parent_keys().
$components = array(
1 => array(
'form_key' => $this->randomName(),
'name' => $this->randomName(),
'pid' => 0,
),
2 => array(
'form_key' => $this->randomName(),
'name' => $this->randomName(),
'pid' => 1,
),
3 => array(
'form_key' => $this->randomName(),
'name' => $this->randomName(),
'pid' => 2,
),
);
$node = new stdClass();
$node->webform['components'] = $components;
$parents = webform_component_parent_keys($node, $components[3]);
$test_parents = array($components[1]['form_key'], $components[2]['form_key'], $components[3]['form_key']);
$this->assertIdentical($parents, $test_parents, 'webform_component_parent_keys() returns expected form_keys.');
$parents = webform_component_parent_keys($node, $components[3], 'name');
$test_parents = array($components[1]['name'], $components[2]['name'], $components[3]['name']);
$this->assertIdentical($parents, $test_parents, 'webform_component_parent_keys() returns expected names.');
$parents = webform_component_parent_keys($node, $components[3], TRUE);
$test_parents = array($components[1], $components[2], $components[3]);
$this->assertIdentical($parents, $test_parents, 'webform_component_parent_keys() returns expected component arrays.');
// Test webform_get_cid().
$settings = array(
'title' => 'Test webform with multiple instances of a Form Key',
'type' => 'webform',
);
$node = $this->drupalCreateNode($settings);
// Add a new textarea component.
$components = $this->webformComponents();
$textarea = $components['textarea'];
$textarea['type'] = 'textarea';
$textarea['form_key'] = 'testing_key';
$textarea['cid'] = 1;
$textarea['pid'] = 0;
$textarea = array_merge(webform_component_invoke('textarea', 'defaults'), $textarea);
$node->webform['components'][1] = $textarea;
// Add a new fieldset component.
$fieldset = array(
'cid' => 2,
'pid' => 0,
'form_key' => 'another_key',
'name' => 'Fieldset',
'type' => 'fieldset',
'value' => '',
'required' => '0',
'weight' => '0',
);
$node->webform['components'][2] = $fieldset;
// Add a new textfield component as child of the fieldset.
$textfield = $components['textfield'];
$textfield['type'] = 'textfield';
$textfield['form_key'] = 'testing_key';
$textfield['cid'] = 3;
$textfield['pid'] = 2;
$textfield = array_merge(webform_component_invoke('textarea', 'defaults'), $textfield);
$node->webform['components'][3] = $textfield;
// Add another textfield component.
$textfield = $components['textfield'];
$textfield['type'] = 'textfield';
$textfield['form_key'] = 'dummy_key';
$textfield['cid'] = 4;
$textfield['pid'] = 0;
$textfield = array_merge(webform_component_invoke('textarea', 'defaults'), $textfield);
$node->webform['components'][4] = $textfield;
node_save($node);
// Test webform_get_cid() with providing a parent cid.
$this->assertTrue(webform_get_cid($node, 'testing_key', 2) == 3, t('Returned expected Webform component id for a given form_key and parent (pid).'));
// Test webform_get_cid() without providing a parent cid.
$this->assertTrue(webform_get_cid($node, 'testing_key') == array(1, 3), t('Returned expected Webform component ids array for a given form_key.'));
// Create and visit a new Webform test node.
$node = $this->webformForm();
$this->drupalGet('node/' . $node->nid);
// Check th elements have the scope attribute.
$this->assertRaw('<th class="checkbox webform-grid-option" scope="col">', 'Grid <code>th</code> elements have @scope of "col".');
// Check that each label @for points to an element.
$labels = $this->xpath('//label/@for');
foreach ($labels as $label) {
$for = $this->xpath("//*[@id=':id']", array(':id' => $label['for']));
$this->assertTrue($for, 'Label with @for "' . $label['for'] . '" points to an element.');
}
}
}

View File

@@ -0,0 +1,224 @@
<?php
/**
* Webform module conditional tests.
*/
class WebformConditionalsTestCase extends WebformTestCase {
/**
* {@inheritdoc}
*/
public static function getInfo() {
return array(
'name' => t('Webform conditionals'),
'description' => t('Generates webforms to test conditional showing and hiding of fields.'),
'group' => t('Webform'),
);
}
/**
* Test that required fields with no default value can't be submitted as-is.
*/
public function testWebformConditionals() {
$this->drupalLogin($this->webform_users['admin']);
$this->webformReset();
$test_components = $this->webformComponents();
$test_specs = array(
'match conditional values' => TRUE,
'mismatch conditional values' => FALSE,
);
// Test each component, processing all 'match conditional values' for TRUE
// and all 'mismatch conditional values for FALSE.
foreach ($test_components as $key => $component_info) {
foreach ($test_specs as $values_key => $result) {
if (isset($component_info[$values_key])) {
foreach ($component_info[$values_key] as $operator => $match_value) {
$this->webformTestConditionalComponent($component_info['component'], $component_info['sample values'], $operator, $match_value, $result);
}
}
}
}
$this->drupalLogout();
// Test that the whole conditional is deleted when all source components are
// deleted. Inital setup: Two components. One source (c1) one target (c2).
$node = (object) array(
'type' => 'webform',
'title' => 'Conditional test',
);
$default = array(
'type' => 'textfield',
'pid' => 0,
);
webform_component_defaults($default);
node_object_prepare($node);
$node->webform['components'][1] = array(
'cid' => 1,
'form_key' => 'c1',
) + $default;
$node->webform['components'][2] = array(
'cid' => 2,
'form_key' => 'c2',
) + $default;
$node->webform['conditionals'][0] = array(
'rgid' => 0,
'andor' => NULL,
'weight' => -1,
'rules' => array(array(
'source_type' => 'component',
'source' => 1,
'operator' => 'not_empty',
),
),
'actions' => array(array(
'target_type' => 'component',
'target' => 2,
'action' => 'show',
),
),
);
node_save($node);
// Delete the source.
unset($node->webform['components'][1]);
node_save($node);
$this->assertEqual(array(), $node->webform['conditionals'], 'The whole conditional is deleted when all source components are deleted.');
}
/**
* Assembles a test node for checking if conditional properties are respected.
*
* @param $component
* The sample component that should be tested as the source of a conditional
* operator.
* @param $input_values
* @param $operator
* The operator that will be used to check the source component, such as
* "equal" or "starts_with". See _webform_conditional_operator_info().
* @param $conditional_values
* The string match value that will be entered into the source component's
* conditional configuration. If passed in as an array, multiple rules
* will be added to the conditional.
* @param $should_match
* Boolean value indicating if the source values will cause the conditional
* operation to trigger or not. If TRUE, the submission should succeed.
* If FALSE, validation errors are expected to be triggered. The input
* $value will be compared against the "sample values" input provided by
* webformComponents().
*/
private function webformTestConditionalComponent($component, $input_values, $operator, $conditional_values, $should_match) {
// Create the Webform test node and add a same-page conditional followed
// by a second-page conditional. Insert page breaks between all components.
$input_string = (is_array($input_values) ? print_r($input_values, 1) : $input_values);
$match_string = (is_array($conditional_values) ? print_r($conditional_values, 1) : $conditional_values);
$conditional_string = $should_match ? 'should' : 'should not';
$settings = array(
'title' => 'Test conditional webform: ' . $component['type'] . ' "' . $input_string . '"' . $conditional_string . ' be ' . $operator . ' "' . $match_string . '"',
'type' => 'webform',
'webform' => webform_node_defaults(),
);
$components = array();
$components[] = $component;
$test_components = $this->webformComponents();
$textfield = $test_components['textfield']['component'];
// Add a test textfield on the first page.
$textfield['weight'] = '199';
$textfield['form_key'] = $this->randomName();
$textfield['required'] = '1';
$components[] = $textfield;
// Then add a page break and another textfield on the second page.
$components[] = array(
'type' => 'pagebreak',
'form_key' => 'pagebreak_' . $this->randomName(),
'pid' => 0,
'name' => 'Page break',
'weight' => '200',
);
$textfield['form_key'] = $this->randomName();
$textfield['weight'] = '201';
$components[] = $textfield;
$settings['webform']['components'] = $components;
$node = $this->drupalCreateNode($settings);
// Needed to get a complete object.
$node = node_load($node->nid);
// We now have a new test node. First try checking same-page conditionals.
$rules = array();
$conditional_values = is_array($conditional_values) ? $conditional_values : array($conditional_values);
foreach ($conditional_values as $conditional_value) {
$rules[] = array(
'source_type' => 'component',
// The first component in the form is always the source.
'source' => 1,
'operator' => $operator,
'value' => $conditional_value,
);
}
$conditional = array(
'rgid' => 0,
'rules' => $rules,
'andor' => 'and',
'actions' => array(
0 => array(
'action' => 'show',
// Target set individually.
'target' => NULL,
'target_type' => 'component',
'invert' => '1',
'argument' => NULL,
),
),
'weight' => 0,
);
// The same-page textfield test.
$conditional['actions'][0]['target'] = 2;
$node->webform['conditionals'] = array($conditional);
node_save($node);
// Submit our test data.
$edit = $this->webformPost(array($component['form_key'] => $input_values));
$this->drupalPost('node/' . $node->nid, $edit, 'Next Page >', array(), array(), 'webform-client-form-' . $node->nid);
// Ensure we reached the second page for matched conditionals.
$message = t('Conditional same-page skipping of validation passed for "%form_key": %input_values @conditional_string be @operator %match_string', array('%form_key' => $component['form_key'], '%input_values' => $input_string, '@conditional_string' => $conditional_string, '@operator' => $operator, '%match_string' => $match_string));
if ($should_match) {
$this->assertRaw('&lt; Previous Page', $message, t('Webform'));
}
// Or that we did not reach the second page for mismatched conditionals.
else {
$this->assertNoRaw('&lt; Previous Page', $message, t('Webform'));
}
// Adjust the conditionals to make them separate-page conditionals.
// The separate-page textfield test.
$conditional['actions'][0]['target'] = 3;
$node->webform['conditionals'] = array($conditional);
$node->webform['components'][2]['required'] = '0';
node_save($node);
// Re-submit the form again, this time checking for the field on the
// second page.
$this->drupalPost('node/' . $node->nid, $edit, 'Next Page >', array(), array(), 'webform-client-form-' . $node->nid);
$string_match = 'name="submitted[' . $textfield['form_key'] . ']"';
// Ensure that the field is properly hidden based on a match.
$message = t('Conditional separate-page skipping of validation passed for "%form_key": %input_values @conditional_string be @operator %match_string', array('%form_key' => $component['form_key'], '%input_values' => $input_string, '@conditional_string' => $conditional_string, '@operator' => $operator, '%match_string' => $match_string));
if ($should_match) {
$this->assertNoRaw($string_match, $message, t('Webform'));
}
// Or that the field is still present on a mismatch.
else {
$this->assertRaw($string_match, $message, t('Webform'));
}
}
}

View File

@@ -0,0 +1,95 @@
<?php
/**
* Test general functionality of Webform.
*/
class WebformGeneralTestCase extends WebformTestCase {
/**
* {@inheritdoc}
*/
public static function getInfo() {
return array(
'name' => t('Webform'),
'description' => t('Checks global Webform settings and content types.'),
'group' => t('Webform'),
);
}
/**
* Test creating a new Webform node.
*/
public function testWebformCreate() {
$settings = array(
'title' => 'Test webform, no components',
'type' => 'webform',
);
$node = $this->drupalCreateNode($settings);
// Because this is a "webform" type node, it should have an entry in the
// database even though it's using the default settings.
$this->assertTrue($this->webformRecordExists($node->nid), t('Webform record made in the database for the new webform node.'));
// Make a change to the node, ensure that the record stays intact.
$node->title .= '!';
node_save($node);
$this->assertTrue($this->webformRecordExists($node->nid), t('Webform record still in the database after modifying webform node.'));
}
/**
* Test webform-enabling a different node type and testing behavior.
*/
public function testWebformCreateNewType() {
// Enable webforms on the page content type.
variable_set('webform_node_webform', TRUE);
variable_set('webform_node_page', TRUE);
$settings = array(
'title' => 'Test webform-enabled page',
'type' => 'page',
);
$node = $this->drupalCreateNode($settings);
// Because this is a webform-enabled type node but does not yet have any
// components, it should not have an entry in the database because it is
// using the default settings.
$this->assertFalse($this->webformRecordExists($node->nid), t('Webform record not in the database for the new page node.'));
// Make a change to the node, ensure that the record stays empty.
$node->title .= '!';
node_save($node);
$this->assertFalse($this->webformRecordExists($node->nid), t('Webform record still not in the database after modifying page node.'));
// Add a new component to the node and check that a record is made in the
// webform table.
$components = $this->webformComponents();
$textarea = $components['textarea'];
$textarea['type'] = 'textarea';
$textarea['form_key'] = 'textarea';
$textarea['cid'] = 1;
$textarea['pid'] = 0;
$textarea = array_merge(webform_component_invoke('textarea', 'defaults'), $textarea);
$node->webform['components'][1] = $textarea;
node_save($node);
$this->assertTrue($this->webformRecordExists($node->nid), t('Webform record now exists after adding a new component.'));
// Remove the new component and ensure that the record is deleted.
$node->webform['components'] = array();
node_save($node);
$this->assertFalse($this->webformRecordExists($node->nid), t('Webform record deleted after deleting last component.'));
}
/**
* Determine whether a Webform record exists for a given nid.
*
* @param int $nid
* The node ID.
*
* @return bool
* Whether or not the Webform record exists.
*/
public function webformRecordExists($nid) {
return (bool) db_query("SELECT nid FROM {webform} WHERE nid = :nid", array(':nid' => $nid))->fetchField();
}
}

View File

@@ -1,15 +1,12 @@
<?php
/**
* @file
* Webform module permission tests.
*/
include_once(dirname(__FILE__) . '/webform.test');
class WebformPermissionsTestCase extends WebformTestCase {
/**
* Implements getInfo().
* {@inheritdoc}
*/
public static function getInfo() {
return array(
@@ -19,33 +16,19 @@ class WebformPermissionsTestCase extends WebformTestCase {
);
}
/**
* Implements setUp().
*/
function setUp() {
parent::setUp();
}
/**
* Implements tearDown().
*/
function tearDown() {
parent::tearDown();
}
/**
* Create a webform node in which authenticated users have access to submit.
*/
function testWebformSubmitAccess() {
*/
public function testWebformSubmitAccess() {
$this->webformReset();
$node = $this->testWebformForm();
$node = $this->webformForm();
$node->webform['roles'] = array(2);
node_save($node);
// Test that the authenticated user is able to access.
$this->drupalLogin($this->webform_users['userAccess']);
$this->drupalGet('node/' . $node->nid);
$this->assertText($node->body[LANGUAGE_NONE][0]['value'], t('Webform node created and accessible to authenticated users at !url', array('!url' => 'node/' . $node->nid)), t('Webform'));
$this->assertText($node->title, t('Webform node created and accessible to authenticated users at !url', array('!url' => 'node/' . $node->nid)), t('Webform'));
// Confirm that the submission has been created.
$this->drupalPost(NULL, array(), 'Submit');
@@ -55,13 +38,10 @@ class WebformPermissionsTestCase extends WebformTestCase {
// The anonymous user should not be able to submit.
$this->drupalGet('node/' . $node->nid);
// Note: Should be: You must <a href="!login">login</a> or <a href="!register">register</a> to view this form.
// Something in SimpleTest isn't handling the string correctly.
// Note: Should be: You must <a href="!login">login</a> or
// <a href="!register">register</a> to view this form. Something in
// SimpleTest isn't handling the string correctly.
$this->assertText('to view this form.', t('Anonymous user is not allowed to submit form.'), t('Webform'));
}
/**
* Create webform
*/
}

View File

@@ -1,15 +1,12 @@
<?php
/**
* @file
* Webform module submission tests.
*/
include_once(dirname(__FILE__) . '/webform.test');
class WebformSubmissionTestCase extends WebformTestCase {
/**
* Implements getInfo().
* {@inheritdoc}
*/
public static function getInfo() {
return array(
@@ -19,24 +16,10 @@ class WebformSubmissionTestCase extends WebformTestCase {
);
}
/**
* Implements setUp().
*/
function setUp() {
parent::setUp();
}
/**
* Implements tearDown().
*/
function tearDown() {
parent::tearDown();
}
/**
* Test sending a submission and check database integrity.
*/
function testWebformSubmission() {
public function testWebformSubmission() {
$this->drupalLogin($this->webform_users['admin']);
$this->webformReset();
$this->webformSubmissionExecute('sample');
@@ -46,7 +29,7 @@ class WebformSubmissionTestCase extends WebformTestCase {
/**
* Test a submission that uses default values, and check database integrity.
*/
function testWebformSubmissionDefault() {
public function testWebformSubmissionDefault() {
$this->drupalLogin($this->webform_users['admin']);
$this->webformReset();
$this->webformSubmissionExecute('default');
@@ -56,7 +39,7 @@ class WebformSubmissionTestCase extends WebformTestCase {
/**
* Test validation errors on each component that has specialized validation.
*/
function testWebformSubmissionValidate() {
public function testWebformSubmissionValidate() {
$this->drupalLogin($this->webform_users['admin']);
$this->webformReset();
$this->webformSubmissionValidateExecute();
@@ -66,25 +49,25 @@ class WebformSubmissionTestCase extends WebformTestCase {
/**
* Test that required fields with no default value can't be submitted as-is.
*/
function testWebformSubmissionRequiredComponents() {
public function testWebformSubmissionRequiredComponents() {
$this->drupalLogin($this->webform_users['admin']);
$this->webformReset();
// Create the Webform test node, and set all components to be mandatory
// Create the Webform test node, and set all components to be required
// with no default value.
$node = $this->testWebformForm();
$node = $this->webformForm();
$node = node_load($node->nid);
foreach ($node->webform['components'] as &$component) {
$component['value'] = '';
$component['mandatory'] = '1';
$component['required'] = '1';
}
node_save($node);
// Submit the webform with no data. We should get a message that all the
// components are required. (The exceptions are hidden fields, which can't
// be made mandatory, and date fields, which default to the current date
// when no default value is provided; therefore, we don't expect a message
// for those.)
// components are required. The exceptions are hidden fields, which can't be
// made required, and date fields, which default to the current date when no
// default value is provided; therefore, we don't expect a message for
// those.
$this->drupalPost('node/' . $node->nid, array(), 'Submit', array(), array(), 'webform-client-form-' . $node->nid);
foreach ($node->webform['components'] as $component) {
if ($component['type'] != 'hidden' && $component['type'] != 'date') {
@@ -95,23 +78,62 @@ class WebformSubmissionTestCase extends WebformTestCase {
$this->drupalLogout();
}
/**
* Test length validation.
*/
public function testWebformSubmissionComponentLength() {
$this->drupalLogin($this->webform_users['admin']);
$this->webformReset();
// Create the Webform test node.
$node = $this->webformForm();
$node = node_load($node->nid);
// Get the cid of the textfield component.
foreach ($node->webform['components'] as &$component) {
if ($component['form_key'] === 'textfield') {
$textfield_cid = $component['cid'];
break;
}
}
// Set length validation rules.
$node->webform['components'][$textfield_cid]['extra']['maxlength'] = 5;
$node->webform['components'][$textfield_cid]['extra']['minlength'] = 4;
node_save($node);
// Text value that is too long.
$this->drupalPost('node/' . $node->nid, array('submitted[textfield]' => '123456'), 'Submit', array(), array(), 'webform-client-form-' . $node->nid);
$this->assertRaw(t('!name cannot be longer than %max characters but is currently %length characters long.', array('!name' => $node->webform['components'][$textfield_cid]['name'], '%max' => 5, '%length' => 6)));
// Text value that is too short.
$this->drupalPost('node/' . $node->nid, array('submitted[textfield]' => '123'), 'Submit', array(), array(), 'webform-client-form-' . $node->nid);
$this->assertRaw(t('!name cannot be shorter than %min characters but is currently %length characters long.', array('!name' => $node->webform['components'][$textfield_cid]['name'], '%min' => 4, '%length' => 3)));
// Test value that meets validation rules has no error message.
$this->drupalPost('node/' . $node->nid, array('submitted[textfield]' => '12345'), 'Submit', array(), array(), 'webform-client-form-' . $node->nid);
$this->assertNoPattern('/ cannot be (longer|shorter) than /');
}
/**
* Execute the submission test.
*
* @param $value_type
* @param string $value_type
* The values to be submitted to the webform. Either "sample" or "default".
*/
function webformSubmissionExecute($value_type = 'sample') {
public function webformSubmissionExecute($value_type = 'sample') {
$path = drupal_get_path('module', 'webform');
module_load_include('inc', 'webform', 'includes/webform.submissions');
// Create a new Webform test node.
$node = $this->testWebformForm();
$submission_values = $value_type == 'sample' ? $this->testWebformPost() : array();
$node = $this->webformForm();
$submission_values = $value_type == 'sample' ? $this->webformPost() : array();
// Visit the node page with the "foo=bar" query, to test %get[] default values.
// Visit the node page with the "foo=bar" query, to test
// [current-page:query:?] default values.
$this->drupalGet('node/' . $node->nid, array('query' => array('foo' => 'bar')));
$this->assertText($node->body[LANGUAGE_NONE][0]['value'], t('Webform node created and accessible at !url', array('!url' => 'node/' . $node->nid)), t('Webform'));
$this->assertText($node->title, t('Webform node created and accessible at !url', array('!url' => 'node/' . $node->nid)), t('Webform'));
// Submit our test data.
$this->drupalPost(NULL, $submission_values, 'Submit', array(), array(), 'webform-client-form-' . $node->nid);
@@ -125,13 +147,14 @@ class WebformSubmissionTestCase extends WebformTestCase {
$sid = $matches[1];
// Pull in the database submission and check the values.
$actual_submission = webform_get_submission($node->nid, $sid, TRUE);
drupal_static_reset('webform_get_submission');
$actual_submission = webform_get_submission($node->nid, $sid);
$component_info = $this->testWebformComponents();
$component_info = $this->webformComponents();
foreach ($node->webform['components'] as $cid => $component) {
$stable_value = $value_type == 'sample' ? $component_info[$component['form_key']]['database values'] : $component_info[$component['form_key']]['database default values'];
$actual_value = $actual_submission->data[$cid]['value'];
$result = $this->assertEqual($stable_value, $actual_value, t('Component @form_key data integrity check', array('@form_key' => $component['form_key'])), t('Webform'));
$actual_value = $actual_submission->data[$cid];
$result = $this->assertEqual($stable_value, $actual_value, t('Component @form_key data integrity check when using @type values.', array('@form_key' => $component['form_key'], '@type' => $value_type)), t('Webform'));
if (!$result || $result === 'fail') {
$this->fail(t('Expected !expected', array('!expected' => print_r($stable_value, TRUE))) . "\n\n" . t('Recieved !recieved', array('!recieved' => print_r($actual_value, TRUE))), t('Webform'));
}
@@ -140,21 +163,18 @@ class WebformSubmissionTestCase extends WebformTestCase {
/**
* Execute a validation check for a single component.
*
* @param $value_type
* The values to be submitted to the webform. Either "sample" or "default".
*/
function webformSubmissionValidateExecute() {
public function webformSubmissionValidateExecute() {
$path = drupal_get_path('module', 'webform');
module_load_include('inc', 'webform', 'includes/webform.submissions');
// Create a new Webform test node.
$node = $this->testWebformForm();
$node = $this->webformForm();
// Visit the node page.
$this->drupalGet('node/' . $node->nid);
foreach ($this->testWebformComponents() as $key => $component_info) {
foreach ($this->webformComponents() as $key => $component_info) {
if (isset($component_info['error values'])) {
foreach ($component_info['error values'] as $value => $error_message) {
$submission_values = array();
@@ -163,7 +183,8 @@ class WebformSubmissionTestCase extends WebformTestCase {
// Submit our test data.
$this->drupalPost('node/' . $node->nid, $submission_values, 'Submit', array(), array(), 'webform-client-form-' . $node->nid);
// Confirm that the validation error occurred and the submission did not save.
// Confirm that the validation error occurred and the submission did
// not save.
$this->assertRaw($error_message, t('Validation message properly thrown: "%message".', array('%message' => $error_message)), t('Webform'));
$this->assertFalse(preg_match('/sid=([0-9]+)/', $this->getUrl()), t('Submission not saved.'));
@@ -171,4 +192,5 @@ class WebformSubmissionTestCase extends WebformTestCase {
}
}
}
}

View File

@@ -1,37 +0,0 @@
<?php
/**
* @file
* Webform module component tests.
*/
include_once(dirname(__FILE__) . '/webform.test');
class WebformComponentsTestCase extends WebformTestCase {
/**
* Implements getInfo().
*/
public static function getInfo() {
return array(
'name' => t('Webform components'),
'description' => t('Add and remove components from a webform.'),
'group' => t('Webform'),
);
}
/**
* Implements setUp().
*/
function setUp() {
parent::setUp();
}
/**
* Implements testWebformDummy(). if it is not present,
* then the test runs fine, but when combined with other tests
* the whole block fails, since there would be no output.
*/
function testWebformDummy() {
$this->pass = t('WebformComponentsTest pass.');
}
}

View File

@@ -0,0 +1,59 @@
<?php
/**
* @file
* View definition webform_analysis.
*/
// --- Paste exported view below ---.
$view = new view();
$view->name = 'webform_analysis';
$view->description = 'Limit submissions for the webform node\'s analysis. Query must have sid as a field. Don\'t duplicate sids in result.';
$view->tag = 'webform';
$view->base_table = 'webform_submissions';
$view->human_name = 'Webform Analysis';
$view->core = 7;
$view->api_version = '3.0';
$view->disabled = FALSE; /* Edit this to true to make a default view disabled initially */
/* Display: Master */
$handler = $view->new_display('default', 'Master', 'default');
$handler->display->display_options['use_more_always'] = FALSE;
$handler->display->display_options['access']['type'] = 'none';
$handler->display->display_options['cache']['type'] = 'none';
$handler->display->display_options['query']['type'] = 'views_query';
$handler->display->display_options['query']['options']['distinct'] = TRUE;
$handler->display->display_options['exposed_form']['type'] = 'basic';
$handler->display->display_options['pager']['type'] = 'none';
$handler->display->display_options['pager']['options']['offset'] = '0';
$handler->display->display_options['style_plugin'] = 'default';
$handler->display->display_options['row_plugin'] = 'fields';
/* Field: Webform submissions: Sid */
$handler->display->display_options['fields']['sid']['id'] = 'sid';
$handler->display->display_options['fields']['sid']['table'] = 'webform_submissions';
$handler->display->display_options['fields']['sid']['field'] = 'sid';
/* Contextual filter: Webform submissions: Node */
$handler->display->display_options['arguments']['nid']['id'] = 'nid';
$handler->display->display_options['arguments']['nid']['table'] = 'webform_submissions';
$handler->display->display_options['arguments']['nid']['field'] = 'nid';
$handler->display->display_options['arguments']['nid']['default_action'] = 'empty';
$handler->display->display_options['arguments']['nid']['default_argument_type'] = 'fixed';
$handler->display->display_options['arguments']['nid']['summary']['number_of_records'] = '0';
$handler->display->display_options['arguments']['nid']['summary']['format'] = 'default_summary';
$handler->display->display_options['arguments']['nid']['summary_options']['items_per_page'] = '25';
$translatables['webform_analysis'] = array(
t('Master'),
t('more'),
t('Apply'),
t('Reset'),
t('Sort by'),
t('Asc'),
t('Desc'),
t('Sid'),
t('.'),
t(','),
t('All'),
);
// --- Paste exported view above ---.
$views[$view->name] = $view;

View File

@@ -0,0 +1,169 @@
<?php
/**
* @file
* View definition webform_results.
*/
// --- Paste exported view below ---.
$view = new view();
$view->name = 'webform_results';
$view->description = '';
$view->tag = 'webform';
$view->base_table = 'webform_submissions';
$view->human_name = 'Webform Results';
$view->core = 7;
$view->api_version = '3.0';
$view->disabled = FALSE; /* Edit this to true to make a default view disabled initially */
/* Display: Master */
$handler = $view->new_display('default', 'Master', 'default');
$handler->display->display_options['use_more_always'] = FALSE;
$handler->display->display_options['access']['type'] = 'none';
$handler->display->display_options['cache']['type'] = 'none';
$handler->display->display_options['query']['type'] = 'views_query';
$handler->display->display_options['exposed_form']['type'] = 'basic';
$handler->display->display_options['pager']['type'] = 'full';
$handler->display->display_options['pager']['options']['items_per_page'] = '50';
$handler->display->display_options['pager']['options']['offset'] = '0';
$handler->display->display_options['pager']['options']['id'] = '0';
$handler->display->display_options['pager']['options']['quantity'] = '9';
$handler->display->display_options['pager']['options']['expose']['items_per_page_options'] = '10, 50, 100, 200, 500, 1000';
$handler->display->display_options['pager']['options']['expose']['items_per_page_options_all'] = TRUE;
$handler->display->display_options['pager']['options']['expose']['items_per_page_options_all_label'] = 'All';
$handler->display->display_options['style_plugin'] = 'table';
$handler->display->display_options['style_options']['columns'] = array(
'view_submission' => 'view_submission',
'submitted' => 'submitted',
'name' => 'name',
'remote_addr' => 'remote_addr',
);
$handler->display->display_options['style_options']['default'] = 'view_submission';
$handler->display->display_options['style_options']['info'] = array(
'view_submission' => array(
'sortable' => 1,
'default_sort_order' => 'desc',
'align' => '',
'separator' => '',
'empty_column' => 0,
),
'submitted' => array(
'sortable' => 1,
'default_sort_order' => 'asc',
'align' => '',
'separator' => '',
'empty_column' => 0,
),
'name' => array(
'sortable' => 1,
'default_sort_order' => 'asc',
'align' => '',
'separator' => '',
'empty_column' => 0,
),
'remote_addr' => array(
'sortable' => 1,
'default_sort_order' => 'asc',
'align' => '',
'separator' => '',
'empty_column' => 0,
),
'webform_all_fields' => array(
'sortable' => 1,
'default_sort_order' => 'asc',
'align' => '',
'separator' => '',
'empty_column' => 0,
),
);
$handler->display->display_options['style_options']['sticky'] = TRUE;
$handler->display->display_options['style_options']['empty_table'] = TRUE;
/* Header: Global: Result summary with additional tokens */
$handler->display->display_options['header']['webform_result']['id'] = 'webform_result';
$handler->display->display_options['header']['webform_result']['table'] = 'views';
$handler->display->display_options['header']['webform_result']['field'] = 'webform_result';
$handler->display->display_options['header']['webform_result']['content'] = '<div class="webform-results-per-page">Showing @start - @end of @total. &nbsp; @items_per_page_links</div>';
/* No results behavior: Global: Unfiltered text */
$handler->display->display_options['empty']['area_text_custom']['id'] = 'area_text_custom';
$handler->display->display_options['empty']['area_text_custom']['table'] = 'views';
$handler->display->display_options['empty']['area_text_custom']['field'] = 'area_text_custom';
$handler->display->display_options['empty']['area_text_custom']['empty'] = TRUE;
$handler->display->display_options['empty']['area_text_custom']['content'] = t('There are no submissions for this form. <a href="!url!1">View this form</a>.', array('!url' => url('node/')));
$handler->display->display_options['empty']['area_text_custom']['tokenize'] = TRUE;
/* Relationship: Webform submissions: User */
$handler->display->display_options['relationships']['uid']['id'] = 'uid';
$handler->display->display_options['relationships']['uid']['table'] = 'webform_submissions';
$handler->display->display_options['relationships']['uid']['field'] = 'uid';
/* Field: Webform submissions: View link */
$handler->display->display_options['fields']['view_submission']['id'] = 'view_submission';
$handler->display->display_options['fields']['view_submission']['table'] = 'webform_submissions';
$handler->display->display_options['fields']['view_submission']['field'] = 'view_submission';
$handler->display->display_options['fields']['view_submission']['label'] = '#';
$handler->display->display_options['fields']['view_submission']['element_label_colon'] = FALSE;
$handler->display->display_options['fields']['view_submission']['text'] = '[serial]';
$handler->display->display_options['fields']['view_submission']['access_check'] = 0;
/* Field: Webform submissions: Submitted */
$handler->display->display_options['fields']['submitted']['id'] = 'submitted';
$handler->display->display_options['fields']['submitted']['table'] = 'webform_submissions';
$handler->display->display_options['fields']['submitted']['field'] = 'submitted';
$handler->display->display_options['fields']['submitted']['date_format'] = 'short';
$handler->display->display_options['fields']['submitted']['second_date_format'] = 'long';
/* Field: User: Name */
$handler->display->display_options['fields']['name']['id'] = 'name';
$handler->display->display_options['fields']['name']['table'] = 'users';
$handler->display->display_options['fields']['name']['field'] = 'name';
$handler->display->display_options['fields']['name']['relationship'] = 'uid';
$handler->display->display_options['fields']['name']['label'] = 'User';
$handler->display->display_options['fields']['name']['element_label_colon'] = FALSE;
/* Field: Webform submissions: Remote address */
$handler->display->display_options['fields']['remote_addr']['id'] = 'remote_addr';
$handler->display->display_options['fields']['remote_addr']['table'] = 'webform_submissions';
$handler->display->display_options['fields']['remote_addr']['field'] = 'remote_addr';
$handler->display->display_options['fields']['remote_addr']['label'] = 'IP address';
/* Field: Webform submission data: All values */
$handler->display->display_options['fields']['webform_all_fields']['id'] = 'webform_all_fields';
$handler->display->display_options['fields']['webform_all_fields']['table'] = 'webform_submissions';
$handler->display->display_options['fields']['webform_all_fields']['field'] = 'webform_all_fields';
$handler->display->display_options['fields']['webform_all_fields']['label'] = '';
$handler->display->display_options['fields']['webform_all_fields']['exclude'] = TRUE;
$handler->display->display_options['fields']['webform_all_fields']['alter']['nl2br'] = TRUE;
$handler->display->display_options['fields']['webform_all_fields']['format'] = 'text';
/* Contextual filter: Webform submissions: Node */
$handler->display->display_options['arguments']['nid']['id'] = 'nid';
$handler->display->display_options['arguments']['nid']['table'] = 'webform_submissions';
$handler->display->display_options['arguments']['nid']['field'] = 'nid';
$handler->display->display_options['arguments']['nid']['default_action'] = 'not found';
$handler->display->display_options['arguments']['nid']['default_argument_type'] = 'fixed';
$handler->display->display_options['arguments']['nid']['summary']['number_of_records'] = '0';
$handler->display->display_options['arguments']['nid']['summary']['format'] = 'default_summary';
$handler->display->display_options['arguments']['nid']['summary_options']['items_per_page'] = '25';
$handler->display->display_options['arguments']['nid']['specify_validation'] = TRUE;
$handler->display->display_options['arguments']['nid']['validate']['type'] = 'node';
$translatables['webform_results'] = array(
t('Master'),
t('more'),
t('Apply'),
t('Reset'),
t('Sort by'),
t('Asc'),
t('Desc'),
t('Items per page'),
t('- All -'),
t('Offset'),
t('« first'),
t(' previous'),
t('next '),
t('last »'),
t('Showing @start - @end of @total'),
t('Webform Submission User'),
t('Serial Number'),
t('[serial]'),
t('Submitted'),
t('User'),
t('IP address'),
t('All'),
);
// --- Paste exported view above ---.
$views[$view->name] = $view;

View File

@@ -0,0 +1,211 @@
<?php
/**
* @file
* View definition webform_submissions.
*/
// --- Paste exported view below ---.
$view = new view();
$view->name = 'webform_submissions';
$view->description = '';
$view->tag = 'webform';
$view->base_table = 'webform_submissions';
$view->human_name = 'Webform Submissions';
$view->core = 7;
$view->api_version = '3.0';
$view->disabled = FALSE; /* Edit this to true to make a default view disabled initially */
/* Display: Master */
$handler = $view->new_display('default', 'Master', 'default');
$handler->display->display_options['use_more_always'] = FALSE;
$handler->display->display_options['access']['type'] = 'none';
$handler->display->display_options['cache']['type'] = 'none';
$handler->display->display_options['query']['type'] = 'views_query';
$handler->display->display_options['exposed_form']['type'] = 'basic';
$handler->display->display_options['pager']['type'] = 'full';
$handler->display->display_options['pager']['options']['items_per_page'] = '50';
$handler->display->display_options['pager']['options']['offset'] = '0';
$handler->display->display_options['pager']['options']['id'] = '0';
$handler->display->display_options['pager']['options']['quantity'] = '9';
$handler->display->display_options['pager']['options']['expose']['items_per_page_options'] = '10, 50, 100, 200, 500, 1000';
$handler->display->display_options['pager']['options']['expose']['items_per_page_options_all'] = TRUE;
$handler->display->display_options['pager']['options']['expose']['items_per_page_options_all_label'] = 'All';
$handler->display->display_options['style_plugin'] = 'table';
$handler->display->display_options['style_options']['columns'] = array(
'view_submission' => 'view_submission',
'submitted' => 'submitted',
'name' => 'name',
'remote_addr' => 'remote_addr',
'view_submission_1' => 'view_submission_1',
'edit_submission' => 'view_submission_1',
'delete_submission' => 'view_submission_1',
);
$handler->display->display_options['style_options']['default'] = 'view_submission';
$handler->display->display_options['style_options']['info'] = array(
'view_submission' => array(
'sortable' => 1,
'default_sort_order' => 'desc',
'align' => '',
'separator' => '',
'empty_column' => 0,
),
'submitted' => array(
'sortable' => 1,
'default_sort_order' => 'asc',
'align' => '',
'separator' => '',
'empty_column' => 0,
),
'name' => array(
'sortable' => 1,
'default_sort_order' => 'asc',
'align' => '',
'separator' => '',
'empty_column' => 0,
),
'remote_addr' => array(
'sortable' => 1,
'default_sort_order' => 'asc',
'align' => '',
'separator' => '',
'empty_column' => 0,
),
'view_submission_1' => array(
'sortable' => 0,
'default_sort_order' => 'asc',
'align' => '',
'separator' => ' &nbsp; ',
'empty_column' => 0,
),
'edit_submission' => array(
'sortable' => 0,
'default_sort_order' => 'asc',
'align' => '',
'separator' => '',
'empty_column' => 0,
),
'delete_submission' => array(
'sortable' => 0,
'default_sort_order' => 'asc',
'align' => '',
'separator' => '',
'empty_column' => 0,
),
);
$handler->display->display_options['style_options']['sticky'] = TRUE;
$handler->display->display_options['style_options']['empty_table'] = TRUE;
/* Header: Global: Result summary with additional tokens */
$handler->display->display_options['header']['webform_result']['id'] = 'webform_result';
$handler->display->display_options['header']['webform_result']['table'] = 'views';
$handler->display->display_options['header']['webform_result']['field'] = 'webform_result';
$handler->display->display_options['header']['webform_result']['content'] = '<div class="webform-results-per-page">Showing @start - @end of @total. &nbsp; @items_per_page_links</div>';
/* No results behavior: Global: Unfiltered text */
$handler->display->display_options['empty']['area_text_custom']['id'] = 'area_text_custom';
$handler->display->display_options['empty']['area_text_custom']['table'] = 'views';
$handler->display->display_options['empty']['area_text_custom']['field'] = 'area_text_custom';
$handler->display->display_options['empty']['area_text_custom']['empty'] = TRUE;
$handler->display->display_options['empty']['area_text_custom']['content'] = t('There are no submissions for this form. <a href="!url!1">View this form</a>.', array('!url' => url('node/')));
$handler->display->display_options['empty']['area_text_custom']['tokenize'] = TRUE;
/* Relationship: Webform submissions: User */
$handler->display->display_options['relationships']['uid']['id'] = 'uid';
$handler->display->display_options['relationships']['uid']['table'] = 'webform_submissions';
$handler->display->display_options['relationships']['uid']['field'] = 'uid';
/* Field: Webform submissions: View link */
$handler->display->display_options['fields']['view_submission']['id'] = 'view_submission';
$handler->display->display_options['fields']['view_submission']['table'] = 'webform_submissions';
$handler->display->display_options['fields']['view_submission']['field'] = 'view_submission';
$handler->display->display_options['fields']['view_submission']['label'] = '#';
$handler->display->display_options['fields']['view_submission']['element_label_colon'] = FALSE;
$handler->display->display_options['fields']['view_submission']['text'] = '[serial]';
$handler->display->display_options['fields']['view_submission']['access_check'] = 0;
/* Field: Webform submissions: Submitted */
$handler->display->display_options['fields']['submitted']['id'] = 'submitted';
$handler->display->display_options['fields']['submitted']['table'] = 'webform_submissions';
$handler->display->display_options['fields']['submitted']['field'] = 'submitted';
$handler->display->display_options['fields']['submitted']['date_format'] = 'short';
$handler->display->display_options['fields']['submitted']['second_date_format'] = 'long';
/* Field: User: Name */
$handler->display->display_options['fields']['name']['id'] = 'name';
$handler->display->display_options['fields']['name']['table'] = 'users';
$handler->display->display_options['fields']['name']['field'] = 'name';
$handler->display->display_options['fields']['name']['relationship'] = 'uid';
$handler->display->display_options['fields']['name']['label'] = 'User';
$handler->display->display_options['fields']['name']['element_label_colon'] = FALSE;
/* Field: Webform submissions: Remote address */
$handler->display->display_options['fields']['remote_addr']['id'] = 'remote_addr';
$handler->display->display_options['fields']['remote_addr']['table'] = 'webform_submissions';
$handler->display->display_options['fields']['remote_addr']['field'] = 'remote_addr';
$handler->display->display_options['fields']['remote_addr']['label'] = 'IP address';
/* Field: Webform submissions: View link */
$handler->display->display_options['fields']['view_submission_1']['id'] = 'view_submission_1';
$handler->display->display_options['fields']['view_submission_1']['table'] = 'webform_submissions';
$handler->display->display_options['fields']['view_submission_1']['field'] = 'view_submission';
$handler->display->display_options['fields']['view_submission_1']['label'] = 'Operations';
$handler->display->display_options['fields']['view_submission_1']['element_label_colon'] = FALSE;
$handler->display->display_options['fields']['view_submission_1']['access_check'] = 0;
/* Field: Webform submissions: Edit link */
$handler->display->display_options['fields']['edit_submission']['id'] = 'edit_submission';
$handler->display->display_options['fields']['edit_submission']['table'] = 'webform_submissions';
$handler->display->display_options['fields']['edit_submission']['field'] = 'edit_submission';
$handler->display->display_options['fields']['edit_submission']['label'] = '';
$handler->display->display_options['fields']['edit_submission']['element_label_colon'] = FALSE;
$handler->display->display_options['fields']['edit_submission']['access_check'] = 1;
/* Field: Webform submissions: Delete link */
$handler->display->display_options['fields']['delete_submission']['id'] = 'delete_submission';
$handler->display->display_options['fields']['delete_submission']['table'] = 'webform_submissions';
$handler->display->display_options['fields']['delete_submission']['field'] = 'delete_submission';
$handler->display->display_options['fields']['delete_submission']['label'] = '';
$handler->display->display_options['fields']['delete_submission']['element_label_colon'] = FALSE;
$handler->display->display_options['fields']['delete_submission']['access_check'] = 1;
/* Contextual filter: Webform submissions: Node */
$handler->display->display_options['arguments']['nid']['id'] = 'nid';
$handler->display->display_options['arguments']['nid']['table'] = 'webform_submissions';
$handler->display->display_options['arguments']['nid']['field'] = 'nid';
$handler->display->display_options['arguments']['nid']['default_action'] = 'not found';
$handler->display->display_options['arguments']['nid']['default_argument_type'] = 'fixed';
$handler->display->display_options['arguments']['nid']['summary']['number_of_records'] = '0';
$handler->display->display_options['arguments']['nid']['summary']['format'] = 'default_summary';
$handler->display->display_options['arguments']['nid']['summary_options']['items_per_page'] = '25';
$handler->display->display_options['arguments']['nid']['specify_validation'] = TRUE;
$handler->display->display_options['arguments']['nid']['validate']['type'] = 'node';
/* Contextual filter: Webform submissions: User */
$handler->display->display_options['arguments']['uid']['id'] = 'uid';
$handler->display->display_options['arguments']['uid']['table'] = 'webform_submissions';
$handler->display->display_options['arguments']['uid']['field'] = 'uid';
$handler->display->display_options['arguments']['uid']['default_argument_type'] = 'fixed';
$handler->display->display_options['arguments']['uid']['summary']['number_of_records'] = '0';
$handler->display->display_options['arguments']['uid']['summary']['format'] = 'default_summary';
$handler->display->display_options['arguments']['uid']['summary_options']['items_per_page'] = '25';
$translatables['webform_submissions'] = array(
t('Master'),
t('more'),
t('Apply'),
t('Reset'),
t('Sort by'),
t('Asc'),
t('Desc'),
t('Items per page'),
t('All'),
t('Offset'),
t('« first'),
t(' previous'),
t('next '),
t('last »'),
t('Showing @start - @end of @total @items_per_page_links'),
t('Webform Submission User'),
t('#'),
t('[serial]'),
t('Submitted'),
t('User'),
t('IP address'),
t('Operations'),
t('view'),
t('edit'),
t('delete'),
);
// --- Paste exported view above ---.
$views[$view->name] = $view;

View File

@@ -0,0 +1,217 @@
<?php
/**
* @file
* Wiew definition webform_webforms.
*/
// --- Paste exported view below ---.
$view = new view();
$view->name = 'webform_webforms';
$view->description = 'An administrative list of webform nodes';
$view->tag = 'webform';
$view->base_table = 'webform';
$view->human_name = 'Webforms';
$view->core = 7;
$view->api_version = '3.0';
$view->disabled = FALSE; /* Edit this to true to make a default view disabled initially */
/* Display: Master */
$handler = $view->new_display('default', 'Master', 'default');
$handler->display->display_options['use_more_always'] = FALSE;
$handler->display->display_options['access']['type'] = 'none';
$handler->display->display_options['cache']['type'] = 'none';
$handler->display->display_options['query']['type'] = 'views_query';
$handler->display->display_options['exposed_form']['type'] = 'basic';
$handler->display->display_options['pager']['type'] = 'full';
$handler->display->display_options['pager']['options']['items_per_page'] = '25';
$handler->display->display_options['pager']['options']['offset'] = '0';
$handler->display->display_options['pager']['options']['id'] = '0';
$handler->display->display_options['pager']['options']['quantity'] = '9';
$handler->display->display_options['style_plugin'] = 'table';
$handler->display->display_options['style_options']['columns'] = array(
'title' => 'title',
'created' => 'created',
'status' => 'status',
'webform_results' => 'webform_results',
'webform_results_2' => 'webform_results',
'webform_results_3' => 'webform_results',
'webform_results_4' => 'webform_results',
'edit_node' => 'edit_node',
'webform_edit' => 'edit_node',
'webform_results_1' => 'edit_node',
);
$handler->display->display_options['style_options']['default'] = 'created';
$handler->display->display_options['style_options']['info'] = array(
'title' => array(
'sortable' => 1,
'default_sort_order' => 'asc',
'align' => '',
'separator' => '',
'empty_column' => 0,
),
'created' => array(
'sortable' => 1,
'default_sort_order' => 'asc',
'align' => '',
'separator' => '',
'empty_column' => 0,
),
'status' => array(
'sortable' => 1,
'default_sort_order' => 'asc',
'align' => '',
'separator' => '',
'empty_column' => 0,
),
'webform_results' => array(
'align' => '',
'separator' => ' &nbsp; ',
'empty_column' => 0,
),
'webform_results_2' => array(
'align' => '',
'separator' => '',
'empty_column' => 0,
),
'webform_results_3' => array(
'align' => '',
'separator' => '',
'empty_column' => 0,
),
'webform_results_4' => array(
'align' => '',
'separator' => '',
'empty_column' => 0,
),
'edit_node' => array(
'align' => '',
'separator' => ' &nbsp; ',
'empty_column' => 0,
),
'webform_edit' => array(
'align' => '',
'separator' => '',
'empty_column' => 0,
),
'webform_results_1' => array(
'align' => '',
'separator' => '',
'empty_column' => 0,
),
);
/* Relationship: Webform: Node */
$handler->display->display_options['relationships']['nid']['id'] = 'nid';
$handler->display->display_options['relationships']['nid']['table'] = 'webform';
$handler->display->display_options['relationships']['nid']['field'] = 'nid';
$handler->display->display_options['relationships']['nid']['required'] = TRUE;
/* Field: Content: Title */
$handler->display->display_options['fields']['title']['id'] = 'title';
$handler->display->display_options['fields']['title']['table'] = 'node';
$handler->display->display_options['fields']['title']['field'] = 'title';
$handler->display->display_options['fields']['title']['relationship'] = 'nid';
/* Field: Content: Post date */
$handler->display->display_options['fields']['created']['id'] = 'created';
$handler->display->display_options['fields']['created']['table'] = 'node';
$handler->display->display_options['fields']['created']['field'] = 'created';
$handler->display->display_options['fields']['created']['relationship'] = 'nid';
$handler->display->display_options['fields']['created']['label'] = 'Created';
$handler->display->display_options['fields']['created']['date_format'] = 'short';
$handler->display->display_options['fields']['created']['second_date_format'] = 'long';
/* Field: Webform: Status */
$handler->display->display_options['fields']['status']['id'] = 'status';
$handler->display->display_options['fields']['status']['table'] = 'webform';
$handler->display->display_options['fields']['status']['field'] = 'status';
/* Field: Webform: Webform results link */
$handler->display->display_options['fields']['webform_results']['id'] = 'webform_results';
$handler->display->display_options['fields']['webform_results']['table'] = 'node';
$handler->display->display_options['fields']['webform_results']['field'] = 'webform_results';
$handler->display->display_options['fields']['webform_results']['relationship'] = 'nid';
$handler->display->display_options['fields']['webform_results']['label'] = 'View';
$handler->display->display_options['fields']['webform_results']['element_label_colon'] = FALSE;
$handler->display->display_options['fields']['webform_results']['text'] = 'Submissions';
/* Field: Webform: Webform results link */
$handler->display->display_options['fields']['webform_results_2']['id'] = 'webform_results_2';
$handler->display->display_options['fields']['webform_results_2']['table'] = 'node';
$handler->display->display_options['fields']['webform_results_2']['field'] = 'webform_results';
$handler->display->display_options['fields']['webform_results_2']['relationship'] = 'nid';
$handler->display->display_options['fields']['webform_results_2']['label'] = 'Analysis';
$handler->display->display_options['fields']['webform_results_2']['element_label_colon'] = FALSE;
$handler->display->display_options['fields']['webform_results_2']['text'] = 'Analysis';
$handler->display->display_options['fields']['webform_results_2']['subpath'] = 'analysis';
/* Field: Webform: Webform results link */
$handler->display->display_options['fields']['webform_results_3']['id'] = 'webform_results_3';
$handler->display->display_options['fields']['webform_results_3']['table'] = 'node';
$handler->display->display_options['fields']['webform_results_3']['field'] = 'webform_results';
$handler->display->display_options['fields']['webform_results_3']['relationship'] = 'nid';
$handler->display->display_options['fields']['webform_results_3']['label'] = 'Table';
$handler->display->display_options['fields']['webform_results_3']['element_label_colon'] = FALSE;
$handler->display->display_options['fields']['webform_results_3']['text'] = 'Table';
$handler->display->display_options['fields']['webform_results_3']['subpath'] = 'table';
/* Field: Webform: Webform results link */
$handler->display->display_options['fields']['webform_results_4']['id'] = 'webform_results_4';
$handler->display->display_options['fields']['webform_results_4']['table'] = 'node';
$handler->display->display_options['fields']['webform_results_4']['field'] = 'webform_results';
$handler->display->display_options['fields']['webform_results_4']['relationship'] = 'nid';
$handler->display->display_options['fields']['webform_results_4']['label'] = 'Download';
$handler->display->display_options['fields']['webform_results_4']['element_label_colon'] = FALSE;
$handler->display->display_options['fields']['webform_results_4']['text'] = 'Download';
$handler->display->display_options['fields']['webform_results_4']['subpath'] = 'download';
/* Field: Content: Edit link */
$handler->display->display_options['fields']['edit_node']['id'] = 'edit_node';
$handler->display->display_options['fields']['edit_node']['table'] = 'views_entity_node';
$handler->display->display_options['fields']['edit_node']['field'] = 'edit_node';
$handler->display->display_options['fields']['edit_node']['relationship'] = 'nid';
$handler->display->display_options['fields']['edit_node']['label'] = 'Operations';
$handler->display->display_options['fields']['edit_node']['alter']['alter_text'] = TRUE;
$handler->display->display_options['fields']['edit_node']['alter']['text'] = 'Edit';
$handler->display->display_options['fields']['edit_node']['element_label_colon'] = FALSE;
/* Field: Webform: Webform edit link */
$handler->display->display_options['fields']['webform_edit']['id'] = 'webform_edit';
$handler->display->display_options['fields']['webform_edit']['table'] = 'node';
$handler->display->display_options['fields']['webform_edit']['field'] = 'webform_edit';
$handler->display->display_options['fields']['webform_edit']['relationship'] = 'nid';
$handler->display->display_options['fields']['webform_edit']['label'] = 'Components';
$handler->display->display_options['fields']['webform_edit']['element_label_colon'] = FALSE;
$handler->display->display_options['fields']['webform_edit']['text'] = 'Components';
$handler->display->display_options['fields']['webform_edit']['subpath'] = 'components';
/* Field: Webform: Webform results link */
$handler->display->display_options['fields']['webform_results_1']['id'] = 'webform_results_1';
$handler->display->display_options['fields']['webform_results_1']['table'] = 'node';
$handler->display->display_options['fields']['webform_results_1']['field'] = 'webform_results';
$handler->display->display_options['fields']['webform_results_1']['relationship'] = 'nid';
$handler->display->display_options['fields']['webform_results_1']['label'] = 'Clear';
$handler->display->display_options['fields']['webform_results_1']['text'] = 'Clear';
$handler->display->display_options['fields']['webform_results_1']['subpath'] = 'clear';
$translatables['webform_webforms'] = array(
t('Master'),
t('more'),
t('Apply'),
t('Reset'),
t('Sort by'),
t('Asc'),
t('Desc'),
t('Items per page'),
t('- All -'),
t('Offset'),
t('« first'),
t(' previous'),
t('next '),
t('last »'),
t('Node for webform'),
t('Title'),
t('Created'),
t('Status'),
t('View'),
t('Submissions'),
t('Analysis'),
t('Table'),
t('Download'),
t('Operations'),
t('Edit'),
t('Components'),
t('Clear'),
);
// --- Paste exported view above ---.
$views[$view->name] = $view;

View File

@@ -5,12 +5,17 @@
* Views hooks implemented for the Webform module.
*/
/**
* Implements hook_views_data().
*/
function webform_views_data() {
/**
* Webform table definitions.
*/
// Webform table definitions.
$data['webform']['table']['group'] = t('Webform');
$data['webform']['table']['base'] = array(
'field' => 'nid',
'title' => t('Webform nodes'),
'help' => t('Nodes which have webforms.'),
);
$data['webform']['table']['join'] = array(
'node' => array(
'left_field' => 'nid',
@@ -19,7 +24,32 @@ function webform_views_data() {
),
);
// status
// NID.
$data['webform']['nid'] = array(
'title' => t('Node'),
'help' => t('The node this webform is part of.'),
'relationship' => array(
'base' => 'node',
'field' => 'nid',
'handler' => 'views_handler_relationship',
'label' => t('Node for webform'),
),
'filter' => array(
'handler' => 'views_handler_filter_numeric',
),
'sort' => array(
'handler' => 'views_handler_sort',
),
'argument' => array(
'handler' => 'views_handler_argument_node_nid',
// The field to display in the summary.
'name field' => 'title',
'numeric' => TRUE,
'validate type' => 'nid',
),
);
// Status.
$data['webform']['status'] = array(
'title' => t('Status'),
'help' => t('The open or closed status of a webform.'),
@@ -37,9 +67,7 @@ function webform_views_data() {
),
);
/**
* Submissions table definitions.
*/
// Submissions table definitions.
$data['webform_submissions']['table']['group'] = t('Webform submissions');
$data['webform_submissions']['table']['base'] = array(
'field' => 'sid',
@@ -47,7 +75,27 @@ function webform_views_data() {
'help' => t('Submissions generated from Webform forms.'),
);
// sid
// Serial number.
$data['webform_submissions']['serial'] = array(
'title' => t('Serial number'),
'help' => t('The serial number of the submission.'),
'field' => array(
'handler' => 'views_handler_field_numeric',
'click sortable' => TRUE,
),
'filter' => array(
'title' => t('Serial number'),
'handler' => 'views_handler_filter_numeric',
),
'sort' => array(
'handler' => 'views_handler_sort',
),
'argument' => array(
'handler' => 'views_handler_argument_numeric',
),
);
// SID.
$data['webform_submissions']['sid'] = array(
'title' => t('Sid'),
'help' => t('The submission ID of the submission.'),
@@ -67,7 +115,33 @@ function webform_views_data() {
),
);
// nid
// Submission data as a field loads the entire submission.
$data['webform_submissions']['value'] = array(
'title' => t('Value'),
'help' => t('The value of a component as submitted by a user.'),
'real field' => 'sid',
'group' => t('Webform submission data'),
'field' => array(
'handler' => 'webform_handler_field_submission_data',
'webform_expand' => FALSE,
'click sortable' => TRUE,
),
);
// Expanded to generate a field for every viewable component.
$data['webform_submissions']['webform_all_fields'] = array(
'title' => t('All values'),
'help' => t('Displays all values as submitted by a user.'),
'real field' => 'sid',
'group' => t('Webform submission data'),
'field' => array(
'handler' => 'webform_handler_field_submission_data',
'webform_expand' => TRUE,
'click sortable' => TRUE,
),
);
// NID.
$data['webform_submissions']['nid'] = array(
'title' => t('Node'),
'help' => t('The webform node this submission was generated from.'),
@@ -77,9 +151,22 @@ function webform_views_data() {
'handler' => 'views_handler_relationship',
'label' => t('Webform Node'),
),
'filter' => array(
'handler' => 'views_handler_filter_numeric',
),
'sort' => array(
'handler' => 'views_handler_sort',
),
'argument' => array(
'handler' => 'views_handler_argument_node_nid',
// The field to display in the summary.
'name field' => 'title',
'numeric' => TRUE,
'validate type' => 'nid',
),
);
// uid
// UID.
$data['webform_submissions']['uid'] = array(
'title' => t('User'),
'help' => t('The user who sent the webform submission.'),
@@ -89,9 +176,21 @@ function webform_views_data() {
'handler' => 'views_handler_relationship',
'label' => t('Webform Submission User'),
),
'filter' => array(
'handler' => 'views_handler_filter_user_name',
),
'sort' => array(
'handler' => 'views_handler_sort',
),
'argument' => array(
'handler' => 'views_handler_argument_numeric',
),
'field' => array(
'handler' => 'views_handler_field_user',
),
);
// is_draft
// Is draft.
$data['webform_submissions']['is_draft'] = array(
'title' => t('Draft'),
'help' => t('Whether or not the submission is a draft.'),
@@ -103,14 +202,14 @@ function webform_views_data() {
'handler' => 'webform_handler_filter_is_draft',
),
'sort' => array(
'handler' => 'views_handler_sort',
'handler' => 'views_handler_sort',
),
);
// submitted
// Submitted timestamp.
$data['webform_submissions']['submitted'] = array(
'title' => t('Submitted'),
'help' => t('The date this submission was submitted.'),
'help' => t('Timestamp when the form was first saved as draft or submitted.'),
'field' => array(
'handler' => 'views_handler_field_date',
'click sortable' => TRUE,
@@ -122,9 +221,52 @@ function webform_views_data() {
'sort' => array(
'handler' => 'views_handler_sort_date',
),
'argument' => array(
'handler' => 'views_handler_argument_date',
),
);
// remote_addr
// Completed timestamp.
$data['webform_submissions']['completed'] = array(
'title' => t('Completed'),
'help' => t('Timestamp when the form was submitted as complete (not draft).'),
'field' => array(
'handler' => 'views_handler_field_date',
'click sortable' => TRUE,
),
'filter' => array(
'title' => t('Completed'),
'handler' => 'views_handler_filter_date',
),
'sort' => array(
'handler' => 'views_handler_sort_date',
),
'argument' => array(
'handler' => 'views_handler_argument_date',
),
);
// Modified timestamp.
$data['webform_submissions']['modified'] = array(
'title' => t('Modified'),
'help' => t('Timestamp when the form was last saved (complete or draft).'),
'field' => array(
'handler' => 'views_handler_field_date',
'click sortable' => TRUE,
),
'filter' => array(
'title' => t('Modified'),
'handler' => 'views_handler_filter_date',
),
'sort' => array(
'handler' => 'views_handler_sort_date',
),
'argument' => array(
'handler' => 'views_handler_argument_date',
),
);
// IP Address (remote_addr).
$data['webform_submissions']['remote_addr'] = array(
'title' => t('Remote address'),
'help' => t('The remote IP address of the user that submitted this submission.'),
@@ -141,34 +283,136 @@ function webform_views_data() {
),
);
// view link
// View submission link.
$data['webform_submissions']['view_submission'] = array(
'title' => t('View link'),
'help' => t('Provide a simple link to view the submission.'),
'real field' => 'serial',
'field' => array(
'title' => t('View link'),
'help' => t('Provide a simple link to view the submission.'),
'handler' => 'webform_handler_field_submission_link',
'click sortable' => TRUE,
'real field' => 'serial',
'link_type' => 'view',
),
);
// edit link
$data['webform_submissions']['edit_submission'] = array(
'field' => array(
'title' => t('Edit link'),
'help' => t('Provide a simple link to edit the submission.'),
'handler' => 'webform_handler_field_submission_link',
'link_type' => 'edit',
'sort' => array(
'handler' => 'views_handler_sort',
),
);
// delete link
$data['webform_submissions']['delete_submission'] = array(
// Edit submission link.
$data['webform_submissions']['edit_submission'] = array(
'title' => t('Edit link'),
'help' => t('Provide a simple link to edit the submission.'),
'real field' => 'serial',
'field' => array(
'title' => t('Delete link'),
'help' => t('Provide a simple link to delete the submission.'),
'handler' => 'webform_handler_field_submission_link',
'click sortable' => TRUE,
'link_type' => 'edit',
),
'sort' => array(
'handler' => 'views_handler_sort',
),
);
// Delete submission link.
$data['webform_submissions']['delete_submission'] = array(
'title' => t('Delete link'),
'help' => t('Provide a simple link to delete the submission.'),
'real field' => 'serial',
'field' => array(
'handler' => 'webform_handler_field_submission_link',
'click sortable' => TRUE,
'link_type' => 'delete',
),
'sort' => array(
'handler' => 'views_handler_sort',
),
);
// Relation to webform_submitted_data table.
$data['webform_submissions']['data'] = array(
'title' => t('Data'),
'help' => t('Relates to a webform submission data'),
'real field' => 'sid',
'relationship' => array(
'handler' => 'webform_handler_relationship_submission_data',
'base' => 'webform_submitted_data',
'base field' => 'sid',
'label' => t('Submission Data'),
),
);
// Submission data table definitions.
$data['webform_submitted_data']['table']['group'] = t('Webform submission data');
// Raw access to the submitted values. This usually will only be used for
// sorts and filters, since the 'value' field for the submission will often
// be faster and easier to configure than the raw values.
$data['webform_submitted_data']['data'] = array(
'table' => 'webform_submitted_data',
'title' => t('Data field'),
'help' => t('The value of a component as submitted by a user.'),
'real field' => 'data',
'field' => array(
// Distinguish from the normal value handler.
'title' => t('Value (raw)'),
'help' => t('The raw value from the database as submitted by a user. Use only when needing to sort on a field value.'),
'handler' => 'views_handler_field',
'click sortable' => TRUE,
),
'argument' => array(
'handler' => 'views_handler_argument_string',
),
'filter' => array(
'handler' => 'webform_handler_filter_submission_data',
),
'sort' => array(
'handler' => 'views_handler_sort',
),
);
// Same as 'data', but handled as numeric data.
$data['webform_submitted_data']['data_numeric'] = array(
'table' => 'webform_submitted_data',
'title' => t('Data field (numeric)'),
'help' => t('The numeric value of a component as submitted by a user.'),
'real field' => 'data',
'field' => array(
// Distinguish from the normal value handler.
'title' => t('Numeric value (raw)'),
'help' => t('The raw value from the database, cast to a number, as submitted by a user. Use only when needing to sort on a field value.'),
'handler' => 'webform_handler_field_numeric_data',
'click sortable' => TRUE,
'float' => TRUE,
),
'argument' => array(
'handler' => 'views_handler_argument_numeric',
),
'filter' => array(
'handler' => 'webform_handler_filter_numeric_data',
),
'sort' => array(
'handler' => 'webform_handler_sort_numeric_data',
),
);
// Number field for multi-value fields.
$data['webform_submitted_data']['no'] = array(
'title' => t('Value delta'),
'help' => t('The delta value of the submitted data in a multi value component (such as checkboxes).'),
'real field' => 'no',
'argument' => array(
'handler' => 'views_handler_argument_numeric',
),
'field' => array(
'handler' => 'views_handler_field_numeric',
),
'filter' => array(
'handler' => 'views_handler_filter_numeric',
),
'sort' => array(
'handler' => 'views_handler_sort',
),
);
return $data;
@@ -206,7 +450,6 @@ function webform_views_data_alter(&$data) {
),
);
// Webform submission of user.
$data['users']['webform_submission'] = array(
'title' => t('Webform submission'),
@@ -258,51 +501,231 @@ function webform_views_data_alter(&$data) {
'handler' => 'webform_handler_field_form_body',
),
);
$data['views']['webform_result'] = array(
'title' => t('Result summary with an additional token to change the items/page'),
'help' => t('Shows result summary, for example the items per page, plus links to change the items per page.'),
'area' => array(
'handler' => 'webform_handler_area_result_pager',
),
);
}
/**
* Implements hook_views_handlers().
* Implements hook_views_plugins().
*/
function webform_views_handlers() {
function webform_views_plugins() {
return array(
'info' => array(
'path' => drupal_get_path('module', 'webform') . '/views',
),
'handlers' => array(
'webform_handler_field_submission_link' => array(
'parent' => 'views_handler_field',
'file' => 'webform_handler_field_submission_link.inc',
),
'webform_handler_field_submission_count' => array(
'parent' => 'views_handler_field',
'file' => 'webform_handler_field_submission_count.inc',
),
'webform_handler_field_node_link_edit' => array(
'parent' => 'views_handler_field_node_link',
),
'webform_handler_field_node_link_results' => array(
'parent' => 'views_handler_field_node_link',
),
'webform_handler_field_form_body' => array(
'parent' => 'views_handler_field',
'file' => 'webform_handler_field_form_body.inc',
),
'webform_handler_field_is_draft' => array(
'parent' => 'views_handler_field',
'file' => 'webform_handler_field_is_draft.inc',
),
'webform_handler_filter_is_draft' => array(
'parent' => 'views_handler_filter_in_operator',
'file' => 'webform_handler_filter_is_draft.inc',
),
'webform_handler_field_webform_status' => array(
'parent' => 'views_handler_field_boolean',
'file' => 'webform_handler_field_webform_status.inc',
),
'webform_handler_filter_webform_status' => array(
'parent' => 'views_handler_filter_boolean_operator',
'file' => 'webform_handler_filter_webform_status.inc',
'row' => array(
'webform_submission' => array(
'title' => t('Rendered submissions'),
'help' => t('Display the rendered submission'),
'handler' => 'webform_views_plugin_row_submission_view',
'uses options' => TRUE,
'type' => 'normal',
),
),
);
}
}
/**
* Implements hook_view_pre_view().
*/
function webform_views_pre_view($view, $display_id, $args) {
$display = $view->display[$display_id];
$all_fields_id = _webform_view_find_id($view, $display_id, 'field', array('field' => 'webform_all_fields'));
if ($all_fields_id !== NULL &&
!empty($args[0]) && is_numeric($args[0]) && $args[0] > 0 &&
_webform_view_find_id($view, $display_id, 'argument', array('field' => 'nid', 'table' => 'webform_submissions')) !== NULL &&
($node = node_load($args[0])) && isset($node->webform['components'])) {
// This is a view/display that needs its fields expanded. It contains the
// webform_all_fields field, has a nid argument to the webform_submission
// table that is a valid node. Retrieve the display's fields and remove any
// fields after the 'webform_all_fields' field.
$fields = $view->get_items('field', $display_id);
$prototype = $fields[$all_fields_id];
$field_index = array_flip(array_keys($fields));
$trailing_fields = array_slice($fields, $field_index[$all_fields_id] + 1, NULL, TRUE);
$fields = array_slice($fields, 0, $field_index[$all_fields_id], TRUE);
// Remove any fields after the webform_add_fields field.
$new_columns = array();
foreach ($node->webform['components'] as $component) {
if (webform_component_invoke($component['type'], 'table', $component, array('')) !== NULL) {
$new_id = 'webform_component_' . $component['cid'];
$new_fields = array(
array(
'id' => $new_id,
'field' => 'value',
'table' => 'webform_submissions',
'label' => $component['name'],
'webform_nid' => $node->nid,
'webform_cid' => $component['cid'],
'exclude' => 0,
) + $prototype,
);
if (webform_component_implements($component['type'], 'view_field')) {
$new_fields = webform_component_invoke($component['type'], 'view_field', $component, $new_fields);
}
foreach ($new_fields as $sub_id => $new_field) {
$field_id = $new_id . ($sub_id ? '_' . $sub_id : '');
$fields[$field_id] = $new_field;
$new_columns[$field_id] = $field_id;
}
}
}
// Add any trailing fields back in.
$fields += $trailing_fields;
// Store. Alas, there is no view::set_items() method.
$display->handler->set_option('fields', $fields);
// If this display's style is a table, add columns for click-sorting.
// Note: Test for count($new_columns) is necessary because prior to PHP 5.6,
// array_fill requires a positive number of elements to insert.
if ($display->handler->get_option('style_plugin') == 'table' && count($new_columns)) {
$style_options = $display->handler->get_option('style_options');
$style_options['columns'] += $new_columns;
$style_prototype = isset($style_options['info'][$all_fields_id])
? $style_options['info'][$all_fields_id]
: array();
$style_prototype += array(
'sortable' => 1,
'default_sort_order' => 'asc',
'align' => '',
'separator' => '',
'empty_column' => 0,
);
$style_options['info'] += array_combine($new_columns, array_fill(1, count($new_columns), $style_prototype));
$display->handler->set_option('style_options', $style_options);
}
// Reset field handlers cache and rebuild field handlers.
unset($display->handler->handlers['field']);
$display->handler->get_handlers('field');
// Allow other modules to alter these modifications to the view.
drupal_alter('webform_view', $view, $display_id, $args);
}
}
/**
* Helper; Finds an item by option.
*/
function _webform_view_find_id($view, $display_id, $type, $options) {
foreach ($view->get_items($type, $display_id) as $id => $item) {
foreach ($options as $option_key => $option_value) {
if ($item[$option_key] != $option_value) {
continue 2;
}
}
return $id;
}
return NULL;
}
/**
* Menu callback; Provide a list of Webform nodes for use in autocomplete.
*/
function webform_views_autocomplete($string = '') {
if ($string) {
$or = db_or();
// Strings with nid: in them can be used as direct matches.
$matches = array();
if (preg_match('/nid:([0-9]+)/', $string, $matches)) {
$or->condition('n.nid', (int) $matches[1]);
}
// Otherwise match on title and optionally indirect NIDs.
else {
$or->condition('n.title', '%' . db_like($string) . '%', 'LIKE');
if (is_numeric($string)) {
$or->condition('n.nid', (int) $string);
}
}
$options = array();
$query = db_select('node', 'n')
->fields('n', array('nid', 'title'))
->condition($or);
$query->innerJoin('webform', 'w', 'w.nid = n.nid');
$result = $query
->range(0, 10)
->execute();
foreach ($result as $node) {
$options[$node->title . ' [nid:' . $node->nid . ']'] = check_plain($node->title) . ' [nid:' . $node->nid . ']';
}
}
drupal_json_output($options);
}
/**
* Shared form for the Webform submission data field and relationship handler.
*/
function _webform_views_options_form(&$form, &$form_state, $nid, $cid) {
form_load_include($form_state, 'inc', 'webform', 'includes/webform.components');
$node = $nid ? node_load($nid) : NULL;
$form['webform_nid'] = array(
'#type' => 'textfield',
'#title' => t('Webform node'),
'#default_value' => isset($node) ? $node->title . ' [nid:' . $node->nid . ']' : '',
'#ajax' => array(
'path' => views_ui_build_form_url($form_state),
'event' => 'blur',
),
'#autocomplete_path' => 'webform/autocomplete',
'#description' => t('Enter the title or NID of the Webform whose values should be made available.'),
'#submit' => array('views_ui_config_item_form_submit_temporary'),
'#executes_submit_callback' => TRUE,
);
$components = array();
if (isset($node->webform['components'])) {
$components = $node->webform['components'];
}
$type_options = array();
foreach (webform_components() as $key => $component) {
$type_options[$key] = check_plain($component['label']);
}
$options = webform_component_list($node, NULL, 'path', TRUE);
$form['webform_cid'] = array(
'#title' => t('Component data'),
'#type' => 'select',
'#options' => $options,
'#default_value' => $cid,
'#access' => count($components),
'#description' => t('Select the component whose values should be made available.'),
);
}
/**
* Validate handler for webform_views_options_form().
*/
function _webform_views_options_validate(&$form, &$form_state) {
// Just store the checked components of the selected type.
if (empty($form_state['values']['options']['webform_nid'])) {
form_error($form['webform_nid'], t('Webform NID is required.'));
}
else {
$nid = preg_replace('/^.*?nid:([0-9]+).*?$/', '$1', $form_state['values']['options']['webform_nid']);
if (!($nid && ($node = node_load($nid)) && !empty($node->webform['components']))) {
form_error($form['webform_nid'], t('The specified node is not valid.'));
}
}
}
/**
* Submit handler for webform_views_options_form().
*/
function _webform_views_options_submit(&$form, &$form_state) {
// Save the NID as just the number instead of the title.
$nid = preg_replace('/^.*?nid:([0-9]+).*?$/', '$1', $form_state['values']['options']['webform_nid']);
$form_state['values']['options']['webform_nid'] = $nid;
}

View File

@@ -0,0 +1,101 @@
<?php
/**
* Definition of views_handler_area_result.
*
* Views area handler to display some configurable result summary.
*
* @ingroup views_area_handlers
*/
class webform_handler_area_result_pager extends views_handler_area_result {
/**
* {@inheritdoc}
*/
public function option_definition() {
$options = parent::option_definition();
$options['content']['default'] = t('Displaying @start - @end of @total. @items_per_page_links');
$options['tokenize'] = array('default' => FALSE, 'bool' => TRUE);
return $options;
}
/**
* {@inheritdoc}
*/
public function options_form(&$form, &$form_state) {
parent::options_form($form, $form_state);
$form['content']['#description'] .= '<br/>' . t('Plus @items_per_page_links -- the list of links to change the items/page, with prompt');
$form['tokenize'] = array(
'#type' => 'checkbox',
'#title' => t('Use replacement tokens from the first row'),
'#default_value' => $this->options['tokenize'],
);
}
/**
* Find out the information to render.
*/
public function render($empty = FALSE) {
$output = parent::render($empty);
if (is_string($output) && isset($this->view->query->pager)) {
$output = str_replace('@items_per_page_links', $this->render_items_per_page($this->view->query->pager), $output);
if ($this->options['tokenize']) {
$output = $this->view->style_plugin->tokenize_value($output, 0);
$output = $this->sanitize_value($output, 'xss_admin');
}
}
return $output;
}
/**
*
*/
public function query() {
$view = $this->view;
if (!empty($_GET['items_per_page']) && $_GET['items_per_page'] > 0) {
$view->set_items_per_page($_GET['items_per_page']);
}
elseif (!empty($_GET['items_per_page']) && $_GET['items_per_page'] == 'All') {
$view->set_items_per_page(0);
}
}
/**
*
*/
public function render_items_per_page($pager) {
$sanitized_options = array();
if (!empty($pager->options['expose']['items_per_page_options'])) {
$options = explode(',', $pager->options['expose']['items_per_page_options']);
foreach ($options as $option) {
if ($pager->total_items <= intval($option)) {
break;
}
$sanitized_options[intval($option)] = intval($option);
}
if (!empty($sanitized_options) && !empty($pager->options['expose']['items_per_page_options_all']) && !empty($pager->options['expose']['items_per_page_options_all_label'])) {
$sanitized_options[0] = $pager->options['expose']['items_per_page_options_all_label'];
}
$url_query = $_GET;
unset($url_query['q']);
foreach ($sanitized_options as $items_per_page => &$label) {
$selected = $pager->options['items_per_page'] == $items_per_page ||
($items_per_page == 0 && $pager->total_items < intval($pager->options['items_per_page']));
$label = l($label, $_GET['q'], array(
'query' => array('items_per_page' => $label) + $url_query,
'attributes' => array(
'class' => $selected ? array('selected') : array(),
),
));
}
// Drop PHP reference.
unset($label);
// Include CSS needed for 'selected' class.
drupal_add_library('webform', 'admin');
}
return $sanitized_options ? t('Show !links results per page.', array('!links' => implode(' | ', $sanitized_options))) : '';
}
}

View File

@@ -1,31 +1,41 @@
<?php
/**
* @file
* Views handler to display the content of a webform form.
*/
/**
*
* Field handler to present the Webform form body to the user.
*/
class webform_handler_field_form_body extends views_handler_field {
function construct() {
/**
* {@inheritdoc}
*/
public function construct() {
parent::construct();
$this->additional_fields['nid'] = 'nid';
}
function option_definition() {
/**
* {@inheritdoc}
*/
public function option_definition() {
$options = parent::option_definition();
$options['label'] = array('default' => 'Form', 'translatable' => TRUE);
return $options;
}
function query() {
/**
*
*/
public function query() {
$this->ensure_my_table();
$this->add_additional_fields();
}
function render($values) {
/**
*
*/
public function render($values) {
$node = node_load($values->{$this->aliases['nid']});
if (node_access('view', $node)) {
@@ -39,4 +49,5 @@ class webform_handler_field_form_body extends views_handler_field {
return $form_body;
}
}

View File

@@ -1,18 +1,22 @@
<?php
/**
* @file
* Views handler to display the draft status of a submission.
*/
/**
*
* Field handler to show if submission is draft or not.
*
* @ingroup views_field_handlers
*/
class webform_handler_field_is_draft extends views_handler_field {
function render($values) {
/**
*
*/
public function render($values) {
$is_draft = $values->{$this->field_alias};
return $is_draft ? t('draft') : t('completed');
return isset($is_draft)
? ($is_draft ? t('draft') : t('completed'))
: t('no submission');
}
}
}

View File

@@ -1,30 +1,49 @@
<?php
/**
* @file
* Views handler to display an edit link for Webform configuration.
*/
/**
*
* Field handler to present a link node edit.
*/
class webform_handler_field_node_link_edit extends views_handler_field_node_link {
/**
* {@inheritdoc}
*/
public function option_definition() {
$options = parent::option_definition();
$options['subpath'] = array('default' => '');
return $options;
}
/**
* {@inheritdoc}
*/
public function options_form(&$form, &$form_state) {
parent::options_form($form, $form_state);
$form['subpath'] = array(
'#type' => 'textfield',
'#title' => t('Subpath'),
'#default_value' => $this->options['subpath'],
'#field_prefix' => 'node/NID/webform/',
);
}
/**
* Renders the link.
*/
function render_link($node, $values) {
// Ensure user has access to edit this node.
if (!node_access('update', $node)) {
public function render_link($node, $values) {
// Ensure node is webform-enabled and user has access to edit this node.
if (!in_array($node->type, webform_node_types()) || !node_access('update', $node)) {
return;
}
$this->options['alter']['make_link'] = TRUE;
$this->options['alter']['path'] = "node/$node->nid/webform";
$this->options['alter']['path'] = "node/$node->nid/webform" .
(strlen($this->options['subpath']) ? '/' . $this->options['subpath'] : '');
$text = !empty($this->options['text']) ? $this->options['text'] : t('edit webform');
return $text;
}
}

View File

@@ -1,30 +1,55 @@
<?php
/**
* @file
* Views handler to display a results link for Webform submissions.
*/
/**
*
* Field handler to present a link node edit.
*/
class webform_handler_field_node_link_results extends views_handler_field_node_link {
/**
* {@inheritdoc}
*/
public function option_definition() {
$options = parent::option_definition();
$options['subpath'] = array('default' => '');
return $options;
}
/**
* {@inheritdoc}
*/
public function options_form(&$form, &$form_state) {
parent::options_form($form, $form_state);
$form['subpath'] = array(
'#type' => 'textfield',
'#title' => t('Subpath'),
'#default_value' => $this->options['subpath'],
'#field_prefix' => 'node/NID/webform-results/',
);
}
/**
* Renders the link.
*/
function render_link($node, $values) {
// Ensure user has access to edit this node.
if (!webform_results_access($node)) {
public function render_link($node, $values) {
// Ensure node is webform-enabled and the user has access node's webform
// results.
if (!in_array($node->type, webform_node_types()) || !webform_results_access($node)) {
return;
}
// For clear, ensure user has access to clear all the submissions.
if (stripos($this->options['subpath'], 'clear') === 0 && !user_access('delete all webform submissions')) {
return;
}
$this->options['alter']['make_link'] = TRUE;
$this->options['alter']['path'] = "node/$node->nid/webform-results";
$this->options['alter']['path'] = "node/$node->nid/webform-results" .
(strlen($this->options['subpath']) ? '/' . $this->options['subpath'] : '');
$text = !empty($this->options['text']) ? $this->options['text'] : t('results');
return $text;
}
}

View File

@@ -1,15 +1,16 @@
<?php
/**
* @file
* Views handler to display the number of submissions in a webform.
*/
/**
*
* Field handler to present the submission count of a node to the user.
*/
class webform_handler_field_submission_count extends views_handler_field {
function construct() {
/**
* {@inheritdoc}
*/
public function construct() {
parent::construct();
$this->count_type = $this->definition['count_type'];
@@ -22,32 +23,44 @@ class webform_handler_field_submission_count extends views_handler_field {
}
}
function option_definition() {
/**
* {@inheritdoc}
*/
public function option_definition() {
$options = parent::option_definition();
$options['label'] = array('default' => '# of Submissions', 'translatable' => TRUE);
return $options;
}
function query() {
/**
*
*/
public function query() {
$this->ensure_my_table();
$this->add_additional_fields();
}
function render($values) {
/**
*
*/
public function render($values) {
global $user;
$output = NULL;
if ($this->count_type == 'node' && in_array($values->{$this->aliases['type']}, webform_variable_get('webform_node_types'))) {
if ($this->count_type == 'node' && variable_get('webform_node_' . $values->{$this->aliases['type']}, FALSE)) {
module_load_include('inc', 'webform', 'includes/webform.submissions');
$node = node_load($values->{$this->aliases['nid']});
if (webform_results_access($node, $user)) {
$count = webform_get_submission_count($node->nid);
$output = l($count, "node/$node->nid/webform-results");
}
else {
elseif (webform_submission_access($node, NULL, 'list', $user)) {
$count = webform_get_submission_count($node->nid, $user->uid);
$output = l($count, "node/$node->nid/submissions");
}
else {
$output = webform_get_submission_count($node->nid);
}
}
elseif ($this->count_type == 'users') {
$output = db_select('webform_submissions')
@@ -57,4 +70,5 @@ class webform_handler_field_submission_count extends views_handler_field {
return $output;
}
}

View File

@@ -0,0 +1,243 @@
<?php
/**
* Views handler to display data value of a webform submission component.
*
* Field handler to show submission data.
*
* @ingroup views_field_handlers
*/
class webform_handler_field_submission_data extends views_handler_field {
/**
* {@inheritdoc}
*/
public function construct() {
// We need to set this property before calling the construct() chain
// as we use it in the option_definintion() call.
$this->webform_expand = $this->definition['webform_expand'];
parent::construct();
}
/**
* {@inheritdoc}
*/
public function option_definition() {
$options = parent::option_definition();
$options['format'] = array('default' => 'html');
$options['custom_label'] = array('default' => 'default');
$options['webform_nid'] = array('default' => NULL);
$options['webform_cid'] = array('default' => NULL);
$options['webform_datatype'] = array('default' => 'string');
return $options;
}
/**
* {@inheritdoc}
*/
public function options_form(&$form, &$form_state) {
parent::options_form($form, $form_state);
form_load_include($form_state, 'inc', 'webform', 'views/webform.views');
$form['custom_label']['#type'] = 'radios';
$form['custom_label']['#options'] = array(
'default' => t('Use component label'),
'custom' => t('Custom label'),
'none' => t('No label'),
);
$form['custom_label']['#default_value'] = $this->options['custom_label'];
$form['label']['#dependency'] = array('radio:options[custom_label]' => array('custom'));
if (!$this->webform_expand) {
$nid = (int) $this->options['webform_nid'];
$cid = (int) $this->options['webform_cid'];
// Helper function provides webform_nid and webform_cid options.
_webform_views_options_form($form, $form_state, $nid, $cid);
}
// Modify behavior for the type of data in the component.
$form['webform_datatype'] = array(
'#type' => 'select',
'#title' => t('Data type'),
'#options' => array(
'string' => t('String'),
'number' => t('Number'),
),
'#default_value' => $this->options['webform_datatype'],
);
// Provide the selection for the display format.
$form['format'] = array(
'#type' => 'select',
'#title' => t('Display format'),
'#options' => array(
'html' => t('HTML'),
'text' => t('Plain text'),
),
'#default_value' => $this->options['format'],
);
}
/**
* {@inheritdoc}
*/
public function options_validate(&$form, &$form_state) {
parent::options_validate($form, $form_state);
if (!$this->webform_expand) {
_webform_views_options_validate($form, $form_state);
}
}
/**
* {@inheritdoc}
*/
public function options_submit(&$form, &$form_state) {
parent::options_submit($form, $form_state);
if (!$this->webform_expand) {
_webform_views_options_submit($form, $form_state);
}
}
/**
* Called to determine what to tell the clicksorter.
*/
public function click_sort($order) {
if (isset($this->field_alias)) {
// Since fields should always have themselves already added, just
// add a sort on the field.
$params = $this->options['group_type'] != 'group' ? array('function' => $this->options['group_type']) : array();
$join = new views_join();
$extra = array(
array(
'field' => 'cid',
'value' => $this->options['webform_cid'],
'numeric' => TRUE,
),
array(
'field' => 'no',
'value' => '0',
'numeric' => TRUE,
),
);
$join->construct('webform_submitted_data', 'webform_submissions', 'sid', 'sid', $extra);
$this->query->add_relationship('webform_submitted_data_click_sort', $join, 'webform_submissions');
switch ($this->options['webform_datatype']) {
case 'number':
$this->query->add_orderby(NULL, "IF(webform_submitted_data_click_sort.data REGEXP '^-?[0-9]+(\\\\.[0-9]*)?$', webform_submitted_data_click_sort.data + 0, NULL)", $order, $this->field_alias . '_click_sort', $params);
break;
default:
$this->query->add_orderby('webform_submitted_data_click_sort', 'data', $order, $this->field_alias . '_click_sort', $params);
break;
}
}
}
/**
* Load the node and submissions needed for this components values.
*/
public function pre_render(&$values) {
$nid = $this->options['webform_nid'];
$this->webform_node = node_load($nid);
// Load all the submissions needed for this page. This is stored at the
// view level to ensure it's available between fields so we don't load
// them twice.
if (!isset($this->view->_webform_submissions[$nid])) {
module_load_include('inc', 'webform', 'includes/webform.submissions');
$this->view->_webform_submissions[$nid] = array();
$sids = array();
foreach ($values as $value) {
$sids[] = $value->{$this->field_alias};
}
if ($sids) {
$this->view->_webform_submissions[$nid] = webform_get_submissions(array('sid' => $sids));
}
}
}
/**
* Get this field's label based on the selected component.
*/
public function label() {
if ($this->options['custom_label'] === 'default' && isset($this->options['webform_cid'])) {
if (isset($this->webform_node)) {
$node = $this->webform_node;
}
else {
$node = node_load($this->options['webform_nid']);
}
if ($node && isset($node->webform['components'][$this->options['webform_cid']])) {
$component = $node->webform['components'][$this->options['webform_cid']];
return $component['name'];
}
}
elseif ($this->options['custom_label'] === 'custom' && isset($this->options['label'])) {
return $this->options['label'];
}
return '';
}
/**
* Render the field using the loaded submissions from pre_render().
*/
public function render($row) {
$sid = $this->get_value($row);
$nid = $this->options['webform_nid'];
$cid = $this->options['webform_cid'];
$webform = $this->webform_node;
if (isset($sid) && isset($webform->webform['components'][$cid])) {
$component = $webform->webform['components'][$cid];
$submission = $this->view->_webform_submissions[$nid][$sid];
if ($submission->nid != $nid) {
// The actual submission is from a different webform than the one used
// to define the view. Rather than using the component with the same
// cid, try to match the form_key.
if (!isset($this->view->_webform_components[$nid][$submission->nid][$cid])) {
if (!isset($this->view->_webform_components[$nid][$submission->nid]['webform'])) {
$this->view->_webform_components[$nid][$submission->nid]['webform'] = $webform;
}
$this->view->_webform_components[$nid][$submission->nid][$cid] = $component;
$submission_node = node_load($submission->nid);
foreach ($submission_node->webform['components'] as $sub_cid => $sub_component) {
if ((string) $sub_component['form_key'] === (string) $component['form_key'] && $sub_component['type'] == $component['type']) {
$this->view->_webform_components[$nid][$submission->nid]['webform'] = $submission_node;
$this->view->_webform_components[$nid][$submission->nid][$cid] = $sub_component;
break;
}
}
}
$webform = $this->view->_webform_components[$nid][$submission->nid]['webform'];
$component = $this->view->_webform_components[$nid][$submission->nid][$cid];
// Note: $nid and $cid refer to the definition webform, not the
// submission webform whereas $component refers to the submission
// component.
}
if ($this->options['format'] == 'html') {
$render = array('#submission' => $submission);
_webform_client_form_add_component($webform, $component, NULL, $render, $render, $submission->data, 'html');
$render = $render[$component['form_key']];
// Remove display label.
$render['#theme_wrappers'] = array();
}
else {
// Plain text format is generated via invoking the table output to
// ensure output is sanitised.
$data = isset($submission->data[$component['cid']]) ? $submission->data[$component['cid']] : NULL;
$render = webform_component_invoke($component['type'], 'table', $component, $data);
}
// Webform renders empty values as a space, which prevents views empty
// rewriting from being used. If empty is in use, change result to an
// actual empty string.
$render = render($render);
if ($render === ' ' && strlen($this->options['empty'])) {
$render = '';
}
return $render;
}
}
}

View File

@@ -1,17 +1,17 @@
<?php
/**
* @file
* Views handler to display links to a submission.
*/
/**
*
* Field handler to present a link to the user.
*/
class webform_handler_field_submission_link extends views_handler_field {
var $link_type;
public $link_type;
function construct() {
/**
* {@inheritdoc}
*/
public function construct() {
// We need to set this property before calling the construct() chain
// as we use it in the option_definintion() call.
$this->link_type = $this->definition['link_type'];
@@ -19,82 +19,89 @@ class webform_handler_field_submission_link extends views_handler_field {
parent::construct();
$this->additional_fields['sid'] = 'sid';
$this->additional_fields['nid'] = 'nid';
$this->additional_fields['uid'] = 'uid';
$this->additional_fields['node_uid'] = array(
'table' => 'node',
'field' => 'uid',
);
$this->additional_fields['serial'] = 'serial';
$this->additional_fields['is_draft'] = 'is_draft';
}
function allow_advanced_render() {
/**
*
*/
public function allow_advanced_render() {
return FALSE;
}
function option_definition() {
/**
* {@inheritdoc}
*/
public function option_definition() {
$options = parent::option_definition();
$options['label'] = array('default' => '', 'translatable' => TRUE);
$options['text'] = array('default' => $this->link_type, 'translatable' => TRUE);
$options['access_check'] = array('default' => TRUE);
return $options;
}
function options_form(&$form, &$form_state) {
/**
* {@inheritdoc}
*/
public function options_form(&$form, &$form_state) {
parent::options_form($form, $form_state);
$form['text'] = array(
'#type' => 'textfield',
'#title' => t('Text to display'),
'#default_value' => $this->options['text'],
'#description' => t('The token [serial] will be replaced with the serial number and draft indication.'),
);
$form['access_check'] = array(
'#type' => 'checkbox',
'#title' => t('Verify submission access for each link'),
'#default_value' => $this->options['access_check'],
);
}
function query() {
$this->ensure_my_table();
// Join to the node table to retrieve the node UID.
$join = new views_join();
$join->construct('node', $this->table_alias, 'nid', 'nid');
$this->query->ensure_table('node', $this->relationship, $join);
$this->add_additional_fields();
}
function render($values) {
$submission = new stdClass();
$submission->sid = $values->{$this->aliases['sid']};
$submission->nid = $values->{$this->aliases['nid']};
$submission->uid = $values->{$this->aliases['uid']};
$node = (object) array(
'nid' => $submission->nid,
'uid' => $values->{$this->aliases['node_uid']},
);
/**
*
*/
public function render($values) {
$sid = $values->{$this->aliases['sid']};
$nid = $values->{$this->aliases['nid']};
$serial = $values->{$this->aliases['serial']};
$is_draft = $values->{$this->aliases['is_draft']};
$text = str_ireplace('[serial]',
$serial . ($is_draft ? ' (' . t('draft') . ')' : ''),
$this->options['text']);
switch ($this->link_type) {
case 'view':
$text = !empty($this->options['text']) ? $this->options['text'] : t('view');
$link = l($text, "node/$submission->nid/submission/$submission->sid");
$access = webform_submission_access($node, $submission, 'view');
$text = !empty($text) ? $text : t('view');
$link = l($text, "node/$nid/submission/$sid");
break;
case 'edit':
$text = !empty($this->options['text']) ? $this->options['text'] : t('edit');
$link = l($text, "node/$submission->nid/submission/$submission->sid/edit");
$access = webform_submission_access($node, $submission, 'edit');
$text = !empty($text) ? $text : t('edit');
$link = l($text, "node/$nid/submission/$sid/edit");
break;
case 'delete':
$text = !empty($this->options['text']) ? $this->options['text'] : t('delete');
$text = !empty($text) ? $text : t('delete');
$path = drupal_get_path_alias($_GET['q']);
$link = l($text, "node/$submission->nid/submission/$submission->sid/delete", array('query' => array('destination' => $path)));
$access = webform_submission_access($node, $submission, 'delete');
$link = l($text, "node/$nid/submission/$sid/delete", array('query' => array('destination' => $path)));
break;
default:
$text = '';
$link = NULL;
$access = FALSE;
return;
}
if (!$access) {
return;
if ($this->options['access_check']) {
module_load_include('inc', 'webform', 'includes/webform.submissions');
$node = node_load($nid);
$submission = webform_get_submission($nid, $sid);
if (!webform_submission_access($node, $submission, $this->link_type)) {
return;
}
}
return $link;
}
}

View File

@@ -1,23 +1,31 @@
<?php
/**
* @file
* Views handler to display the open or closed status of a webform.
*/
class webform_handler_field_webform_status extends views_handler_field_boolean {
function options_form(&$form, &$form_state) {
/**
* {@inheritdoc}
*/
public function options_form(&$form, &$form_state) {
parent::options_form($form, $form_state);
$form['type']['#options'] = array('open-closed' => t('Open/Closed')) + $form['type']['#options'];
}
function option_definition() {
/**
* {@inheritdoc}
*/
public function option_definition() {
$options = parent::option_definition();
$options['type']['default'] = 'open-closed';
return $options;
}
function render($values) {
/**
*
*/
public function render($values) {
$value = $values->{$this->field_alias};
if (!empty($this->options['not'])) {
$value = !$value;
@@ -25,14 +33,29 @@ class webform_handler_field_webform_status extends views_handler_field_boolean {
switch ($this->options['type']) {
case 'yes-no':
return $value ? t('Yes') : t('No');
case 'true-false':
return $value ? t('True') : t('False');
case 'unicode-yes-no':
return $value ? t('✔') : t('✖');
case 'enabled-disabled':
return $value ? t('Enabled') : t('Disabled');
case 'boolean':
return $value ? 1 : 0;
case 'on-off':
return $value ? t('On') : t('Off');
case 'custom':
return $value ? $this->options['type_custom_true'] : $this->options['type_custom_false'];
case 'open-closed':
default:
return $value ? t('Open') : t('Closed');
}
}
}
}

View File

@@ -1,15 +1,16 @@
<?php
/**
* @file
* Views handler to filter submissions by draft state.
*/
/**
* Filter by submission status
*
* Filter by submission status.
*/
class webform_handler_filter_is_draft extends views_handler_filter_in_operator {
function get_value_options() {
/**
*
*/
public function get_value_options() {
if (!isset($this->value_options)) {
$this->value_title = t('Status');
$options = array('0' => t('Completed'), '1' => t('Draft'));
@@ -17,9 +18,12 @@ class webform_handler_filter_is_draft extends views_handler_filter_in_operator {
}
}
// '0' won't work as a key for checkboxes.
function value_form(&$form, &$form_state) {
/**
* '0' won't work as a key for checkboxes.
*/
public function value_form(&$form, &$form_state) {
parent::value_form($form, $form_state);
$form['value']['#type'] = 'select';
}
}

View File

@@ -0,0 +1,102 @@
<?php
/**
* Definition of webform_handler_filter_submission_data.
*
* Extended version of the string filter handler specialized for Webform values.
*
* @ingroup views_filter_handlers
*/
class webform_handler_filter_submission_data extends views_handler_filter_string {
/**
* Add to the list of operators.
*
* This kind of construct makes it relatively easy for a child class to add or
* remove functionality by overriding this function and adding/removing items
* from this array.
*/
public function operators() {
$operators = parent::operators();
// Add additional operators for date/time ranges.
$operators['>'] = array(
'title' => t('Greater than'),
'short' => t('>'),
'method' => 'op_greater_than',
'values' => 1,
);
$operators['<'] = array(
'title' => t('Less than'),
'short' => t('<'),
'method' => 'op_less_than',
'values' => 1,
);
return $operators;
}
/**
* Build strings from the operators() for 'select' options.
*/
public function operator_options($which = 'title') {
$options = parent::operator_options($which);
// Adjust the exposed filter options based on the component selected.
if ($which === 'title') {
$nid = $this->view->relationship[$this->options['relationship']]->options['webform_nid'];
$cid = $this->view->relationship[$this->options['relationship']]->options['webform_cid'];
if ($nid && $node = $node = node_load($nid)) {
module_load_include('inc', 'webform', 'includes/webform.components');
$component = $node->webform['components'][$cid];
if (webform_component_feature($component['type'], 'views_range')) {
$options['='] = t('Is');
$options['!='] = t('Is not');
$options['>'] = t('After');
$options['<'] = t('Before');
$options = array_intersect_key($options, array('=' => '=', '!=' => '!=', '>' => '>', '<' => '<'));
}
}
}
return $options;
}
/**
*
*/
public function operator_values($values = 1) {
$options = array();
foreach ($this->operators() as $id => $info) {
if (isset($info['values']) && $info['values'] == $values) {
$options[] = $id;
}
}
return $options;
}
/**
* Provide a simple textfield for equality.
*/
public function value_form(&$form, &$form_state) {
// @todo: Adjust the exposed filter form based on component form.
return parent::value_form($form, $form_state);
}
/**
*
*/
public function op_greater_than($field) {
$this->query->add_where($this->options['group'], $field, $this->value, '>');
}
/**
*
*/
public function op_less_than($field) {
$this->query->add_where($this->options['group'], $field, $this->value, '<');
}
}

View File

@@ -1,15 +1,19 @@
<?php
/**
* @file
* Views handler to filter webforms by open or closed status.
*/
class webform_handler_filter_webform_status extends views_handler_filter_boolean_operator {
function get_value_options() {
/**
*
*/
public function get_value_options() {
if (!isset($this->value_options)) {
$this->value_title = t('Status');
$options = array('1' => t('Open'), '0' => t('Closed'));
$this->value_options = $options;
}
}
}

View File

@@ -0,0 +1,173 @@
<?php
/**
* @file
* Definition of handlers for using numeric submission data.
*/
/**
* Extended version of the numeric field handler specialized for Webform values.
*
* @ingroup views_field_handlers
*/
class webform_handler_field_numeric_data extends views_handler_field_numeric {
public $formula = NULL;
/**
* {@inheritdoc}
*/
public function construct() {
parent::construct();
$this->formula = TRUE;
}
/**
* Get the formula for this argument.
*
* $this->ensure_my_table() MUST have been called prior to this.
*/
public function get_formula() {
return ("(0.0 + $this->table_alias.$this->real_field)");
}
/**
* Called to add the field to a query.
*/
public function query() {
$this->ensure_my_table();
// Add the field.
$params = $this->options['group_type'] != 'group' ? array('function' => $this->options['group_type']) : array();
$this->field_alias = $this->query->add_field(NULL, $this->get_formula(), $this->table_alias . '_' . $this->field, $params);
$this->add_additional_fields();
}
/**
* Shortcut to get a handler's raw field value.
*
* This should be overridden for handlers with formulae or other
* non-standard fields. Because this takes an argument, fields
* overriding this can just call return parent::get_field($formula)
*/
public function get_field($field = NULL) {
return parent::get_field($this->get_formula());
}
}
/**
* Numeric filter handler that works with Webform numeric submission data.
*
* @ingroup views_filter_handlers
*/
class webform_handler_filter_numeric_data extends views_handler_filter_numeric {
/**
* Get the formula for this argument.
*
* $this->ensure_my_table() MUST have been called prior to this.
*/
public function get_formula() {
return ("(0.0 + $this->table_alias.$this->real_field)");
}
/**
* Called to add the filter to a query.
*/
public function query() {
$this->ensure_my_table();
$info = $this->operators();
if (!empty($info[$this->operator]['method'])) {
$this->{$info[$this->operator]['method']}($this->get_formula());
}
}
/**
* Adds a simple operator condition to the query.
*/
public function op_simple($field) {
static $sequence = 1;
$param = ":value" . $sequence++;
$this->query->add_where_expression($this->options['group'],
$field . $this->operator . $param,
array($param => $this->value['value']));
}
/**
* Adds a between or not-between condition to the query.
*/
public function op_between($field) {
static $sequence = 1;
$min = ":min" . $sequence;
$max = ":max" . $sequence++;
if ($this->operator == 'between') {
$this->query->add_where_expression($this->options['group'],
"($min <= $field AND $field <= $max)",
array($min => $this->value['min'], $max => $this->value['max']));
}
else {
$this->query->add_where_expression($this->options['group'],
"($min > $field OR $field > $max)",
array($min => $this->value['min'], $max => $this->value['max']));
}
}
/**
* Adds an empty or not-empty condition to the query.
*/
public function op_empty($field) {
if ($this->operator == 'empty') {
$operator = "IS NULL";
}
else {
$operator = "IS NOT NULL";
}
$this->query->add_where_expression($this->options['group'],
"$field $operator");
}
/**
* Adds a regular expression condition to the query.
*/
public function op_regex($field) {
static $sequence = 1;
$param = ":expression" . $sequence++;
$this->query->add_where_expression($this->options['group'],
"$field RLIKE $param",
array($param => $this->value['value']));
}
}
/**
* Sort handler that works with Webform numeric submission data.
*
* @ingroup views_sort_handlers
*/
class webform_handler_sort_numeric_data extends views_handler_sort {
/**
* Get the formula for this sort.
*
* $this->ensure_my_table() MUST have been called prior to this.
*/
public function get_formula() {
return ("(0.0 + $this->table_alias.$this->real_field)");
}
/**
* Called to add the sort to a query.
*/
public function query() {
$this->ensure_my_table();
// Add the field.
$alias = $this->query->add_field(NULL, $this->get_formula(), $this->table_alias . '_' . $this->field . '_sort');
// Add the sort for the field using only the alias.
$this->query->add_orderby(NULL, NULL, $this->options['order'], $alias);
}
}

View File

@@ -0,0 +1,141 @@
<?php
/**
* Views' relationship handlers.
*/
class webform_handler_relationship_submission_data extends views_handler_relationship {
/**
* {@inheritdoc}
*/
public function option_definition() {
$options = parent::option_definition();
$options['webform_nid'] = array('default' => NULL);
$options['webform_cid'] = array('default' => NULL);
$options['webform_form_key'] = array('default' => NULL);
$options['webform_join_by'] = array('default' => 'nid_cid');
return $options;
}
/**
* {@inheritdoc}
*/
public function options_form(&$form, &$form_state) {
parent::options_form($form, $form_state);
form_load_include($form_state, 'inc', 'webform', 'views/webform.views');
$nid = (int) $this->options['webform_nid'];
$cid = (int) $this->options['webform_cid'];
// Helper function provides webform_nid and webform_cid options.
_webform_views_options_form($form, $form_state, $nid, $cid);
$form['webform_join_by'] = array(
'#type' => 'select',
'#title' => t('Relate using'),
'#default_value' => $this->options['webform_join_by'],
'#options' => array(
'nid_cid' => t('Node and Component ID'),
'cid' => t('Component ID'),
'form_key' => t('Component Form Key'),
),
'#description' => t('Choose <em>Node and Component ID</em> when this view will display data from only this webform.</br>Choose <em>Component ID</em> when this view will display data from other webforms and where the Component ID is identical.</br>Choose <em>Component Form Key</em> when this view will display data from other webforms with varying Component IDs.'),
);
}
/**
* {@inheritdoc}
*/
public function options_validate(&$form, &$form_state) {
parent::options_validate($form, $form_state);
_webform_views_options_validate($form, $form_state);
}
/**
* {@inheritdoc}
*/
public function options_submit(&$form, &$form_state) {
parent::options_submit($form, $form_state);
_webform_views_options_submit($form, $form_state);
$options =& $form_state['values']['options'];
$options['webform_form_key'] = $options['webform_join_by_form_key'] == 'form_key' && ($node = node_load($options['webform_nid']))
? $node->webform['components'][$options['webform_cid']]['form_key']
: NULL;
// Drop PHP reference.
unset($options);
}
/**
* Called to implement a relationship in a query.
*
* It respects the given component ids, provided via options form.
*/
public function query() {
// When defining extra clauses, the 'table' key can be used to specify the
// alias of another table. If NULL is specified, then the field is not
// qualified with a table. Therefore, do NOT specify "'table' => NULL".
switch ($this->options['webform_join_by']) {
case 'form_key':
$form_key = $this->options['webform_form_key'];
$join_type = $this->options['required'] ? 'INNER' : 'LEFT';
$this->ensure_my_table();
$join = new views_join();
$join->construct(
// The table to be joined.
'webform_component',
// The left table (i.e. this table, webform_submission).
$this->table,
// The left field (i.e. the webform node id).
'nid',
// The field (i.e. webform_components.nid).
'nid',
// Extra array of additional conditions.
array(
array(
// Extra join of form_key.
'field' => 'form_key',
// ... = $form_key (operator '=' is default)
'value' => $form_key,
),
),
// Join type is the same as this relationship's join type.
$join_type);
$alias = $this->query->add_relationship(
// Requested alias for new reliationship.
'webform_component_' . $form_key,
// Addition join to be added to this relatinship.
$join,
// Base table (i.e. drivingevals_submission)
$this->table_alias,
// Add the view to this relationship.
$this->relationship);
// The actual alias for this relationship's join is not known yet. Becasue
// of name conflicts, it may have a number appended to the end. %alias is
// substitued when the query is build with the actual alias name.
$this->definition['extra'][] = "%alias.cid = {$alias}.cid";
break;
case 'nid_cid':
$this->definition['extra'][] = array(
'field' => "nid",
'value' => $this->options['webform_nid'],
);
// FALL THROUGH.
case 'cid':
$this->definition['extra'][] = array(
'field' => "cid",
'value' => $this->options['webform_cid'],
);
break;
}
// The rest of building the join is performed by the parent.
parent::query();
}
}

View File

@@ -0,0 +1,118 @@
<?php
/**
* Contains the submission view row style plugin.
*
* Plugin which performs a webform_submission_render on the resulting object.
*
* Most of the code on this object is in the theme function.
*
* @ingroup views_row_plugins
*/
class webform_views_plugin_row_submission_view extends views_plugin_row {
/**
* Basic properties that let the row style follow relationships.
*
* @var string
*/
public $base_table = 'webform_submissions';
public $base_field = 'sid';
/**
* Stores the nodes loaded with pre_render.
*
* @var array
*/
private $submissions = array();
private $nodes = array();
/**
* {@inheritdoc}
*/
public function option_definition() {
$options = parent::option_definition();
$options['format'] = array('default' => 'html');
return $options;
}
/**
* {@inheritdoc}
*/
public function options_form(&$form, &$form_state) {
parent::options_form($form, $form_state);
$options = $this->options_form_summary_options();
$form['format'] = array(
'#type' => 'radios',
'#options' => $options,
'#title' => t('Display mode'),
'#default_value' => $this->options['format'],
);
}
/**
* Return the main options, which are shown in the summary title.
*/
public function options_form_summary_options() {
return array(
'html' => t('HTML'),
'text' => t('Plain text'),
);
}
/**
*
*/
public function summary_title() {
$options = $this->options_form_summary_options();
return check_plain($options[$this->options['format']]);
}
/**
*
*/
public function pre_render($values) {
$sids = array();
foreach ($values as $row) {
$sids[] = $row->{$this->field_alias};
}
module_load_include('inc', 'webform', 'includes/webform.submissions');
$this->submissions = $sids ? webform_get_submissions(array('sid' => $sids)) : array();
$nids = array();
foreach ($this->submissions as $sid => $submission) {
$nids[] = $submission->nid;
}
$nids = array_unique($nids);
$this->nodes = $nids ? node_load_multiple($nids) : array();
}
/**
*
*/
public function render($row) {
if (isset($this->submissions[$row->{$this->field_alias}])) {
$submission = $this->submissions[$row->{$this->field_alias}];
$node = $this->nodes[$submission->nid];
$submission->view = $this->view;
$format = $this->options['format'];
$build = webform_submission_render($node, $submission, NULL, $format);
// Add extra theme functions:
$themes = array();
foreach ($build['#theme'] as $hook) {
$themes = array_merge($themes, _views_theme_functions($hook, $this->view, $this->view->display[$this->view->current_display]));
}
$build['#theme'] = $themes;
// Render built submission, and if unsanitized plain text is used, make
// it safe for display.
$render = drupal_render($build);
return $format == 'html' ? $render : nl2br(check_plain($render));
}
}
}

View File

@@ -24,12 +24,13 @@
* @see webform_options_example()
* @see hook_webform_select_options_info_alter()
*
* @return
* @return array
* An array of callbacks that can be used for select list options. This array
* should be keyed by the "name" of the pre-defined list. The values should
* be an array with the following additional keys:
* - title: The translated title for this list.
* - options callback: The name of the function that will return the list.
* - options callback: The name of a function implementing
* callback_webform_options() that will return the list.
* - options arguments: Any additional arguments to send to the callback.
* - file: Optional. The file containing the options callback, relative to
* the module root.
@@ -49,7 +50,7 @@ function hook_webform_select_options_info() {
/**
* Alter the list of select list options provided by Webform and other modules.
*
* @see hook_webform_select_options_info().
* @see hook_webform_select_options_info()
*/
function hook_webform_select_options_info_alter(&$items) {
// Remove the days of the week options.
@@ -57,12 +58,9 @@ function hook_webform_select_options_info_alter(&$items) {
}
/**
* This is an example function to demonstrate a webform options callback.
* Define a list of options that Webform may use in a select component.
*
* This function returns a list of options that Webform may use in a select
* component. In order to be called, the function name
* ("webform_options_example" in this case), needs to be specified as a callback
* in hook_webform_select_options_info().
* Callback for hook_webform_select_options_info().
*
* @param $component
* The Webform component array for the select component being displayed.
@@ -71,17 +69,14 @@ function hook_webform_select_options_info_alter(&$items) {
* of key => value pairs. Select components support up to one level of
* nesting, but when results are displayed, the list needs to be returned
* without the nesting.
* @param $filter
* Boolean value indicating whether the included options should be passed
* through the _webform_filter_values() function for token replacement (only)
* needed if your list contains tokens).
* @param $arguments
* The "options arguments" specified in hook_webform_select_options_info().
* @return
*
* @return array
* An array of key => value pairs suitable for a select list's #options
* FormAPI property.
*/
function webform_options_example($component, $flat, $filter, $arguments) {
function callback_webform_options($component, $flat, $arguments) {
$options = array(
'one' => t('Pre-built option one'),
'two' => t('Pre-built option two'),
@@ -104,6 +99,31 @@ function hook_webform_submission_load(&$submissions) {
}
}
/**
* Respond to the creation of a new submission from form values.
*
* This hook is called when a user has completed a submission to initialize the
* submission object. After this object has its values populated, it will be
* saved by webform_submission_insert(). Note that this hook is only called for
* new submissions, not for submissions being edited. If responding to the
* saving of all submissions, it's recommended to use
* hook_webform_submission_presave().
*
* @param $submission
* The submission object that has been created.
* @param $node
* The Webform node for which this submission is being saved.
* @param $account
* The user account that is creating the submission.
* @param $form_state
* The contents of form state that is the basis for this submission.
*
* @see webform_submission_create()
*/
function hook_webform_submission_create_alter(&$submission, &$node, &$account, &$form_state) {
$submission->new_property = TRUE;
}
/**
* Modify a Webform submission, prior to saving it in the database.
*
@@ -115,7 +135,7 @@ function hook_webform_submission_load(&$submissions) {
function hook_webform_submission_presave($node, &$submission) {
// Update some component's value before it is saved.
$component_id = 4;
$submission->data[$component_id]['value'][0] = 'foo';
$submission->data[$component_id][0] = 'foo';
}
/**
@@ -193,8 +213,13 @@ function hook_webform_submission_delete($node, $submission) {
* The Webform node on which this submission was made.
* @param $submission
* The Webform submission on which the actions may be performed.
*
* @return array
* List of action.
*/
function hook_webform_submission_actions($node, $submission) {
$actions = array();
if (webform_results_access($node)) {
$actions['myaction'] = array(
'title' => t('Do my action'),
@@ -206,11 +231,32 @@ function hook_webform_submission_actions($node, $submission) {
return $actions;
}
/**
* Modify the draft to be presented for editing.
*
* When drafts are enabled for the webform, by default, a pre-existing draft is
* presented when the webform is displayed to that user. To allow multiple
* drafts, implement this alter function to set the $sid to NULL, or use your
* application's business logic to determine whether a new draft or which of
* he pre-existing drafts should be presented.
*
* @param int $sid
* The id of the most recent submission to be presented for editing. Change
* to a different draft's sid or set to NULL for a new draft.
* @param array $context
* Array of context with indices 'nid' and 'uid'.
*/
function hook_webform_draft_alter(&$sid, array $context) {
if ($_GET['newdraft']) {
$sid = NULL;
}
}
/**
* Alter the display of a Webform submission.
*
* This function applies to both e-mails sent by Webform and normal display of
* submissions when viewing through the adminsitrative interface.
* submissions when viewing through the administrative interface.
*
* @param $renderable
* The Webform submission in a renderable array, similar to FormAPI's
@@ -252,11 +298,11 @@ function hook_webform_component_load() {
* automatically add data to the component based on the component form. Using
* hook_form_alter() will be sufficient in most cases.
*
* @see hook_form_alter()
* @see webform_component_edit_form()
*
* @param $component
* The Webform component being saved.
*
* @see hook_form_alter()
* @see webform_component_edit_form()
*/
function hook_webform_component_presave(&$component) {
$component['extra']['new_option'] = 'foo';
@@ -301,11 +347,70 @@ function hook_webform_component_delete($component) {
->execute();
}
/**
* Alter the entire analysis before rendering to the page on the Analysis tab.
*
* This alter hook allows modification of the entire analysis of a node's
* Webform results. The resulting analysis is displayed on the Results ->
* Analysis tab on the Webform.
*
* @param array $analysis
* A Drupal renderable array, passed by reference, containing the entire
* contents of the analysis page. This typically will contain the following
* two major keys:
* - form: The form for configuring the shown analysis.
* - components: The list of analyses for each analysis-enabled component
* for the node. Each keyed by its component ID.
*/
function hook_webform_analysis_alter(array &$analysis) {
$node = $analysis['#node'];
// Add an additional piece of information to every component's analysis:
foreach (element_children($analysis['components']) as $cid) {
$component = $node->components[$cid];
$analysis['components'][$cid]['chart'] = array(
'#markup' => t('Chart for the @name component', array('@name' => $component['name'])),
);
}
}
/**
* Alter data when displaying an analysis on that component.
*
* This hook modifies the data from an individual component's analysis results.
* It can be used to add additional analysis, or to modify the existing results.
* If needing to alter the entire set of analyses rather than an individual
* component, hook_webform_analysis_alter() may be used instead.
*
* @param array $data
* An array containing the result of a components analysis hook, passed by
* reference. This is passed directly from a component's
* _webform_analysis_component() function. See that hook for more information
* on this value.
* @param object $node
* The node object that contains the component being analyzed.
* @param array $component
* The Webform component array whose analysis results are being displayed.
*
* @see _webform_analysis_component()
* @see hook_webform_analysis_alter()
*/
function hook_webform_analysis_component_data_alter(array &$data, $node, array $component) {
if ($component['type'] === 'textfield') {
// Do not display rows that contain a zero value.
foreach ($data as $row_number => $row_data) {
if ($row_data[1] === 0) {
unset($data[$row_number]);
}
}
}
}
/**
* Alter a Webform submission's header when exported.
*/
function hook_webform_csv_header_alter(&$header, $component) {
// Use the machine name for component headers, but only for the webform
// Use the machine name for component headers, but only for the webform
// with node 5 and components that are text fields.
if ($component['nid'] == 5 && $component['type'] == 'textfield') {
$header[2] = $component['form_key'];
@@ -326,7 +431,7 @@ function hook_webform_csv_data_alter(&$data, $component, $submission) {
/**
* Define components to Webform.
*
* @return
* @return array
* An array of components, keyed by machine name. Required properties are
* "label" and "description". The "features" array defines which capabilities
* the component has, such as being displayed in e-mails or csv downloads.
@@ -341,6 +446,7 @@ function hook_webform_csv_data_alter(&$data, $component, $submission) {
* - conditional
* - spam_analysis
* - group
* - private
*
* Note that most of these features do not indicate the default state, but
* determine if the component can have this property at all. Setting
@@ -371,8 +477,8 @@ function hook_webform_csv_data_alter(&$data, $component, $submission) {
* - csv_headers
* - csv_data
*
* See the sample component implementation for details on each one of these
* callbacks.
* See the sample component implementation for details on each one of these
* callbacks.
*
* @see webform_components()
*/
@@ -383,6 +489,9 @@ function hook_webform_component_info() {
'label' => t('Textfield'),
'description' => t('Basic textfield type.'),
'features' => array(
// This component includes an analysis callback. Defaults to TRUE.
'analysis' => TRUE,
// Add content to CSV downloads. Defaults to TRUE.
'csv' => TRUE,
@@ -424,13 +533,29 @@ function hook_webform_component_info() {
// (like a fieldset or tabs). Defaults to FALSE.
'group' => FALSE,
// If this component can be used for SPAM analysis, usually with Mollom.
// If this component can be used for SPAM analysis.
'spam_analysis' => FALSE,
// If this component saves a file that can be used as an e-mail
// attachment. Defaults to FALSE.
'attachment' => FALSE,
// If this component reflects a time range and should use labels such as
// "Before" and "After" when exposed as filters in Views module.
'views_range' => FALSE,
// Set this to FALSE if this component cannot be used as a private
// component. If this is not FALSE, in your implementation of
// _webform_defaults_COMPONENT(), set ['extra']['private'] property to
// TRUE or FALSE.
'private' => FALSE,
),
// Specify the conditional behaviour of this component.
// Examples are 'string', 'date', 'time', 'numeric', 'select'.
// Defaults to 'string'.
'conditional_type' => 'string',
'file' => 'components/textfield.inc',
);
@@ -498,7 +623,8 @@ function hook_webform_component_defaults_alter(&$defaults, $type) {
* - "list"
* @param $account
* A user account object.
* @return
*
* @return bool
* TRUE if the current user has access to submission,
* or FALSE otherwise.
*/
@@ -506,16 +632,15 @@ function hook_webform_submission_access($node, $submission, $op = 'view', $accou
switch ($op) {
case 'view':
return TRUE;
break;
case 'edit':
return FALSE;
break;
case 'delete':
return TRUE;
break;
case 'list':
return TRUE;
break;
}
}
@@ -524,15 +649,18 @@ function hook_webform_submission_access($node, $submission, $op = 'view', $accou
*
* Note in addition to the view access to the results granted here, the $account
* must also have view access to the Webform node in order to see results.
*
* @see webform_results_access().
* Access via this hook is in addition (adds permission) to the standard
* webform access.
*
* @param $node
* The Webform node to check access on.
* @param $account
* The user account to check access on.
* @return
*
* @return bool
* TRUE or FALSE if the user can access the webform results.
*
* @see webform_results_access()
*/
function hook_webform_results_access($node, $account) {
// Let editors view results of unpublished webforms.
@@ -550,19 +678,57 @@ function hook_webform_results_access($node, $account) {
* Access via this hook is in addition (adds permission) to the standard
* webform access (delete all webform submissions).
*
* @see webform_results_clear_access().
*
* @param $node object
* @param object $node
* The Webform node to check access on.
* @param $account object
* @param object $account
* The user account to check access on.
* @return boolean
*
* @return bool
* TRUE or FALSE if the user can access the webform results.
*
* @see webform_results_clear_access()
*/
function hook_webform_results_clear_access($node, $account) {
return user_access('my additional access', $account);
}
/**
* Overrides the node_access and user_access permissions.
*
* Overrides the node_access and user_access permission to access and edit
* webform components, e-mails, conditions, and form settings.
*
* Return NULL to defer to other modules. If all implementations defer, then
* access to the node's EDIT tab plus 'edit webform components' permission
* determines access. To grant access, return TRUE; to deny access, return
* FALSE. If more than one implementation return TRUE/FALSE, all must be TRUE
* to grant access.
*
* In this way, access to the EDIT tab of the node may be decoupled from
* access to the WEBFORM tab. When returning TRUE, consider all aspects of
* access as this will be the only test. For example, 'return TRUE;' would grant
* annonymous access to creating webform components, which seldom be desired.
*
* @param object $node
* The Webform node to check access on.
* @param object $account
* The user account to check access on.
*
* @return bool|null
* TRUE or FALSE if the user can access the webform results, or NULL if
* access should be deferred to other implementations of this hook or
* node_access('update') plus user_access('edit webform components').
*
* @see webform_node_update_access()
*/
function hook_webform_update_access($node, $account) {
// Allow anyone who can see webform_editable_by_user nodes and who has
// 'my webform component edit access' permission to see, edit, and delete the
// webform components, e-mails, conditionals, and form settings.
if ($node->type == 'webform_editable_by_user') {
return node_access('view', $node, $account) && user_access('my webform component edit access', $account);
}
}
/**
* Return an array of files associated with the component.
@@ -574,19 +740,21 @@ function hook_webform_results_clear_access($node, $account) {
* @param $value
* An array of information containing the submission result, directly
* correlating to the webform_submitted_data database schema.
* @return
*
* @return array
* An array of files, each file is an array with following keys:
* - filepath: The relative path to the file.
* - filename: The name of the file including the extension.
* - filemime: The mimetype of the file.
* This will result in an array looking something like this:
* @code
*
* @code
* array[0] => array(
* 'filepath' => '/sites/default/files/attachment.txt',
* 'filename' => 'attachment.txt',
* 'filemime' => 'text/plain',
* );
* @endcode
* @endcode
*/
function _webform_attachments_component($component, $value) {
$files = array();
@@ -594,6 +762,64 @@ function _webform_attachments_component($component, $value) {
return $files;
}
/**
* Alter default settings for a newly created webform node.
*
* @param array $defaults
* Default settings for a newly created webform node as defined by
* webform_node_defaults().
*
* @see webform_node_defaults()
*/
function hook_webform_node_defaults_alter(array &$defaults) {
$defaults['allow_draft'] = '1';
}
/**
* Add additional fields to submission data downloads.
*
* @return array
* Keys and titles for default submission information.
*
* @see hook_webform_results_download_submission_information_data()
*/
function hook_webform_results_download_submission_information_info() {
return array(
'field_key_1' => t('Field Title 1'),
'field_key_2' => t('Field Title 2'),
);
}
/**
* Return values for submission data download fields.
*
* @param $token
* The name of the token being replaced.
* @param $submission
* The data for an individual submission from webform_get_submissions().
* @param array $options
* A list of options that define the output format. These are generally passed
* through from the GUI interface.
* @param $serial_start
* The starting position for the Serial column in the output.
* @param $row_count
* The number of the row being generated.
*
* @return string
* Value for requested submission information field.
*
* @see hook_webform_results_download_submission_information_info()
*/
function hook_webform_results_download_submission_information_data($token, $submission, array $options, $serial_start, $row_count) {
switch ($token) {
case 'field_key_1':
return 'Field Value 1';
case 'field_key_2':
return 'Field Value 2';
}
}
/**
* @}
*/
@@ -610,14 +836,14 @@ function _webform_attachments_component($component, $value) {
/**
* Specify the default properties of a component.
*
* @return
* @return array
* An array defining the default structure of a component.
*/
function _webform_defaults_component() {
return array(
'name' => '',
'form_key' => NULL,
'mandatory' => 0,
'required' => 0,
'pid' => 0,
'weight' => 0,
'extra' => array(
@@ -626,6 +852,9 @@ function _webform_defaults_component() {
'optrand' => 0,
'qrand' => 0,
'description' => '',
'description_above' => FALSE,
'private' => FALSE,
'analysis' => TRUE,
),
);
}
@@ -639,16 +868,19 @@ function _webform_defaults_component() {
* every component type and are not necessary to specify here (although they
* may be overridden if desired).
*
* @param $component
* @param array $component
* A Webform component array.
* @return
* @param array $form
* The form array.
* @param array $form_state
* The form state array.
*
* @return array
* An array of form items to be displayed on the edit component page
*/
function _webform_edit_component($component) {
$form = array();
function _webform_edit_component(array $component, array &$form, array &$form_state) {
// Disabling the description if not wanted.
$form['description'] = array();
$form['description']['#access'] = FALSE;
// Most options are stored in the "extra" array, which stores any settings
// unique to a particular component type.
@@ -656,7 +888,7 @@ function _webform_edit_component($component) {
'#type' => 'textarea',
'#title' => t('Options'),
'#default_value' => $component['extra']['options'],
'#description' => t('Key-value pairs may be entered separated by pipes. i.e. safe_key|Some readable option') . theme('webform_token_help'),
'#description' => t('Key-value pairs may be entered separated by pipes. i.e. safe_key|Some readable option') . ' ' . theme('webform_token_help'),
'#cols' => 60,
'#rows' => 5,
'#weight' => -3,
@@ -679,19 +911,25 @@ function _webform_edit_component($component) {
* Whether or not to filter the contents of descriptions and values when
* rendering the component. Values need to be unfiltered to be editable by
* Form Builder.
* @param $submission
* The submission from which this component is being rendered. Usually not
* needed. Used by _webform_render_date() to validate using the submission's
* completion date.
*
* @return array
* $form_item
*
* @see _webform_client_form_add_component()
*/
function _webform_render_component($component, $value = NULL, $filter = TRUE) {
function _webform_render_component($component, $value = NULL, $filter = TRUE, $submission = NULL) {
$form_item = array(
'#type' => 'textfield',
'#title' => $filter ? _webform_filter_xss($component['name']) : $component['name'],
'#required' => $component['mandatory'],
'#title' => $filter ? webform_filter_xss($component['name']) : $component['name'],
'#required' => $component['required'],
'#weight' => $component['weight'],
'#description' => $filter ? _webform_filter_descriptions($component['extra']['description']) : $component['extra']['description'],
'#default_value' => $filter ? _webform_filter_values($component['value']) : $component['value'],
'#prefix' => '<div class="webform-component-textfield" id="webform-component-' . $component['form_key'] . '">',
'#suffix' => '</div>',
'#description' => $filter ? webform_filter_descriptions($component['extra']['description']) : $component['extra']['description'],
'#default_value' => $filter ? webform_replace_tokens($component['value']) : $component['value'],
'#theme_wrappers' => array('webform_element'),
);
if (isset($value)) {
@@ -701,6 +939,23 @@ function _webform_render_component($component, $value = NULL, $filter = TRUE) {
return $form_item;
}
/**
* Allow modules to modify a webform component that will be rendered in a form.
*
* @param array $element
* The display element as returned by _webform_render_component().
* @param array $component
* A Webform component array.
*
* @see _webform_render_component()
*/
function hook_webform_component_render_alter(array &$element, array &$component) {
if ($component['cid'] == 10) {
$element['#title'] = 'My custom title';
$element['#default_value'] = 42;
}
}
/**
* Display the result of a submission for a component.
*
@@ -716,7 +971,10 @@ function _webform_render_component($component, $value = NULL, $filter = TRUE) {
* Either 'html' or 'text'. Defines the format that the content should be
* returned as. Make sure that returned content is run through check_plain()
* or other filtering functions when returning HTML.
* @return
* @param $submission
* The submission. Used to generate tokens.
*
* @return array
* A renderable element containing at the very least these properties:
* - #title
* - #weight
@@ -727,7 +985,7 @@ function _webform_render_component($component, $value = NULL, $filter = TRUE) {
* which will properly format the label and content for use within an e-mail
* (such as wrapping the text) or as HTML (ensuring consistent output).
*/
function _webform_display_component($component, $value, $format = 'html') {
function _webform_display_component($component, $value, $format = 'html', $submission = array()) {
return array(
'#title' => $component['name'],
'#weight' => $component['weight'],
@@ -742,6 +1000,44 @@ function _webform_display_component($component, $value, $format = 'html') {
);
}
/**
* Allow modules to modify a "display only" webform component.
*
* @param array $element
* The display element as returned by _webform_display_component().
* @param array $component
* A Webform component array.
*
* @see _webform_display_component()
*/
function hook_webform_component_display_alter(array &$element, array &$component) {
if ($component['cid'] == 10) {
$element['#title'] = 'My custom title';
$element['#default_value'] = 42;
}
}
/**
* Performs the conditional action set on an implemented component.
*
* Setting the form element allows form validation functions to see the value
* that webform has set for the given component.
*
* @param array $component
* The webform component array whose value is being set for the currently-
* edited submission.
* @param array $element
* The form element currently being set.
* @param array $form_state
* The form's state.
* @param string $value
* The value to be set, as defined in the conditional action.
*/
function _webform_action_set_component(array $component, array &$element, array &$form_state, $value) {
$element['#value'] = $value;
form_set_value($element, $value, $form_state);
}
/**
* A hook for changing the input values before saving to the database.
*
@@ -758,15 +1054,15 @@ function _webform_display_component($component, $value, $format = 'html') {
* A Webform component array.
* @param $value
* The POST data associated with the user input.
* @return
*
* @return array
* An array of values to be saved into the database. Note that this should be
* a numerically keyed array.
*/
function _webform_submit_component($component, $value) {
// Clean up a phone number into 123-456-7890 format.
if ($component['extra']['phone_number']) {
$matches = array();
$number = preg_replace('[^0-9]', $value[0]);
$number = preg_replace('/[^0-9]/', '', $value[0]);
if (strlen($number) == 7) {
$number = substr($number, 0, 3) . '-' . substr($number, 3, 4);
}
@@ -846,11 +1142,31 @@ function _webform_theme_component() {
* Boolean flag determining if the details about a single component are being
* shown. May be used to provided detailed information about a single
* component's analysis, such as showing "Other" options within a select list.
* @return
* An array of data rows, each containing a statistic for this component's
* submissions.
* @param $join
* An optional SelectQuery object to be used to join with the submissions
* table to restrict the submissions being analyzed.
*
* @return array
* An array containing one or more of the following keys:
* - table_rows: If this component has numeric data that can be represented in
* a grid, return the values here. This array assumes a 2-dimensional
* structure, with the first value being a label and subsequent values
* containing a decimal or integer.
* - table_header: If this component has more than a single set of values,
* include a table header so each column can be labeled.
* - other_data: If your component has non-numeric data to include, such as
* a description or link, include that in the other_data array. Each item
* may be a string or an array of values that matches the number of columns
* in the table_header property.
* At the very least, either table_rows or other_data should be provided.
* Note that if you want your component's analysis to be available by default
* without the user specifically enabling it, you must set
* $component['extra']['analysis'] = TRUE in your
* _webform_defaults_component() callback.
*
* @see _webform_defaults_component()
*/
function _webform_analysis_component($component, $sids = array(), $single = FALSE) {
function _webform_analysis_component($component, $sids = array(), $single = FALSE, $join = NULL) {
// Generate the list of options and questions.
$options = _webform_select_options_from_text($component['extra']['options'], TRUE);
$questions = _webform_select_options_from_text($component['extra']['questions'], TRUE);
@@ -869,6 +1185,10 @@ function _webform_analysis_component($component, $sids = array(), $single = FALS
$query->condition('sid', $sids, 'IN');
}
if ($join) {
$query->innerJoin($join, 'ws2_', 'wsd.sid = ws2_.sid');
}
$result = $query->execute();
$counts = array();
foreach ($result as $data) {
@@ -892,9 +1212,15 @@ function _webform_analysis_component($component, $sids = array(), $single = FALS
}
$rows[] = $row;
}
$output = theme('table', array('header' => $header, 'rows' => $rows, 'attributes' => array('class' => array('webform-grid'))));
return array(array(array('data' => $output, 'colspan' => 2)));
$other = array();
$other[] = l(t('More information'), 'node/' . $component['nid'] . '/webform-results/analysis/' . $component['cid']);
return array(
'table_header' => $header,
'table_rows' => $rows,
'other_data' => $other,
);
}
/**
@@ -908,7 +1234,8 @@ function _webform_analysis_component($component, $sids = array(), $single = FALS
* @param $value
* An array of information containing the submission result, directly
* correlating to the webform_submitted_data database schema.
* @return
*
* @return string
* Textual output formatted for human reading.
*/
function _webform_table_component($component, $value) {
@@ -938,14 +1265,15 @@ function _webform_table_component($component, $value) {
* A Webform component array.
* @param $export_options
* An array of options that may configure export of this field.
* @return
*
* @return array
* An array of data to be displayed in the first three rows of a CSV file, not
* including either prefixed or trailing commas.
*/
function _webform_csv_headers_component($component, $export_options) {
$header = array();
$header[0] = array('');
$header[1] = array($component['name']);
$header[1] = array($export_options['header_keys'] ? $component['form_key'] : $component['name']);
$items = _webform_component_options($component['extra']['questions']);
$count = 0;
foreach ($items as $key => $item) {
@@ -975,7 +1303,8 @@ function _webform_csv_headers_component($component, $export_options) {
* @param $value
* An array of information containing the submission result, directly
* correlating to the webform_submitted_data database schema.
* @return
*
* @return array
* An array of items to be added to the CSV file. Each value within the array
* will be another column within the file. This function is called once for
* every row of data.
@@ -989,6 +1318,210 @@ function _webform_csv_data_component($component, $export_options, $value) {
return $return;
}
/**
* Fix the view field(s) that are automatically generated for number components.
*
* Provides each component the opportunity to adjust how this component is
* displayed in a view as a field in a view table. For example, a component may
* modify how it responds to click-sorting. Or it may add additional fields,
* such as a grid component having a column for each question.
*
* @param array $component
* A Webform component array.
* @param array $fields
* An array of field-definition arrays. Will be passed one field definition,
* which may be modified. Additional fields may be added to the array.
*
* @return array
* The modified $fields array.
*/
function _webform_view_field_component(array $component, array $fields) {
foreach ($fields as &$field) {
$field['webform_datatype'] = 'number';
}
return $fields;
}
/**
* Modify the how a view was expanded to show all the components.
*
* This alter function is only called when the view is actually modified. It
* provides modules an opportunity to alter the changes that webform made to
* the view.
*
* This hook is called from webform_views_pre_view. If another module also
* changes views by implementing this same views hook, the relative order of
* execution of the two implementations will depend upon the module weights of
* the two modules. Using hook_webform_view_alter instead guarantees an
* opportunity to modify the view AFTER webform.
*
* @param object $view
* The view object.
* @param string $display_id
* The display_id that was expanded by webform.
* @param array $args
* The arguments that were passed to the view.
*/
function hook_webform_view_alter($view, $display_id, array $args) {
// Don't show component with cid == 4.
$fields = $view->get_items('field', $display_id);
foreach ($fields as $id => $field) {
if (isset($field['webform_cid']) && $field['webform_cid'] == 4) {
unset($fields[$id]);
}
}
$view->display[$display_id]->handler->set_option('fields', $fields);
}
/**
* Modify the list of mail systems that are capable of sending HTML email.
*
* @param array &$systems
* An array of mail system class names.
*/
function hook_webform_html_capable_mail_systems_alter(array &$systems) {
if (module_exists('my_module')) {
$systems[] = 'MyModuleMailSystem';
}
}
/**
* Define a list of webform exporters.
*
* @return array
* A list of the available exporters provided by the module.
*
* @see webform_webform_exporters()
*/
function hook_webform_exporters() {
$exporters = array(
'webform_exporter_custom' => array(
'title' => t('Webform exporter name'),
'description' => t('The description for this exporter.'),
'handler' => 'webform_exporter_custom',
'file' => drupal_get_path('module', 'yourmodule') . '/includes/webform_exporter_custom.inc',
'weight' => 10,
),
);
return $exporters;
}
/**
* Modify the list of webform exporters definitions.
*
* @param array &$exporters
* A list of all available webform exporters.
*/
function hook_webform_exporters_alter(array &$exporters) {
$exporters['excel']['handler'] = 'customized_excel_exporter';
$exporters['excel']['file'] = drupal_get_path('module', 'yourmodule') . '/includes/customized_excel_exporter.inc';
}
/**
* Declare conditional types and their operators.
*
* Each conditional type defined here may then be referenced in
* hook_webform_component_info(). For each type this hook also declares a set of
* operators that may be applied to a component of this conditional type in
* conditionals.
*
* @return array
* A 2-dimensional array of operator configurations. The configurations are
* keyed first by their conditional type then by operator key. Each operator
* declaration is an array with the following keys:
* - label: Translated label for this operator that is shown in the UI.
* - comparison callback: A callback for server-side evaluation.
* - js comparison callback: A JavaScript callback for client-side evaluation.
* The callback will be looked for in the Drupal.webform object.
* - form callback (optional): A form callback that allows configuring
* additional parameters for this operator. Default:
* 'webform_conditional_operator_text'.
*
* @see hook_webform_component_info()
* @see callback_webform_conditional_comparision_operator()
* @see callback_webform_conditional_rule_value_form()
*/
function hook_webform_conditional_operator_info() {
$operators = array();
$operators['string']['not_equal'] = array(
'label' => t('is not'),
'comparison callback' => 'webform_conditional_operator_string_not_equal',
'js comparison callback' => 'conditionalOperatorStringNotEqual',
);
return $operators;
}
/**
* Alter the list of operators and conditional types.
*
* @param array $operators
* A data structure as described in hook_webform_conditional_operator_info().
*
* @see hook_webform_conditional_operator_info()
*/
function hook_webform_conditional_operators_alter(array &$operators) {
$operators['string']['not_equal']['label'] = t('not equal');
}
/**
* Evaluate the operator for a given set of values.
*
* This function will be called two times with potentially different kinds of
* values: Once in _webform_client_form_validate() before any of the validate
* handlers or the _webform_submit_COMPONENT() callback is called, and once in
* webform_client_form_pages() after those handlers have been called.
*
* @param array $input_values
* The values received from the browser.
* @param mixed $rule_value
* The value as configured in the form callback.
* @param array $component
* The component for which we are evaluating the operator.
*
* @return bool
* The operation result.
*/
function callback_webfom_conditional_comparison_operator(array $input_values, $rule_value, array $component) {
foreach ($input_values as $value) {
if (strcasecmp($value, $rule_value)) {
return TRUE;
}
}
return FALSE;
}
/**
* Define a form element that configures your operator.
*
* @param object $node
* The node for which the conditionals are being configured.
*
* @return string|string[]
* Either a single rendered form element or a rendered form element per
* component (keyed by cid). Make sure that none of the rendered strings
* contains any HTML IDs as the form element will be rendered multiple times.
* The JavaScript will take care of adding the appropriate name attributes.
*
* @see _webform_conditional_expand_value_forms()
*/
function callback_webform_conditional_rule_value_form($node) {
$forms = [];
foreach ($node->webform['components'] as $cid => $component) {
if (webform_component_property($component['type'], 'conditional_type') == 'newsletter') {
$element = [
'#type' => 'select',
'#options' => [
'yes' => t('Opt-in'),
'no' => t('No opt-in'),
],
];
$forms[$cid] = drupal_render($element);
}
}
return $forms;
}
/**
* @}
*/

View File

@@ -0,0 +1,225 @@
<?php
/**
* @file
* Functions relating to Drush integration.
*/
/**
* Implements hook_drush_command().
*/
function webform_drush_command() {
return array(
'webform-export' => array(
'description' => 'Exports webform data to a file.',
'arguments' => array(
'nid' => 'The node ID of the webform you want to export (required)',
),
'options' => array(
'file' => 'The file path to export to (defaults to print to stdout)',
'format' => 'The exporter format to use. Out-of-the-box this may be "delimited" or "excel".',
'delimiter' => 'Delimiter between columns (defaults to site-wide setting). This option may need to be wrapped in quotes. i.e. --delimter="\t".',
'components' => 'Comma-separated list of component IDs or form keys to include.' . "\n" .
'May also include "webform_serial", "webform_sid", "webform_time", "webform_complete_time", "webform_modified_time", "webform_draft", "webform_ip_address", "webform_uid", and "webform_username".',
'header-keys' => 'Integer -1 for none, 0 for label (default) or 1 for form key.',
'select-keys' => 'Integer 0 or 1 value. Set to 1 to print select list values by their form keys instead of labels.',
'select-format' => 'Set to "separate" (default) or "compact" to determine how select list values are exported.',
'range-type' => 'Range of submissions to export: "all", "new", "latest", "range" (by sid, default if start is supplied), "range-serial", or "range-date".',
'range-latest' => 'Integer specifying the latest X submissions will be downloaded. Used if "range-type" is "latest" or no other range options are provided.',
'range-start' => 'The submission ID, serial number, or start date at which to start exporting.',
'range-end' => 'The submission ID, serial number, or end date at which to end exporting.',
'completion-type' => 'Submissions to be included: "finished", "draft" or "all" (default).',
'batch-size' => 'The size of batches in rows (default 10000). If encountering out of memory errors, set this number lower to export fewer submissions per batch.',
),
'aliases' => array('wfx'),
),
'webform-clear' => array(
'description' => 'Clear a webform by deleting all its submissions.',
'arguments' => array(
'nid' => 'The node ID of the webform you want to clear (required)',
),
'options' => array(
'batch-size' => 'The size of batches in rows (default 10000). If encountering out of memory errors, set this number lower to export fewer submissions per batch.',
),
),
);
}
/**
* Exports a webform via drush.
*
* This is useful for large data dumps that would otherwise time out due to
* memory consumption.
*
* @param bool|int $nid
* Node ID of the webform that we want to export.
*
* @return false|null
* The value returned from drush_set_error() or NULL if that function is not
* called.
*/
function drush_webform_export($nid = FALSE) {
if (!$nid) {
return drush_set_error('The node ID of the webform you want to export is required.');
}
$node = node_load($nid);
if (!$node) {
return drush_set_error(dt('Node !nid was not found.', array('!nid' => $nid)));
}
module_load_include('inc', 'webform', 'includes/webform.submissions');
module_load_include('inc', 'webform', 'includes/webform.export');
module_load_include('inc', 'webform', 'includes/webform.components');
module_load_include('inc', 'webform', 'includes/webform.report');
// Pull in options from drush to override the defaults.
$format = drush_get_option('format', 'delimited');
$options = webform_results_download_default_options($node, $format);
foreach ($options as $option_name => $option_value) {
$options[$option_name] = drush_get_option(str_replace('_', '-', $option_name), $option_value);
}
$options['components'] = is_array($options['components']) ? $options['components'] : explode(',', $options['components']);
// Map form keys to cids.
$form_keys = array();
foreach ($node->webform['components'] as $cid => $component) {
$form_keys[$component['form_key']] = $cid;
}
foreach ($options['components'] as $key => &$component) {
if (isset($form_keys[$component])) {
$component = $form_keys[$component];
}
}
// Drop PHP reference.
unset($component);
// Get the range options.
unset($options['range']['range_type']);
foreach (drush_get_merged_prefixed_options('range-') as $option_name => $option_value) {
if ($option_name == 'type' && in_array($option_value, array('all', 'new', 'latest', 'range', 'range-serial', 'range-date'))) {
$options['range']['range_type'] = str_replace('-', '_', $option_value);
}
elseif (in_array($option_name, array('start', 'end', 'latest')) && is_numeric($option_value)) {
$options['range'][$option_name] = $option_value;
}
elseif (in_array($option_name, array('start', 'end')) && strtotime($option_value)) {
$options['range']['range_type'] = 'range_date';
$options['range'][$option_name . '_date'] = $option_value;
}
else {
return drush_set_error(dt('Unsupported range option or argument: !opt=!val',
array('!opt' => "range-$option_name", '!val' => $option_value)));
}
}
// Determine the range type based on provided input, if not explicitly set.
if (empty($options['range']['range_type'])) {
$options['range']['range_type'] = isset($options['range']['start'])
? 'range'
: (isset($options['range']['latest'])
? 'latest'
: 'all');
}
// Set defaults for any missing range arguments.
switch ($options['range']['range_type']) {
case 'latest':
if (empty($options['range']['latest'])) {
drush_log('Argument range-latest defaulted to 100.', 'ok');
$options['range']['latest'] = 100;
}
break;
case 'range':
case 'range_serial':
if (empty($options['range']['start'])) {
$options['range']['start'] = 1;
}
break;
case 'range_date':
if (empty($options['range']['start_date'])) {
$options['range']['start_date'] = "1/1/1970";
}
break;
}
// Get the preferred completion type.
$options['range']['completion_type'] = drush_get_option('completion-type', NULL);
if (isset($options['range']['completion_type']) && !in_array($options['range']['completion_type'], array('finished', 'draft', 'all'))) {
return drush_set_error('Unsupported completion-type. The available options are "finished", "draft", or "all".');
}
// Set the export options.
$options['range']['batch_size'] = drush_get_option('batch-size', 10000);
$options['file_name'] = drush_get_option('file', tempnam(variable_get('file_directory_temp', file_directory_temp()), 'webform_'));
$batch = webform_results_export_batch($node, $format, $options);
batch_set($batch);
drush_backend_batch_process();
// If no filename was specified, print the file and delete it.
if (drush_get_option('file', FALSE) === FALSE) {
// The @ makes it silent.
drush_print(file_get_contents($options['file_name']));
// Clean up, the @ makes it silent.
@unlink($options['file_name']);
}
}
/**
* Clears a webform via drush.
*
* This is useful for webforms with many submissions that would otherwise fail
* due to time out due or memory consumption.
*
* @param int $nid
* Node ID of the webform to clear.
*/
function drush_webform_clear($nid = FALSE) {
if (!$nid) {
return drush_set_error('The node ID of the webform to be cleared is required.');
}
$node = node_load($nid);
if (!$node) {
return drush_set_error(dt('Node !nid was not found.', array('!nid' => $nid)));
}
if (!drush_confirm(dt('Clear submissions from webform "@title"?', array('@title' => $node->title)))) {
return drush_set_error('webform-clear cancelled.');
}
// @code
// module_load_include('inc', 'webform', 'includes/webform.submissions');
// module_load_include('inc', 'webform', 'includes/webform.components');
// @endcode
module_load_include('inc', 'webform', 'includes/webform.report');
// Pull in option from drush to override the default.
$batch_size = drush_get_option('batch-size', 10000);
$count = 0;
while ($deleted = webform_results_clear($nid, $batch_size)) {
$count += $deleted;
}
// Alas, there is no drush version of format_plural, so use the ugly "(s)".
drush_log(dt('@count submission(s) in webform "@title" cleared.', array('@count' => $count, '@title' => $node->title)), 'ok');
}
/**
* Implements hook_drush_sql_sync_sanitize().
*/
function webform_drush_sql_sync_sanitize($source) {
// Fetch list of all table.
$all_tables = drush_sql_get_class()->listTables();
$tables_to_truncate = array('webform_submitted_data', 'webform_submissions');
$truncate_webform_tables_query = array();
foreach ($tables_to_truncate as $table) {
if (in_array($table, $all_tables, TRUE)) {
$truncate_webform_tables_query[] = 'TRUNCATE ' . $table . ';';
}
}
drush_sql_register_post_sync_op('webform_submitted_data',
dt('Delete all data submitted to webforms (depending on the site config, may contain sensitive data).'),
implode(' ', $truncate_webform_tables_query));
}

View File

@@ -1,32 +1,46 @@
; $Id: $
name = Webform
description = Enables the creation of forms and questionnaires.
core = 7.x
package = Webform
configure = admin/config/content/webform
php = 5.3
dependencies[] = ctools
dependencies[] = views
test_dependencies[] = token
; Files that contain classes:
files[] = includes/webform.export.inc
files[] = includes/webform.webformconditionals.inc
files[] = includes/exporters/webform_exporter_delimited.inc
files[] = includes/exporters/webform_exporter_excel_delimited.inc
files[] = includes/exporters/webform_exporter_excel_xlsx.inc
files[] = includes/exporters/webform_exporter.inc
files[] = views/webform_handler_area_result_pager.inc
files[] = views/webform_handler_field_form_body.inc
files[] = views/webform_handler_field_is_draft.inc
files[] = views/webform_handler_field_node_link_edit.inc
files[] = views/webform_handler_field_node_link_results.inc
files[] = views/webform_handler_field_submission_count.inc
files[] = views/webform_handler_field_submission_data.inc
files[] = views/webform_handler_field_submission_link.inc
files[] = views/webform_handler_field_webform_status.inc
files[] = views/webform_handler_filter_is_draft.inc
files[] = views/webform_handler_filter_submission_data.inc
files[] = views/webform_handler_filter_webform_status.inc
files[] = views/webform.views.inc
files[] = views/webform_handler_numeric_data.inc
files[] = views/webform_handler_relationship_submission_data.inc
files[] = views/webform_plugin_row_submission_view.inc
files[] = tests/components.test
files[] = tests/permissions.test
files[] = tests/submission.test
files[] = tests/webform.test
files[] = tests/WebformComponentsTestCase.test
files[] = tests/WebformConditionalsTestCase.test
files[] = tests/WebformGeneralTestCase.test
files[] = tests/WebformPermissionsTestCase.test
files[] = tests/WebformSubmissionTestCase.test
files[] = tests/WebformTestCase.test
; Information added by Drupal.org packaging script on 2017-02-09
version = "7.x-3.27"
; Information added by Drupal.org packaging script on 2019-01-07
version = "7.x-4.19"
core = "7.x"
project = "webform"
datestamp = "1486665496"
datestamp = "1546876989"

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,299 @@
<?php
/**
* @file
* Builds placeholder replacement tokens for webform-related data.
*/
/**
* Implements hook_token_info().
*/
function webform_token_info() {
// Webform submission tokens.
$info['types']['submission'] = array(
'name' => t('Submission'),
'description' => t('Tokens related to webform submissions.'),
'needs-data' => 'webform-submission',
);
$info['tokens']['submission']['serial'] = array(
'name' => t('Serial number'),
'description' => t('The serial number of this webform submission.'),
);
$info['tokens']['submission']['sid'] = array(
'name' => t('Submission ID'),
'description' => t('The unique indentifier for the webform submission.'),
);
$info['tokens']['submission']['access-token'] = array(
'name' => t('Access token'),
'description' => t('The security token used to gain access to this webform submission.'),
);
$info['tokens']['submission']['date'] = array(
'name' => t('Date submitted'),
'description' => t('The date the webform was first save as draft or completed.'),
'type' => 'date',
);
$info['tokens']['submission']['completed_date'] = array(
'name' => t('Date completed'),
'description' => t('The date the webform was first completed (not draft).'),
'type' => 'date',
);
$info['tokens']['submission']['modified_date'] = array(
'name' => t('Date modified'),
'description' => t('The date the webform was last saved (draft or completed).'),
'type' => 'date',
);
$info['tokens']['submission']['ip-address'] = array(
'name' => t('IP address'),
'description' => t('The IP address that was used when submitting the webform.'),
);
$info['tokens']['submission']['user'] = array(
'name' => t('Submitter'),
'description' => t('The user that submitted the webform result.'),
'type' => 'user',
);
$info['tokens']['submission']['url'] = array(
'name' => t('URL'),
'description' => t("Webform tokens related to the submission's URL."),
'type' => 'url',
);
$info['tokens']['submission']['edit-url'] = array(
'name' => t('Edit URL'),
'description' => t("Webform tokens related to the submission's Edit URL."),
'type' => 'url',
);
$info['tokens']['submission']['values'] = array(
'name' => t('Webform submission values'),
'description' => '<div>' . t('Webform tokens from submitted data. Replace the "?" with the "form key", including any parent form keys separated by colons. You can append:') . '</div><ul>' .
'<li>' . t('the question key for just that one question (grid components).') . '</li>' .
'<li>' . t('the option key for just that one option (grid and select components).') . '</li>' .
'<li>' . t('<code>@token</code> for the value without the label (the default).', array('@token' => ':nolabel')) . '</li>' .
'<li>' . t('<code>@token</code> for just the label.', array('@token' => ':label')) . '</li>' .
'<li>' . t('<code>@token</code> for both the label and value together.', array('@token' => ':withlabel')) . '</li>' .
'<li>' . t('<code>@token</code> for just the key in a key|label pair (grid and select components).', array('@token' => ':key')) . '</li></ul>',
'dynamic' => TRUE,
);
return $info;
}
/**
* Implements hook_tokens().
*/
function webform_tokens($type, $tokens, array $data = array(), array $options = array()) {
static $recursion_level = 0;
// Return early unless submission tokens are needed and there is a submission.
if ($type != 'submission' || empty($data['webform-submission']) || !webform_variable_get('webform_token_access')) {
return array();
}
// Generate Webform tokens.
$replacements = array();
// Prepare all the data that we will likely need more than once.
$submission = $data['webform-submission'];
$node = isset($data['node']) ? $data['node'] : node_load($submission->nid);
$email = isset($data['webform-email']) ? $data['webform-email'] : NULL;
$sanitize = !empty($options['sanitize']);
$format = $sanitize ? 'html' : 'text';
$url_options = isset($options['language']) ? $options['language'] : array('absolute' => TRUE);
$language_code = isset($options['language']) ? $options['language']->language : NULL;
$markup_components = array();
// Markup components may use tokens when displayed. Displaying the tokens
// requires rendering the components. Rendering a markup component can then
// cause infinite recursion. To prevent this, markup components are omitted
// from the rendered submission if recursion has been detected.
if ($recursion_level) {
$markup_components = array_keys(array_filter($node->webform['components'],
function ($component) {
return $component['type'] == 'markup';
}));
}
$recursion_level++;
// Replace individual tokens that have an exact replacement.
foreach ($tokens as $name => $original) {
switch ($name) {
case 'serial':
$replacements[$original] = $submission->serial ? $submission->serial : '';
break;
case 'sid':
$replacements[$original] = $submission->sid ? $submission->sid : '';
break;
case 'access-token':
$replacements[$original] = webform_get_submission_access_token($submission);
break;
case 'date':
$replacements[$original] = format_date($submission->submitted, webform_variable_get('webform_date_type'), '', NULL, $language_code);
break;
case 'completed_date':
if ($submission->completed) {
$replacements[$original] = format_date($submission->completed, webform_variable_get('webform_date_type'), '', NULL, $language_code);
}
break;
case 'modified_date':
$replacements[$original] = format_date($submission->modified, webform_variable_get('webform_date_type'), '', NULL, $language_code);
break;
case 'ip-address':
$replacements[$original] = $sanitize ? check_plain($submission->remote_addr) : $submission->remote_addr;
break;
case 'user':
$account = user_load($submission->uid);
$name = format_username($account);
$replacements[$original] = $sanitize ? check_plain($name) : $name;
break;
case 'url':
$replacements[$original] = $submission->sid ? url("node/{$node->nid}/submission/{$submission->sid}", $url_options) : '';
break;
case 'edit-url':
$replacements[$original] = $submission->sid ? url("node/{$node->nid}/submission/{$submission->sid}/edit", $url_options) : '';
break;
case 'values':
$excluded_components = isset($email['excluded_components']) ? $email['excluded_components'] : array();
$excluded_components = array_merge($excluded_components, $markup_components);
$submission_renderable = webform_submission_render($node, $submission, $email, $format, $excluded_components);
$replacements[$original] = drupal_render($submission_renderable);
break;
}
}
// Webform submission tokens for individual components.
if ($value_tokens = token_find_with_prefix($tokens, 'values')) {
// Get the full submission renderable without $excluded_components so that
// individually referenced values are available.
$submission_renderable = webform_submission_render($node, $submission, $email, $format, $markup_components);
$available_modifiers = array(
'label',
'withlabel',
'nolabel',
'key',
);
foreach ($node->webform['components'] as $cid => $component) {
// Build the list of parents for this component.
$parents = ($component['pid'] == 0) ? array($component['form_key']) : webform_component_parent_keys($node, $component);
$parent_token = implode(':', $parents);
foreach ($value_tokens as $name => $original) {
if (strpos($name, $parent_token) !== 0) {
// Token not found as a prefix or exact match for this component.
// Token loop continue.
continue;
}
// Drill down into the renderable to find the element.
$display_element = $submission_renderable;
foreach ($parents as $parent) {
if (!isset($display_element[$parent])) {
// Sometimes an element won't exist in the submission renderable
// due to conditional logic. If not found, skip that element.
// Token loop continue.
continue 2;
}
$display_element = $display_element[$parent];
}
// Individual tokens always have access granted even if they're
// not displayed when printing the whole renderable.
$display_element['#access'] = TRUE;
// For grid components, see if optional question key is present.
$matched_token = $parent_token;
if ($display_element['#webform_component']['type'] === 'grid') {
list($question_key) = explode(':', substr($name, strlen($matched_token) + 1));
if (strlen($question_key) && isset($display_element[$question_key]['#value'])) {
// Generate a faux select component for this grid question.
$select_component = _webform_defaults_select();
$select_component['type'] = 'select';
$select_component['nid'] = $display_element['#webform_component']['nid'];
$select_component['name'] = $display_element['#grid_questions'][$question_key];
$select_component['extra']['items'] = $display_element['#webform_component']['extra']['options'];
$display_element = _webform_display_select($select_component, $display_element[$question_key]['#value'], $format);
$display_element['#webform_component'] = $select_component;
$matched_token .= ':' . $question_key;
}
}
// For select components, see if the optional option key is present.
if ($display_element['#webform_component']['type'] === 'select') {
list($option_key) = explode(':', substr($name, strlen($matched_token) + 1));
if (strlen($option_key) && strpos("\n" . $display_element['#webform_component']['extra']['items'], "\n" . $option_key . '|') !== FALSE) {
// Return only this specified option and no other values.
$display_element['#value'] = array_intersect($display_element['#value'], array($option_key));
$matched_token .= ':' . $option_key;
}
}
// Assume no modifier (implied 'nolabel').
$modifier = NULL;
if (strcmp($name, $matched_token) !== 0) {
// Check if this matches the key plus a modifier.
$modifier = substr($name, strrpos($name, ':') + 1);
// @todo: Allow components to provide additional modifiers per
// type, i.e. key, day, hour, minute, etc.
if (strcmp($name, $matched_token . ':' . $modifier) !== 0 || !in_array($modifier, $available_modifiers)) {
// No match.
// Token loop continue.
continue;
}
}
if ($modifier === 'label') {
$replacements[$original] = webform_filter_xss($display_element['#title']);
}
elseif ($modifier === 'key' && $display_element['#webform_component']['type'] === 'select') {
$values = array();
foreach ($display_element['#value'] as $value) {
$values[] = webform_filter_xss($value);
}
$replacements[$original] = implode(' ', $values);
}
else {
// Remove theme wrappers for the nolabel modifier.
if ($modifier === 'nolabel' || empty($modifier)) {
$display_element['#theme_wrappers'] = array();
}
$replacements[$original] = render($display_element);
}
// Continue processing tokens in case another modifier is used.
}
}
}
// Chained token relationships.
if ($date_tokens = token_find_with_prefix($tokens, 'date')) {
$replacements += token_generate('date', $date_tokens, array('date' => $submission->submitted), $options);
}
if ($submission->completed && ($date_tokens = token_find_with_prefix($tokens, 'completed_date'))) {
$replacements += token_generate('date', $date_tokens, array('date' => $submission->completed), $options);
}
if ($date_tokens = token_find_with_prefix($tokens, 'modified_date')) {
$replacements += token_generate('date', $date_tokens, array('date' => $submission->modified), $options);
}
if (($user_tokens = token_find_with_prefix($tokens, 'user')) && $account = user_load($submission->uid)) {
$replacements += token_generate('user', $user_tokens, array('user' => $account), $options);
}
if ($submission->sid) {
if ($url_tokens = token_find_with_prefix($tokens, 'url')) {
$replacements += token_generate('url', $url_tokens, array('path' => "node/{$node->nid}/submission/{$submission->sid}"), $options);
}
if ($url_tokens = token_find_with_prefix($tokens, 'edit-url')) {
$replacements += token_generate('url', $url_tokens, array('path' => "node/{$node->nid}/submission/{$submission->sid}/edit"), $options);
}
}
$recursion_level--;
return $replacements;
}

View File

@@ -0,0 +1,59 @@
<?php
/**
* @file
* Webform localizations for grid component.
* Translates the analysis component properties that are translatable.
*
* These are found in under 'translated_strings' in the 'extra' array of the
* component, which is build when the component is inserted / updated, or
* when all webform strings are updated from
* admin/config/regional/translate/i18n_string.
*/
/**
* Implements _webform_localization_analysis_data_component().
*
* @param array $data
* The data array of component results.
* @param array $node
* The node
* @param array $component
* The component.
*
* @return array
* Translated data array of component results.
*/
function _webform_localization_analysis_data_grid($data, $node, $component) {
if (!isset($component['extra']['translated_strings']) || !is_array($component['extra']['translated_strings'])) {
return $data;
}
$options_key_lookup = _webform_localization_string_to_key($component['extra']['options']);
$questions_key_lookup = _webform_localization_string_to_key($component['extra']['questions']);
foreach ($component['extra']['translated_strings'] as $name) {
$name_list = explode(':', $name);
// Translate options / questions.
list (, $key) = explode('-', $name_list[3]);
if (strpos($name_list[3], 'grid_options') && $name_list[3] !== '#title') {
if (isset($options_key_lookup[$key])) {
foreach ($data['table_header'] as $index => $row) {
if ($row == $options_key_lookup[$key]) {
$data['table_header'][$index] = i18n_string($name, $row);
}
}
}
}
if (strpos($name_list[3], 'grid_questions') && $name_list[3] !== '#title') {
if (isset($questions_key_lookup[$key])) {
foreach ($data['table_rows'] as $index => $row) {
if (trim($row[0]) == trim($questions_key_lookup[$key])) {
$data['table_rows'][$index][0] = i18n_string($name, $row[0]);
}
}
}
}
}
return $data;
}

View File

@@ -0,0 +1,102 @@
<?php
/**
* @file
* Webform localizations for select component.
*/
/**
* Translate a single option from component.
*
* @param array $component
* The select component
* @param string $option
* Untranslated option string.
*
* @return string
* The translated option string, if found.
*/
function webform_localization_translate_select_option($component, $option) {
// Find the source for data value and translate it.
$item_key_lookup = _webform_localization_string_to_key($component['extra']['items']);
foreach ($component['extra']['translated_strings'] as $name) {
$name_list = explode(':', $name);
// Translate options.
if (strpos($name_list[3], '-') !== FALSE) {
list (, $key) = explode('-', $name_list[3]);
if (isset($item_key_lookup[$key]) && $option == $item_key_lookup[$key]) {
return i18n_string($name, $option);
}
}
}
return $option;
}
/**
* Implements _webform_localization_csv_header_component().
*/
function _webform_localization_csv_header_select($header, $component) {
if (!isset($component['extra']['translated_strings']) || !is_array($component['extra']['translated_strings'])) {
return $header;
}
// Each component has own methods and tricks to add different items to header
// rows. Attempt to translate whatever we can.
foreach ($component['extra']['translated_strings'] as $name) {
$name_list = explode(':', $name);
// Translate header from #title property, this is rather common scenario.
if ($name_list[3] == '#title' && $component['name'] == $header[2][0]) {
$header[2] = i18n_string($name, $component['name']);
break;
}
// Title could be found from position [1][0] and in this case the select
// options are on row 2.
if ($name_list[3] == '#title' && $component['name'] == $header[1][0]) {
$header[1] = i18n_string($name, $component['name']);
foreach ($header[2] as $i => $option) {
$header[2][$i] = webform_localization_translate_select_option($component, $option);
}
break;
}
}
return $header;
}
/**
* Implements _webform_localization_csv_data_component().
*/
function _webform_localization_csv_data_select($data, $component, $submission) {
// If data is an array then answers are being marked as X:es and there is no
// need to translate these.
if (is_array($data)) {
return $data;
}
if (!isset($component['extra']['translated_strings']) || !is_array($component['extra']['translated_strings'])) {
return $data;
}
return webform_localization_translate_select_option($component, $data);
}
/**
* Implements _webform_localization_analysis_data_component().
*/
function _webform_localization_analysis_data_select($data, $node, $component) {
if (!isset($component['extra']['translated_strings']) || !is_array($component['extra']['translated_strings'])) {
return $data;
}
$item_key_lookup = _webform_localization_string_to_key($component['extra']['items']);
foreach ($component['extra']['translated_strings'] as $name) {
$name_list = explode(':', $name);
// Translate options.
if (strpos($name_list[3], '-') !== FALSE) {
list (, $key) = explode('-', $name_list[3]);
if (isset($item_key_lookup[$key])) {
foreach ($data['table_rows'] as $index => $row) {
if ($row[0] == $item_key_lookup[$key]) {
$data['table_rows'][$index][0] = i18n_string($name, $row[0]);
}
}
}
}
}
return $data;
}

View File

@@ -18,38 +18,42 @@
*
* @staticvar array $component_translations
* An array of webform components for each tnid.
*
* @param array $component
* A webform component array.
* @return
* An array of webform components that match a tnid.
*
* @return array
* An array of webform components that match a tnid.
*/
function webform_localization_component_get_translations($component) {
static $component_translations = array();
$node = node_load($component['nid']);
$translations = translation_node_get_translations($node->tnid);
$component_translations[$node->tnid] = array();
if (!isset($component_translations[$node->tnid])) {
if (!empty($translations) && !isset($component_translations[$node->tnid])) {
$nid_list = array();
foreach ($translations as $trans_node) {
$nid_list[] = $trans_node->nid;
}
// Load components for each translated node.
$components = db_select('webform_component')
->fields('webform_component')
->condition('nid', $nid_list, 'IN')
->condition('cid', $component['cid'], '=')
->orderBy('nid')
->execute()
->fetchAllAssoc('nid', PDO::FETCH_ASSOC);
// Cleanup on each component.
foreach ($components as $cid => $c) {
$components[$cid]['nid'] = $c['nid'];
$components[$cid]['extra'] = unserialize($c['extra']);
webform_component_defaults($components[$cid]);
if (!empty($nid_list)) {
$components = db_select('webform_component')
->fields('webform_component')
->condition('nid', $nid_list, 'IN')
->condition('cid', $component['cid'], '=')
->orderBy('nid')
->execute()
->fetchAllAssoc('nid', PDO::FETCH_ASSOC);
// Cleanup on each component.
foreach ($components as $cid => $c) {
$components[$cid]['nid'] = $c['nid'];
$components[$cid]['extra'] = unserialize($c['extra']);
webform_component_defaults($components[$cid]);
}
$component_translations[$node->tnid] = $components;
}
$component_translations[$node->tnid] = $components;
}
return $component_translations[$node->tnid];
@@ -58,17 +62,17 @@ function webform_localization_component_get_translations($component) {
/**
* Synchronize the changed component with it's translations versions.
*
* @param $component
* @param array $component
* A webform component that have been modified.
* @param $translations
* @param array $translations
* An Array of the translated webform components to sync with.
*/
function webform_localization_component_sync($component, &$translations) {
/**
* Get properties to sync
* $sync_properties['standar_values'] = array('mandatory', 'weight', 'pid');
* $sync_properties['extra_values'] = array('options', 'private');
*/
// Get properties to sync.
// $sync_properties['standar_values'] = array('mandatory', 'weight', 'pid');
// $sync_properties['extra_values'] = array('options', 'private');
$sync_properties = webform_localization_synchronizable_properties($component);
foreach ($translations as $component_key => $translation) {
foreach ($sync_properties['standar_values'] as $sync_key) {
@@ -85,13 +89,14 @@ function webform_localization_component_sync($component, &$translations) {
}
/**
* Get synchronizable properties for a webform component
* Get synchronizable properties for a webform component.
*
* @param array $component
* A webform component.
* @param boolean $clear_cache
* @param bool $clear_cache
* A flag to force a database reading in case that properties are cached.
* @return
*
* @return array
* An array with synchronizable properties.
*/
function webform_localization_synchronizable_properties($component, $clear_cache = FALSE) {
@@ -102,11 +107,11 @@ function webform_localization_synchronizable_properties($component, $clear_cache
if ($clear_cache || !isset($webform_component_localization_options[$nid][$cid])) {
// Select webform localization options that match this node ID.
$options = db_select('webform_component_localization')
->fields('webform_component_localization')
->condition('nid', $nid, '=')
->condition('cid', $cid, '=')
->execute()
->fetchObject();
->fields('webform_component_localization')
->condition('nid', $nid, '=')
->condition('cid', $cid, '=')
->execute()
->fetchObject();
if (!$options) {
$synchronizable = _webform_localization_default_properties($component);
$webform_component_localization_options[$nid][$cid] = $synchronizable;
@@ -136,7 +141,8 @@ function webform_localization_synchronizable_properties($component, $clear_cache
*
* @param array $component
* A webform component.
* @return
*
* @return array
* An array with webform synchronizable default properties.
*/
function _webform_localization_default_properties($component) {
@@ -171,23 +177,23 @@ function webform_localization_synchronizable_properties_delete($component) {
}
/**
* Load a Webform Component
* Load a Webform Component.
*
* @param $nid
* @param int $nid
* A node Id.
* @param $cid
* @param int $cid
* A webform component Id.
* @return
*
* @return array
* A webform component array.
*
*/
function webform_localization_component_load($nid, $cid) {
$component = db_select('webform_component')
->fields('webform_component')
->condition('nid', $nid, '=')
->condition('cid', $cid, '=')
->execute()
->fetchAllAssoc('nid', PDO::FETCH_ASSOC);
->fields('webform_component')
->condition('nid', $nid, '=')
->condition('cid', $cid, '=')
->execute()
->fetchAllAssoc('nid', PDO::FETCH_ASSOC);
$component[$nid]['nid'] = $nid;
$component[$nid]['extra'] = unserialize($component[$nid]['extra']);
webform_component_defaults($component[$nid]);

View File

@@ -4,6 +4,7 @@
* @file
* Webform Localization i18n_string integration.
*/
/**
* Provides interface with the i18n_string module.
* Based in patch http://drupal.org/node/245424#comment-5244256
@@ -27,9 +28,16 @@
*/
function _webform_localization_translate_component(&$element, $component) {
if (isset($component['extra']['translated_strings']) && is_array($component['extra']['translated_strings'])) {
$node = !empty($component['nid']) ? node_load($component['nid']) : NULL;
foreach ($component['extra']['translated_strings'] as $name) {
$name_list = explode(':', $name);
$current_element = &$element;
$current_element_format;
if (isset($current_element['#format'])) {
$current_element_format = $current_element['#format'];
} else {
$current_element_format = I18N_STRING_FILTER_XSS_ADMIN;
}
if (strpos($name_list[3], '[') !== FALSE) {
// The property is deeper in the renderable array, we must extract the
// the place where it is.
@@ -52,9 +60,15 @@ function _webform_localization_translate_component(&$element, $component) {
}
if (strpos($property, '-') !== FALSE) {
// If property is array, we extract the key from the property.
list ($property, $key) = explode('-', $property);
list ($property, $key) = explode('-', $property, 2);
if (isset($current_element['#' . $property][$key])) {
$current_element['#' . $property][$key] = i18n_string($name, $current_element['#' . $property][$key], array('sanitize' => FALSE));
$text = i18n_string($name, $current_element['#' . $property][$key], array('format' => I18N_STRING_FILTER_XSS));
if (module_exists('token')) {
$current_element['#' . $property][$key] = webform_replace_tokens($text, $node);
}
else {
$current_element['#' . $property][$key] = $text;
}
}
}
else {
@@ -63,11 +77,23 @@ function _webform_localization_translate_component(&$element, $component) {
$option_group = str_replace('/-', '', $name_list[4]);
// If it's a element.
if (isset($name_list[5])) {
$current_element['#' . $property][$option_group][$name_list[5]] = i18n_string($name, $current_element['#' . $property][$option_group][$name_list[5]]);
$text = i18n_string($name, $current_element['#' . $property][$option_group][$name_list[5]], array('format' => $current_element_format));
if (module_exists('token')) {
$current_element['#' . $property][$option_group][$name_list[5]] = webform_replace_tokens($text, $node);
}
else {
$current_element['#' . $property][$option_group][$name_list[5]] = $text;
}
}
else {
// If it's a option group we translate the key.
$translated_option_group = i18n_string($name, $option_group);
$text = i18n_string($name, $option_group, array('format' => $current_element_format));
if (module_exists('token')) {
$translated_option_group = webform_replace_tokens($text, $node);
}
else {
$translated_option_group = $text;
}
if ($translated_option_group != $option_group) {
_webform_localization_array_key_replace($current_element['#' . $property], $option_group, $translated_option_group);
}
@@ -77,14 +103,15 @@ function _webform_localization_translate_component(&$element, $component) {
// Else we can treat the property as string.
if (isset($current_element['#' . $property])) {
if ($property == 'markup' && $current_element['#type'] == 'markup') {
$current_element['#' . $property] = i18n_string($name, $current_element['#' . $property], array('format' => $current_element['#format']));
$text = i18n_string($name, $current_element['#' . $property], array('format' => $current_element['#format']));
}
elseif ($property == 'description') {
$current_element['#' . $property] = i18n_string($name, $current_element['#' . $property], array('format' => I18N_STRING_FILTER_XSS));
$text = i18n_string($name, $current_element['#' . $property], array('sanitize' => FALSE));
}
else {
$current_element['#' . $property] = i18n_string($name, $current_element['#' . $property]);
$text = i18n_string($name, $current_element['#' . $property], array('sanitize' => FALSE));
}
$current_element['#' . $property] = $text;
}
}
}
@@ -93,77 +120,7 @@ function _webform_localization_translate_component(&$element, $component) {
}
/**
* Translates the analysis component properties that are translatable.
*
* These are found in under 'translated_strings' in the 'extra' array of the
* component, which is build when the component is inserted / updated, or
* when all webform strings are updated from
* admin/config/regional/translate/i18n_string.
*
* @param array $data
* The data array of component results.
* @param array $component
* The component.
*/
function _webform_localization_translate_analysis_component(&$data, &$component) {
if (!isset($component['extra']['translated_strings']) || !is_array($component['extra']['translated_strings'])) {
return;
}
// Attempt to translate select options.
if ($component['type'] == 'select') {
$item_key_lookup = _webform_localization_string_to_key($component['extra']['items']);
}
// Attempt to translate grid options / questions.
if ($component['type'] == 'grid') {
$options_key_lookup = _webform_localization_string_to_key($component['extra']['options']);
$questions_key_lookup = _webform_localization_string_to_key($component['extra']['questions']);
}
foreach ($component['extra']['translated_strings'] as $name) {
$name_list = explode(':', $name);
// Translate component name from title property.
if ($name_list[3] == '#title') {
$component['name'] = i18n_string($name, $component['name']);
continue;
}
// Translate options for select elements.
if ($component['type'] == 'select' && strpos($name_list[3], '-') !== FALSE) {
list (, $key) = explode('-', $name_list[3]);
if (isset($item_key_lookup[$key])) {
foreach ($data['table_rows'] as $index => $row) {
if ($row[0] == $item_key_lookup[$key]) {
$data['table_rows'][$index][0] = i18n_string($name, $row[0]);
}
}
}
}
// Translate options / questions for grid elements.
if ($component['type'] == 'grid' && $name_list[3] !== '#title') {
list (, $key) = explode('-', $name_list[3]);
if (strpos($name_list[3], 'grid_options')) {
if (isset($options_key_lookup[$key])) {
foreach ($data['table_header'] as $index => $row) {
if ($row == $options_key_lookup[$key]) {
$data['table_header'][$index] = i18n_string($name, $row);
}
}
}
}
if (strpos($name_list[3], 'grid_questions')) {
if (isset($questions_key_lookup[$key])) {
foreach ($data['table_rows'] as $index => $row) {
if (trim($row[0]) == trim($questions_key_lookup[$key])) {
$data['table_rows'][$index][0] = i18n_string($name, $row[0]);
}
}
}
}
}
}
}
/**
* Update / create translation source for all the translatable poperties.
* Update / create translation source for all the translatable properties.
*
* @param array $component
* A webform component.
@@ -194,9 +151,9 @@ function webform_localization_component_update_translation_strings(&$component)
* The renderable array to be parsed.
* @param array $component
* The component which was rendered.
* @return
* An array of translatabled webform properties.
*
* @return array
* An array of translatabled webform properties.
*/
function _webform_localization_component_translation_parse($element, $component) {
$translated_properies = array();
@@ -204,19 +161,26 @@ function _webform_localization_component_translation_parse($element, $component)
$element['#parents'] = array();
}
$element['#translatable'][] = 'placeholder';
if (isset($element['#translatable']) && is_array($element['#translatable'])) {
foreach ($element['#translatable'] as $key) {
if (isset($element['#' . $key]) && $element['#' . $key] != '') {
if (!empty($element['#' . $key]) || !empty($element['#attributes'][$key])) {
if (isset($element['#parents']) && count($element['#parents'])) {
$property = '[' . implode('][', $element['#parents']) . ']#' . $key;
}
else {
$property = '#' . $key;
}
if (is_array($element['#' . $key])) {
$element_key = '';
if ($key == 'placeholder' && !empty($element['#attributes']['placeholder'])) {
$element_key = $element['#attributes']['placeholder'];
} elseif ($key != 'placeholder' && !empty($element['#' . $key])) {
$element_key = $element['#' . $key];
}
if (is_array($element_key)) {
// If the translatable property is an array, we translate the
// children.
foreach ($element['#' . $key] as $elem_key => $elem_value) {
foreach ($element_key as $elem_key => $elem_value) {
// If the child if an array, we translate the elements.
if (is_array($elem_value)) {
foreach ($elem_value as $k => $v) {
@@ -237,13 +201,11 @@ function _webform_localization_component_translation_parse($element, $component)
}
}
else {
/**
* If the translatable property is not an array,
* it can be treated as a string.
*/
// If the translatable property is not an array,
// it can be treated as a string.
$name = webform_localization_i18n_string_name($component['nid'], $component['cid'], $property);
$translated_properies[] = $name;
i18n_string($name, $element['#' . $key], array('update' => TRUE));
i18n_string($name, $element_key, array('update' => TRUE));
}
}
}
@@ -256,8 +218,7 @@ function _webform_localization_component_translation_parse($element, $component)
$element[$child]['#parents'][] = $child;
// Add the translated propertied to the list.
$translated_properies = array_merge(
$translated_properies,
_webform_localization_component_translation_parse($element[$child], $component)
$translated_properies, _webform_localization_component_translation_parse($element[$child], $component)
);
}
@@ -270,18 +231,18 @@ function _webform_localization_component_translation_parse($element, $component)
* Additional arguments can be passed to add more depth to context
*
* @param int $node_identifier
* webform nid
* webform nid.
*
* @return string
* i18n string name grouped by nid or uuid if module is available
* i18n string name grouped by nid or uuid if module is available.
*/
function webform_localization_i18n_string_name($node_identifier) {
if (module_exists('uuid')) {
if (module_exists('uuid') and !uuid_is_valid($node_identifier)) {
$node_identifier = current(entity_get_uuid_by_id('node', array($node_identifier)));
}
$name = array('webform', $node_identifier);
$args = func_get_args();
// Remove $node_identifier from args
// Remove $node_identifier from args.
array_shift($args);
foreach ($args as $arg) {
$name[] = $arg;
@@ -290,7 +251,7 @@ function webform_localization_i18n_string_name($node_identifier) {
}
/**
* Delete translation source for all the translatable poperties
* Delete translation source for all the translatable properties.
*
* Process components matching webforms configuration.
*/
@@ -312,7 +273,7 @@ function webform_localization_delete_all_strings() {
}
/**
* Remove translation source for all the translatable poperties.
* Remove translation source for all the translatable properties.
*
* @param array $component
* A webform component array.
@@ -326,58 +287,116 @@ function webform_localization_component_delete_translation_strings($component) {
}
/**
* Update / create translation source for general webform poperties.
* Update / create translation source for general webform properties.
*
* @param array $properties
* The form_state values that have been saved.
*/
function webform_localization_update_translation_strings($properties) {
$options = array('update' => TRUE, 'translate' => FALSE);
if (!empty($properties['confirmation']['value'])) {
$name = webform_localization_i18n_string_name($properties['nid'], 'confirmation');
i18n_string($name, $properties['confirmation']['value'], array('update' => TRUE));
$confirmationOptions = $options + array('format' => I18N_STRING_FILTER_XSS);
if (isset($properties['confirmation']['format'])) {
if (i18n_string_allowed_format($properties['confirmation']['format'])) {
$confirmationOptions['format'] = $properties['confirmation']['format'];
}
else {
drupal_set_message(t('The string @name could not be refreshed with the text format @format because it is not allowed for translation.', array(
'@name' => $name,
'@format' => $properties['confirmation']['format']
)), 'warning', FALSE);
}
}
i18n_string($name, $properties['confirmation']['value'], $confirmationOptions);
}
if (!empty($properties['preview_message']['value'])) {
$name = webform_localization_i18n_string_name($properties['nid'], 'preview_message');
$confirmationOptions = $options + array('format' => I18N_STRING_FILTER_XSS);
if (isset($properties['preview_message']['format'])) {
if (i18n_string_allowed_format($properties['preview_message']['format'])) {
$confirmationOptions['format'] = $properties['preview_message']['format'];
}
else {
drupal_set_message(t('The string @name could not be refreshed with the text format @format because it is not allowed for translation.', array(
'@name' => $name,
'@format' => $properties['preview_message']['format']
)), 'warning', FALSE);
}
}
i18n_string($name, $properties['preview_message']['value'], $confirmationOptions);
}
if (!empty($properties['submit_text'])) {
$name = webform_localization_i18n_string_name($properties['nid'], 'submit_text');
i18n_string($name, $properties['submit_text'], array('update' => TRUE));
i18n_string($name, $properties['submit_text'], $options);
}
// Allow to translate the redirect url if it's not set to none or the
// default confirmation page.
if (!in_array($properties['redirect_url'], array('<confirmation>', '<none>'))) {
if (!in_array($properties['redirect_url'], array('<confirmation>', '<none>', '<front>'))) {
$name = webform_localization_i18n_string_name($properties['nid'], 'redirect_url');
i18n_string($name, $properties['redirect_url'], array('update' => TRUE));
i18n_string($name, $properties['redirect_url'], $options);
}
}
/**
* Translate general webform properties.
*
* @param $node
* @param object $node
* A node object.
*/
function webform_localization_translate_strings(&$node, $update = FALSE) {
$option = array('update' => $update, 'sanitize' => FALSE);
$options = array('update' => $update);
if (!array_key_exists('nid', $node->webform)) {
$node->webform['nid'] = $node->nid;
}
$name = webform_localization_i18n_string_name($node->webform['nid'], 'confirmation');
$confirmationOptions = $options + array('format' => I18N_STRING_FILTER_XSS);
if (in_array($node->webform['redirect_url'], array('<confirmation>', '<none>')) && isset($node->webform['confirmation_format'])) {
if (i18n_string_allowed_format($node->webform['confirmation_format'])) {
$confirmationOptions['format'] = $node->webform['confirmation_format'];
}
else {
drupal_set_message(t('The string @name could not be refreshed with the text format @format because it is not allowed for translation.', array(
'@name' => $name,
'@format' => $node->webform['confirmation_format']
)), 'warning', FALSE);
}
}
$node->webform['confirmation'] = i18n_string(
$name,
$node->webform['confirmation'],
$option);
$name, $node->webform['confirmation'], $confirmationOptions);
$name = webform_localization_i18n_string_name($node->webform['nid'], 'preview_message');
if (isset($node->webform['preview_message_format'])) {
if (i18n_string_allowed_format($node->webform['preview_message_format'])) {
$confirmationOptions['format'] = $node->webform['preview_message_format'];
}
else {
drupal_set_message(t('The string @name could not be refreshed with the text format @format because it is not allowed for translation.', array(
'@name' => $name,
'@format' => $node->webform['preview_message_format']
)), 'warning', FALSE);
}
}
$node->webform['preview_message'] = i18n_string(
$name, $node->webform['preview_message'], $confirmationOptions);
$name = webform_localization_i18n_string_name($node->webform['nid'], 'submit_text');
$node->webform['submit_text'] = i18n_string(
$name,
$node->webform['submit_text'],
$option);
$name, $node->webform['submit_text'], $options);
// Allow to translate the redirect url if it's not set to none or the
// default confirmation page.
if (!in_array($node->webform['redirect_url'], array('<confirmation>', '<none>'))) {
if (!in_array($node->webform['redirect_url'], array('<confirmation>', '<none>', '<front>'))) {
$name = webform_localization_i18n_string_name($node->webform['nid'], 'redirect_url');
$node->webform['redirect_url'] = i18n_string($name, $node->webform['redirect_url'], $option);
$node->webform['redirect_url'] = i18n_string($name, $node->webform['redirect_url'], $options);
}
}
/**
* Update / create translation source for webform email poperties.
* Update / create translation source for webform email properties.
*
* @param array $properties
* The form_state values that have been saved.
@@ -405,11 +424,11 @@ function webform_localization_emails_update_translation_string($properties) {
}
/**
* Update / create translation source for webform email poperties.
* Update / create translation source for webform email properties.
*
* @param $emails
* @param array $emails
* An array of webform emails.
* @param $nid
* @param int $nid
* The node Id of the webform.
*/
function webform_localization_emails_translation_string_refresh($emails, $nid) {
@@ -436,9 +455,9 @@ function webform_localization_emails_translation_string_refresh($emails, $nid) {
}
/**
* Translate webform email poperties.
* Translate webform email properties.
*
* @param $node
* @param object $node
* A node object.
*/
function webform_localization_email_translate_strings(&$node) {
@@ -459,17 +478,17 @@ function webform_localization_email_translate_strings(&$node) {
}
if (!empty($email['template']) && $email['template'] != 'default') {
$name = webform_localization_i18n_string_name($nid, 'email', $eid, 'template');
$email['template'] = i18n_string($name, $email['template']);
$email['template'] = i18n_string($name, $email['template'], array('sanitize' => FALSE));
}
}
}
/**
* Remove translation source for webform email poperties.
* Remove translation source for webform email properties.
*
* @param $eid
* @param int $eid
* A webform email Id.
* @param $nid
* @param int $nid
* A node Id.
*/
function webform_localization_emails_delete_translation_string($eid, $nid) {
@@ -482,9 +501,9 @@ function webform_localization_emails_delete_translation_string($eid, $nid) {
}
/**
* Translate general webform poperties.
* Translate general webform properties.
*
* @param $node
* @param object $node
* A node object.
*/
function webform_localization_delete_translate_strings($node) {
@@ -499,12 +518,11 @@ function webform_localization_delete_translate_strings($node) {
/**
* Update i18n string contexts if uuid module is enabled/disabled.
*
*/
function webform_localization_uuid_update_strings($disabling_uuid = FALSE) {
module_load_install('i18n_string');
$old_ids = db_query('SELECT distinct type FROM {i18n_string} WHERE textgroup = :webform', array(
':webform' => 'webform'
':webform' => 'webform',
))->fetchCol();
variable_set('webform_localization_using_uuid', !$disabling_uuid);
if (empty($old_ids)) {
@@ -528,13 +546,13 @@ function webform_localization_uuid_update_strings($disabling_uuid = FALSE) {
/**
* Helper function that retrieves entity IDs by their UUIDs.
*
*
* @param $entity_type
* @param string $entity_type
* The entity type we should be dealing with.
* @param $uuids
* @param array $uuids
* An array of UUIDs for which we should find their entity IDs. If $revision
* is TRUE this should be revision UUIDs instead.
* @return
*
* @return array
* Array of entity IDs keyed by their UUIDs. If $revision is TRUE revision
* IDs and UUIDs are returned instead.
*/
@@ -552,22 +570,21 @@ function webform_localization_get_id_by_uuid($entity_type, $uuids) {
// Get all UUIDs in one query.
return db_select($table, 't')
->fields('t', array($uuid_key, $id_key))
->condition($uuid_key, array_values($uuids), 'IN')
->execute()
->fetchAllKeyed();
->fields('t', array($uuid_key, $id_key))
->condition($uuid_key, array_values($uuids), 'IN')
->execute()
->fetchAllKeyed();
}
/**
* Helper function to replace an array key and its content.
*
* @param $array
* @param array $array
* Array To process.
* @param $old_key
* @param string $old_key
* Array key to be replaced.
* @param $new_key
* @param string $new_key
* The new array key.
*
*/
function _webform_localization_array_key_replace(&$array, $old_key, $new_key) {
$keys = array_keys($array);
@@ -583,17 +600,23 @@ function _webform_localization_array_key_replace(&$array, $old_key, $new_key) {
/**
* Helper function to convert select / grid strings to array.
*
* @param $string_array
* @param string $string_array
* Array To process.
*
* @return array
* Processed array.
*/
function _webform_localization_string_to_key($string_array) {
$key_array = array();
$items = explode("\n", trim($string_array));
foreach ($items as $item) {
$item_data = explode('|', $item);
$key_array[$item_data[0]] = $item_data[1];
if (isset($item_data[1]) && isset($item_data[0])) {
$key_array[$item_data[0]] = $item_data[1];
}
elseif (isset($item_data[1]) && !isset($item_data[0])) {
$key_array[$item_data[0]] = '';
}
}
return $key_array;
}

View File

@@ -14,10 +14,11 @@
/**
* Sync webform configured properties with its translated versions.
*
* @param $nid
* @param int $nid
* A node Id.
*/
function webform_localization_webform_properties_sync($nid) {
// Gets webform localization options that match this node ID.
$webform_localization_options = webform_localization_get_config($nid, TRUE);
if (count($webform_localization_options['webform_properties']) > 0) {
@@ -25,10 +26,10 @@ function webform_localization_webform_properties_sync($nid) {
if (count($node_list) > 1) {
// Select all webforms that match these node IDs.
$result = db_select('webform')
->fields('webform')
->condition('nid', $node_list, 'IN')
->execute()
->fetchAllAssoc('nid', PDO::FETCH_ASSOC);
->fields('webform')
->condition('nid', $node_list, 'IN')
->execute()
->fetchAllAssoc('nid', PDO::FETCH_ASSOC);
if ($result) {
$origin = $result[$nid];
unset($result[$nid]);
@@ -47,16 +48,16 @@ function webform_localization_webform_properties_sync($nid) {
/**
* Sync webform roles with its translated versions.
*
* @param $nid
* @param int $nid
* A node Id.
*/
function webform_localization_roles_sync($nid) {
$node_list = _webform_localization_translation_set_node_list($nid);
$roles = db_select('webform_roles')
->fields('webform_roles', array('rid'))
->condition('nid', $nid)
->execute()
->fetchCol();
->fields('webform_roles', array('rid'))
->condition('nid', $nid)
->execute()
->fetchCol();
foreach ($node_list as $n) {
if ($n != $nid) {
db_delete('webform_roles')->condition('nid', $n)->execute();
@@ -70,7 +71,7 @@ function webform_localization_roles_sync($nid) {
/**
* Sync webform emails recipients with its translated versions.
*
* @param $nid
* @param int $nid
* A node Id.
*/
function webform_localization_emails_sync($nid) {
@@ -111,17 +112,18 @@ function webform_localization_emails_sync($nid) {
/**
* Get an Array of webform emails recipients for a Node Id.
*
* @param $nid
* @param int $nid
* A node Id.
* @return
*
* @return array
* An array of webform emails.
*/
function _webform_localization_emails_load($nid) {
$emails = db_select('webform_emails')
->fields('webform_emails')
->condition('nid', $nid)
->execute()
->fetchAllAssoc('eid', PDO::FETCH_ASSOC);
->fields('webform_emails')
->condition('nid', $nid)
->execute()
->fetchAllAssoc('eid', PDO::FETCH_ASSOC);
// Unserialize the exclude component list for e-mails.
foreach ($emails as $eid => $email) {
$emails[$eid]['excluded_components'] = array_filter(explode(',', $email['excluded_components']));
@@ -135,9 +137,10 @@ function _webform_localization_emails_load($nid) {
/**
* Get a node Id list of a translation set.
*
* @param $nid
* @param int $nid
* A node Id.
* @return
*
* @return array
* An array of node ids that share a tnid.
*/
function _webform_localization_translation_set_node_list($nid) {

View File

@@ -0,0 +1,250 @@
<?php
/**
* @file
* This file contains test classes for testing localized webform submissions.
*/
/**
* Abstract class that defines the actual tests.
*
* Subclasses can be created to set the specific translation method.
*/
abstract class WebformLocalizationSubmissionTestCase extends WebformLocalizationWebTestCase {
/**
* First translation node.
*
* @var object
*/
protected $nodeTranslation1;
/**
* Second translation node.
*
* @var object
*/
protected $nodeTranslation2;
/**
* Implements setUp().
*/
public function setUp($modules = array()) {
parent::setUp($modules);
// Set "Webform" content type to use multilingual support with translation.
$this->drupalGet('admin/structure/types/manage/webform');
$edit = array();
$edit['language_content_type'] = 2;
$this->drupalPost('admin/structure/types/manage/webform', $edit, t('Save content type'));
$this->assertRaw(t('The content type %type has been updated.', array('%type' => 'Webform')), 'Webform content type has been updated.');
}
/**
* Create a webform with translations, and performs some submissions.
*/
public function testLocalizedSubmission() {
// Create the webform node.
$node = $this->createWebformForm();
// Set the translation method.
$this->setTranslationOptions();
// Create the translated nodes.
$this->createTranslationNodes();
// Submit in original language.
$this->webformSubmissionExecute($node);
// Submit translation 1.
$this->webformSubmissionExecute($this->nodeTranslation1);
// Submit translation 2.
$this->webformSubmissionExecute($this->nodeTranslation2);
// Submit translation 2.
$this->webformSubmissionExecute($this->nodeTranslation2);
// Submit original language once more.
$this->webformSubmissionExecute($node);
}
/**
* Set the translation options.
*
* The options can be set here for either string translation or localization
* by sync, or can be extended to set some other options.
*/
abstract public function setTranslationOptions();
/**
* Create the translated webform nodes.
*/
public function createTranslationNodes() {
// Create the translation nodes:
// Submit translation in Spanish.
$node_translation_title = 'Webform title in Spanish';
$node_translation_body = 'Content in Spanish';
$this->nodeTranslation1 = $this->createWebformTranslation($this->createWebformForm(), $node_translation_title, $node_translation_body, 'es');
// Submit translation in German.
$node_translation_title = 'Webform title in German';
$node_translation_body = 'Content in German';
$this->nodeTranslation2 = $this->createWebformTranslation($this->createWebformForm(), $node_translation_title, $node_translation_body, 'de');
}
/**
* Get values to submit to the test webform.
*/
public function webformPost($value_type = 'sample') {
$submission_values_list = $value_type == 'sample' ? $this->wtc->webformPost() : array();
foreach ($submission_values_list as $submission_key => $submission_value) {
$skipped = FALSE;
foreach ($this->skippedComponents() as $skip) {
if (substr_count($submission_key, $skip) > 0) {
$skipped = TRUE;
}
}
if (!$skipped) {
$submission_values[$submission_key] = $submission_value;
}
}
return $submission_values;
}
/**
* Execute the submission test.
*
* @param object $node
* The webform node used to submit the values.
* @param string $value_type
* The values to be submitted to the webform. Either "sample" or "default".
*/
public function webformSubmissionExecute($node, $value_type = 'sample') {
module_load_include('inc', 'webform', 'includes/webform.submissions');
$submission_values = $this->webformPost($value_type);
// Visit the node page with the "foo=bar" query, to test
// [current-page:query:?] default values.
$this->drupalGet('node/' . $node->nid, array('query' => array('foo' => 'bar')));
$this->assertText($node->title, t('Webform node created and accessible at !url', array('!url' => 'node/' . $node->nid)), t('Webform'));
// Submit our test data.
$this->drupalPost(NULL, $submission_values, 'Submit', array(), array(), 'webform-client-form-' . $node->nid);
// Confirm that the submission has been created.
$webform = $this->createWebformForm()->webform;
$this->assertText(t($webform['confirmation']), t('Confirmation message "@confirmation" received.', array('@confirmation' => t($webform['confirmation']))), t('Webform'));
// Get the SID of the new submission.
$matches = array();
preg_match('/sid=([0-9]+)/', $this->getUrl(), $matches);
$sid = $matches[1];
// Pull in the database submission and check the values.
drupal_static_reset('webform_get_submission');
$actual_submission = webform_get_submission($node->nid, $sid);
$component_info = $this->wtc->webformComponents();
foreach ($node->webform['components'] as $cid => $component) {
$stable_value = $value_type == 'sample' ? $component_info[$component['form_key']]['database values'] : $component_info[$component['form_key']]['database default values'];
$actual_value = $actual_submission->data[$cid];
$result = $this->assertEqual($stable_value, $actual_value, t('Component @form_key data integrity check when using @type values.', array('@form_key' => $component['form_key'], '@type' => $value_type)), t('Webform'));
if (!$result || $result === 'fail') {
$this->fail(t('Expected !expected', array('!expected' => print_r($stable_value, TRUE))) . "\n\n" . t('Received !received', array('!received' => print_r($actual_value, TRUE))), t('Webform'));
}
}
}
}
/**
* Test Localized webform submission (String translation)
*/
class WebformLocalizationStringSubmissionTestCase extends WebformLocalizationSubmissionTestCase {
/**
* Implements getInfo().
*/
public static function getInfo() {
return array(
'name' => t('Localized webform submission (String translation)'),
'description' => t('Submits a sample webform and checks the database integrity.'),
'group' => t('Webform Localization'),
'dependencies' => array(
'webform_localization',
'webform',
'views',
'ctools',
'i18n_translation',
'i18n_string',
'i18n',
'variable',
),
);
}
/**
* Set options to use localization by string translation.
*
* Enables localization by string translation and reuse the single webform
* across the translation set.
*/
public function setTranslationOptions() {
$node = $this->createWebformForm();
$edit = array();
$edit['expose_strings'] = 1;
$edit['single_webform'] = 1;
$this->drupalPost('node/' . $node->nid . '/webform/configure', $edit, t('Save configuration'));
$this->assertRaw(t('The form settings have been updated.'), 'Webform string translation and single webform enabled.');
}
}
/**
* Localized webform submission (Localization by Sync)
*/
class WebformLocalizationSyncSubmissionTestCase extends WebformLocalizationSubmissionTestCase {
/**
* Implements getInfo().
*/
public static function getInfo() {
return array(
'name' => t('Localized webform submission (Localization by Sync)'),
'description' => t('Submits a sample webform and checks the database integrity.'),
'group' => t('Webform Localization'),
'dependencies' => array(
'webform_localization',
'webform',
'views',
'ctools',
'i18n_translation',
'i18n_string',
'i18n',
'variable',
),
);
}
/**
* Enables localization by sync: sync components, roles and emails.
*/
public function setTranslationOptions() {
$node = $this->createWebformForm();
$edit = array();
$edit['sync_components'] = 1;
$edit['sync_roles'] = 1;
$edit['sync_emails'] = 1;
$this->drupalPost('node/' . $node->nid . '/webform/configure', $edit, t('Save configuration'));
$this->assertRaw(t('The form settings have been updated.'), 'Webform string translation and single webform enabled.');
}
}

View File

@@ -9,44 +9,61 @@ class WebformLocalizationWebTestCase extends DrupalWebTestCase {
// Webform test class instance.
public $wtc;
// Users.
public $admin_user;
public $adminuser;
public $translator;
public $normal_user;
public $normaluser;
public $perms;
protected $profile = 'testing';
/**
* Implements setUp().
*/
function setUp($modules = array()) {
$modules = array_merge($modules, array('locale', 'webform', 'webform_localization'));
parent::setUp($modules);
function setUp($added_modules = array()) {
$modules = array('webform_localization', 'webform', 'views', 'ctools', 'i18n_translation', 'i18n_string', 'i18n', 'variable', 'translation', 'locale', 'block');
$added_modules = array_merge($modules, $added_modules);
parent::setUp($added_modules);
// We load webform test class to reuse webform and components creation functions.
module_load_include('test', 'webform', 'tests/webform');
$this->wtc = new WebformTestCase;
/* Reset the permissions cache prior to calling drupalCreateUser
* see notes here: https://api.drupal.org/comment/28739#comment-28739
*/
$this->checkPermissions(array(), TRUE);
// Setup users.
$this->admin_user = $this->drupalCreateUser(array('bypass node access', 'administer nodes', 'administer languages', 'administer content types', 'administer blocks', 'access administration pages', 'translate content', 'create webform content',
$this->adminuser = $this->drupalCreateUser(array('bypass node access', 'administer nodes', 'administer languages', 'administer content types', 'administer blocks', 'access administration pages', 'translate content', 'create webform content',
'edit any webform content',
'access all webform results',
'edit all webform submissions',
'delete all webform submissions',
'edit webform components',
'translate interface',
'translate user-defined strings'));
/* Reset the permissions cache prior to calling drupalCreateUser
* see notes here: https://api.drupal.org/comment/28739#comment-28739
*/
$this->checkPermissions(array('access content'), TRUE);
$this->translator = $this->drupalCreateUser(array('translate content',
'create webform content',
'edit any webform content',
'access all webform results',
'edit webform components',
'translate interface',
'translate user-defined strings'));
$this->normal_user = $this->drupalCreateUser(array('access content', 'edit own webform submissions'));
/* Reset the permissions cache prior to calling drupalCreateUser
* see notes here: https://api.drupal.org/comment/28739#comment-28739
*/
$this->checkPermissions(array('access content'), TRUE);
$this->normaluser = $this->drupalCreateUser(array('access content', 'edit own webform submissions'));
// Fix for reuse of webform test class.
$this->wtc->webform_users['admin'] = $this->admin_user;
$this->wtc->webform_users['admin']->profile_gender = array('Female', 'Male');
$this->wtc->webform_users['admin'] = $this->adminuser;
$this->wtc->webform_users['admin']->gender = array(LANGUAGE_NONE => array(array('value' => 'Female')));
$this->drupalLogin($this->admin_user);
$this->drupalLogin($this->adminuser);
// Add languages.
$this->addLanguage('en');
@@ -60,6 +77,20 @@ class WebformLocalizationWebTestCase extends DrupalWebTestCase {
drupal_static_reset('locale_url_outbound_alter');
}
/**
* @todo : We need further debug to find how to support
* this components or why are they breaking everything
*/
public function skippedComponents() {
return array(
'select_no_default_zero',
'radios_zero',
'checkboxes_zero',
'grid_keyed',
'select_zero',
);
}
/**
* Create a webform node with test components.
*/
@@ -99,16 +130,11 @@ class WebformLocalizationWebTestCase extends DrupalWebTestCase {
'confirmation' => 'Thanks!',
) + webform_node_defaults(),
);
$components = $this->wtc->testWebformComponents();
$components = $this->wtc->webformComponents();
/*
* @todo : We need further debug to find how to support
* this components or why are they breaking everything
*/
unset($components['select_no_default_zero']);
unset($components['radios_zero']);
unset($components['select_zero']);
unset($components['select_optgroup']);
foreach ($this->skippedComponents() as $skip) {
unset($components[$skip]);
}
$cid = 0;
foreach ($components as $key => $component_info) {
@@ -286,14 +312,15 @@ class WebformLocalizationStringTranslationTestCase extends WebformLocalizationWe
'name' => 'Webform Localization',
'description' => 'Webform localization String Translations Tests.',
'group' => 'Webform Localization',
'dependencies' => array('webform_localization', 'webform', 'views', 'ctools', 'i18n_translation', 'i18n_string', 'i18n', 'variable'),
);
}
/**
* Set up test.
*/
public function setUp($modules = array()) {
parent::setUp(array('translation', 'i18n', 'i18n_string'));
function setUp($added_modules = array()) {
parent::setUp($added_modules);
// Set "Webform" content type to use multilingual support with translation.
$this->drupalGet('admin/structure/types/manage/webform');
@@ -307,7 +334,7 @@ class WebformLocalizationStringTranslationTestCase extends WebformLocalizationWe
* Test creating a webform and enabling localization by string translation
*/
function testWebformLocalizationStringTranslation() {
$this->drupalLogin($this->admin_user);
$this->drupalLogin($this->adminuser);
/**
* Create the Webform test node, and enable
* localization by string translation feature
@@ -400,10 +427,61 @@ class WebformLocalizationStringTranslationTestCase extends WebformLocalizationWe
}
$this->assertRaw($translation, format_string('%c translation is present.', array('%c' => $name)), 'Deutsch Webform translation');
}
// Log in as normal user and add a answer to webform so string
// translatability can be tested at various webform report pages.
$this->drupalLogin($this->normaluser);
$this->drupalPost('node/' . $node->nid, array(), t('Submit'));
// Log back to admin account and download results, using Deutsch Webform
// translations
$this->drupalLogin($this->adminuser);
$edit = array(
'format' => 'delimited',
);
$this->drupalPost('de/node/' . $node->nid . '/webform-results/download', $edit, t('Download'));
// Previous form submission batch created the export file. Click the link
// to access export file itself.
$this->clickLink(t('download the file here'));
// Export file should contain all element titles translated
foreach ($options as $key => $value) {
$context = $value['context'];
if (substr($context, -5) != ':#title') {
continue;
}
$translation = 'de:' . $value['source'];
$this->assertRaw($translation, t('%c translation is present.', array('%c' => $value['source'])), 'Deutsch Webform translation');
}
}
}
/**
* The same test as WebformLocalizationStringTranslationTestCase, but with the
* Token module enabled.
*/
class WebformLocalizationStringTranslationTokenTestCase extends WebformLocalizationStringTranslationTestCase {
/**
* Test info.
*/
public static function getInfo() {
$info = parent::getInfo();
$info = array_merge($info, array(
'name' => 'Webform Localization Token',
'description' => 'Webform localization String Translations with Token Tests.',
));
$info['dependencies'][] = 'token';
return $info;
}
/**
* Set up test.
*/
function setUp($added_modules = array()) {
$added_modules = array_merge(array('token'), $added_modules);
parent::setUp($added_modules);
}
}
class WebformLocalizationApiTestCase extends WebformLocalizationWebTestCase {
/**
@@ -414,14 +492,15 @@ class WebformLocalizationApiTestCase extends WebformLocalizationWebTestCase {
'name' => 'Test Webform Localization API.',
'description' => 'Test webform and webform localization interaction at API level.',
'group' => 'Webform Localization',
'dependencies' => array('webform_localization', 'webform', 'views', 'ctools', 'i18n_translation', 'i18n_string', 'i18n', 'variable'),
);
}
/**
* Set up test.
*/
public function setUp($modules = array()) {
parent::setUp(array('translation', 'i18n', 'i18n_string'));
function setUp($added_modules = array()) {
parent::setUp($added_modules);
// Set "Webform" content type to use multilingual support with translation.
$this->drupalGet('admin/structure/types/manage/webform');
@@ -435,7 +514,7 @@ class WebformLocalizationApiTestCase extends WebformLocalizationWebTestCase {
* Test submissions API function with webform localization presence.
*/
function testWebformLocalizationApi() {
$this->drupalLogin($this->admin_user);
$this->drupalLogin($this->adminuser);
$node = $this->createWebformForm();
/**
* Enables localization by string translation and reuse the single webform

View File

@@ -0,0 +1,72 @@
<?php
/**
* @file
* Webform localization hooks.
*/
/**
* Module specific instance of _webform_localization_csv_header().
*
* This allows each component to translate the analysis report CSV header.
* These are found in under 'translated_strings' in the 'extra' array of the
* component, which is build when the component is inserted / updated, or
* when all webform strings are updated from
* admin/config/regional/translate/i18n_string.
*
* @param array $header
* The untranslated CSV header to be altered.
* @param array $component
* The webform component.
*
* @return array
* The translated header array.
*/
function _webform_localization_csv_header_component($header, $component) {
$header[1] = 'Custom string';
return $header;
}
/**
* Module specific instance of _webform_localization_csv_data().
*
* This allows each component to translate the analysis report CSV data.
*
* @param array $data
* The untranslated CSV data to be altered.
* @param array $component
* The webform component.
* @param array $submission
* The webform submission.
*
* @return array
* The translated data array.
*/
function _webform_localization_csv_data_component($data, $component, $submission) {
$data[1] = 'Custom string';
return $data;
}
/**
* Module specific instance of _webform_localization_analysis_data().
*
* This allows each component to translate the analysis report data.
* These are found in under 'translated_strings' in the 'extra' array of the
* component, which is build when the component is inserted / updated, or
* when all webform strings are updated from
* admin/config/regional/translate/i18n_string.
*
* @param array $data
* The untranslated data to be altered
* @param object $node
* The webform node.
* @param array $component
* The webform component.
*
* @return array
* The translated data array.
*/
function _webform_localization_analysis_data_component($data, $node, $component) {
$data[1] = 'Custom string';
return $data;
}

View File

@@ -1,16 +1,22 @@
; $Id: $
name = Webform Localization
description = Enables localization features to forms and questionnaires.
dependencies[] = webform
dependencies[] = i18n_string
core = 7.x
package = Webform
php = 5.3
dependencies[] = locale
dependencies[] = variable
dependencies[] = i18n_translation
dependencies[] = i18n_string
dependencies[] = webform (4.x)
test_dependencies[] = token
test_dependencies[] = i18n_block
files[] = tests/webform_localization.test
files[] = tests/webform_localization.submission.test
; Information added by Drupal.org packaging script on 2014-03-28
version = "7.x-4.x-dev"
; Information added by Drupal.org packaging script on 2018-06-04
version = "7.x-4.14"
core = "7.x"
project = "webform_localization"
datestamp = "1396049366"
datestamp = "1528148886"

View File

@@ -9,12 +9,11 @@
* Implements hook_install().
*/
function webform_localization_install() {
/**
* NOTE:
* We add a field to record the language of the submission since when using
* "Localization by String Translation" you can get single webform been
* submitted by several nodes in different languages.
*/
// NOTE:
// We add a field to record the language of the submission since when using
// "Localization by String Translation" you can get single webform been
// submitted by several nodes in different languages.
db_add_field('webform_submissions', 'language', array(
'description' => 'The {languages}.language source of this submission.',
'type' => 'varchar',