Projet

Général

Profil

Paste
Télécharger (37,1 ko) Statistiques
| Branche: | Révision:

root / drupal7 / sites / all / modules / webform / components / select.inc @ cb17e347

1
<?php
2

    
3
/**
4
 * @file
5
 * Webform module multiple select component.
6
 */
7

    
8
/**
9
 * Implements _webform_defaults_component().
10
 */
11
function _webform_defaults_select() {
12
  return array(
13
    'name' => '',
14
    'form_key' => NULL,
15
    'required' => 0,
16
    'pid' => 0,
17
    'weight' => 0,
18
    'value' => '',
19
    'extra' => array(
20
      'items' => '',
21
      'multiple' => NULL,
22
      'aslist' => NULL,
23
      'empty_option' => '',
24
      'optrand' => 0,
25
      'other_option' => NULL,
26
      'other_text' => t('Other...'),
27
      'title_display' => 0,
28
      'description' => '',
29
      'custom_keys' => FALSE,
30
      'options_source' => '',
31
      'private' => FALSE,
32
      'analysis' => TRUE,
33
    ),
34
  );
35
}
36

    
37
/**
38
 * Implements _webform_theme_component().
39
 */
40
function _webform_theme_select() {
41
  return array(
42
    'webform_display_select' => array(
43
      'render element' => 'element',
44
      'file' => 'components/select.inc',
45
    ),
46
  );
47
}
48

    
49
/**
50
 * Implements _webform_edit_component().
51
 */
52
function _webform_edit_select($component) {
53
  $form = array(
54
    '#attached' => array(
55
      'js' => array(
56
        drupal_get_path('module', 'webform') . '/js/select-admin.js' => array('preprocess' => FALSE),
57
        array('data' => array('webform' => array('selectOptionsUrl' => url('webform/ajax/options/' . $component['nid']))), 'type' => 'setting'),
58
      ),
59
    ),
60
  );
61

    
62
  $other = array();
63
  if ($info = _webform_select_options_info()) {
64
    $options = array('' => t('None'));
65
    foreach ($info as $name => $source) {
66
      $options[$name] = $source['title'];
67
    }
68

    
69
    $other['options_source'] = array(
70
      '#title' => t('Load a pre-built option list'),
71
      '#type' => 'select',
72
      '#options' => $options,
73
      '#default_value' => $component['extra']['options_source'],
74
      '#weight' => 1,
75
      '#description' => t('Use a pre-built list of options rather than entering options manually. Options will not be editable if using pre-built list.'),
76
      '#parents' => array('extra', 'options_source'),
77
      '#weight' => 5,
78
    );
79
  }
80

    
81
  if (module_exists('select_or_other')) {
82
    $other['other_option'] = array(
83
      '#type' => 'checkbox',
84
      '#title' => t('Allow "Other..." option'),
85
      '#default_value' => $component['extra']['other_option'],
86
      '#description' => t('Check this option if you want to allow users to enter an option not on the list.'),
87
      '#parents' => array('extra', 'other_option'),
88
      '#attributes' => array('class' => array('other-option-checkbox')),
89
      '#weight' => 2,
90
    );
91
    $other['other_text'] = array(
92
      '#type' => 'textfield',
93
      '#title' => t('Text for "Other..." option'),
94
      '#default_value' => $component['extra']['other_text'],
95
      '#description' => t('If allowing other options, enter text to be used for other-enabling option.'),
96
      '#parents' => array('extra', 'other_text'),
97
      '#weight' => 3,
98
      '#states' => array(
99
        'visible' => array(
100
          ':input.other-option-checkbox' => array('checked' => TRUE),
101
        ),
102
      ),
103
    );
104
  }
105

    
106
  if (module_exists('options_element')) {
107
    $options = _webform_select_options($component, FALSE, FALSE);
108

    
109
    $form['items'] = array(
110
      '#type' => 'fieldset',
111
      '#title' => t('Options'),
112
      '#collapsible' => TRUE,
113
      '#attributes' => array('class' => array('webform-options-element')),
114
      '#element_validate' => array('_webform_edit_validate_options'),
115
      '#weight' => 2,
116
    );
117

    
118
    $form['items']['options'] = array(
119
      '#type' => 'options',
120
      '#limit' => 500,
121
      '#optgroups' => TRUE,
122
      '#multiple' => $component['extra']['multiple'],
123
      '#multiple_toggle' => t('Multiple'),
124
      '#default_value' => $component['value'],
125
      '#options' => $options,
126
      '#options_readonly' => !empty($component['extra']['options_source']),
127
      '#key_type' => 'mixed',
128
      '#key_type_toggle' => t('Customize keys (Advanced)'),
129
      '#key_type_toggled' => $component['extra']['custom_keys'],
130
      '#default_value_pattern' => '^%.+\[.+\]$',
131
      '#weight' => 1,
132
    );
133

    
134
    $form['items']['options']['option_settings'] = $other;
135
  }
136
  else {
137
    $form['extra']['items'] = array(
138
      '#type' => 'textarea',
139
      '#title' => t('Options'),
140
      '#default_value' => $component['extra']['items'],
141
      '#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'),
142
      '#cols' => 60,
143
      '#rows' => 5,
144
      '#weight' => 0,
145
      '#required' => TRUE,
146
      '#wysiwyg' => FALSE,
147
      '#element_validate' => array('_webform_edit_validate_select'),
148
    );
149

    
150
    if (!empty($component['extra']['options_source'])) {
151
      $form['extra']['items']['#attributes'] = array('readonly' => 'readonly');
152
    }
153

    
154
    $form['extra'] = array_merge($form['extra'], $other);
155
    $form['value'] = array(
156
      '#type' => 'textfield',
157
      '#title' => t('Default value'),
158
      '#default_value' => $component['value'],
159
      '#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'),
160
      '#size' => 60,
161
      '#maxlength' => 1024,
162
      '#weight' => 0,
163
    );
