Project

General

Profile

Paste
Download (46.5 KB) Statistics
| Branch: | Revision:

root / drupal7 / sites / all / modules / webform / includes / webform.components.inc @ 8d02775b

1
<?php
2

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

    
8
/**
9
 * Provides interface and database handling for editing components of a webform.
10
 *
11
 * @author Nathan Haug <nate@lullabot.com>
12
 */
13

    
14
/**
15
 * Overview page of all components for this webform.
16
 */
17
function webform_components_page($node, $page_number = 1) {
18
  $output = drupal_get_form('webform_components_form', $node);
19

    
20
  return array(
21
    '#theme' => 'webform_components_page',
22
    '#node' => $node,
23
    '#form' => $output,
24
  );
25
}
26

    
27
/**
28
 * Theme the output of the main components page.
29
 *
30
 * This theming provides a way to toggle between the editing modes if Form
31
 * Builder module is available.
32
 */
33
function theme_webform_components_page($variables) {
34
  $form = $variables['form'];
35

    
36
  return drupal_render($form);
37
}
38

    
39
/**
40
 * The table-based listing of all components for this webform.
41
 */
42
function webform_components_form($form, $form_state, $node) {
43
  $form = array(
44
    '#tree' => TRUE,
45
    '#node' => $node,
46
    'components' => array(),
47
  );
48

    
49
  $form['nid'] = array(
50
    '#type' => 'value',
51
    '#value' => $node->nid,
52
  );
53

    
54
  $options = array();
55
  foreach ($node->webform['components'] as $cid => $component) {
56
    $options[$cid] = check_plain($component['name']);
57
    $form['components'][$cid]['cid'] = array(
58
      '#type' => 'hidden',
59
      '#default_value' => $component['cid'],
60
    );
61
    $form['components'][$cid]['pid'] = array(
62
      '#type' => 'hidden',
63
      '#default_value' => $component['pid'],
64
    );
65
    $form['components'][$cid]['weight'] = array(
66
      '#type' => 'textfield',
67
      '#size' => 4,
68
      '#title' => t('Weight'),
69
      '#default_value' => $component['weight'],
70
    );
71
    $form['components'][$cid]['required'] = array(
72
      '#type' => 'checkbox',
73
      '#title' => t('Required'),
74
      '#default_value' => $component['required'],
75
      '#access' => webform_component_feature($component['type'], 'required'),
76
    );
77
    if (!isset($max_weight) || $component['weight'] > $max_weight) {
78
      $max_weight = $component['weight'];
79
    }
80
  }
81

    
82
  $form['add']['name'] = array(
83
    '#type' => 'textfield',
84
    '#size' => 30,
85
    '#maxlength' => NULL,
86
  );
87

    
88
  $form['add']['type'] = array(
89
    '#type' => 'select',
90
    '#options' => webform_component_options(),
91
    '#weight' => 3,
92
    '#default_value' => (isset($_GET['cid']) && isset($node->webform['components'][$_GET['cid']])) ? $node->webform['components'][$_GET['cid']]['type'] : 'textfield',
93
  );
94
  $form['add']['required'] = array(
95
    '#type' => 'checkbox',
96
  );
97
  $form['add']['cid'] = array(
98
    '#type' => 'hidden',
99
    '#default_value' => '',
100
  );
101
  $form['add']['pid'] = array(
102
    '#type' => 'hidden',
103
    '#default_value' => (isset($_GET['cid']) && isset($node->webform['components'][$_GET['cid']])) ? $node->webform['components'][$_GET['cid']]['pid'] : 0,
104
  );
105
  $form['add']['weight'] = array(
106
    '#type' => 'textfield',
107
    '#size' => 4,
108
    '#delta' => count($node->webform['components']) > 10 ? count($node->webform['components']) : 10,
109
  );
110

    
111
  if (isset($_GET['cid']) && isset($node->webform['components'][$_GET['cid']])) {
112
    // Make the new component appear by default directly after the one that was
113
    // just added.
114
    $form['add']['weight']['#default_value'] = $node->webform['components'][$_GET['cid']]['weight'] + 1;
115
    foreach (array_keys($node->webform['components']) as $cid) {
116
      // Adjust all later components also, to make sure none of them have the
117
      // same weight as the new component.
118
      if ($form['components'][$cid]['weight']['#default_value'] >= $form['add']['weight']['#default_value']) {
119
        $form['components'][$cid]['weight']['#default_value']++;
120
      }
121
    }
122
  }
123
  else {
124
    // If no component was just added, the new component should appear by
125
    // default at the end of the list.
126
    $form['add']['weight']['#default_value'] = isset($max_weight) ? $max_weight + 1 : 0;
127
  }
128

    
129
  $form['add']['add'] = array(
130
    '#type' => 'submit',
131
    '#value' => t('Add'),
132
    '#weight' => 45,
133
    '#validate' => array('webform_components_form_add_validate', 'webform_components_form_validate'),
134
    '#submit' => array('webform_components_form_add_submit'),
135
  );
136

    
137
  $form['actions'] = array(
138
    '#type' => 'actions',
139
    '#weight' => 45,
140
  );
141
  $form['actions']['submit'] = array(
142
    '#type' => 'submit',
143
    '#value' => t('Save'),
144
    '#access' => count($node->webform['components']) > 0,
145
  );
146
  $form['warning'] = array(
147
    '#weight' => -1,
148
  );
149
  webform_input_vars_check($form, $form_state, 'components', 'warning');
150

    
151
  return $form;
152
}
153

    
154
/**
155
 * Preprocess variables for theming the webform components form.
156
 */
