Projet

Général

Profil

Paste
Télécharger (46 ko) Statistiques
| Branche: | Révision:

root / drupal7 / sites / all / modules / webform / includes / webform.components.inc @ 651307cd

1 85ad3d82 Assos Assos
<?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
  $node = $variables['node'];
35
  $form = $variables['form'];
36
37
  return drupal_render($form);
38
}
39
40
/**
41
 * The table-based listing of all components for this webform.
42
 */
43
function webform_components_form($form, $form_state, $node) {
44
  $form = array(
45
    '#tree' => TRUE,
46
    '#node' => $node,
47
    'components' => array(),
48
  );
49
50
  $form['nid'] = array(
51
    '#type' => 'value',
52
    '#value' => $node->nid,
53
  );
54
55
  $options = array();
56
  foreach ($node->webform['components'] as $cid => $component) {
57
    $options[$cid] = check_plain($component['name']);
58
    $form['components'][$cid]['cid'] = array(
59
      '#type' => 'hidden',
60
      '#default_value' => $component['cid'],
61
    );
62
    $form['components'][$cid]['pid'] = array(
63
      '#type' => 'hidden',
64
      '#default_value' => $component['pid'],
65
    );
66
    $form['components'][$cid]['weight'] = array(
67
      '#type' => 'textfield',
68
      '#size' => 4,
69
      '#title' => t('Weight'),
70
      '#default_value' => $component['weight'],
71
    );
72 a45e4bc1 Assos Assos
    $form['components'][$cid]['required'] = array(
73 85ad3d82 Assos Assos
      '#type' => 'checkbox',
74 a45e4bc1 Assos Assos
      '#title' => t('Required'),
75
      '#default_value' => $component['required'],
76 85ad3d82 Assos Assos
      '#access' => webform_component_feature($component['type'], 'required'),
77
    );
78
    if (!isset($max_weight) || $component['weight'] > $max_weight) {
79
      $max_weight = $component['weight'];
80
    }
81
  }
82
83
  $form['add']['name'] = array(
84
    '#type' => 'textfield',
85 ba09eb79 Assos Assos
    '#size' => 30,
86 a45e4bc1 Assos Assos
    '#maxlength' => NULL,
87 85ad3d82 Assos Assos
  );
88
89
  $form['add']['type'] = array(
90
    '#type' => 'select',
91
    '#options' => webform_component_options(),
92
    '#weight' => 3,
93
    '#default_value' => (isset($_GET['cid']) && isset($node->webform['components'][$_GET['cid']])) ? $node->webform['components'][$_GET['cid']]['type'] : 'textfield',
94
  );
95 a45e4bc1 Assos Assos
  $form['add']['required'] = array(
96 85ad3d82 Assos Assos
    '#type' => 'checkbox',
97
  );
98
  $form['add']['cid'] = array(
99
    '#type' => 'hidden',
100
    '#default_value' => '',
101
  );
102
  $form['add']['pid'] = array(
103
    '#type' => 'hidden',
104
    '#default_value' => (isset($_GET['cid']) && isset($node->webform['components'][$_GET['cid']])) ? $node->webform['components'][$_GET['cid']]['pid'] : 0,
105
  );
106
  $form['add']['weight'] = array(
107
    '#type' => 'textfield',
108
    '#size' => 4,
109
    '#delta' => count($node->webform['components']) > 10 ? count($node->webform['components']) : 10,
110
  );
111
112
  if (isset($_GET['cid']) && isset($node->webform['components'][$_GET['cid']])) {
113
    // Make the new component appear by default directly after the one that was
114
    // just added.
115
    $form['add']['weight']['#default_value'] = $node->webform['components'][$_GET['cid']]['weight'] + 1;
116
    foreach (array_keys($node->webform['components']) as $cid) {
117
      // Adjust all later components also, to make sure none of them have the
118
      // same weight as the new component.
119
      if ($form['components'][$cid]['weight']['#default_value'] >= $form['add']['weight']['#default_value']) {
120
        $form['components'][$cid]['weight']['#default_value']++;
121
      }
122
    }
123
  }
124
  else {
125
    // If no component was just added, the new component should appear by
126
    // default at the end of the list.
127
    $form['add']['weight']['#default_value'] = isset($max_weight) ? $max_weight + 1 : 0;
128
  }
129
130
  $form['add']['add'] = array(
131
    '#type' => 'submit',
132
    '#value' => t('Add'),
133
    '#weight' => 45,
134
    '#validate' => array('webform_components_form_add_validate', 'webform_components_form_validate'),
135
    '#submit' => array('webform_components_form_add_submit'),
136
  );
137
138 a45e4bc1 Assos Assos
  $form['actions'] = array(
139
    '#type' => 'actions',
140
    '#weight' => 45,
141
  );
142 3753f249 Assos Assos
  $form['actions']['submit'] = array(
143 85ad3d82 Assos Assos
    '#type' => 'submit',
144
    '#value' => t('Save'),
145
    '#access' => count($node->webform['components']) > 0,
146
  );
147 a45e4bc1 Assos Assos
  $form['warning'] = array(
148
    '#weight' => -1,
149
  );
150
  webform_input_vars_check($form, $form_state, 'components', 'warning');
151 85ad3d82 Assos Assos
152
  return $form;
153
}
154
155
/**
156 a45e4bc1 Assos Assos
 * Preprocess variables for theming the webform components form.
157 85ad3d82 Assos Assos
 */