164
    $form['extra']['multiple'] = array(
165
      '#type' => 'checkbox',
166
      '#title' => t('Multiple'),
167
      '#default_value' => $component['extra']['multiple'],
168
      '#description' => t('Check this option if the user should be allowed to choose multiple values.'),
169
      '#weight' => 0,
170
    );
171
  }
172

    
173
  $form['display']['aslist'] = array(
174
    '#type' => 'checkbox',
175
    '#title' => t('Listbox'),
176
    '#default_value' => $component['extra']['aslist'],
177
    '#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.'),
178
    '#parents' => array('extra', 'aslist'),
179
  );
180
  $form['display']['empty_option'] = array(
181
    '#type' => 'textfield',
182
    '#title' => t('Empty option'),
183
    '#default_value' => $component['extra']['empty_option'],
184
    '#size' => 60,
185
    '#maxlength' => 255,
186
    '#description' => t('The list item to show when no default is provided. Leave blank for "- None -" or "- Select -".'),
187
    '#parents' => array('extra', 'empty_option'),
188
    '#states' => array(
189
      'visible' => array(
190
        ':input[name="extra[aslist]"]' => array('checked' => TRUE),
191
        ':input[name="extra[multiple]"]' => array('checked' => FALSE),
192
        ':input[name="value"]' => array('filled' => FALSE),
193
      ),
194
    ),
195
  );
196
  $form['display']['optrand'] = array(
197
    '#type' => 'checkbox',
198
    '#title' => t('Randomize options'),
199
    '#default_value' => $component['extra']['optrand'],
200
    '#description' => t('Randomizes the order of the options when they are displayed in the form.'),
201
    '#parents' => array('extra', 'optrand'),
202
  );
203

    
204
  return $form;
205
}
206

    
207
/**
208
 * Element validation callback. Ensure keys are not duplicated.
209
 */
210
function _webform_edit_validate_select($element, &$form_state) {
211
  // Check for duplicate key values to prevent unexpected data loss. Require
212
  // all options to include a safe_key.
213
  if (!empty($element['#value'])) {
214
    $lines = explode("\n", trim($element['#value']));
215
    $existing_keys = array();
216
    $duplicate_keys = array();
217
    $missing_keys = array();
218
    $long_keys = array();
219
    $group = '';
220
    foreach ($lines as $line) {
221
      $matches = array();
222
      $line = trim($line);
223
      if (preg_match('/^\<([^>]*)\>$/', $line, $matches)) {
224
        $group = $matches[1];
225
        $key = NULL; // No need to store group names.
226
      }
227
      elseif (preg_match('/^([^|]*)\|(.*)$/', $line, $matches)) {
228
        $key = $matches[1];
229
        if (strlen($key) > 128) {
230
          $long_keys[] = $key;
231
        }
232
      }
233
      else {
234
        $missing_keys[] = $line;
235
      }
236

    
237
      if (isset($key)) {
238
        if (isset($existing_keys[$group][$key])) {
239
          $duplicate_keys[$key] = $key;
240
        }
241
        else {
242
          $existing_keys[$group][$key] = $key;
243
        }
244
      }
245
    }
246

    
247
    if (!empty($missing_keys)) {
248
      form_error($element, t('Every option must have a key specified. Specify each option as "safe_key|Some readable option".'));
249
    }
250

    
251
    if (!empty($long_keys)) {
252
      form_error($element, t('Option keys must be less than 128 characters. The following keys exceed this limit:') . theme('item_list', $long_keys));
253
    }
254

    
255
    if (!empty($duplicate_keys)) {
256
      form_error($element, t('Options within the select list must be unique. The following keys have been used multiple times:') . theme('item_list', array('items' => $duplicate_keys)));
257
    }
258

    
259
    // Set the listbox option if needed.
260
    if (empty($missing_keys) && empty($long_keys) && empty($duplicate_keys)) {
261
      $options = _webform_select_options_from_text($element['#value']);
262
      _webform_edit_validate_set_aslist($options, $form_state);
263
    }
264
  }
265

    
266
  return TRUE;
267
}
268

    
269
/**
270
 * Set the appropriate webform values when using the options element module.
271
 */