157
function template_preprocess_webform_components_form(&$variables) {
158
  $form = $variables['form'];
159

    
160
  $form['components']['#attached']['library'][] = array('webform', 'admin');
161

    
162
  // @todo: Attach these. See http://drupal.org/node/732022.
163
  drupal_add_tabledrag('webform-components', 'order', 'sibling', 'webform-weight');
164
  drupal_add_tabledrag('webform-components', 'match', 'parent', 'webform-pid', 'webform-pid', 'webform-cid');
165

    
166
  $node = $form['#node'];
167

    
168
  $header = array(t('Label'), t('Form key'), t('Type'), t('Value'), t('Required'), t('Weight'), array('data' => t('Operations'), 'colspan' => 3));
169
  $rows = array();
170

    
171
  // Add a row containing form elements for a new item.
172
  unset($form['add']['name']['#title'], $form['add_type']['#description']);
173
  $form['add']['name']['#attributes']['placeholder'] = t('New component name');
174
  $form['add']['cid']['#attributes']['class'][] = 'webform-cid';
175
  $form['add']['pid']['#attributes']['class'][] = 'webform-pid';
176
  $form['add']['weight']['#attributes']['class'][] = 'webform-weight';
177
  $row_data = array(
178
    array('data' => drupal_render($form['add']['name']), 'class' => array('webform-component-name'), 'colspan' => 2),
179
    array('data' => drupal_render($form['add']['type']), 'class' => array('webform-component-type')),
180
    array('data' => '', 'class' => array('webform-component-value')),
181
    array('data' => drupal_render($form['add']['required']), 'class' => array('webform-component-required', 'checkbox')),
182
    array('data' => drupal_render($form['add']['cid']) . drupal_render($form['add']['pid']) . drupal_render($form['add']['weight'])),
183
    array('colspan' => 3, 'data' => drupal_render($form['add']['add']), 'class' => array('webform-component-add')),
184
  );
185
  $add_form = array('data' => $row_data, 'class' => array('draggable', 'webform-add-form', 'tabledrag-leaf'));
186

    
187
  if (!empty($node->webform['components'])) {
188
    $component_tree = array();
189
    $page_count = 1;
190
    _webform_components_tree_build($node->webform['components'], $component_tree, 0, $page_count);
191
    $component_tree = _webform_components_tree_sort($component_tree);
192
    // Build the table rows recursively.
193
    foreach ($component_tree['children'] as $cid => $component) {
194
      _webform_components_form_rows($node, $cid, $component, 0, $form, $rows, $add_form);
195
    }
196
  }
197
  else {
198
    $rows[] = array(array('data' => t('No Components, add a component below.'), 'colspan' => 9));
199
    // When there are no components, tabledrag.js will look to the add component
200
    // row to locate the weight column. Because of the name/form_key colspan
201
    // it will mis-count the columns and locate the Required column instead of
202
    // the Weight column.
203
    unset($add_form['data'][0]['colspan']);
204
    array_splice($add_form['data'], 1, 0, '&nbsp;');
205
  }
206

    
207
  // Append the add form if not already printed.
208
  if ($add_form) {
209
    $rows[] = $add_form;
210
  }
211

    
212
  $variables['rows'] = $rows;
213
  $variables['header'] = $header;
214
  $variables['form'] = $form;
215
}
216

    
217
/**
218
 * Recursive function for nesting components into a table.
219
 *
220
 * @see preprocess_webform_components_form()
221
 */
222
function _webform_components_form_rows($node, $cid, $component, $level, &$form, &$rows, &$add_form) {
223
  // Create presentable values.
224
  $form_key = truncate_utf8($component['form_key'], 30, FALSE, TRUE);
225
  $value = truncate_utf8($component['value'], 30, TRUE, TRUE, 20);
226

    
227
  // Remove individual titles from the required and weight fields.
228
  unset($form['components'][$cid]['required']['#title']);
229
  unset($form['components'][$cid]['pid']['#title']);
230
  unset($form['components'][$cid]['weight']['#title']);
231

    
232
  // Add special classes for weight and parent fields.
233
  $form['components'][$cid]['cid']['#attributes']['class'] = array('webform-cid');
234
  $form['components'][$cid]['pid']['#attributes']['class'] = array('webform-pid');
235
  $form['components'][$cid]['weight']['#attributes']['class'] = array('webform-weight');
236

    
237
  // Build indentation for this row.
238
  $indents = '';
239
  for ($n = 1; $n <= $level; $n++) {
240
    $indents .= '<div class="indentation">&nbsp;</div>';
241
  }
242

    
243
  // Add each component to a table row.
244
  $row_data = array(
245
    array('data' => $indents . filter_xss($component['name']), 'class' => array('webform-component-name')),
246
    array('data' => check_plain($form_key), 'class' => array('webform-component-formkey')) +
247
    ((string) $component['form_key'] === (string) $form_key ? array() : array('title' => $component['form_key'])),
248
    array('data' => $form['add']['type']['#options'][$component['type']], 'class' => array('webform-component-type')),
249
    array('data' => ($value == '') ? '-' : check_plain($value), 'class' => array('webform-component-value')) +
250
    ($component['value'] == $value ? array() : array('title' => $component['value'])),
251
    array('data' => drupal_render($form['components'][$cid]['required']), 'class' => array('webform-component-required', 'checkbox')),
252
    array('data' => drupal_render($form['components'][$cid]['cid']) . drupal_render($form['components'][$cid]['pid']) . drupal_render($form['components'][$cid]['weight'])),
253
    array('data' => l(t('Edit'), 'node/' . $node->nid . '/webform/components/' . $cid, array('query' => drupal_get_destination())), 'class' => array('webform-component-edit')),
254
    array('data' => l(t('Clone'), 'node/' . $node->nid . '/webform/components/' . $cid . '/clone', array('query' => drupal_get_destination())), 'class' => array('webform-component-clone')),
255
    array('data' => l(t('Delete'), 'node/' . $node->nid . '/webform/components/' . $cid . '/delete', array('query' => drupal_get_destination())), 'class' => array('webform-component-delete')),
256
  );
257
  $row_class = array('draggable');
258
  if (!webform_component_feature($component['type'], 'group')) {
259
    $row_class[] = 'tabledrag-leaf';
260
  }
261
  if ($component['type'] == 'pagebreak') {
262
    $row_class[] = 'tabledrag-root';
263
    $row_class[] = 'webform-pagebreak';
264
    $row_data[0]['class'][] = 'webform-pagebreak';
265
  }
266
  $rows[] = array('data' => $row_data, 'class' => $row_class, 'data-cid' => $cid);
267
  if (isset($component['children']) && is_array($component['children'])) {
268
    foreach ($component['children'] as $cid => $component) {
269
      _webform_components_form_rows($node, $cid, $component, $level + 1, $form, $rows, $add_form);
270
    }
271
  }
272

    
273
  // Add the add form if this was the last edited component.
274
  if (isset($_GET['cid']) && $component['cid'] == $_GET['cid'] && $add_form) {
275
    $add_form['data'][0]['data'] = $indents . $add_form['data'][0]['data'];
276
    $rows[] = $add_form;
277
    $add_form = FALSE;
278
  }
279
}
280

    
281
/**
282
 * Theme the node components form. Use a table to organize the components.
283
 *
284
 * @return string
285
 *   Formatted HTML form, ready for display.
286
 */