158 a45e4bc1 Assos Assos
function template_preprocess_webform_components_form(&$variables) {
159 85ad3d82 Assos Assos
  $form = $variables['form'];
160
161
  $form['components']['#attached']['library'][] = array('webform', 'admin');
162
163
  // TODO: Attach these. See http://drupal.org/node/732022.
164
  drupal_add_tabledrag('webform-components', 'order', 'sibling', 'webform-weight');
165
  drupal_add_tabledrag('webform-components', 'match', 'parent', 'webform-pid', 'webform-pid', 'webform-cid');
166
167
  $node = $form['#node'];
168
169 ba09eb79 Assos Assos
  $header = array(t('Label'), t('Form key'), t('Type'), t('Value'), t('Required'), t('Weight'), array('data' => t('Operations'), 'colspan' => 3));
170 85ad3d82 Assos Assos
  $rows = array();
171
172
  // Add a row containing form elements for a new item.
173
  unset($form['add']['name']['#title'], $form['add_type']['#description']);
174 a45e4bc1 Assos Assos
  $form['add']['name']['#attributes']['placeholder'] = t('New component name');
175
  $form['add']['cid']['#attributes']['class'][] = 'webform-cid';
176
  $form['add']['pid']['#attributes']['class'][] = 'webform-pid';
177
  $form['add']['weight']['#attributes']['class'][] = 'webform-weight';
178 85ad3d82 Assos Assos
  $row_data = array(
179 ba09eb79 Assos Assos
    array('data' => drupal_render($form['add']['name']), 'class' => array('webform-component-name'), 'colspan' => 2),
180 a45e4bc1 Assos Assos
    array('data' => drupal_render($form['add']['type']), 'class' => array('webform-component-type')),
181
    array('data' => '', 'class' => array('webform-component-value')),
182
    array('data' => drupal_render($form['add']['required']), 'class' => array('webform-component-required', 'checkbox')),
183
    array('data' => drupal_render($form['add']['cid']) . drupal_render($form['add']['pid']) . drupal_render($form['add']['weight'])),
184
    array('colspan' => 3, 'data' => drupal_render($form['add']['add']), 'class' => array('webform-component-add')),
185 85ad3d82 Assos Assos
  );
186 a45e4bc1 Assos Assos
  $add_form = array('data' => $row_data, 'class' => array('draggable', 'webform-add-form', 'tabledrag-leaf'));
187 85ad3d82 Assos Assos
188
  if (!empty($node->webform['components'])) {
189
    $component_tree = array();
190
    $page_count = 1;
191
    _webform_components_tree_build($node->webform['components'], $component_tree, 0, $page_count);
192
    $component_tree = _webform_components_tree_sort($component_tree);
193 a45e4bc1 Assos Assos
    // Build the table rows recursively.
194 85ad3d82 Assos Assos
    foreach ($component_tree['children'] as $cid => $component) {
195
      _webform_components_form_rows($node, $cid, $component, 0, $form, $rows, $add_form);
196
    }
197
  }
198
  else {
199
    $rows[] = array(array('data' => t('No Components, add a component below.'), 'colspan' => 9));
200 e9984459 Assos Assos
    // When there are no components, tabledrag.js will look to the add component
201
    // row to locate the weight column. Because of the name/form_key colspan
202
    // it will mis-count the columns and locate the Required column instead of
203
    // the Weight column.
204
    unset($add_form['data'][0]['colspan']);
205
    array_splice($add_form['data'], 1, 0, '&nbsp;');
206 85ad3d82 Assos Assos
  }
207
208
  // Append the add form if not already printed.
209
  if ($add_form) {
210
    $rows[] = $add_form;
211
  }
212
213 a45e4bc1 Assos Assos
  $variables['rows'] = $rows;
214
  $variables['header'] = $header;
215
  $variables['form'] = $form;
216
}
217
218
/**
219
 * Recursive function for nesting components into a table.
220
 *
221
 * @see preprocess_webform_components_form()
222
 */
223
function _webform_components_form_rows($node, $cid, $component, $level, &$form, &$rows, &$add_form) {
224
  // Create presentable values.
225 ba09eb79 Assos Assos
  $form_key = truncate_utf8($component['form_key'], 30, FALSE, TRUE);
226
  $value = truncate_utf8($component['value'], 30, TRUE, TRUE, 20);
227 a45e4bc1 Assos Assos
228
  // Remove individual titles from the required and weight fields.
229
  unset($form['components'][$cid]['required']['#title']);
230
  unset($form['components'][$cid]['pid']['#title']);
231
  unset($form['components'][$cid]['weight']['#title']);
232
233
  // Add special classes for weight and parent fields.
234
  $form['components'][$cid]['cid']['#attributes']['class'] = array('webform-cid');
235
  $form['components'][$cid]['pid']['#attributes']['class'] = array('webform-pid');
236
  $form['components'][$cid]['weight']['#attributes']['class'] = array('webform-weight');
237
238
  // Build indentation for this row.
239
  $indents = '';
240
  for ($n = 1; $n <= $level; $n++) {
241
    $indents .= '<div class="indentation">&nbsp;</div>';
242
  }
243
244
  // Add each component to a table row.
245
  $row_data = array(
246
    array('data' => $indents . filter_xss($component['name']), 'class' => array('webform-component-name')),
247 ba09eb79 Assos Assos
    array('data' => check_plain($form_key), 'class' => array('webform-component-formkey')) +
248 8c72e82a Assos Assos
          ((string) $component['form_key'] === (string) $form_key ? array() : array('title' => $component['form_key'])),
249 a45e4bc1 Assos Assos
    array('data' => $form['add']['type']['#options'][$component['type']], 'class' => array('webform-component-type')),
250 ba09eb79 Assos Assos
    array('data' => ($value == '') ? '-' : check_plain($value), 'class' => array('webform-component-value')) +
251
          ($component['value'] == $value ? array() : array('title' => $component['value'])),
252 a45e4bc1 Assos Assos
    array('data' => drupal_render($form['components'][$cid]['required']), 'class' => array('webform-component-required', 'checkbox')),
253
    array('data' => drupal_render($form['components'][$cid]['cid']) . drupal_render($form['components'][$cid]['pid']) . drupal_render($form['components'][$cid]['weight'])),
254
    array('data' => l(t('Edit'), 'node/' . $node->nid . '/webform/components/' . $cid, array('query' => drupal_get_destination())), 'class' => array('webform-component-edit')),
255
    array('data' => l(t('Clone'), 'node/' . $node->nid . '/webform/components/' . $cid . '/clone', array('query' => drupal_get_destination())), 'class' => array('webform-component-clone')),
256
    array('data' => l(t('Delete'), 'node/' . $node->nid . '/webform/components/' . $cid . '/delete', array('query' => drupal_get_destination())), 'class' => array('webform-component-delete')),
257
  );
258
  $row_class = array('draggable');
259
  if (!webform_component_feature($component['type'], 'group')) {
260
    $row_class[] = 'tabledrag-leaf';
261
  }
262
  if ($component['type'] == 'pagebreak') {
263
    $row_class[] = 'tabledrag-root';
264
    $row_class[] = 'webform-pagebreak';
265
    $row_data[0]['class'][] = 'webform-pagebreak';
266
  }
267
  $rows[] = array('data' => $row_data, 'class' => $row_class, 'data-cid' => $cid);
268
  if (isset($component['children']) && is_array($component['children'])) {
269
    foreach ($component['children'] as $cid => $component) {
270
      _webform_components_form_rows($node, $cid, $component, $level + 1, $form, $rows, $add_form);
271
    }
272
  }
273
274
  // Add the add form if this was the last edited component.
275
  if (isset($_GET['cid']) && $component['cid'] == $_GET['cid'] && $add_form) {
276
    $add_form['data'][0]['data'] = $indents . $add_form['data'][0]['data'];
277
    $rows[] = $add_form;
278
    $add_form = FALSE;
279
  }
280
}
281
282
/**
283
 * Theme the node components form. Use a table to organize the components.
284
 *
285
 * @return
286
 *   Formatted HTML form, ready for display.
287
 */