272
function _webform_edit_validate_options($element, &$form_state) {
273
  $key = end($element['#parents']);
274
  $element_options = $form_state['values'][$key]['options'];
275
  unset($form_state['values'][$key]);
276

    
277
  $form_state['values']['extra'][$key] = form_options_to_text($element_options['options'], 'custom');
278

    
279
  // Options saved for select components.
280
  if ($key == 'items') {
281
    $form_state['values']['extra']['multiple'] = $element_options['multiple'];
282
    $form_state['values']['extra']['custom_keys'] = $element_options['custom_keys'];
283
    $form_state['values']['value'] = is_array($element_options['default_value']) ? implode(', ', $element_options['default_value']) : $element_options['default_value'];
284

    
285
    // Set the listbox option if needed.
286
    _webform_edit_validate_set_aslist($element_options['options'], $form_state);
287
  }
288
  // Options saved for grid components.
289
  else {
290
    $form_state['values']['extra']['custom_' . rtrim($key, 's') . '_keys'] = $element_options['custom_keys'];
291
    // There is only one 'value', but grids have two options widgets, one for questions and one for options.
292
    // Only options have a default 'value'. Note that multiple selection is now allowed for grid options.
293
    if ($key == 'options') {
294
      $form_state['values']['value'] = $element_options['default_value'];
295
    }
296
  }
297
}
298

    
299
/**
300
 * Ensure "aslist" is used for option groups. Called from options validations.
301
 */
302
function _webform_edit_validate_set_aslist($options, &$form_state) {
303
  if (empty($form_state['values']['extra']['aslist']) && !empty($options)) {
304
    foreach ($options as $option) {
305
      if (is_array($option)) {
306
        $form_state['values']['extra']['aslist'] = 1;
307
        drupal_set_message(t('The component %name has automatically been set to display as a listbox in order to support option groups.', array('%name' => $form_state['values']['name'])), 'warning');
308
        break;
309
      }
310
    }
311
  }
312
}
313

    
314
/**
315
 * Implements _webform_render_component().
316
 */
317
function _webform_render_select($component, $value = NULL, $filter = TRUE, $submission = NULL) {
318
  $node = isset($component['nid']) ? node_load($component['nid']) : NULL;
319

    
320
  $element = array(
321
    '#title' => $filter ? webform_filter_xss($component['name']) : $component['name'],
322
    '#title_display' => $component['extra']['title_display'] ? $component['extra']['title_display'] : 'before',
323
    '#required' => $component['required'],
324
    '#weight' => $component['weight'],
325
    '#description' => $filter ? webform_filter_descriptions($component['extra']['description'], $node) : $component['extra']['description'],
326
    '#theme_wrappers' => array('webform_element'),
327
    '#pre_render' => array(), // Needed to disable double-wrapping of radios and checkboxes.
328
    '#translatable' => array('title', 'description', 'options'),
329
  );
330

    
331
  // Convert the user-entered options list into an array.
332
  $default_value = $filter ? webform_replace_tokens($component['value'], $node) : $component['value'];
333
  $options = _webform_select_options($component, !$component['extra']['aslist'], $filter);
334
  if (empty($options)) {
335
    // Make element inaccessible if there are no options as there is no point in showing it.
336
    $element['#access'] = FALSE;
337
  }
338

    
339
  if ($component['extra']['optrand']) {
340
    _webform_shuffle_options($options);
341
  }
342

    
343
  // Add HTML5 required attribute, if needed and possible (not working on more than one checkboxes).
344
  if ($component['required'] && ($component['extra']['aslist'] || !$component['extra']['multiple'] || count($options) == 1)) {
345
    $element['#attributes']['required'] = 'required';
346
  }
347

    
348
  // Add default options if using a select list with no default. This trigger's
349
  // Drupal 7's adding of the option for us. See @form_process_select().
350
  if ($component['extra']['aslist'] && !$component['extra']['multiple'] && $default_value === '') {
351
    $element['#empty_value'] = '';
352
    if (strlen($component['extra']['empty_option'])) {
353
      $element['#empty_option'] =  $component['extra']['empty_option'];
354
      $element['#translatable'][] = 'empty_option';
355
    }
356
  }
357

    
358
  // Set the component options.
359
  $element['#options'] = $options;
360

    
361
  // Use the component's default value if the component is currently empty.
362
  if (!isset($value)) {
363
    // The default for multiple selects is a comma-delimited list, without white-space or empty entries.
364
    $value = $component['extra']['multiple'] ? array_filter(array_map('trim', explode(',', $default_value)), 'strlen') : $default_value;
365
  }
366

    
367
  // Convert all values into an array; component may now be single but was previously multiple, or vice-versa
368
  $value = (array)$value;
369

    
370
  // Set the default value. Note: "No choice" is stored as an empty string,
371
  // which will match a 0 key for radios; NULL is used to avoid unintentional
372
  // defaulting to the 0 option.
373
  if ($component['extra']['multiple']) {
374
    // Set the value as an array.
375
    $element['#default_value'] = array();
376
    foreach ($value as $option_value) {
377
      $element['#default_value'][] = $option_value === '' ? NULL : $option_value;
378
    }
379
  }
380
  else {
381
    // Set the value as a single string.
382
    $option_value = reset($value);
383
    $element['#default_value'] = $option_value === '' ? NULL : $option_value;
384
  }
385
  
386
  if ($component['extra']['other_option'] && module_exists('select_or_other')) {
387
    // Set display as a select_or_other element:
388
    $element['#type'] = 'select_or_other';
389
    $element['#other'] = !empty($component['extra']['other_text']) ? check_plain($component['extra']['other_text']) : t('Other...');
390
    $element['#translatable'][] = 'other';
391
    $element['#other_title'] = $element['#title'] . ' ' . $element['#other'];
392
    $element['#other_title_display'] = 'invisible';
393
    $element['#other_unknown_defaults'] = 'other';
394
    $element['#other_delimiter'] = ', ';
395
    // Merge in Webform's #process function for Select or other.
396
    $element['#process'] = array_merge(element_info_property('select_or_other', '#process'), array('webform_expand_select_or_other'));
397
    // Inherit select_or_other settings or set defaults.
398
    $element['#disabled'] = isset($component['extra']['#disabled']) ? $component['extra']['#disabled'] : element_info_property('select_or_other', 'disabled');
399
    $element['#size'] = isset($component['extra']['#size']) ? $component['extra']['#size'] : element_info_property('select_or_other', 'size');
400

    
401
    if ($component['extra']['multiple']) {
402
      $element['#multiple'] = TRUE;
403
      $element['#select_type'] = 'checkboxes';
404
    }
405
    else {
406
      $element['#multiple'] = FALSE;
407
      $element['#select_type'] = 'radios';
408
    }
409
    if ($component['extra']['aslist']) {
410
      $element['#select_type'] = 'select';
411
    }
412
  }
413
  elseif ($component['extra']['aslist']) {
414
    // Set display as a select list:
415
    $element['#type'] = 'select';
416
    if ($component['extra']['multiple']) {
417
      $element['#size'] = 4;
418
      $element['#multiple'] = TRUE;
419
    }
420
  }
421
  else {
422
    if ($component['extra']['multiple']) {
423
      // Set display as a checkbox set.
424
      $element['#type'] = 'checkboxes';
425
      $element['#theme_wrappers'] = array_merge(array('checkboxes'), $element['#theme_wrappers']);
426
      $element['#process'] = array_merge(element_info_property('checkboxes', '#process'), array('webform_expand_select_ids'));
427
    }
428
    else {
429
      // Set display as a radio set.
430
      $element['#type'] = 'radios';
431
      $element['#theme_wrappers'] = array_merge(array('radios'), $element['#theme_wrappers']);
432
      $element['#process'] = array_merge(element_info_property('radios', '#process'), array('webform_expand_select_ids'));
433
    }
434
  }
435

    
436
  return $element;
437
}
438

    
439
/**
440
 * Process function to ensure select_or_other elements validate properly.
441
 */