287
function theme_webform_components_form($variables) {
288
  $output = '';
289
  $output .= drupal_render_children($variables['form']['warning']);
290
  $output .= theme('table', array('header' => $variables['header'], 'rows' => $variables['rows'], 'attributes' => array('id' => 'webform-components')));
291
  $output .= drupal_render_children($variables['form']);
292
  return $output;
293
}
294

    
295
/**
296
 * Validate handler for webform_components_form().
297
 */
298
function webform_components_form_validate($form, &$form_state) {
299
  // Check that no two components end up with the same form key.
300
  $duplicates = array();
301
  $parents = array();
302
  if (isset($form_state['values']['components'])) {
303
    foreach ($form_state['values']['components'] as $cid => $component) {
304
      $form_key = (string) $form['#node']->webform['components'][$cid]['form_key'];
305
      if (isset($parents[$component['pid']]) && ($existing = array_search($form_key, $parents[$component['pid']], TRUE)) && $existing !== FALSE) {
306
        if (!isset($duplicates[$form_key])) {
307
          $duplicates[$form_key] = array($existing);
308
        }
309
        $duplicates[$form_key][] = $cid;
310
      }
311
      $parents[$component['pid']][$cid] = $form_key;
312
    }
313
  }
314

    
315
  if (!empty($duplicates)) {
316
    $error = t('The form order failed to save because the following elements have same form keys and are under the same parent. Edit each component and give them a unique form key, then try moving them again.');
317
    $items = array();
318
    foreach ($duplicates as $form_key => $cids) {
319
      foreach ($cids as $cid) {
320
        $items[] = webform_filter_xss($form['#node']->webform['components'][$cid]['name']);
321
      }
322
    }
323

    
324
    form_error($form['components'], $error . theme('item_list', array('items' => $items)));
325
  }
326
}
327

    
328
/**
329
 * Validate handler for webform_component_form() when adding a new component.
330
 */
331
function webform_components_form_add_validate($form, &$form_state) {
332
  // Check that the entered component name is valid.
333
  if (drupal_strlen(trim($form_state['values']['add']['name'])) <= 0) {
334
    form_error($form['add']['name'], t('When adding a new component, the name field is required.'));
335
  }
336
}
337

    
338
/**
339
 * Submit handler for webform_components_form() to save component order.
340
 */
341
function webform_components_form_submit($form, &$form_state) {
342
  $node = node_load($form_state['values']['nid']);
343

    
344
  // Update all required and weight values.
345
  $changes = FALSE;
346
  foreach ($node->webform['components'] as $cid => $component) {
347
    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']) {
348
      $changes = TRUE;
349
      $node->webform['components'][$cid]['weight'] = $form_state['values']['components'][$cid]['weight'];
350
      $node->webform['components'][$cid]['required'] = $form_state['values']['components'][$cid]['required'];
351
      $node->webform['components'][$cid]['pid'] = $form_state['values']['components'][$cid]['pid'];
352
    }
353
  }
354

    
355
  if ($changes) {
356
    node_save($node);
357
  }
358

    
359
  drupal_set_message(t('The component positions and required values have been updated.'));
360
}
361

    
362
/**
363
 * Submit handler for webform_components_form() that adds a new component.
364
 */
365
function webform_components_form_add_submit($form, &$form_state) {
366
  $node = node_load($form_state['values']['nid']);
367

    
368
  $component = $form_state['values']['add'];
369

    
370
  // Set the values in the query string for the add component page.
371
  $query = array(
372
    'name' => $component['name'],
373
    'required' => $component['required'],
374
    'pid' => $component['pid'],
375
    'weight' => $component['weight'],
376
  );
377

    
378
  // Forward the "destination" query string value to the next form.
379
  if (isset($_GET['destination'])) {
380
    $query['destination'] = $_GET['destination'];
381
    unset($_GET['destination']);
382
    drupal_static_reset('drupal_get_destination');
383
  }
384
  $form_state['redirect'] = array('node/' . $node->nid . '/webform/components/new/' . $component['type'], array('query' => $query));
385
}
386

    
387
/**
388
 * Form to configure a webform component.
389
 */
390
function webform_component_edit_form($form, $form_state, $node, $component, $clone = FALSE) {
391
  drupal_set_title(t('Edit component: @name', array('@name' => $component['name'])), PASS_THROUGH);
392

    
393
  $form['#node'] = $node;
394
  $form['#tree'] = TRUE;
395

    
396
  // Print the correct field type specification.
397
  // We always need: name and description.
398
  $form['type'] = array(
399
    '#type' => 'value',
400
    '#value' => $component['type'],
401
  );
402
  $form['nid'] = array(
403
    '#type' => 'value',
404
    '#value' => $node->nid,
405
  );
406
  $form['cid'] = array(
407
    '#type' => 'value',
408
    '#value' => isset($component['cid']) ? $component['cid'] : NULL,
409
  );
410
  $form['clone'] = array(
411
    '#type' => 'value',
412
    '#value' => $clone,
413
  );
414

    
415
  if (webform_component_feature($component['type'], 'title')) {
416
    $form['name'] = array(
417
      '#type' => 'textfield',
418
      '#default_value' => $component['name'],
419
      '#title' => t('Label'),
420
      '#description' => t('This is used as a descriptive label when displaying this form element.'),
421
      '#required' => TRUE,
422
      '#weight' => -10,
423
      '#maxlength' => NULL,
424
    );
425
  }
426

    
427
  $form['form_key'] = array(
428
    '#type' => 'textfield',
429
    '#default_value' => empty($component['form_key']) ? _webform_safe_name($component['name']) : $component['form_key'],
430
    '#title' => t('Form Key'),
431
    '#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.'),
432
    '#required' => TRUE,
433
    '#weight' => -9,
434
  );
435

    
436
  $form['extra'] = array();
437
  if (webform_component_feature($component['type'], 'description')) {
438
    $form['extra']['description'] = array(
439
      '#type' => 'textarea',
440
      '#default_value' => isset($component['extra']['description']) ? $component['extra']['description'] : '',
441
      '#title' => t('Description'),
442
      '#description' => t('A short description of the field used as help for the user when he/she uses the form.') . ' ' . theme('webform_token_help'),
443
      '#weight' => -1,
444
    );
445
  }
446

    
447
  // Display settings.