288
function theme_webform_components_form($variables) {
289 85ad3d82 Assos Assos
  $output = '';
290 a45e4bc1 Assos Assos
  $output .= drupal_render_children($variables['form']['warning']);
291
  $output .= theme('table', array('header' => $variables['header'], 'rows' => $variables['rows'], 'attributes' => array('id' => 'webform-components')));
292
  $output .= drupal_render_children($variables['form']);
293 85ad3d82 Assos Assos
  return $output;
294
}
295
296
/**
297
 * Validate handler for webform_components_form().
298
 */
299
function webform_components_form_validate($form, &$form_state) {
300
  // Check that no two components end up with the same form key.
301
  $duplicates = array();
302
  $parents = array();
303
  if (isset($form_state['values']['components'])) {
304
    foreach ($form_state['values']['components'] as $cid => $component) {
305 8c72e82a Assos Assos
      $form_key = (string) $form['#node']->webform['components'][$cid]['form_key'];
306
      if (isset($parents[$component['pid']]) && ($existing = array_search($form_key, $parents[$component['pid']], TRUE)) && $existing !== FALSE) {
307 85ad3d82 Assos Assos
        if (!isset($duplicates[$form_key])) {
308
          $duplicates[$form_key] = array($existing);
309
        }
310
        $duplicates[$form_key][] = $cid;
311
      }
312
      $parents[$component['pid']][$cid] = $form_key;
313
    }
314
  }
315
316
  if (!empty($duplicates)) {
317
    $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.');
318
    $items = array();
319
    foreach ($duplicates as $form_key => $cids) {
320
      foreach ($cids as $cid) {
321 a45e4bc1 Assos Assos
        $items[] = webform_filter_xss($form['#node']->webform['components'][$cid]['name']);
322 85ad3d82 Assos Assos
      }
323
    }
324
325
    form_error($form['components'], $error . theme('item_list', array('items' => $items)));
326
  }
327
}
328
329
/**
330
 * Validate handler for webform_component_form() when adding a new component.
331
 */
332
function webform_components_form_add_validate($form, &$form_state) {
333
  // Check that the entered component name is valid.
334
  if (drupal_strlen(trim($form_state['values']['add']['name'])) <= 0) {
335
    form_error($form['add']['name'], t('When adding a new component, the name field is required.'));
336
  }
337
}
338
339
/**
340
 * Submit handler for webform_components_form() to save component order.
341
 */
342
function webform_components_form_submit($form, &$form_state) {
343
  $node = node_load($form_state['values']['nid']);
344
345 a45e4bc1 Assos Assos
  // Update all required and weight values.
346 85ad3d82 Assos Assos
  $changes = FALSE;
347
  foreach ($node->webform['components'] as $cid => $component) {
348 a45e4bc1 Assos Assos
    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']) {
349 85ad3d82 Assos Assos
      $changes = TRUE;
350
      $node->webform['components'][$cid]['weight'] = $form_state['values']['components'][$cid]['weight'];
351 a45e4bc1 Assos Assos
      $node->webform['components'][$cid]['required'] = $form_state['values']['components'][$cid]['required'];
352 85ad3d82 Assos Assos
      $node->webform['components'][$cid]['pid'] = $form_state['values']['components'][$cid]['pid'];
353
    }
354
  }
355
356
  if ($changes) {
357
    node_save($node);
358
  }
359
360 a45e4bc1 Assos Assos
  drupal_set_message(t('The component positions and required values have been updated.'));
361 85ad3d82 Assos Assos
}
362
363
/**
364
 * Submit handler for webform_components_form() that adds a new component.
365
 */
366
function webform_components_form_add_submit($form, &$form_state) {
367
  $node = node_load($form_state['values']['nid']);
368
369
  $component = $form_state['values']['add'];
370 a45e4bc1 Assos Assos
371
  // Set the values in the query string for the add component page.
372
  $query = array(
373
    'name' => $component['name'],
374
    'required' => $component['required'],
375
    'pid' => $component['pid'],
376
    'weight' => $component['weight'],
377
  );
378
379
  // Forward the "destination" query string value to the next form.
380
  if (isset($_GET['destination'])) {
381
    $query['destination'] = $_GET['destination'];
382
    unset($_GET['destination']);
383
    drupal_static_reset('drupal_get_destination');
384
  }
385
  $form_state['redirect'] = array('node/' . $node->nid . '/webform/components/new/' . $component['type'], array('query' => $query));
386 85ad3d82 Assos Assos
}
387
388
/**
389
 * Form to configure a webform component.
390
 */
