Project

General

Profile

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

root / drupal7 / sites / all / modules / webform / includes / webform.components.inc @ 389fb945

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
    if (webform_component_feature($component['type'], 'title_inline')) {
457
      $form['display']['title_display'] = array(
458
        '#type' => 'select',
459
        '#title' => t('Label display'),
460
        '#default_value' => !empty($component['extra']['title_display']) ? $component['extra']['title_display'] : 'before',
461
        '#options' => array(
462
          'before' => t('Above'),
463
          'inline' => t('Inline'),
464
          'none' => t('None'),
465
        ),
466
        '#description' => t("Determines the placement of the component's label."),
467
      );
468
    }
469
    else {
470
      $form['display']['title_display'] = array(
471
        '#type' => 'checkbox',
472
        '#title' => t('Hide label'),
473
        '#default_value' => strcmp($component['extra']['title_display'], 'none') === 0,
474
        '#return_value' => 'none',
475
        '#description' => t('Do not display the label of this component.'),
476
      );
477
    }
478
    $form['display']['title_display']['#weight'] = 8;
479
    $form['display']['title_display']['#parents'] = array('extra', 'title_display');
480
  }
481

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

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

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

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

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

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

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

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

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

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

    
630
  return $form;
631
}
632

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

    
784
  if (!isset($component['cid'])) {
785
    if (lock_acquire('webform_component_insert_' . $component['nid'], 5)) {
786
      $next_id_query = db_select('webform_component')->condition('nid', $component['nid']);
787
      $next_id_query->addExpression('MAX(cid) + 1', 'cid');
788
      $component['cid'] = $next_id_query->execute()->fetchField();
789
      if ($component['cid'] == NULL) {
790
        $component['cid'] = 1;
791
      }
792
      lock_release('webform_component_insert_' . $component['nid']);
793
    }
794
    else {
795
      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)));
796
      return FALSE;
797
    }
798
  }
799

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

    
1090
  return $options;
1091
}
1092

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

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

    
1125
  return $element;
1126
}
1127

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

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

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

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

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

    
1168
  return theme('fieldset', array('element' => $element));
1169
}
1170

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

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

    
1217
/**
1218
 * Validate an element value is unique with no duplicates in the database.
1219
 */
1220
function webform_validate_unique($element, $form_state) {
1221
  if ($element['#value'] !== '') {
1222
    $nid = $form_state['values']['details']['nid'];
1223
    $sid = $form_state['values']['details']['sid'];
1224
    $query = db_select('webform_submitted_data')
1225
      ->fields('webform_submitted_data', array('sid'))
1226
      ->condition('nid', $nid)
1227
      ->condition('cid', $element['#webform_component']['cid'])
1228
      ->condition('data', $element['#value'])
1229
      // More efficient than using countQuery() for data checks.
1230
      ->range(0, 1);
1231
    if ($sid) {
1232
      $query->condition('sid', $sid, '<>');
1233
    }
1234
    $count = $query->execute()->fetchField();
1235
    if ($count) {
1236
      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'])));
1237
    }
1238
  }
1239
}