448
  $form['display'] = array(
449
    '#type' => 'fieldset',
450
    '#title' => t('Display'),
451
    '#collapsible' => TRUE,
452
    '#collapsed' => FALSE,
453
    '#weight' => 8,
454
  );
455
  if (webform_component_feature($component['type'], 'title_display')) {
456
    $inline_option = webform_component_feature($component['type'], 'title_inline') ? array('inline' => t('Inline')) : array();
457
    $internal_option = webform_component_feature($component['type'], 'title_internal') ? array('internal' => t('Inside the component')) : array();
458
    if ($inline_option || $internal_option) {
459
      $form['display']['title_display'] = array(
460
        '#type' => 'select',
461
        '#title' => t('Label display'),
462
        '#default_value' => !empty($component['extra']['title_display']) ? $component['extra']['title_display'] : 'before',
463
        '#options' => array('before' => t('Above')) +
464
                      $inline_option +
465
                      $internal_option +
466
                      array('none' => t('None')),
467
        '#description' => t("Determines the placement of the component's label."),
468
      );
469
    }
470
    else {
471
      $form['display']['title_display'] = array(
472
        '#type' => 'checkbox',
473
        '#title' => t('Hide label'),
474
        '#default_value' => strcmp($component['extra']['title_display'], 'none') === 0,
475
        '#return_value' => 'none',
476
        '#description' => t('Do not display the label of this component.'),
477
      );
478
    }
479
    $form['display']['title_display']['#weight'] = 8;
480
    $form['display']['title_display']['#parents'] = array('extra', 'title_display');
481
  }
482

    
483
  if (webform_component_feature($component['type'], 'description')) {
484
    $form['display']['description_above'] = array(
485
      '#type' => 'checkbox',
486
      '#default_value' => !empty($component['extra']['description_above']),
487
      '#title' => t('Description above field'),
488
      '#description' => t('Place the description above &mdash; rather than below &mdash; the field.'),
489
      '#weight' => 8.5,
490
      '#parents' => array('extra', 'description_above'),
491
    );
492
  }
493

    
494
  if (webform_component_feature($component['type'], 'private')) {
495
    // May user mark fields as Private?
496
    $form['display']['private'] = array(
497
      '#type' => 'checkbox',
498
      '#title' => t('Private'),
499
      '#default_value' => ($component['extra']['private'] == '1' ? TRUE : FALSE),
500
      '#description' => t('Private fields are shown only to users with results access.'),
501
      '#weight' => 45,
502
      '#parents' => array('extra', 'private'),
503
      '#disabled' => empty($node->nid) ? FALSE : !webform_results_access($node),
504
    );
505
  }
506

    
507
  if (webform_component_feature($component['type'], 'wrapper_classes')) {
508
    $form['display']['wrapper_classes'] = array(
509
      '#type' => 'textfield',
510
      '#title' => t('Wrapper CSS classes'),
511
      '#default_value' => isset($component['extra']['wrapper_classes']) ? $component['extra']['wrapper_classes'] : '',
512
      '#description' => t('Apply a class to the wrapper around both the field and its label. Separate multiple by spaces.'),
513
      '#weight' => 50,
514
      '#parents' => array('extra', 'wrapper_classes'),
515
    );
516
  }
517
  if (webform_component_feature($component['type'], 'css_classes')) {
518
    $form['display']['css_classes'] = array(
519
      '#type' => 'textfield',
520
      '#title' => t('CSS classes'),
521
      '#default_value' => isset($component['extra']['css_classes']) ? $component['extra']['css_classes'] : '',
522
      '#description' => t('Apply a class to the field. Separate multiple by spaces.'),
523
      '#weight' => 51,
524
      '#parents' => array('extra', 'css_classes'),
525
    );
526
  }
527

    
528
  // Validation settings.
529
  $form['validation'] = array(
530
    '#type' => 'fieldset',
531
    '#title' => t('Validation'),
532
    '#collapsible' => TRUE,
533
    '#collapsed' => FALSE,
534
    '#weight' => 5,
535
  );
536
  if (webform_component_feature($component['type'], 'required')) {
537
    $form['validation']['required'] = array(
538
      '#type' => 'checkbox',
539
      '#title' => t('Required'),
540
      '#default_value' => ($component['required'] == '1' ? TRUE : FALSE),
541
      '#description' => t('Check this option if the user must enter a value.'),
542
      '#weight' => -1,
543
      '#parents' => array('required'),
544
    );
545
  }
546

    
547
  // Position settings, only shown if JavaScript is disabled.
548
  $form['position'] = array(
549
    '#type' => 'fieldset',
550
    '#title' => t('Position'),
551
    '#collapsible' => TRUE,
552
    '#collapsed' => TRUE,
553
    '#tree' => FALSE,
554
    '#weight' => 20,
555
    '#attributes' => array('class' => array('webform-position')),
556
  );
557

    
558
  $options = array('0' => t('Root'));
559
  foreach ($node->webform['components'] as $existing_cid => $value) {
560
    if (webform_component_feature($value['type'], 'group') && (!isset($component['cid']) || $existing_cid != $component['cid'])) {
561
      $options[$existing_cid] = $value['name'];
562
    }
563
  }
564
  $form['position']['pid'] = array(
565
    '#type' => 'select',
566
    '#title' => t('Parent'),
567
    '#default_value' => $component['pid'],
568
    '#description' => t('Optional. You may organize your form by placing this component inside another fieldset.'),
569
    '#options' => $options,
570
    '#access' => count($options) > 1,
571
    '#weight' => 3,
572
  );
573
  $form['position']['weight'] = array(
574
    '#type' => 'textfield',
575
    '#size' => 4,
576
    '#title' => t('Weight'),
577
    '#default_value' => $component['weight'],
578
    '#description' => t('Optional. In the menu, the heavier items will sink and the lighter items will be positioned nearer the top.'),
579
    '#weight' => 4,
580
  );
581

    
582
  // Add the fields specific to this component type:
583
  $additional_form_elements = (array) webform_component_invoke($component['type'], 'edit', $component, $form, $form_state);
584
  if (empty($additional_form_elements)) {
585
    drupal_set_message(t('The webform component of type @type does not have an edit function defined.', array('@type' => $component['type'])));
586
  }
587

    
588
  // Merge the additional fields with the current fields:
589
  if (isset($additional_form_elements['extra'])) {
590
    $form['extra'] = array_merge($form['extra'], $additional_form_elements['extra']);
591
    unset($additional_form_elements['extra']);
592
  }