391
function webform_component_edit_form($form, $form_state, $node, $component, $clone = FALSE) {
392
  drupal_set_title(t('Edit component: @name', array('@name' => $component['name'])), PASS_THROUGH);
393
394 a45e4bc1 Assos Assos
  $form['#node'] = $node;
395 85ad3d82 Assos Assos
  $form['#tree'] = TRUE;
396
397
  // Print the correct field type specification.
398
  // We always need: name and description.
399
  $form['type'] = array(
400
    '#type' => 'value',
401
    '#value' => $component['type'],
402
  );
403
  $form['nid'] = array(
404
    '#type' => 'value',
405
    '#value' => $node->nid,
406
  );
407
  $form['cid'] = array(
408
    '#type' => 'value',
409
    '#value' => isset($component['cid']) ? $component['cid'] : NULL,
410
  );
411
  $form['clone'] = array(
412
    '#type' => 'value',
413
    '#value' => $clone,
414
  );
415
416
  if (webform_component_feature($component['type'], 'title')) {
417
    $form['name'] = array(
418
      '#type' => 'textfield',
419
      '#default_value' => $component['name'],
420
      '#title' => t('Label'),
421
      '#description' => t('This is used as a descriptive label when displaying this form element.'),
422
      '#required' => TRUE,
423
      '#weight' => -10,
424 a45e4bc1 Assos Assos
      '#maxlength' => NULL,
425 85ad3d82 Assos Assos
    );
426
  }
427
428
  $form['form_key'] = array(
429
    '#type' => 'textfield',
430
    '#default_value' => empty($component['form_key']) ? _webform_safe_name($component['name']) : $component['form_key'],
431 6a93dd76 Assos Assos
    '#title' => t('Form Key'),
432 85ad3d82 Assos Assos
    '#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.'),
433
    '#required' => TRUE,
434
    '#weight' => -9,
435
  );
436
437
  $form['extra'] = array();
438
  if (webform_component_feature($component['type'], 'description')) {
439
    $form['extra']['description'] = array(
440
      '#type' => 'textarea',
441
      '#default_value' => isset($component['extra']['description']) ? $component['extra']['description'] : '',
442
      '#title' => t('Description'),
443 a45e4bc1 Assos Assos
      '#description' => t('A short description of the field used as help for the user when he/she uses the form.') . ' ' . theme('webform_token_help'),
444 85ad3d82 Assos Assos
      '#weight' => -1,
445
    );
446
  }
447
448
  // Display settings.
449
  $form['display'] = array(
450
    '#type' => 'fieldset',
451
    '#title' => t('Display'),
452
    '#collapsible' => TRUE,
453
    '#collapsed' => FALSE,
454
    '#weight' => 8,
455
  );
456
  if (webform_component_feature($component['type'], 'title_display')) {
457
    if (webform_component_feature($component['type'], 'title_inline')) {
458
      $form['display']['title_display'] = array(
459
        '#type' => 'select',
460
        '#title' => t('Label display'),
461
        '#default_value' => !empty($component['extra']['title_display']) ? $component['extra']['title_display'] : 'before',
462
        '#options' => array(
463
          'before' => t('Above'),
464
          'inline' => t('Inline'),
465
          'none' => t('None'),
466
        ),
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 01d522a6 Assos Assos
  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 85ad3d82 Assos Assos
  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 a45e4bc1 Assos Assos
  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 85ad3d82 Assos Assos
  // 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 a45e4bc1 Assos Assos
    $form['validation']['required'] = array(
538 85ad3d82 Assos Assos
      '#type' => 'checkbox',
539 a45e4bc1 Assos Assos
      '#title' => t('Required'),
540
      '#default_value' => ($component['required'] == '1' ? TRUE : FALSE),
541 85ad3d82 Assos Assos
      '#description' => t('Check this option if the user must enter a value.'),
542
      '#weight' => -1,
543 a45e4bc1 Assos Assos
      '#parents' => array('required'),
544 85ad3d82 Assos Assos
    );
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);
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 01d522a6 Assos Assos
  // Ensure that the webform admin library is attached, possibly in addition to
610
  // component-specific attachments.
611
  $form['#attached']['library'][] = array('webform', 'admin');
612 85ad3d82 Assos Assos
613
  // Add the submit button.
614 a45e4bc1 Assos Assos
  $form['actions'] = array(
615
    '#type' => 'actions',
616
    '#weight' => 50,
617
  );
618 3753f249 Assos Assos
  $form['actions']['submit'] = array(
619 85ad3d82 Assos Assos
    '#type' => 'submit',
620
    '#value' => t('Save component'),
621
  );
622
623 3753f249 Assos Assos
  // Remove fieldsets without any child form controls.
624 a45e4bc1 Assos Assos
  foreach (element_children($form) as $group_key) {
625
    $group = $form[$group_key];
626 3753f249 Assos Assos
    if (isset($group['#type']) && $group['#type'] === 'fieldset' && !element_children($group)) {
627
      unset($form[$group_key]);
628
    }
629
  }
630
631 85ad3d82 Assos Assos
  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 a45e4bc1 Assos Assos
  $node = $form['#node'];;
639 85ad3d82 Assos Assos
640
  if (!preg_match('/^[a-z0-9_]+$/i', $form_state['values']['form_key'])) {
641 6a93dd76 Assos Assos
    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 85ad3d82 Assos Assos
  }
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 6a93dd76 Assos Assos
      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 85ad3d82 Assos Assos
    }
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 a45e4bc1 Assos Assos
  $node = $form['#node'];
657 85ad3d82 Assos Assos
  webform_ensure_record($node);
658
659 a45e4bc1 Assos Assos
  // Remove extra values that match the default.
660 85ad3d82 Assos Assos
  if (isset($form_state['values']['extra'])) {
661 a45e4bc1 Assos Assos
    $default = array('type' => $form_state['values']['type'], 'extra' => array());
662
    webform_component_defaults($default);
663 85ad3d82 Assos Assos
    foreach ($form_state['values']['extra'] as $key => $value) {
664 a45e4bc1 Assos Assos
      if (isset($default['extra'][$key]) && $default['extra'][$key] === $value) {
665 85ad3d82 Assos Assos
        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 a45e4bc1 Assos Assos
  // the updated webform is visible to anonymous users. This resets the page
695
  // and block caches (only);
696 85ad3d82 Assos Assos
  cache_clear_all();
697
698 a45e4bc1 Assos Assos
  // Refresh the entity cache, should it be cached in persistent storage.
699
  entity_get_controller('node')->resetCache(array($node->nid));
700 85ad3d82 Assos Assos
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 ba09eb79 Assos Assos
  $component_type = $node->webform['components'][$cid]['type'];
721
  if (webform_component_feature($component_type, 'group')) {
722 85ad3d82 Assos Assos
    $question = t('Delete the %name fieldset?', array('%name' => $node->webform['components'][$cid]['name']));
723 ba09eb79 Assos Assos
    $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 8c72e82a Assos Assos
      array('%name' => $node->webform['components'][$cid]['name'],
725
        '@type' => webform_component_property($component_type, 'label'),
726
        '%webform' => $node->title));
727 85ad3d82 Assos Assos
  }
728
  else {
729
    $question = t('Delete the %name component?', array('%name' => $node->webform['components'][$cid]['name']));
730
    $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));
731
  }
732
733
  return confirm_form($form, $question, 'node/' . $node->nid . '/webform/components', $description, t('Delete'));
734
}
735
736
/**
737
 * Submit handler for webform_component_delete_form().
738
 */