442
function webform_expand_select_or_other($element) {
443
  // Disable validation for back-button and save draft.
444
  $element['select']['#validated'] = TRUE;
445
  $element['select']['#webform_validated'] = FALSE;
446

    
447
  $element['other']['#validated'] = TRUE;
448
  $element['other']['#webform_validated'] = FALSE;
449

    
450
  // The Drupal FAPI does not support #title_display inline so we need to move
451
  // to a supported value here to be compatible with select_or_other.
452
  $element['select']['#title_display'] = $element['#title_display'] === 'inline' ? 'before' : $element['#title_display'];
453

    
454
  // If the default value contains "select_or_other" (the key of the select
455
  // element for the "other..." choice), discard it and set the "other" value.
456
  if (is_array($element['#default_value']) && in_array('select_or_other', $element['#default_value'])) {
457
    $key = array_search('select_or_other', $element['#default_value']);
458
    unset($element['#default_value'][$key]);
459
    $element['#default_value'] = array_values($element['#default_value']);
460
    $element['other']['#default_value'] = implode(', ', $element['#default_value']);
461
  }
462

    
463
  // Sanitize the options in Select or other check boxes and radio buttons.
464
  if ($element['#select_type'] == 'checkboxes' || $element['#select_type'] == 'radios') {
465
    $element['select']['#process'] = array_merge(element_info_property($element['#select_type'], '#process'), array('webform_expand_select_ids'));
466
  }
467

    
468
  return $element;
469
}
470

    
471
/**
472
 * FAPI process function to rename IDs attached to checkboxes and radios.
473
 */
474
function webform_expand_select_ids($element) {
475
  $id = $element['#id'] = str_replace('_', '-', _webform_safe_name(strip_tags($element['#id'])));
476
  $delta = 0;
477
  foreach (element_children($element) as $key) {
478
    $delta++;
479
    // Convert the #id for each child to a safe name, regardless of key.
480
    $element[$key]['#id'] = $id . '-' . $delta;
481

    
482
    // Prevent scripts or CSS in the labels for each checkbox or radio.
483
    $element[$key]['#title'] = isset($element[$key]['#title']) ? webform_filter_xss($element[$key]['#title']) : '';
484
  }
485
  return $element;
486
}
487

    
488
/**
489
 * Implements _webform_display_component().
490
 */
491
function _webform_display_select($component, $value, $format = 'html', $submission = array()) {
492
  // Sort values by numeric key. These may be in alphabetic order from the database query,
493
  // which is not numeric order for keys '10' and higher.
494
  $value = (array) $value;
495
  ksort($value, SORT_NUMERIC);
496
  return array(
497
    '#title' => $component['name'],
498
    '#title_display' => $component['extra']['title_display'] ? $component['extra']['title_display'] : 'before',
499
    '#weight' => $component['weight'],
500
    '#multiple' => $component['extra']['multiple'],
501
    '#theme' => 'webform_display_select',
502
    '#theme_wrappers' => $format == 'html' ? array('webform_element') : array('webform_element_text'),
503
    '#format' => $format,
504
    '#options' => _webform_select_options($component, !$component['extra']['aslist']),
505
    '#value' => $value,
506
    '#translatable' => array('title', 'options'),
507
  );
508
}
509

    
510
/**
511
 * Implements _webform_submit_component().
512
 *
513
 * Handle select or other... modifications and convert FAPI 0/1 values into
514
 * something saveable.
515
 */