593
  if (isset($additional_form_elements['position'])) {
594
    $form['position'] = array_merge($form['position'], $additional_form_elements['position']);
595
    unset($additional_form_elements['position']);
596
  }
597
  if (isset($additional_form_elements['display'])) {
598
    $form['display'] = array_merge($form['display'], $additional_form_elements['display']);
599
    unset($additional_form_elements['display']);
600
  }
601
  if (isset($additional_form_elements['validation'])) {
602
    $form['validation'] = array_merge($form['validation'], $additional_form_elements['validation']);
603
    unset($additional_form_elements['validation']);
604
  }
605
  elseif (count(element_children($form['validation'])) == 0) {
606
    unset($form['validation']);
607
  }
608
  $form = array_merge($form, $additional_form_elements);
609
  // Ensure that the webform admin library is attached, possibly in addition to
610
  // component-specific attachments.
611
  $form['#attached']['library'][] = array('webform', 'admin');
612

    
613
  // Add the submit button.
614
  $form['actions'] = array(
615
    '#type' => 'actions',
616
    '#weight' => 50,
617
  );
618
  $form['actions']['submit'] = array(
619
    '#type' => 'submit',
620
    '#value' => t('Save component'),
621
  );
622

    
623
  // Remove fieldsets without any child form controls.
624
  foreach (element_children($form) as $group_key) {
625
    $group = $form[$group_key];
626
    if (isset($group['#type']) && $group['#type'] === 'fieldset' && !element_children($group)) {
627
      unset($form[$group_key]);
628
    }
629
  }
630

    
631
  return $form;
632
}
633

    
634
/**
635
 * Field name validation for the webform unique key. Must be alphanumeric.
636
 */
637
function webform_component_edit_form_validate($form, &$form_state) {
638
  $node = $form['#node'];
639

    
640
  if (!preg_match('/^[a-z0-9_]+$/i', $form_state['values']['form_key'])) {
641
    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'])));
642
  }
643

    
644
  foreach ($node->webform['components'] as $cid => $component) {
645
    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)) {
646
      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'])));
647
    }
648
  }
649
}
650

    
651
/**
652
 * Submit handler for webform_component_edit_form().
653
 */
654
function webform_component_edit_form_submit($form, &$form_state) {
655
  // Ensure a webform record exists.
656
  $node = $form['#node'];
657
  webform_ensure_record($node);
658

    
659
  // Remove extra values that match the default.
660
  if (isset($form_state['values']['extra'])) {
661
    $default = array('type' => $form_state['values']['type'], 'extra' => array());
662
    webform_component_defaults($default);
663
    foreach ($form_state['values']['extra'] as $key => $value) {
664
      if (isset($default['extra'][$key]) && $default['extra'][$key] === $value) {
665
        unset($form_state['values']['extra'][$key]);
666
      }
667
    }
668
  }
669

    
670
  // Remove empty attribute values.
671
  if (isset($form_state['values']['extra']['attributes'])) {
672
    foreach ($form_state['values']['extra']['attributes'] as $key => $value) {
673
      if ($value === '') {
674
        unset($form_state['values']['extra']['attributes'][$key]);
675
      }
676
    }
677
  }
678

    
679
  if ($form_state['values']['clone']) {
680
    webform_component_clone($node, $form_state['values']);
681
    drupal_set_message(t('Component %name cloned.', array('%name' => $form_state['values']['name'])));
682
  }
683
  elseif (!empty($form_state['values']['cid'])) {
684
    webform_component_update($form_state['values']);
685
    drupal_set_message(t('Component %name updated.', array('%name' => $form_state['values']['name'])));
686
  }
687
  else {
688
    $cid = webform_component_insert($form_state['values']);
689
    drupal_set_message(t('New component %name added.', array('%name' => $form_state['values']['name'])));
690
  }
691

    
692
  // Since Webform components have been updated but the node itself has not
693
  // been saved, it is necessary to explicitly clear the cache to make sure
694
  // the updated webform is visible to anonymous users. This resets the page
695
  // and block caches (only).
696
  cache_clear_all();
697

    
698
  // Refresh the entity cache, should it be cached in persistent storage.
699
  entity_get_controller('node')->resetCache(array($node->nid));
700

    
701
  $form_state['redirect'] = array('node/' . $node->nid . '/webform/components', isset($cid) ? array('query' => array('cid' => $cid)) : array());
702
}
703

    
704
/**
705
 * Form to confirm deletion of a component.
706
 */
707
function webform_component_delete_form($form, $form_state, $node, $component) {
708
  $cid = $component['cid'];
709

    
710
  $form = array();
711
  $form['node'] = array(
712
    '#type' => 'value',
713
    '#value' => $node,
714
  );
715
  $form['component'] = array(
716
    '#type' => 'value',
717
    '#value' => $component,
718
  );
719

    
720
  $component_type = $node->webform['components'][$cid]['type'];
721
  if (webform_component_feature($component_type, 'group')) {
722
    $question = t('Delete the %name fieldset?', array('%name' => $node->webform['components'][$cid]['name']));
723
    $description = t('This will immediately delete the %name @type component and all nested components within %name from the %webform webform. This cannot be undone.',
724
      array(
725
        '%name' => $node->webform['components'][$cid]['name'],
726
        '@type' => webform_component_property($component_type, 'label'),
727
        '%webform' => $node->title,
728
      ));
729
  }
730
  else {
731
    $question = t('Delete the %name component?', array('%name' => $node->webform['components'][$cid]['name']));
732
    $description = t('This will immediately delete the %name component from the %webform webform. This cannot be undone.', array('%name' => $node->webform['components'][$cid]['name'], '%webform' => $node->title));
733
  }
734

    
735
  return confirm_form($form, $question, 'node/' . $node->nid . '/webform/components', $description, t('Delete'));
736
}
737

    
738
/**
739
 * Submit handler for webform_component_delete_form().
740
 */