739
function webform_component_delete_form_submit($form, &$form_state) {
740
  // Delete the component.
741
  $node = $form_state['values']['node'];
742
  $component = $form_state['values']['component'];
743
  webform_component_delete($node, $component);
744
  drupal_set_message(t('Component %name deleted.', array('%name' => $component['name'])));
745
746
  // Check if this webform still contains any information.
747
  unset($node->webform['components'][$component['cid']]);
748
  webform_check_record($node);
749
750
  // Since Webform components have been updated but the node itself has not
751
  // been saved, it is necessary to explicitly clear the cache to make sure
752 a45e4bc1 Assos Assos
  // the updated webform is visible to anonymous users. This resets the page
753
  // and block caches (only);
754 85ad3d82 Assos Assos
  cache_clear_all();
755
756 a45e4bc1 Assos Assos
  // Refresh the entity cache, should it be cached in persistent storage.
757
  entity_get_controller('node')->resetCache(array($node->nid));
758 85ad3d82 Assos Assos
759
  $form_state['redirect'] = 'node/' . $node->nid . '/webform/components';
760
}
761
762
/**
763
 * Insert a new component into the database.
764
 *
765
 * @param $component
766
 *   A full component containing fields from the component form.
767 8c72e82a Assos Assos
 *
768
 * @return false|int
769
 *   On success return identifier for the components within node.
770
 *   FALSE on failure
771 85ad3d82 Assos Assos
 */
772
function webform_component_insert(&$component) {
773
  // Allow modules to modify the component before saving.
774
  foreach (module_implements('webform_component_presave') as $module) {
775
    $function = $module . '_webform_component_presave';
776
    $function($component);
777
  }
778
779
  $component['value'] = isset($component['value']) ? $component['value'] : NULL;
780 a45e4bc1 Assos Assos
  $component['required'] = isset($component['required']) ? $component['required'] : 0;
781 85ad3d82 Assos Assos
  $component['extra']['private'] = isset($component['extra']['private']) ? $component['extra']['private'] : 0;
782
783
  if (!isset($component['cid'])) {
784
    if (lock_acquire('webform_component_insert_' . $component['nid'], 5)) {
785
      $next_id_query = db_select('webform_component')->condition('nid', $component['nid']);
786
      $next_id_query->addExpression('MAX(cid) + 1', 'cid');
787
      $component['cid'] = $next_id_query->execute()->fetchField();
788
      if ($component['cid'] == NULL) {
789
        $component['cid'] = 1;
790
      }
791
      lock_release('webform_component_insert_' . $component['nid']);
792
    }
793
    else {
794
      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)));
795
      return FALSE;
796
    }
797
  }
798
799
  $query = db_insert('webform_component')
800
    ->fields(array(
801
      'nid' => $component['nid'],
802
      'cid' => $component['cid'],
803
      'pid' => $component['pid'],
804
      'form_key' => $component['form_key'],
805
      'name' => $component['name'],
806
      'type' => $component['type'],
807
      'value' => (string) $component['value'],
808
      'extra' => serialize($component['extra']),
809 a45e4bc1 Assos Assos
      'required' => $component['required'],
810 85ad3d82 Assos Assos
      'weight' => $component['weight'],
811
    ))
812
    ->execute();
813
814
  // Post-insert actions.
815
  module_invoke_all('webform_component_insert', $component);
816
817
  return $component['cid'];
818
}
819
820
/**
821
 * Update an existing component with new values.
822
 *
823
 * @param $component
824
 *   A full component containing a nid, cid, and all other fields from the
825
 *   component form. Additional properties are stored in the extra array.
826
 */