516
function _webform_submit_select($component, $value) {
517
  if (module_exists('select_or_other') && $component['extra']['other_option'] && is_array($value) && count($value) == 2 && isset($value['select'])) {
518
    if (is_array($value['select']) && isset($value['select']['select_or_other'])) {
519
      unset($value['select']['select_or_other']);
520
      $value['select'][] = $value['other'];
521
    }
522
    elseif ($value['select'] == 'select_or_other') {
523
      $value['select'] = $value['other'];
524
    }
525
    $value = $value['select'];
526
  }
527

    
528
  // Build a list of all valid keys expected to be submitted.
529
  $options = _webform_select_options($component, TRUE);
530

    
531
  $return = NULL;
532
  if (is_array($value)) {
533
    $return = array();
534
    foreach ($value as $key => $option_value) {
535
      // Handle options that are specified options.
536
      if ($option_value !== '' && isset($options[$option_value])) {
537
        // Checkboxes submit an integer value of 0 when unchecked. A checkbox
538
        // with a value of '0' is valid, so we can't use empty() here.
539
        if ($option_value === 0 && !$component['extra']['aslist'] && $component['extra']['multiple']) {
540
          // Don't save unchecked values
541
        }
542
        else {
543
          $return[] = $option_value;
544
        }
545
      }
546
      // Handle options that are added through the "other" field. Specifically
547
      // exclude the "select_or_other" value, which is added by the select list.
548
      elseif ($component['extra']['other_option'] && module_exists('select_or_other') && $option_value != 'select_or_other') {
549
        $return[] = $option_value;
550
      }
551
    }
552

    
553
    // If no elements are selected, then save an empty string to indicate that this components should not be defaulted again
554
    if (!$return) {
555
      $return = array('');
556
    }
557
  }
558
  elseif (is_string($value)) {
559
    $return = $value;
560
  }
561

    
562
  return $return;
563
}
564

    
565
/**
566
 * Format the text output for this component.
567
 */
568
function theme_webform_display_select($variables) {
569
  $element = $variables['element'];
570

    
571
  // Flatten the list of options so we can get values easily. These options
572
  // may be translated by hook_webform_display_component_alter().
573
  $options = array();
574
  foreach ($element['#options'] as $key => $value) {
575
    if (is_array($value)) {
576
      foreach ($value as $subkey => $subvalue) {
577
        $options[$subkey] = $subvalue;
578
      }
579
    }
580
    else {
581
      $options[$key] = $value;
582
    }
583
  }
584

    
585
  $items = array();
586
  if ($element['#multiple']) {
587
    foreach ((array) $element['#value'] as $option_value) {
588
      if ($option_value !== '') {
589
        // Administer provided values.
590
        if (isset($options[$option_value])) {
591
          $items[] = $element['#format'] == 'html' ? webform_filter_xss($options[$option_value]) : $options[$option_value];
592
        }
593
        // User-specified in the "other" field.
594
        else {
595
          $items[] = $element['#format'] == 'html' ? check_plain($option_value) : $option_value;
596
        }
597
      }
598
    }
599
  }
600
  else {
601
    if (isset($element['#value'][0]) && $element['#value'][0] !== '') {
602
      // Administer provided values.
603
      if (isset($options[$element['#value'][0]])) {
604
        $items[] = $element['#format'] == 'html' ? webform_filter_xss($options[$element['#value'][0]]) : $options[$element['#value'][0]];
605
      }
606
      // User-specified in the "other" field.
607
      else {
608
        $items[] = $element['#format'] == 'html' ? check_plain($element['#value'][0]) : $element['#value'][0];
609
      }
610
    }
611
  }
612

    
613
  if ($element['#format'] == 'html') {
614
    $output = count($items) > 1 ? theme('item_list', array('items' => $items)) : (isset($items[0]) ? $items[0] : ' ');
615
  }
616
  else {
617
    if (count($items) > 1) {
618
      foreach ($items as $key => $item) {
619
        $items[$key] = ' - ' . $item;
620
      }
621
      $output = implode("\n", $items);
622
    }
623
    else {
624
      $output = isset($items[0]) ? $items[0] : ' ';
625
    }
626
  }
627

    
628
  return $output;
629
}
630

    
631
/**
632
 * Implements _webform_analysis_component().
633
 */