741
function webform_component_delete_form_submit($form, &$form_state) {
742
  // Delete the component.
743
  $node = $form_state['values']['node'];
744
  $component = $form_state['values']['component'];
745
  webform_component_delete($node, $component);
746
  drupal_set_message(t('Component %name deleted.', array('%name' => $component['name'])));
747

    
748
  // Check if this webform still contains any information.
749
  unset($node->webform['components'][$component['cid']]);
750
  webform_check_record($node);
751

    
752
  // Since Webform components have been updated but the node itself has not
753
  // been saved, it is necessary to explicitly clear the cache to make sure
754
  // the updated webform is visible to anonymous users. This resets the page
755
  // and block caches (only).
756
  cache_clear_all();
757

    
758
  // Refresh the entity cache, should it be cached in persistent storage.
759
  entity_get_controller('node')->resetCache(array($node->nid));
760

    
761
  $form_state['redirect'] = 'node/' . $node->nid . '/webform/components';
762
}
763

    
764
/**
765
 * Insert a new component into the database.
766
 *
767
 * @param $component
768
 *   A full component containing fields from the component form.
769
 *
770
 * @return false|int
771
 *   On success return identifier for the components within node.
772
 *   FALSE on failure
773
 */
774
function webform_component_insert(&$component) {
775
  // Allow modules to modify the component before saving.
776
  foreach (module_implements('webform_component_presave') as $module) {
777
    $function = $module . '_webform_component_presave';
778
    $function($component);
779
  }
780

    
781
  $component['value'] = isset($component['value']) ? $component['value'] : NULL;
782
  $component['required'] = isset($component['required']) ? $component['required'] : 0;
783
  $component['extra']['private'] = isset($component['extra']['private']) ? $component['extra']['private'] : 0;
784

    
785
  if (!isset($component['cid'])) {
786
    if (lock_acquire('webform_component_insert_' . $component['nid'], 5)) {
787
      $next_id_query = db_select('webform_component')->condition('nid', $component['nid']);
788
      $next_id_query->addExpression('MAX(cid) + 1', 'cid');
789
      $component['cid'] = $next_id_query->execute()->fetchField();
790
      if ($component['cid'] == NULL) {
791
        $component['cid'] = 1;
792
      }
793
      lock_release('webform_component_insert_' . $component['nid']);
794
    }
795
    else {
796
      watchdog('webform', 'A Webform component could not be saved because a timeout occurred while trying to acquire a lock for the node. Details: <pre>@component</pre>', array('@component' => print_r($component, TRUE)));
797
      return FALSE;
798
    }
799
  }
800

    
801
  db_insert('webform_component')
802
    ->fields(array(
803
      'nid' => $component['nid'],
804
      'cid' => $component['cid'],
805
      'pid' => $component['pid'],
806
      'form_key' => $component['form_key'],
807
      'name' => $component['name'],
808
      'type' => $component['type'],
809
      'value' => (string) $component['value'],
810
      'extra' => serialize($component['extra']),
811
      'required' => $component['required'],
812
      'weight' => $component['weight'],
813
    ))
814
    ->execute();
815

    
816
  // Post-insert actions.
817
  module_invoke_all('webform_component_insert', $component);
818

    
819
  return $component['cid'];
820
}
821

    
822
/**
823
 * Update an existing component with new values.
824
 *
825
 * @param $component
826
 *   A full component containing a nid, cid, and all other fields from the
827
 *   component form. Additional properties are stored in the extra array.
828
 */
829
function webform_component_update($component) {
830
  // Allow modules to modify the component before saving.
831
  foreach (module_implements('webform_component_presave') as $module) {
832
    $function = $module . '_webform_component_presave';
833
    $function($component);
834
  }
835

    
836
  $component['value'] = isset($component['value']) ? $component['value'] : NULL;
837
  $component['required'] = isset($component['required']) ? $component['required'] : 0;
838
  $component['extra']['private'] = isset($component['extra']['private']) ? $component['extra']['private'] : 0;
839
  db_update('webform_component')
840
    ->fields(array(
841
      'pid' => $component['pid'],
842
      'form_key' => $component['form_key'],
843
      'name' => $component['name'],
844
      'type' => $component['type'],
845
      'value' => isset($component['value']) ? $component['value'] : '',
846
      'extra' => serialize($component['extra']),
847
      'required' => $component['required'],
848
      'weight' => $component['weight'],
849
    ))
850
    ->condition('nid', $component['nid'])
851
    ->condition('cid', $component['cid'])
852
    ->execute();
853

    
854
  // Post-update actions.
855
  module_invoke_all('webform_component_update', $component);
856
}
857

    
858
/**
859
 * Delete a Webform component.
860
 */
861
function webform_component_delete($node, $component) {
862
  // Check if a delete function is available for this component. If so,
863
  // load all submissions and allow the component to delete each one.
864
  webform_component_include($component['type']);
865
  $delete_function = '_webform_delete_' . $component['type'];
866
  if (function_exists($delete_function)) {
867
    module_load_include('inc', 'webform', 'includes/webform.submissions');
868
    $submissions = webform_get_submissions($node->nid);
869
    foreach ($submissions as $submission) {
870
      if (isset($submission->data[$component['cid']])) {
871
        webform_component_invoke($component['type'], 'delete', $component, $submission->data[$component['cid']]);
872
      }
873
    }
874
  }
875

    
876
  // Remove database entries.
877
  db_delete('webform_component')
878
    ->condition('nid', $node->nid)
879
    ->condition('cid', $component['cid'])
880
    ->execute();
881
  db_delete('webform_submitted_data')
882
    ->condition('nid', $node->nid)
883
    ->condition('cid', $component['cid'])
884
    ->execute();
885

    
886
  // Delete any conditionals dependent on this component.
887
  module_load_include('inc', 'webform', 'includes/webform.conditionals');
888
  foreach ($node->webform['conditionals'] as $rgid => &$conditional) {
889
    $specs = array(
890
      array(
891
        'field' => 'rules',
892
        'table' => 'webform_conditional_rules',
893
        'component' => 'source',
894
        'component_type' => 'source_type',
895
        'index' => 'rid',
896
      ),
897
      array(
898
        'field' => 'actions',
899
        'table' => 'webform_conditional_actions',
900
        'component' => 'target',
901
        'component_type' => 'target_type',
902
        'index' => 'aid',
903
      ),
904
    );
905
    foreach ($specs as $spec) {
906
      $deleted = array();
907
      $field = $spec['field'];
908
      foreach ($conditional[$field] as $key => $thing) {
909
        if ($thing[$spec['component_type']] === 'component' && $thing[$spec['component']] == $component['cid']) {
910
          $deleted[$key] = $key;
911
          unset($conditional[$field][$key]);
912
        }
913
      }
914
      if ($spec['field'] == 'rules') {
915
        // Rules deleted because of the source component being deleted may have left
916
        // empty sub-conditionals. Delete them, and then the entire rule group if
917
        // there aren't any rules left.
918
        $deleted += webform_delete_empty_subconditionals($conditional);
919
      }
920
      // Delete the conditional if this component is the only source / target.
921
      if (empty($conditional[$field])) {
922
        webform_conditional_delete($node, $conditional);
923
        // Also delete the conditional from the $node so it is not re-created
924
        // later on in webform_node_update().
925
        unset($node->webform['conditionals'][$conditional['rgid']]);
926
        // Loop exit.
927
        break;
928
      }
929
      // Remove the deleted rules / actions from the database.
930
      foreach ($deleted as $key) {
931
        db_delete($spec['table'])
932
          ->condition('nid', $node->nid)
933
          ->condition('rgid', $rgid)
934
          ->condition($spec['index'], $key)
935
          ->execute();
936
      }
937
    }
938
  }
939

    
940
  // Delete all elements under this element.
941
  $result = db_select('webform_component', 'c')
942
    ->fields('c')
943
    ->condition('nid', $node->nid)
944
    ->condition('pid', $component['cid'])
945
    ->execute();
946
  foreach ($result as $row) {
947
    $child_component = $node->webform['components'][$row->cid];
948
    webform_component_delete($node, $child_component);
949
  }
950

    
951
  // Post-delete actions.
952
  module_invoke_all('webform_component_delete', $component);
953
}
954

    
955
/**
956
 * Recursively insert components into the database.
957
 *
958
 * @param $node
959
 *   The node object containing the current webform.
960
 * @param $component
961
 *   A full component containing fields from the component form.
962
 *
963
 * @return false|int
964
 *   On success return identifier for the components within node.
965
 *   FALSE on failure
966
 */