827
function webform_component_update($component) {
828
  // Allow modules to modify the component before saving.
829
  foreach (module_implements('webform_component_presave') as $module) {
830
    $function = $module . '_webform_component_presave';
831
    $function($component);
832
  }
833
834
  $component['value'] = isset($component['value']) ? $component['value'] : NULL;
835 a45e4bc1 Assos Assos
  $component['required'] = isset($component['required']) ? $component['required'] : 0;
836 85ad3d82 Assos Assos
  $component['extra']['private'] = isset($component['extra']['private']) ? $component['extra']['private'] : 0;
837
  db_update('webform_component')
838
    ->fields(array(
839
      'pid' => $component['pid'],
840
      'form_key' => $component['form_key'],
841
      'name' => $component['name'],
842
      'type' => $component['type'],
843
      'value' => isset($component['value']) ? $component['value'] : '',
844
      'extra' => serialize($component['extra']),
845 a45e4bc1 Assos Assos
      'required' => $component['required'],
846 85ad3d82 Assos Assos
      'weight' => $component['weight']
847
    ))
848
    ->condition('nid', $component['nid'])
849
    ->condition('cid', $component['cid'])
850
    ->execute();
851
852
  // Post-update actions.
853
  module_invoke_all('webform_component_update', $component);
854
}
855
856
function webform_component_delete($node, $component) {
857
  // Check if a delete function is available for this component. If so,
858
  // load all submissions and allow the component to delete each one.
859
  webform_component_include($component['type']);
860
  $delete_function = '_webform_delete_' . $component['type'];
861
  if (function_exists($delete_function)) {
862
    module_load_include('inc', 'webform', 'includes/webform.submissions');
863
    $submissions = webform_get_submissions($node->nid);
864
    foreach ($submissions as $submission) {
865
      if (isset($submission->data[$component['cid']])) {
866 a45e4bc1 Assos Assos
        webform_component_invoke($component['type'], 'delete', $component, $submission->data[$component['cid']]);
867 85ad3d82 Assos Assos
      }
868
    }
869
  }
870
871
  // Remove database entries.
872
  db_delete('webform_component')
873
    ->condition('nid', $node->nid)
874
    ->condition('cid', $component['cid'])
875
    ->execute();
876
  db_delete('webform_submitted_data')
877
    ->condition('nid', $node->nid)
878
    ->condition('cid', $component['cid'])
879
    ->execute();
880
881 a45e4bc1 Assos Assos
  // Delete any conditionals dependent on this component.
882
  module_load_include('inc', 'webform', 'includes/webform.conditionals');
883 e9984459 Assos Assos
  foreach ($node->webform['conditionals'] as $rgid => &$conditional) {
884 a45e4bc1 Assos Assos
    $specs = array(
885
      array(
886
        'field' => 'rules',
887
        'table' => 'webform_conditional_rules',
888
        'component' => 'source',
889
        'component_type' => 'source_type',
890
        'index' => 'rid',
891
      ),
892
      array(
893
        'field' => 'actions',
894
        'table' => 'webform_conditional_actions',
895
        'component' => 'target',
896
        'component_type' => 'target_type',
897
        'index' => 'aid',
898
      ),
899
    );
900
    foreach ($specs as $spec) {
901 e9984459 Assos Assos
      $deleted = array();
902
      $field = $spec['field'];
903
      foreach ($conditional[$field] as $key => $thing) {
904 a45e4bc1 Assos Assos
        if ($thing[$spec['component_type']] === 'component' && $thing[$spec['component']] == $component['cid']) {
905 e9984459 Assos Assos
          $deleted[$key] = $key;
906
          unset($conditional[$field][$key]);
907 a45e4bc1 Assos Assos
        }
908
      }
909 e9984459 Assos Assos
      if ($spec['field'] == 'rules') {
910
        // Rules deleted because of the source component being deleted may have left
911
        // empty sub-conditionals. Delete them, and then the entire rule group if
912
        // there aren't any rules left.
913
        $deleted += webform_delete_empty_subconditionals($conditional);
914
      }
915
      // Delete the conditional if this component is the only source / target.
916
      if (empty($conditional[$field])) {
917
        webform_conditional_delete($node, $conditional);
918
        break; // Loop exit.
919
      }
920
      // Remove the deleted rules / actions from the database.
921
      foreach ($deleted as $key) {
922
        db_delete($spec['table'])
923
          ->condition('nid', $node->nid)
924
          ->condition('rgid', $rgid)
925
          ->condition($spec['index'], $key)
926
          ->execute();
927
      }
928 a45e4bc1 Assos Assos
    }
929
  }
930
931 85ad3d82 Assos Assos
  // Delete all elements under this element.
932
  $result = db_select('webform_component', 'c')
933
    ->fields('c')
934
    ->condition('nid', $node->nid)
935
    ->condition('pid', $component['cid'])
936
    ->execute();
937
  foreach ($result as $row) {
938
    $child_component = $node->webform['components'][$row->cid];
939
    webform_component_delete($node, $child_component);
940
  }
941
942
  // Post-delete actions.
943
  module_invoke_all('webform_component_delete', $component);
944
}
945
946
/**
947
 * Recursively insert components into the database.
948
 *
949
 * @param $node
950
 *   The node object containing the current webform.
951
 * @param $component
952
 *   A full component containing fields from the component form.
953 8c72e82a Assos Assos
 *
954
 * @return false|int
955
 *   On success return identifier for the components within node.
956
 *   FALSE on failure
957 85ad3d82 Assos Assos
 */
958
function webform_component_clone(&$node, &$component) {
959
  $original_cid = $component['cid'];
960
  $component['cid'] = NULL;
961
  $new_cid = webform_component_insert($component);
962
  $component['cid'] = $new_cid;
963
  if (webform_component_feature($component['type'], 'group')) {
964
    foreach ($node->webform['components'] as $cid => $child_component) {
965
      if ($child_component['pid'] == $original_cid) {
966
        $child_component['pid'] = $new_cid;
967
        webform_component_clone($node, $child_component);
968
      }
969
    }
970
  }
971
  return $new_cid;
972
}
973
974
/**
975
 * Check if a component has a particular feature.
976
 *
977
 * @see hook_webform_component_info()
978
 */