634
function _webform_analysis_select($component, $sids = array(), $single = FALSE, $join = NULL) {
635
  $options = _webform_select_options($component, TRUE);
636

    
637
  // Create a generic query for the component.
638
  $query = db_select('webform_submitted_data', 'wsd', array('fetch' => PDO::FETCH_ASSOC))
639
    ->condition('wsd.nid', $component['nid'])
640
    ->condition('wsd.cid', $component['cid'])
641
    ->condition('wsd.data', '', '<>');
642

    
643
  if ($sids) {
644
    $query->condition('wsd.sid', $sids, 'IN');
645
  }
646

    
647
  if ($join) {
648
    $query->innerJoin($join, 'ws2_', 'wsd.sid = ws2_.sid');
649
  }
650

    
651
  // Clone the query for later use, if needed.
652
  if ($component['extra']['other_option']) {
653
    $count_query = clone $query;
654
    if ($single) {
655
      $other_query = clone $query;
656
    }
657
  }
658

    
659
  $rows = array();
660
  $other = array();
661
  $normal_count = 0;
662

    
663
  if ($options) {
664
    // Gather the normal results first (not "other" options).
665
    $query->addExpression('COUNT(wsd.data)', 'datacount');
666
    $result = $query
667
      ->condition('wsd.data', array_keys($options), 'IN')
668
      ->fields('wsd', array('data'))
669
      ->groupBy('wsd.data')
670
      ->execute();
671
    foreach ($result as $data) {
672
      $display_option = isset($options[$data['data']]) ? $options[$data['data']] : $data['data'];
673
      $rows[$data['data']] = array(webform_filter_xss($display_option), $data['datacount']);
674
      $normal_count += $data['datacount'];
675
    }
676

    
677
    // Order the results according to the normal options array.
678
    $ordered_rows = array();
679
    foreach (array_intersect_key($options, $rows) as $key => $label) {
680
      $ordered_rows[] = $rows[$key];
681
    }
682
    $rows = $ordered_rows;
683
  }
684

    
685

    
686
  // Add a row for displaying the total unknown or user-entered values.
687
  if ($component['extra']['other_option']) {
688
    $count_query->addExpression('COUNT(*)', 'datacount');
689
    $full_count = $count_query->execute()->fetchField();
690
    $other_count = $full_count - $normal_count;
691
    $display_option = !empty($component['extra']['other_text']) ? check_plain($component['extra']['other_text']) : t('Other...');
692
    $other_text = ($other_count && !$single) ? $other_count . ' (' . l(t('view'), 'node/' . $component['nid'] . '/webform-results/analysis/' . $component['cid']) . ')' : $other_count;
693
    $rows[] = array($display_option, $other_text);
694

    
695
    // If showing all results, execute the "other" query and append their rows.
696
    if ($single) {
697
      $other_query->addExpression('COUNT(wsd.data)', 'datacount');
698
      $other_query
699
        ->fields('wsd', array('data'))
700
        ->groupBy('wsd.data');
701
      if ($options) {
702
        $other_query->condition('wsd.data', array_keys($options), 'NOT IN');
703
      }
704
      $other_result = $other_query->execute();
705
      foreach ($other_result as $data) {
706
        $other[] = array(check_plain($data['data']), $data['datacount']);
707
      }
708
      if ($other) {
709
        array_unshift($other, '<strong>' . t('Other responses') . '</strong>');
710
      }
711
    }
712
  }
713

    
714
  return array(
715
    'table_rows' => $rows,
716
    'other_data' => $other,
717
  );
718
}
719

    
720
/**
721
 * Implements _webform_table_component().
722
 */
723
function _webform_table_select($component, $value) {
724
  // Convert submitted 'safe' values to un-edited, original form.
725
  $options = _webform_select_options($component, TRUE);
726

    
727
  $value = (array) $value;
728
  ksort($value, SORT_NUMERIC);
729
  $items = array();
730
  // Set the value as a single string.
731
  foreach ($value as $option_value) {
732
    if ($option_value !== '') {
733
      if (isset($options[$option_value])) {
734
        $items[] = webform_filter_xss($options[$option_value]);
735
      }
736
      else {
737
        $items[] = check_plain($option_value);
738
      }
739
    }
740
  }
741

    
742
  return implode('<br />', $items);
743
}
744

    
745
/**
746
 * Implements _webform_action_set_component().
747
 */
748
function _webform_action_set_select($component, &$element, &$form_state, $value) {
749
  // Set the value as an array for multiple select or single value otherwise.
750
  if ($element['#type'] == 'checkboxes') {
751
    $checkbox_values = $element['#options'];
752
    array_walk($checkbox_values, function(&$value, $key) use ($value) {
753
      $value = (int)(strval($key) === $value);
754
    });
755
  }
756
  else {
757
    $value = $component['extra']['multiple'] ? array($value) : $value;
758
  }
759
  $element['#value'] = $value;
760
  form_set_value($element, $value, $form_state);
761
}
762

    
763
/**
764
 * Implements _webform_csv_headers_component().
765
 */