967
function webform_component_clone(&$node, &$component) {
968
  $original_cid = $component['cid'];
969
  $component['cid'] = NULL;
970
  $new_cid = webform_component_insert($component);
971
  $component['cid'] = $new_cid;
972
  if (webform_component_feature($component['type'], 'group')) {
973
    foreach ($node->webform['components'] as $cid => $child_component) {
974
      if ($child_component['pid'] == $original_cid) {
975
        $child_component['pid'] = $new_cid;
976
        webform_component_clone($node, $child_component);
977
      }
978
    }
979
  }
980
  return $new_cid;
981
}
982

    
983
/**
984
 * Check if a component has a particular feature.
985
 *
986
 * @see hook_webform_component_info()
987
 */
988
function webform_component_feature($type, $feature) {
989
  $components = webform_components();
990
  $defaults = array(
991
    'analysis' => TRUE,
992
    'csv' => TRUE,
993
    'default_value' => TRUE,
994
    'description' => TRUE,
995
    'email' => TRUE,
996
    'email_address' => FALSE,
997
    'email_name' => FALSE,
998
    'required' => TRUE,
999
    'title' => TRUE,
1000
    'title_display' => TRUE,
1001
    'title_inline' => TRUE,
1002
    'title_internal' => FALSE,
1003
    'conditional' => TRUE,
1004
    'conditional_action_set' => FALSE,
1005
    'spam_analysis' => FALSE,
1006
    'group' => FALSE,
1007
    'attachment' => FALSE,
1008
    'private' => TRUE,
1009
    'placeholder' => FALSE,
1010
    'wrapper_classes' => TRUE,
1011
    'css_classes' => TRUE,
1012
    'views_range' => FALSE,
1013
  );
1014
  return isset($components[$type]['features'][$feature]) ? $components[$type]['features'][$feature] : !empty($defaults[$feature]);
1015
}
1016

    
1017
/**
1018
 * Get a component property from the component definition.
1019
 *
1020
 * @see hook_webform_component_info()
1021
 */
1022
function webform_component_property($type, $property) {
1023
  $components = webform_components();
1024
  $defaults = array(
1025
    'conditional_type' => 'string',
1026
  );
1027
  return isset($components[$type][$property]) ? $components[$type][$property] : $defaults[$property];
1028
}
1029

    
1030
/**
1031
 * Create a list of components suitable for a select list.
1032
 *
1033
 * @param $node
1034
 *   The webform node.
1035
 * @param $component_filter
1036
 *   Either an array of components, or a string containing a feature name (csv,
1037
 *   email, required, conditional) on which this list of components will be
1038
 *   restricted.
1039
 * @param $prefix_group
1040
 *   TRUE to indent with a hyphen, or 'path" to Prepend enclosing group (for example,
1041
 *   fieldset) name(s)
1042
 * @param $pagebreak_groups
1043
 *   Determine if pagebreaks should be converted to option groups in the
1044
 *   returned list of options.
1045
 *
1046
 * @return array
1047
 *   An array of options.
1048
 */
1049
function webform_component_list($node, $component_filter = NULL, $prepend_group = TRUE, $pagebreak_groups = FALSE) {
1050
  $options = array();
1051
  $page_names = array();
1052
  $parent_names = array();
1053

    
1054
  $components = is_array($component_filter) ? $component_filter : $node->webform['components'];
1055
  $feature = is_string($component_filter) ? $component_filter : NULL;
1056

    
1057
  foreach ($components as $cid => $component) {
1058
    // If this component is a group (for example, fieldset), then remember its name, including any parents.
1059
    if ($prepend_group && webform_component_feature($component['type'], 'group')) {
1060
      $parent_names[$cid] = ($component['pid'] ? $parent_names[$component['pid']] : '') .
1061
                            ($prepend_group === 'path' ? $component['name'] . ': ' : '-');
1062
    }
1063
    $page_num = $component['page_num'];
1064
    // If this component is a pagebreak, then generate an option group, ensuring a unique name.
1065
    if ($pagebreak_groups && $component['type'] == 'pagebreak') {
1066
      $page_name = $component['name'];
1067

    
1068
      // When a $page_name consists only of digits, append a space to ensure it
1069
      // is never the same as a $cid.
1070
      if ((string) $page_name === (string) (int) $page_name) {
1071
        $page_name .= ' ';
1072
      }
1073

    
1074
      $copy = 1;
1075
      while (in_array($page_name, $page_names)) {
1076
        $page_name = $component['name'] . '_' . ++$copy;
1077
      }
1078
      $page_names[$page_num] = $page_name;
1079
    }
1080
    // If this component should be included in the options, add it with any prefix, in a page group, as needed.
1081
    if (!isset($feature) || webform_component_feature($component['type'], $feature) || $prepend_group === TRUE) {
1082
      $prefix = ($prepend_group && $component['pid']) ? $parent_names[$component['pid']] : '';
1083
      if ($pagebreak_groups && $page_num > 1) {
1084
        $options[$page_names[$page_num]][$cid] = $prefix . $component['name'];
1085
      }
1086
      else {
1087
        $options[$cid] = $prefix . $component['name'];
1088
      }
1089
    }
1090
  }
1091

    
1092
  return $options;
1093
}
1094

    
1095
/**
1096
 * A Form API process function to expand a component list into checkboxes.
1097
 */