979
function webform_component_feature($type, $feature) {
980
  $components = webform_components();
981
  $defaults = array(
982 a45e4bc1 Assos Assos
    'analysis' => TRUE,
983 85ad3d82 Assos Assos
    'csv' => TRUE,
984
    'default_value' => TRUE,
985
    'description' => TRUE,
986
    'email' => TRUE,
987
    'email_address' => FALSE,
988
    'email_name' => FALSE,
989
    'required' => TRUE,
990
    'title' => TRUE,
991
    'title_display' => TRUE,
992
    'title_inline' => TRUE,
993
    'conditional' => TRUE,
994 a45e4bc1 Assos Assos
    'conditional_action_set' => FALSE,
995 85ad3d82 Assos Assos
    'spam_analysis' => FALSE,
996
    'group' => FALSE,
997
    'attachment' => FALSE,
998
    'private' => TRUE,
999 a45e4bc1 Assos Assos
    'placeholder' => FALSE,
1000
    'wrapper_classes' => TRUE,
1001
    'css_classes' => TRUE,
1002
    'views_range' => FALSE,
1003 85ad3d82 Assos Assos
  );
1004
  return isset($components[$type]['features'][$feature]) ? $components[$type]['features'][$feature] : !empty($defaults[$feature]);
1005
}
1006
1007 a45e4bc1 Assos Assos
/**
1008
 * Get a component property from the component definition.
1009
 *
1010
 * @see hook_webform_component_info()
1011
 */
1012
function webform_component_property($type, $property) {
1013
  $components = webform_components();
1014
  $defaults = array(
1015
    'conditional_type' => 'string',
1016
  );
1017
  return isset($components[$type][$property]) ? $components[$type][$property] : $defaults[$property];
1018
}
1019
1020 85ad3d82 Assos Assos
/**
1021
 * Create a list of components suitable for a select list.
1022
 *
1023
 * @param $node
1024
 *   The webform node.
1025
 * @param $component_filter
1026
 *   Either an array of components, or a string containing a feature name (csv,
1027
 *   email, required, conditional) on which this list of components will be
1028
 *   restricted.
1029 a45e4bc1 Assos Assos
 * @param $prefix_group
1030 8c72e82a Assos Assos
 *   TRUE to indent with a hyphen, or 'path" to Prepend enclosing group (for example,
1031 a45e4bc1 Assos Assos
 *   fieldset) name(s)
1032
 * @param $pagebreak_groups
1033 85ad3d82 Assos Assos
 *   Determine if pagebreaks should be converted to option groups in the
1034
 *   returned list of options.
1035 8c72e82a Assos Assos
 *
1036
 * @return array
1037 85ad3d82 Assos Assos
 */
1038 a45e4bc1 Assos Assos
function webform_component_list($node, $component_filter = NULL, $prepend_group = TRUE, $pagebreak_groups = FALSE) {
1039 85ad3d82 Assos Assos
  $options = array();
1040
  $page_names = array();
1041 a45e4bc1 Assos Assos
  $parent_names = array();
1042 85ad3d82 Assos Assos
1043
  $components = is_array($component_filter) ? $component_filter : $node->webform['components'];
1044
  $feature = is_string($component_filter) ? $component_filter : NULL;
1045
1046
  foreach ($components as $cid => $component) {
1047 8c72e82a Assos Assos
    // If this component is a group (for example, fieldset), then remember its name, including any parents.
1048 a45e4bc1 Assos Assos
    if ($prepend_group && webform_component_feature($component['type'], 'group')) {
1049
      $parent_names[$cid] = ($component['pid'] ? $parent_names[$component['pid']] : '') .
1050
                            ($prepend_group === 'path' ? $component['name'] . ': ' : '-');
1051
    }
1052
    $page_num = $component['page_num'];
1053
    // If this component is a pagebreak, then generate an option group, ensuring a unique name.
1054
    if ($pagebreak_groups && $component['type'] == 'pagebreak') {
1055
      $page_name = $component['name'];
1056 8c72e82a Assos Assos
1057
      // When a $page_name consists only of digits, append a space to ensure it
1058
      // is never the same as a $cid.
1059
      if ((string) $page_name === (string) (int) $page_name) {
1060
        $page_name .= ' ';
1061
      }
1062
1063 a45e4bc1 Assos Assos
      $copy = 1;
1064
      while (in_array($page_name, $page_names)) {
1065
        $page_name = $component['name'] . '_' . ++$copy;
1066 85ad3d82 Assos Assos
      }
1067 a45e4bc1 Assos Assos
      $page_names[$page_num] = $page_name;
1068 85ad3d82 Assos Assos
    }
1069 a45e4bc1 Assos Assos
    // If this component should be included in the options, add it with any prefix, in a page group, as needed.
1070
    if (!isset($feature) || webform_component_feature($component['type'], $feature) || $prepend_group === TRUE) {
1071
      $prefix = ($prepend_group && $component['pid']) ? $parent_names[$component['pid']] : '';
1072
      if ($pagebreak_groups && $page_num > 1) {
1073
        $options[$page_names[$page_num]][$cid] = $prefix . $component['name'];
1074 85ad3d82 Assos Assos
      }
1075
      else {
1076 a45e4bc1 Assos Assos
        $options[$cid] = $prefix . $component['name'];
1077 85ad3d82 Assos Assos
      }
1078
    }
1079
  }
1080
1081
  return $options;
1082
}
1083
1084
/**
1085
 * A Form API process function to expand a component list into checkboxes.
1086
 */