766
function _webform_csv_headers_select($component, $export_options) {
767
  $headers = array(
768
    0 => array(),
769
    1 => array(),
770
    2 => array(),
771
  );
772

    
773
  if ($component['extra']['multiple'] && $export_options['select_format'] == 'separate') {
774
    $headers[0][] = '';
775
    $headers[1][] = $export_options['header_keys'] ? $component['form_key'] : $component['name'];
776
    $items = _webform_select_options($component, TRUE);
777
    if ($component['extra']['other_option']) {
778
      if ($export_options['header_keys']) {
779
        $other_label = $component['form_key'] . '_other';
780
      }
781
      else {
782
        $other_label = !empty($component['extra']['other_text']) ? check_plain($component['extra']['other_text']) : t('Other...');
783
      }
784
      $items[$other_label] = $other_label;
785
    }
786
    $count = 0;
787
    foreach ($items as $key => $item) {
788
      // Empty column per sub-field in main header.
789
      if ($count != 0) {
790
        $headers[0][] = '';
791
        $headers[1][] = '';
792
      }
793
      if ($export_options['select_keys']) {
794
        $headers[2][] = $key;
795
      }
796
      else {
797
        $headers[2][] = $item;
798
      }
799
      $count++;
800
    }
801
  }
802
  else {
803
    $headers[0][] = '';
804
    $headers[1][] = '';
805
    $headers[2][] = $export_options['header_keys'] ? $component['form_key'] : $component['name'];
806
  }
807
  return $headers;
808
}
809

    
810
/**
811
 * Implements _webform_csv_data_component().
812
 */
813
function _webform_csv_data_select($component, $export_options, $value) {
814
  $options = _webform_select_options($component, TRUE);
815
  $return = array();
816

    
817
  if ($component['extra']['multiple']) {
818
    foreach ($options as $key => $item) {
819
      $index = array_search($key, (array) $value);
820
      if ($index !== FALSE) {
821
        if ($export_options['select_format'] == 'separate') {
822
          $return[] = 'X';
823
        }
824
        else {
825
          $return[] = $export_options['select_keys'] ? $key : $item;
826
        }
827
        unset($value[$index]);
828
      }
829
      elseif ($export_options['select_format'] == 'separate') {
830
        $return[] = '';
831
      }
832
    }
833

    
834
    // Any remaining items in the $value array will be user-added options.
835
    if ($component['extra']['other_option']) {
836
      $return[] = count($value) ? implode(',', $value) : '';
837
    }
838
  }
839
  else {
840
    $key = $value[0];
841
    if ($export_options['select_keys']) {
842
      $return = $key;
843
    }
844
    else {
845
      $return = isset($options[$key]) ? $options[$key] : $key;
846
    }
847
  }
848

    
849
  if ($component['extra']['multiple'] && $export_options['select_format'] == 'compact') {
850
    $return = implode(',', (array) $return);
851
  }
852

    
853
  return $return;
854
}
855

    
856

    
857
/**
858
 * Implements _webform_options_component().
859
 *
860
 * This function is confusingly an alias of _webform_select_options(). However
861
 * this version is intended to be accessed publicly via
862
 * webform_component_invoke(), since it is a Webform "hook", rather than an
863
 * internal, private function. To get the list of select list options for
864
 * a component, use:
865
 *
866
 * @code
867
 * $options = webform_component_invoke($component['type'], 'options', $component);
868
 * @endcode
869
 */
870
function _webform_options_select($component, $flat = FALSE) {
871
  return _webform_select_options($component, $flat);
872
}
873

    
874
/**
875
 * Menu callback; Return a predefined list of select options as JSON.
876
 */
877
function webform_select_options_ajax($source_name = '') {
878
  $info = _webform_select_options_info();
879

    
880
  $component['extra']['options_source'] = $source_name;
881
  if ($source_name && isset($info[$source_name])) {
882
    $options = _webform_select_options_to_text(_webform_select_options($component, FALSE, FALSE));
883
  }
884
  else {
885
    $options = '';
886
  }
887

    
888
  $return = array(
889
    'elementId' => module_exists('options_element') ? 'edit-items-options-options-field-widget' : 'edit-extra-items',
890
    'options' => $options,
891
  );
892

    
893
  drupal_json_output($return);
894
}
895

    
896
/**
897
 * Generate a list of options for a select list.
898
 */
899
function _webform_select_options($component, $flat = FALSE, $filter = TRUE) {
900
  if ($component['extra']['options_source']) {
901
    $options = _webform_select_options_callback($component['extra']['options_source'], $component, $flat);
902
  }
903
  else {
904
    $options = _webform_select_options_from_text($component['extra']['items'], $flat);
905
  }
906

    
907
  // Replace tokens if needed in the options.
908
  if ($filter) {
909
    $node = node_load($component['nid']);
910
    $options = _webform_select_replace_tokens($options, $node);
911
  }
912

    
913
  return isset($options) ? $options : array();
914
}
915

    
916
/**
917
 * Replace tokens in the values of a list of select options.
918
 */
919
function _webform_select_replace_tokens($options, $node) {
920
  foreach ($options as $key => $option) {
921
    if (is_array($option)) {
922
      $options[$key] = _webform_select_replace_tokens($option, $node);
923
    }
924
    else {
925
      $options[$key] = webform_replace_tokens($option, $node);
926
    }
927
  }
928
  return $options;
929
}
930

    
931
/**
932
 * Load Webform select option info from 3rd party modules.
933
 */
934
function _webform_select_options_info() {
935
  static $info;
936
  if (!isset($info)) {
937
    $info = array();
938

    
939
    foreach (module_implements('webform_select_options_info') as $module) {
940
      $additions = module_invoke($module, 'webform_select_options_info');
941
      foreach ($additions as $key => $addition) {
942
        $additions[$key]['module'] = $module;
943
      }
944
      $info = array_merge($info, $additions);
945
    }
946
    drupal_alter('webform_select_options_info', $info);
947
  }
948
  return $info;
949
}
950

    
951
/**
952
 * Execute a select option callback.
953
 *
954
 * @param $name
955
 *   The name of the options group.
956
 * @param $component
957
 *   The full Webform component.
958
 * @param $flat
959
 *   Whether the information returned should exclude any nested groups.
960
 */