1098
function webform_component_select($element) {
1099
  // Split the select list into checkboxes.
1100
  foreach ($element['#options'] as $key => $label) {
1101
    $label_length = strlen($label);
1102
    $label = preg_replace('/^(\-)+/', '', $label);
1103
    $indents = $label_length - strlen($label);
1104
    $element[$key] = array(
1105
      '#title' => $label,
1106
      '#type' => 'checkbox',
1107
      '#default_value' => array_search($key, $element['#value']) !== FALSE,
1108
      '#return_value' => $key,
1109
      '#parents' => array_merge($element['#parents'], array($key)),
1110
      '#indent' => $indents,
1111
    );
1112
  }
1113

    
1114
  $element['#theme_wrappers'] = array();
1115
  $element['#type'] = 'webform_component_select';
1116
  $element['#theme'] = 'webform_component_select';
1117
  $element['#attached'] = array(
1118
    'library' => array(
1119
      array('webform', 'admin'),
1120
      array('system', 'drupal.collapse'),
1121
    ),
1122
    'js' => array(
1123
      'misc/tableselect.js' => array(),
1124
    ),
1125
  );
1126

    
1127
  return $element;
1128
}
1129

    
1130
/**
1131
 * Theme the contents of a Webform component select element.
1132
 */
1133
function theme_webform_component_select($variables) {
1134
  $element = $variables['element'];
1135

    
1136
  $rows = array();
1137
  $header = array();
1138
  if (!isset($element['#all_checkbox']) || $element['#all_checkbox']) {
1139
    $header = array(array('class' => array('select-all'), 'data' => ' ' . t('Include all components')));
1140
  }
1141
  foreach (element_children($element) as $key) {
1142
    if ($key != 'suffix') {
1143
      $rows[] = array(
1144
        theme('indentation', array('size' => $element[$key]['#indent'])) . drupal_render($element[$key]),
1145
      );
1146
    }
1147
  }
1148

    
1149
  $element['#type'] = 'fieldset';
1150
  $element['#value'] = NULL;
1151
  $element['#attributes']['class'] = array('webform-component-select-table');
1152
  if (!isset($element['#collapsible']) || $element['#collapsible']) {
1153
    $element['#attributes']['class'][] = 'collapsible';
1154
  }
1155
  if (!isset($element['#collapsed']) || $element['#collapsed']) {
1156
    $element['#attributes']['class'][] = 'collapsed';
1157
  }
1158

    
1159
  if (empty($rows)) {
1160
    $element['#children'] = t('No available components.');
1161
  }
1162
  else {
1163
    $element['#children'] = '<div class="webform-component-select-wrapper">' . theme('table', array('header' => $header, 'rows' => $rows, 'sticky' => FALSE)) . '</div>';
1164
  }
1165

    
1166
  if (isset($element['suffix'])) {
1167
    $element['#children'] .= '<div class="webform-component-select-suffix">' . drupal_render($element['suffix']) . '</div>';
1168
  }
1169

    
1170
  return theme('fieldset', array('element' => $element));
1171
}
1172

    
1173
/**
1174
 * Find a component's parents within a node.
1175
 *
1176
 * @param object $node
1177
 *   The webform node.
1178
 * @param array $component
1179
 *   The component to start with.
1180
 * @param string|true $what_to_return
1181
 *   If TRUE, return complete component arrays. Otherwise, return the property
1182
 *   of each component named in this parametre.
1183
 *
1184
 * @return array
1185
 *   An array with a value for each parent and for the start component, in order
1186
 *   ending with start component. What the value is is controlled by
1187
 *   $what_to_return.
1188
 */
1189
function webform_component_parent_keys($node, array $component, $what_to_return = 'form_key') {
1190
  $parents = array(($what_to_return === TRUE) ? $component : $component[$what_to_return]);
1191
  $pid = $component['pid'];
1192
  while ($pid) {
1193
    $parents[] = ($what_to_return === TRUE) ? $node->webform['components'][$pid] : $node->webform['components'][$pid][$what_to_return];
1194
    $pid = $node->webform['components'][$pid]['pid'];
1195
  }
1196
  return array_reverse($parents);
1197
}
1198

    
1199
/**
1200
 * Populate a component with the defaults for that type.
1201
 */
1202
function webform_component_defaults(&$component) {
1203
  $defaults = webform_component_invoke($component['type'], 'defaults');
1204
  drupal_alter('webform_component_defaults', $defaults, $component['type']);
1205
  if (!empty($defaults)) {
1206
    foreach ($defaults as $key => $default) {
1207
      if (!isset($component[$key])) {
1208
        $component[$key] = $default;
1209
      }
1210
    }
1211
    foreach ($defaults['extra'] as $extra => $default) {
1212
      if (!isset($component['extra'][$extra])) {
1213
        $component['extra'][$extra] = $default;
1214
      }
1215
    }
1216
  }
1217
}
1218

    
1219
/**
1220
 * Validate an element value is unique with no duplicates in the database.
1221
 */
1222
function webform_validate_unique($element, $form_state) {
1223
  if ($element['#value'] !== '') {
1224
    $nid = $form_state['values']['details']['nid'];
1225
    $sid = $form_state['values']['details']['sid'];
1226
    $query = db_select('webform_submitted_data')
1227
      ->fields('webform_submitted_data', array('sid'))
1228
      ->condition('nid', $nid)
1229
      ->condition('cid', $element['#webform_component']['cid'])
1230
      ->condition('data', $element['#value'])
1231
      // More efficient than using countQuery() for data checks.
1232
      ->range(0, 1);
1233
    if ($sid) {
1234
      $query->condition('sid', $sid, '<>');
1235
    }
1236
    $count = $query->execute()->fetchField();
1237
    if ($count) {
1238
      form_error($element, t('The value %value has already been submitted once for the %title field. You may have already submitted this form, or you need to use a different value.', array('%value' => $element['#value'], '%title' => $element['#title'])));
1239
    }
1240
  }
1241
}