1087
function webform_component_select($element) {
1088
  // Split the select list into checkboxes.
1089
  foreach ($element['#options'] as $key => $label) {
1090
    $label_length = strlen($label);
1091
    $label = preg_replace('/^(\-)+/', '', $label);
1092
    $indents = $label_length - strlen($label);
1093
    $element[$key] = array(
1094
      '#title' => $label,
1095
      '#type' => 'checkbox',
1096
      '#default_value' => array_search($key, $element['#value']) !== FALSE,
1097
      '#return_value' => $key,
1098
      '#parents' => array_merge($element['#parents'], array($key)),
1099
      '#indent' => $indents,
1100
    );
1101
  }
1102
1103
  $element['#theme_wrappers'] = array();
1104
  $element['#type'] = 'webform_component_select';
1105
  $element['#theme'] = 'webform_component_select';
1106
  $element['#attached'] = array(
1107
    'library' => array(
1108
      array('webform', 'admin'),
1109 a45e4bc1 Assos Assos
      array('system', 'drupal.collapse'),
1110 85ad3d82 Assos Assos
    ),
1111
    'js' => array(
1112
      'misc/tableselect.js' => array(),
1113
    ),
1114
  );
1115
1116
  return $element;
1117
}
1118
1119
/**
1120
 * Theme the contents of a Webform component select element.
1121
 */
1122
function theme_webform_component_select($variables) {
1123
  $element = $variables['element'];
1124
1125
  $rows = array();
1126
  $header = array();
1127
  if (!isset($element['#all_checkbox']) || $element['#all_checkbox']) {
1128
    $header = array(array('class' => array('select-all'), 'data' => ' ' . t('Include all components')));
1129
  }
1130
  foreach (element_children($element) as $key) {
1131 a45e4bc1 Assos Assos
    if ($key != 'suffix') {
1132
      $rows[] = array(
1133
        theme('indentation', array('size' => $element[$key]['#indent'])) . drupal_render($element[$key]),
1134
      );
1135
    }
1136 85ad3d82 Assos Assos
  }
1137
1138
  $element['#type'] = 'fieldset';
1139
  $element['#value'] = NULL;
1140
  $element['#attributes']['class'] = array('webform-component-select-table');
1141
  if (!isset($element['#collapsible']) || $element['#collapsible']) {
1142
    $element['#attributes']['class'][] = 'collapsible';
1143
  }
1144
  if (!isset($element['#collapsed']) || $element['#collapsed']) {
1145
    $element['#attributes']['class'][] = 'collapsed';
1146
  }
1147
1148
  if (empty($rows)) {
1149
    $element['#children'] = t('No available components.');
1150
  }
1151
  else {
1152 a45e4bc1 Assos Assos
    $element['#children'] = '<div class="webform-component-select-wrapper">' . theme('table', array('header' => $header, 'rows' => $rows, 'sticky' => FALSE)) . '</div>';
1153 85ad3d82 Assos Assos
  }
1154
1155 a45e4bc1 Assos Assos
  if (isset($element['suffix'])) {
1156
    $element['#children'] .= '<div class="webform-component-select-suffix">' . drupal_render($element['suffix']) . '</div>';
1157
  }
1158
1159
1160 85ad3d82 Assos Assos
  return theme('fieldset', array('element' => $element));
1161
}
1162
1163
/**
1164 8c72e82a Assos Assos
 * Find a component's parents within a node.
1165
 *
1166
 * @param object $node
1167
 *   The webform node.
1168
 * @param array $component
1169
 *   The component to start with.
1170
 * @param string|true $what_to_return
1171
 *   If TRUE, return complete component arrays. Otherwise, return the property
1172
 *   of each component named in this parametre.
1173
 *
1174
 * @return array
1175
 *   An array with a value for each parent and for the start component, in order
1176
 *   ending with start component. What the value is is controlled by
1177
 *   $what_to_return.
1178 85ad3d82 Assos Assos
 */
1179 8c72e82a Assos Assos
function webform_component_parent_keys($node, array $component, $what_to_return = 'form_key') {
1180
  $parents = array(($what_to_return === TRUE) ? $component : $component[$what_to_return]);
1181 85ad3d82 Assos Assos
  $pid = $component['pid'];
1182
  while ($pid) {
1183 8c72e82a Assos Assos
    $parents[] = ($what_to_return === TRUE) ? $node->webform['components'][$pid] : $node->webform['components'][$pid][$what_to_return];
1184 85ad3d82 Assos Assos
    $pid = $node->webform['components'][$pid]['pid'];
1185
  }
1186
  return array_reverse($parents);
1187
}
1188
1189
/**
1190
 * Populate a component with the defaults for that type.
1191
 */
1192
function webform_component_defaults(&$component) {
1193 3753f249 Assos Assos
  $defaults = webform_component_invoke($component['type'], 'defaults');
1194
  drupal_alter('webform_component_defaults', $defaults, $component['type']);
1195
  if (!empty($defaults)) {
1196 85ad3d82 Assos Assos
    foreach ($defaults as $key => $default) {
1197
      if (!isset($component[$key])) {
1198
        $component[$key] = $default;
1199
      }
1200
    }
1201
    foreach ($defaults['extra'] as $extra => $default) {
1202
      if (!isset($component['extra'][$extra])) {
1203
        $component['extra'][$extra] = $default;
1204
      }
1205
    }
1206
  }
1207
}
1208
1209
/**
1210
 * Validate an element value is unique with no duplicates in the database.
1211
 */
1212
function webform_validate_unique($element, $form_state) {
1213
  if ($element['#value'] !== '') {
1214
    $nid = $form_state['values']['details']['nid'];
1215
    $sid = $form_state['values']['details']['sid'];
1216
    $query = db_select('webform_submitted_data')
1217
      ->fields('webform_submitted_data', array('sid'))
1218
      ->condition('nid', $nid)
1219
      ->condition('cid', $element['#webform_component']['cid'])
1220
      ->condition('data', $element['#value'])
1221
      ->range(0, 1); // More efficient than using countQuery() for data checks.
1222
    if ($sid) {
1223
      $query->condition('sid', $sid, '<>');
1224
    }
1225
    $count = $query->execute()->fetchField();
1226
    if ($count) {
1227
      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'])));
1228
    }
1229
  }
1230
}