961
function _webform_select_options_callback($name, $component, $flat = FALSE) {
962
  $info = _webform_select_options_info();
963

    
964
  // Include any necessary files.
965
  if (isset($info[$name]['file'])) {
966
    $pathinfo = pathinfo($info[$name]['file']);
967
    $path = ($pathinfo['dirname'] ? $pathinfo['dirname'] . '/' : '') . basename($pathinfo['basename'], '.' . $pathinfo['extension']);
968
    module_load_include($pathinfo['extension'], $info[$name]['module'], $path);
969
  }
970

    
971
  // Execute the callback function.
972
  if (isset($info[$name]['options callback']) && function_exists($info[$name]['options callback'])) {
973
    $function = $info[$name]['options callback'];
974

    
975
    $arguments = array();
976
    if (isset($info[$name]['options arguments'])) {
977
      $arguments = $info[$name]['options arguments'];
978
    }
979

    
980
    return $function($component, $flat, $arguments);
981
  }
982
}
983

    
984
/**
985
 * Utility function to split user-entered values from new-line separated
986
 * text into an array of options.
987
 *
988
 * @param string $text
989
 *   Text to be converted into a select option array.
990
 * @param bool $flat
991
 *   Optional. If specified, return the option array and exclude any optgroups.
992
 * @return array
993
 *   An array of options suitable for use as a #options property. Note that
994
 *   values are not filtered and may contain tokens. Individual values should be
995
 *   run through webform_replace_tokens() if displaying to an end-user.
996
 */
997
function _webform_select_options_from_text($text, $flat = FALSE) {
998
  static $option_cache = array();
999

    
1000
  // Keep each processed option block in an array indexed by the MD5 hash of
1001
  // the option text and the value of the $flat variable.
1002
  $md5 = md5($text);
1003

    
1004
  // Check if this option block has been previously processed.
1005
  if (!isset($option_cache[$flat][$md5])) {
1006
    $options = array();
1007
    $rows = array_filter(explode("\n", trim($text)));
1008
    $group = NULL;
1009
    foreach ($rows as $option) {
1010
      $option = trim($option);
1011
      /**
1012
       * If the Key of the option is within < >, treat as an optgroup
1013
       *
1014
       * <Group 1>
1015
       *   creates an optgroup with the label "Group 1"
1016
       *
1017
       * <>
1018
       *   Unsets the current group, allowing items to be inserted at the root element.
1019
       */
1020
      if (preg_match('/^\<([^>]*)\>$/', $option, $matches)) {
1021
        if (empty($matches[1])) {
1022
          unset($group);
1023
        }
1024
        elseif (!$flat) {
1025
          $group = $matches[1];
1026
        }
1027
      }
1028
      elseif (preg_match('/^([^|]+)\|(.*)$/', $option, $matches)) {
1029
        $key = $matches[1];
1030
        $value = $matches[2];
1031
        isset($group) ? $options[$group][$key] = $value : $options[$key] = $value;
1032
      }
1033
      else {
1034
        isset($group) ? $options[$group][$option] = $option : $options[$option] = $option;
1035
      }
1036
    }
1037

    
1038
    $option_cache[$flat][$md5] = $options;
1039
  }
1040

    
1041
  // Return our options from the option_cache array.
1042
  return $option_cache[$flat][$md5];
1043
}
1044

    
1045
/**
1046
 * Convert an array of options into text.
1047
 */
1048
function _webform_select_options_to_text($options) {
1049
  $output = '';
1050
  $previous_key = FALSE;
1051

    
1052
  foreach ($options as $key => $value) {
1053
    // Convert groups.
1054
    if (is_array($value)) {
1055
      $output .= '<' . $key . '>' . "\n";
1056
      foreach ($value as $subkey => $subvalue) {
1057
        $output .= $subkey . '|' . $subvalue . "\n";
1058
      }
1059
      $previous_key = $key;
1060
    }
1061
    // Typical key|value pairs.
1062
    else {
1063
      // Exit out of any groups.
1064
      if (isset($options[$previous_key]) && is_array($options[$previous_key])) {
1065
        $output .= "<>\n";
1066
      }
1067
      // Skip empty rows.
1068
      if ($options[$key] !== '') {
1069
        $output .= $key . '|' . $value . "\n";
1070
      }
1071
      $previous_key = $key;
1072
    }
1073
  }
1074

    
1075
  return $output;
1076
}
1077

    
1078
/**
1079
 * Utility function to shuffle an array while preserving key-value pairs.
1080
 */
1081
function _webform_shuffle_options(&$array) {
1082
  // First shuffle the array keys, then use them as the basis for ordering
1083
  // the options.
1084
  $aux = array();
1085
  $keys = array_keys($array);
1086
  shuffle($keys);
1087
  foreach ($keys as $key) {
1088
    $aux[$key] = $array[$key];
1089
  }
1090
  $array = $aux;
1091
}