Projet

Général

Profil

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

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

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
  $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 a45e4bc1 Assos Assos
    $form['components'][$cid]['required'] = array(
72 85ad3d82 Assos Assos
      '#type' => 'checkbox',
73 a45e4bc1 Assos Assos
      '#title' => t('Required'),
74
      '#default_value' => $component['required'],
75 85ad3d82 Assos Assos
      '#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 ba09eb79 Assos Assos
    '#size' => 30,
85 a45e4bc1 Assos Assos
    '#maxlength' => NULL,
86 85ad3d82 Assos Assos
  );
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 a45e4bc1 Assos Assos
  $form['add']['required'] = array(
95 85ad3d82 Assos Assos
    '#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 a45e4bc1 Assos Assos
  $form['actions'] = array(
138
    '#type' => 'actions',
139
    '#weight' => 45,
140
  );
141 3753f249 Assos Assos
  $form['actions']['submit'] = array(
142 85ad3d82 Assos Assos
    '#type' => 'submit',
143
    '#value' => t('Save'),
144
    '#access' => count($node->webform['components']) > 0,
145
  );
146 a45e4bc1 Assos Assos
  $form['warning'] = array(
147
    '#weight' => -1,
148
  );
149
  webform_input_vars_check($form, $form_state, 'components', 'warning');
150 85ad3d82 Assos Assos
151
  return $form;
152
}
153
154
/**
155 a45e4bc1 Assos Assos
 * Preprocess variables for theming the webform components form.
156 85ad3d82 Assos Assos
 */
157 a45e4bc1 Assos Assos
function template_preprocess_webform_components_form(&$variables) {
158 85ad3d82 Assos Assos
  $form = $variables['form'];
159
160
  $form['components']['#attached']['library'][] = array('webform', 'admin');
161
162 01f36513 Assos Assos
  // @todo: Attach these. See http://drupal.org/node/732022.
163 85ad3d82 Assos Assos
  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 ba09eb79 Assos Assos
  $header = array(t('Label'), t('Form key'), t('Type'), t('Value'), t('Required'), t('Weight'), array('data' => t('Operations'), 'colspan' => 3));
169 85ad3d82 Assos Assos
  $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 a45e4bc1 Assos Assos
  $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 85ad3d82 Assos Assos
  $row_data = array(
178 ba09eb79 Assos Assos
    array('data' => drupal_render($form['add']['name']), 'class' => array('webform-component-name'), 'colspan' => 2),
179 a45e4bc1 Assos Assos
    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 85ad3d82 Assos Assos
  );
185 a45e4bc1 Assos Assos
  $add_form = array('data' => $row_data, 'class' => array('draggable', 'webform-add-form', 'tabledrag-leaf'));
186 85ad3d82 Assos Assos
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 a45e4bc1 Assos Assos
    // Build the table rows recursively.
193 85ad3d82 Assos Assos
    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 e9984459 Assos Assos
    // 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 85ad3d82 Assos Assos
  }
206
207
  // Append the add form if not already printed.
208
  if ($add_form) {
209
    $rows[] = $add_form;
210
  }
211
212 a45e4bc1 Assos Assos
  $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 ba09eb79 Assos Assos
  $form_key = truncate_utf8($component['form_key'], 30, FALSE, TRUE);
225
  $value = truncate_utf8($component['value'], 30, TRUE, TRUE, 20);
226 a45e4bc1 Assos Assos
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 ba09eb79 Assos Assos
    array('data' => check_plain($form_key), 'class' => array('webform-component-formkey')) +
247 feca1e4a Assos Assos
    ((string) $component['form_key'] === (string) $form_key ? array() : array('title' => $component['form_key'])),
248 a45e4bc1 Assos Assos
    array('data' => $form['add']['type']['#options'][$component['type']], 'class' => array('webform-component-type')),
249 ba09eb79 Assos Assos
    array('data' => ($value == '') ? '-' : check_plain($value), 'class' => array('webform-component-value')) +
250 feca1e4a Assos Assos
    ($component['value'] == $value ? array() : array('title' => $component['value'])),
251 a45e4bc1 Assos Assos
    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 01f36513 Assos Assos
 * @return string
285 a45e4bc1 Assos Assos
 *   Formatted HTML form, ready for display.
286
 */
287
function theme_webform_components_form($variables) {
288 85ad3d82 Assos Assos
  $output = '';
289 a45e4bc1 Assos Assos
  $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 85ad3d82 Assos Assos
  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 8c72e82a Assos Assos
      $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 85ad3d82 Assos Assos
        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 a45e4bc1 Assos Assos
        $items[] = webform_filter_xss($form['#node']->webform['components'][$cid]['name']);
321 85ad3d82 Assos Assos
      }
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 a45e4bc1 Assos Assos
  // Update all required and weight values.
345 85ad3d82 Assos Assos
  $changes = FALSE;
346
  foreach ($node->webform['components'] as $cid => $component) {
347 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']) {
348 85ad3d82 Assos Assos
      $changes = TRUE;
349
      $node->webform['components'][$cid]['weight'] = $form_state['values']['components'][$cid]['weight'];
350 a45e4bc1 Assos Assos
      $node->webform['components'][$cid]['required'] = $form_state['values']['components'][$cid]['required'];
351 85ad3d82 Assos Assos
      $node->webform['components'][$cid]['pid'] = $form_state['values']['components'][$cid]['pid'];
352
    }
353
  }
354
355
  if ($changes) {
356
    node_save($node);
357
  }
358
359 a45e4bc1 Assos Assos
  drupal_set_message(t('The component positions and required values have been updated.'));
360 85ad3d82 Assos Assos
}
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 a45e4bc1 Assos Assos
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 85ad3d82 Assos Assos
}
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 a45e4bc1 Assos Assos
  $form['#node'] = $node;
394 85ad3d82 Assos Assos
  $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 a45e4bc1 Assos Assos
      '#maxlength' => NULL,
424 85ad3d82 Assos Assos
    );
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 6a93dd76 Assos Assos
    '#title' => t('Form Key'),
431 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.'),
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 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'),
443 85ad3d82 Assos Assos
      '#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 8d02775b Assos Assos
    $inline_option = webform_component_feature($component['type'], 'title_inline') ? array('inline' => t('Inline')) : array();
457
    $internal_option = webform_component_feature($component['type'], 'title_internal') ? array('internal' => t('Inside the component')) : array();
458
    if ($inline_option || $internal_option) {
459 85ad3d82 Assos Assos
      $form['display']['title_display'] = array(
460
        '#type' => 'select',
461
        '#title' => t('Label display'),
462
        '#default_value' => !empty($component['extra']['title_display']) ? $component['extra']['title_display'] : 'before',
463 8d02775b Assos Assos
        '#options' => array('before' => t('Above')) +
464
                      $inline_option +
465
                      $internal_option +
466
                      array('none' => t('None')),
467 76bdcd04 Assos Assos
        '#description' => t("Determines the placement of the component's label."),
468 85ad3d82 Assos Assos
      );
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 01f36513 Assos Assos
  $additional_form_elements = (array) webform_component_invoke($component['type'], 'edit', $component, $form, $form_state);
584 85ad3d82 Assos Assos
  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 389fb945 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 feca1e4a Assos Assos
  // 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 feca1e4a Assos Assos
      array(
725
        '%name' => $node->webform['components'][$cid]['name'],
726 8c72e82a Assos Assos
        '@type' => webform_component_property($component_type, 'label'),
727 feca1e4a Assos Assos
        '%webform' => $node->title,
728
      ));
729 85ad3d82 Assos Assos
  }
730
  else {
731
    $question = t('Delete the %name component?', array('%name' => $node->webform['components'][$cid]['name']));
732
    $description = t('This will immediately delete the %name component from the %webform webform. This cannot be undone.', array('%name' => $node->webform['components'][$cid]['name'], '%webform' => $node->title));
733
  }
734
735
  return confirm_form($form, $question, 'node/' . $node->nid . '/webform/components', $description, t('Delete'));
736
}
737
738
/**
739
 * Submit handler for webform_component_delete_form().
740
 */
741
function webform_component_delete_form_submit($form, &$form_state) {
742
  // Delete the component.
743
  $node = $form_state['values']['node'];
744
  $component = $form_state['values']['component'];
745
  webform_component_delete($node, $component);
746
  drupal_set_message(t('Component %name deleted.', array('%name' => $component['name'])));
747
748
  // Check if this webform still contains any information.
749
  unset($node->webform['components'][$component['cid']]);
750
  webform_check_record($node);
751
752
  // Since Webform components have been updated but the node itself has not
753
  // been saved, it is necessary to explicitly clear the cache to make sure
754 a45e4bc1 Assos Assos
  // the updated webform is visible to anonymous users. This resets the page
755 feca1e4a Assos Assos
  // and block caches (only).
756 85ad3d82 Assos Assos
  cache_clear_all();
757
758 a45e4bc1 Assos Assos
  // Refresh the entity cache, should it be cached in persistent storage.
759
  entity_get_controller('node')->resetCache(array($node->nid));
760 85ad3d82 Assos Assos
761
  $form_state['redirect'] = 'node/' . $node->nid . '/webform/components';
762
}
763
764
/**
765
 * Insert a new component into the database.
766
 *
767
 * @param $component
768
 *   A full component containing fields from the component form.
769 8c72e82a Assos Assos
 *
770
 * @return false|int
771
 *   On success return identifier for the components within node.
772
 *   FALSE on failure
773 85ad3d82 Assos Assos
 */
774
function webform_component_insert(&$component) {
775
  // Allow modules to modify the component before saving.
776
  foreach (module_implements('webform_component_presave') as $module) {
777
    $function = $module . '_webform_component_presave';
778
    $function($component);
779
  }
780
781
  $component['value'] = isset($component['value']) ? $component['value'] : NULL;
782 a45e4bc1 Assos Assos
  $component['required'] = isset($component['required']) ? $component['required'] : 0;
783 85ad3d82 Assos Assos
  $component['extra']['private'] = isset($component['extra']['private']) ? $component['extra']['private'] : 0;
784
785
  if (!isset($component['cid'])) {
786
    if (lock_acquire('webform_component_insert_' . $component['nid'], 5)) {
787
      $next_id_query = db_select('webform_component')->condition('nid', $component['nid']);
788
      $next_id_query->addExpression('MAX(cid) + 1', 'cid');
789
      $component['cid'] = $next_id_query->execute()->fetchField();
790
      if ($component['cid'] == NULL) {
791
        $component['cid'] = 1;
792
      }
793
      lock_release('webform_component_insert_' . $component['nid']);
794
    }
795
    else {
796
      watchdog('webform', 'A Webform component could not be saved because a timeout occurred while trying to acquire a lock for the node. Details: <pre>@component</pre>', array('@component' => print_r($component, TRUE)));
797
      return FALSE;
798
    }
799
  }
800
801 feca1e4a Assos Assos
  db_insert('webform_component')
802 85ad3d82 Assos Assos
    ->fields(array(
803
      'nid' => $component['nid'],
804
      'cid' => $component['cid'],
805
      'pid' => $component['pid'],
806
      'form_key' => $component['form_key'],
807
      'name' => $component['name'],
808
      'type' => $component['type'],
809
      'value' => (string) $component['value'],
810
      'extra' => serialize($component['extra']),
811 a45e4bc1 Assos Assos
      'required' => $component['required'],
812 85ad3d82 Assos Assos
      'weight' => $component['weight'],
813
    ))
814
    ->execute();
815
816
  // Post-insert actions.
817
  module_invoke_all('webform_component_insert', $component);
818
819
  return $component['cid'];
820
}
821
822
/**
823
 * Update an existing component with new values.
824
 *
825
 * @param $component
826
 *   A full component containing a nid, cid, and all other fields from the
827
 *   component form. Additional properties are stored in the extra array.
828
 */
829
function webform_component_update($component) {
830
  // Allow modules to modify the component before saving.
831
  foreach (module_implements('webform_component_presave') as $module) {
832
    $function = $module . '_webform_component_presave';
833
    $function($component);
834
  }
835
836
  $component['value'] = isset($component['value']) ? $component['value'] : NULL;
837 a45e4bc1 Assos Assos
  $component['required'] = isset($component['required']) ? $component['required'] : 0;
838 85ad3d82 Assos Assos
  $component['extra']['private'] = isset($component['extra']['private']) ? $component['extra']['private'] : 0;
839
  db_update('webform_component')
840
    ->fields(array(
841
      'pid' => $component['pid'],
842
      'form_key' => $component['form_key'],
843
      'name' => $component['name'],
844
      'type' => $component['type'],
845
      'value' => isset($component['value']) ? $component['value'] : '',
846
      'extra' => serialize($component['extra']),
847 a45e4bc1 Assos Assos
      'required' => $component['required'],
848 feca1e4a Assos Assos
      'weight' => $component['weight'],
849 85ad3d82 Assos Assos
    ))
850
    ->condition('nid', $component['nid'])
851
    ->condition('cid', $component['cid'])
852
    ->execute();
853
854
  // Post-update actions.
855
  module_invoke_all('webform_component_update', $component);
856
}
857
858 feca1e4a Assos Assos
/**
859 76bdcd04 Assos Assos
 * Delete a Webform component.
860 feca1e4a Assos Assos
 */
861 85ad3d82 Assos Assos
function webform_component_delete($node, $component) {
862
  // Check if a delete function is available for this component. If so,
863
  // load all submissions and allow the component to delete each one.
864
  webform_component_include($component['type']);
865
  $delete_function = '_webform_delete_' . $component['type'];
866
  if (function_exists($delete_function)) {
867
    module_load_include('inc', 'webform', 'includes/webform.submissions');
868
    $submissions = webform_get_submissions($node->nid);
869
    foreach ($submissions as $submission) {
870
      if (isset($submission->data[$component['cid']])) {
871 a45e4bc1 Assos Assos
        webform_component_invoke($component['type'], 'delete', $component, $submission->data[$component['cid']]);
872 85ad3d82 Assos Assos
      }
873
    }
874
  }
875
876
  // Remove database entries.
877
  db_delete('webform_component')
878
    ->condition('nid', $node->nid)
879
    ->condition('cid', $component['cid'])
880
    ->execute();
881
  db_delete('webform_submitted_data')
882
    ->condition('nid', $node->nid)
883
    ->condition('cid', $component['cid'])
884
    ->execute();
885
886 a45e4bc1 Assos Assos
  // Delete any conditionals dependent on this component.
887
  module_load_include('inc', 'webform', 'includes/webform.conditionals');
888 e9984459 Assos Assos
  foreach ($node->webform['conditionals'] as $rgid => &$conditional) {
889 a45e4bc1 Assos Assos
    $specs = array(
890
      array(
891
        'field' => 'rules',
892
        'table' => 'webform_conditional_rules',
893
        'component' => 'source',
894
        'component_type' => 'source_type',
895
        'index' => 'rid',
896
      ),
897
      array(
898
        'field' => 'actions',
899
        'table' => 'webform_conditional_actions',
900
        'component' => 'target',
901
        'component_type' => 'target_type',
902
        'index' => 'aid',
903
      ),
904
    );
905
    foreach ($specs as $spec) {
906 e9984459 Assos Assos
      $deleted = array();
907
      $field = $spec['field'];
908
      foreach ($conditional[$field] as $key => $thing) {
909 a45e4bc1 Assos Assos
        if ($thing[$spec['component_type']] === 'component' && $thing[$spec['component']] == $component['cid']) {
910 e9984459 Assos Assos
          $deleted[$key] = $key;
911
          unset($conditional[$field][$key]);
912 a45e4bc1 Assos Assos
        }
913
      }
914 e9984459 Assos Assos
      if ($spec['field'] == 'rules') {
915
        // Rules deleted because of the source component being deleted may have left
916
        // empty sub-conditionals. Delete them, and then the entire rule group if
917
        // there aren't any rules left.
918
        $deleted += webform_delete_empty_subconditionals($conditional);
919
      }
920
      // Delete the conditional if this component is the only source / target.
921
      if (empty($conditional[$field])) {
922
        webform_conditional_delete($node, $conditional);
923 76bdcd04 Assos Assos
        // Also delete the conditional from the $node so it is not re-created
924
        // later on in webform_node_update().
925
        unset($node->webform['conditionals'][$conditional['rgid']]);
926 feca1e4a Assos Assos
        // Loop exit.
927
        break;
928 e9984459 Assos Assos
      }
929
      // Remove the deleted rules / actions from the database.
930
      foreach ($deleted as $key) {
931
        db_delete($spec['table'])
932
          ->condition('nid', $node->nid)
933
          ->condition('rgid', $rgid)
934
          ->condition($spec['index'], $key)
935
          ->execute();
936
      }
937 a45e4bc1 Assos Assos
    }
938
  }
939
940 85ad3d82 Assos Assos
  // Delete all elements under this element.
941
  $result = db_select('webform_component', 'c')
942
    ->fields('c')
943
    ->condition('nid', $node->nid)
944
    ->condition('pid', $component['cid'])
945
    ->execute();
946
  foreach ($result as $row) {
947
    $child_component = $node->webform['components'][$row->cid];
948
    webform_component_delete($node, $child_component);
949
  }
950
951
  // Post-delete actions.
952
  module_invoke_all('webform_component_delete', $component);
953
}
954
955
/**
956
 * Recursively insert components into the database.
957
 *
958
 * @param $node
959
 *   The node object containing the current webform.
960
 * @param $component
961
 *   A full component containing fields from the component form.
962 8c72e82a Assos Assos
 *
963
 * @return false|int
964
 *   On success return identifier for the components within node.
965
 *   FALSE on failure
966 85ad3d82 Assos Assos
 */
967
function webform_component_clone(&$node, &$component) {
968
  $original_cid = $component['cid'];
969
  $component['cid'] = NULL;
970
  $new_cid = webform_component_insert($component);
971
  $component['cid'] = $new_cid;
972
  if (webform_component_feature($component['type'], 'group')) {
973
    foreach ($node->webform['components'] as $cid => $child_component) {
974
      if ($child_component['pid'] == $original_cid) {
975
        $child_component['pid'] = $new_cid;
976
        webform_component_clone($node, $child_component);
977
      }
978
    }
979
  }
980
  return $new_cid;
981
}
982
983
/**
984
 * Check if a component has a particular feature.
985
 *
986
 * @see hook_webform_component_info()
987
 */
988
function webform_component_feature($type, $feature) {
989
  $components = webform_components();
990
  $defaults = array(
991 a45e4bc1 Assos Assos
    'analysis' => TRUE,
992 85ad3d82 Assos Assos
    'csv' => TRUE,
993
    'default_value' => TRUE,
994
    'description' => TRUE,
995
    'email' => TRUE,
996
    'email_address' => FALSE,
997
    'email_name' => FALSE,
998
    'required' => TRUE,
999
    'title' => TRUE,
1000
    'title_display' => TRUE,
1001
    'title_inline' => TRUE,
1002 8d02775b Assos Assos
    'title_internal' => FALSE,
1003 85ad3d82 Assos Assos
    'conditional' => TRUE,
1004 a45e4bc1 Assos Assos
    'conditional_action_set' => FALSE,
1005 85ad3d82 Assos Assos
    'spam_analysis' => FALSE,
1006
    'group' => FALSE,
1007
    'attachment' => FALSE,
1008
    'private' => TRUE,
1009 a45e4bc1 Assos Assos
    'placeholder' => FALSE,
1010
    'wrapper_classes' => TRUE,
1011
    'css_classes' => TRUE,
1012
    'views_range' => FALSE,
1013 85ad3d82 Assos Assos
  );
1014
  return isset($components[$type]['features'][$feature]) ? $components[$type]['features'][$feature] : !empty($defaults[$feature]);
1015
}
1016
1017 a45e4bc1 Assos Assos
/**
1018
 * Get a component property from the component definition.
1019
 *
1020
 * @see hook_webform_component_info()
1021
 */
1022
function webform_component_property($type, $property) {
1023
  $components = webform_components();
1024
  $defaults = array(
1025
    'conditional_type' => 'string',
1026
  );
1027
  return isset($components[$type][$property]) ? $components[$type][$property] : $defaults[$property];
1028
}
1029
1030 85ad3d82 Assos Assos
/**
1031
 * Create a list of components suitable for a select list.
1032
 *
1033
 * @param $node
1034
 *   The webform node.
1035
 * @param $component_filter
1036
 *   Either an array of components, or a string containing a feature name (csv,
1037
 *   email, required, conditional) on which this list of components will be
1038
 *   restricted.
1039 a45e4bc1 Assos Assos
 * @param $prefix_group
1040 8c72e82a Assos Assos
 *   TRUE to indent with a hyphen, or 'path" to Prepend enclosing group (for example,
1041 a45e4bc1 Assos Assos
 *   fieldset) name(s)
1042
 * @param $pagebreak_groups
1043 85ad3d82 Assos Assos
 *   Determine if pagebreaks should be converted to option groups in the
1044
 *   returned list of options.
1045 8c72e82a Assos Assos
 *
1046
 * @return array
1047 01f36513 Assos Assos
 *   An array of options.
1048 85ad3d82 Assos Assos
 */
1049 a45e4bc1 Assos Assos
function webform_component_list($node, $component_filter = NULL, $prepend_group = TRUE, $pagebreak_groups = FALSE) {
1050 85ad3d82 Assos Assos
  $options = array();
1051
  $page_names = array();
1052 a45e4bc1 Assos Assos
  $parent_names = array();
1053 85ad3d82 Assos Assos
1054
  $components = is_array($component_filter) ? $component_filter : $node->webform['components'];
1055
  $feature = is_string($component_filter) ? $component_filter : NULL;
1056
1057
  foreach ($components as $cid => $component) {
1058 8c72e82a Assos Assos
    // If this component is a group (for example, fieldset), then remember its name, including any parents.
1059 a45e4bc1 Assos Assos
    if ($prepend_group && webform_component_feature($component['type'], 'group')) {
1060
      $parent_names[$cid] = ($component['pid'] ? $parent_names[$component['pid']] : '') .
1061
                            ($prepend_group === 'path' ? $component['name'] . ': ' : '-');
1062
    }
1063
    $page_num = $component['page_num'];
1064
    // If this component is a pagebreak, then generate an option group, ensuring a unique name.
1065
    if ($pagebreak_groups && $component['type'] == 'pagebreak') {
1066
      $page_name = $component['name'];
1067 8c72e82a Assos Assos
1068
      // When a $page_name consists only of digits, append a space to ensure it
1069
      // is never the same as a $cid.
1070
      if ((string) $page_name === (string) (int) $page_name) {
1071
        $page_name .= ' ';
1072
      }
1073
1074 a45e4bc1 Assos Assos
      $copy = 1;
1075
      while (in_array($page_name, $page_names)) {
1076
        $page_name = $component['name'] . '_' . ++$copy;
1077 85ad3d82 Assos Assos
      }
1078 a45e4bc1 Assos Assos
      $page_names[$page_num] = $page_name;
1079 85ad3d82 Assos Assos
    }
1080 a45e4bc1 Assos Assos
    // If this component should be included in the options, add it with any prefix, in a page group, as needed.
1081
    if (!isset($feature) || webform_component_feature($component['type'], $feature) || $prepend_group === TRUE) {
1082
      $prefix = ($prepend_group && $component['pid']) ? $parent_names[$component['pid']] : '';
1083
      if ($pagebreak_groups && $page_num > 1) {
1084
        $options[$page_names[$page_num]][$cid] = $prefix . $component['name'];
1085 85ad3d82 Assos Assos
      }
1086
      else {
1087 a45e4bc1 Assos Assos
        $options[$cid] = $prefix . $component['name'];
1088 85ad3d82 Assos Assos
      }
1089
    }
1090
  }
1091
1092
  return $options;
1093
}
1094
1095
/**
1096
 * A Form API process function to expand a component list into checkboxes.
1097
 */
1098
function webform_component_select($element) {
1099
  // Split the select list into checkboxes.
1100
  foreach ($element['#options'] as $key => $label) {
1101
    $label_length = strlen($label);
1102
    $label = preg_replace('/^(\-)+/', '', $label);
1103
    $indents = $label_length - strlen($label);
1104
    $element[$key] = array(
1105
      '#title' => $label,
1106
      '#type' => 'checkbox',
1107
      '#default_value' => array_search($key, $element['#value']) !== FALSE,
1108
      '#return_value' => $key,
1109
      '#parents' => array_merge($element['#parents'], array($key)),
1110
      '#indent' => $indents,
1111
    );
1112
  }
1113
1114
  $element['#theme_wrappers'] = array();
1115
  $element['#type'] = 'webform_component_select';
1116
  $element['#theme'] = 'webform_component_select';
1117
  $element['#attached'] = array(
1118
    'library' => array(
1119
      array('webform', 'admin'),
1120 a45e4bc1 Assos Assos
      array('system', 'drupal.collapse'),
1121 85ad3d82 Assos Assos
    ),
1122
    'js' => array(
1123
      'misc/tableselect.js' => array(),
1124
    ),
1125
  );
1126
1127
  return $element;
1128
}
1129
1130
/**
1131
 * Theme the contents of a Webform component select element.
1132
 */
1133
function theme_webform_component_select($variables) {
1134
  $element = $variables['element'];
1135
1136
  $rows = array();
1137
  $header = array();
1138
  if (!isset($element['#all_checkbox']) || $element['#all_checkbox']) {
1139
    $header = array(array('class' => array('select-all'), 'data' => ' ' . t('Include all components')));
1140
  }
1141
  foreach (element_children($element) as $key) {
1142 a45e4bc1 Assos Assos
    if ($key != 'suffix') {
1143
      $rows[] = array(
1144
        theme('indentation', array('size' => $element[$key]['#indent'])) . drupal_render($element[$key]),
1145
      );
1146
    }
1147 85ad3d82 Assos Assos
  }
1148
1149
  $element['#type'] = 'fieldset';
1150
  $element['#value'] = NULL;
1151
  $element['#attributes']['class'] = array('webform-component-select-table');
1152
  if (!isset($element['#collapsible']) || $element['#collapsible']) {
1153
    $element['#attributes']['class'][] = 'collapsible';
1154
  }
1155
  if (!isset($element['#collapsed']) || $element['#collapsed']) {
1156
    $element['#attributes']['class'][] = 'collapsed';
1157
  }
1158
1159
  if (empty($rows)) {
1160
    $element['#children'] = t('No available components.');
1161
  }
1162
  else {
1163 a45e4bc1 Assos Assos
    $element['#children'] = '<div class="webform-component-select-wrapper">' . theme('table', array('header' => $header, 'rows' => $rows, 'sticky' => FALSE)) . '</div>';
1164 85ad3d82 Assos Assos
  }
1165
1166 a45e4bc1 Assos Assos
  if (isset($element['suffix'])) {
1167
    $element['#children'] .= '<div class="webform-component-select-suffix">' . drupal_render($element['suffix']) . '</div>';
1168
  }
1169
1170 85ad3d82 Assos Assos
  return theme('fieldset', array('element' => $element));
1171
}
1172
1173
/**
1174 8c72e82a Assos Assos
 * Find a component's parents within a node.
1175
 *
1176
 * @param object $node
1177
 *   The webform node.
1178
 * @param array $component
1179
 *   The component to start with.
1180
 * @param string|true $what_to_return
1181
 *   If TRUE, return complete component arrays. Otherwise, return the property
1182
 *   of each component named in this parametre.
1183
 *
1184
 * @return array
1185
 *   An array with a value for each parent and for the start component, in order
1186
 *   ending with start component. What the value is is controlled by
1187
 *   $what_to_return.
1188 85ad3d82 Assos Assos
 */
1189 8c72e82a Assos Assos
function webform_component_parent_keys($node, array $component, $what_to_return = 'form_key') {
1190
  $parents = array(($what_to_return === TRUE) ? $component : $component[$what_to_return]);
1191 85ad3d82 Assos Assos
  $pid = $component['pid'];
1192
  while ($pid) {
1193 8c72e82a Assos Assos
    $parents[] = ($what_to_return === TRUE) ? $node->webform['components'][$pid] : $node->webform['components'][$pid][$what_to_return];
1194 85ad3d82 Assos Assos
    $pid = $node->webform['components'][$pid]['pid'];
1195
  }
1196
  return array_reverse($parents);
1197
}
1198
1199
/**
1200
 * Populate a component with the defaults for that type.
1201
 */
1202
function webform_component_defaults(&$component) {
1203 3753f249 Assos Assos
  $defaults = webform_component_invoke($component['type'], 'defaults');
1204
  drupal_alter('webform_component_defaults', $defaults, $component['type']);
1205
  if (!empty($defaults)) {
1206 85ad3d82 Assos Assos
    foreach ($defaults as $key => $default) {
1207
      if (!isset($component[$key])) {
1208
        $component[$key] = $default;
1209
      }
1210
    }
1211
    foreach ($defaults['extra'] as $extra => $default) {
1212
      if (!isset($component['extra'][$extra])) {
1213
        $component['extra'][$extra] = $default;
1214
      }
1215
    }
1216
  }
1217
}
1218
1219
/**
1220
 * Validate an element value is unique with no duplicates in the database.
1221
 */
1222
function webform_validate_unique($element, $form_state) {
1223
  if ($element['#value'] !== '') {
1224
    $nid = $form_state['values']['details']['nid'];
1225
    $sid = $form_state['values']['details']['sid'];
1226
    $query = db_select('webform_submitted_data')
1227
      ->fields('webform_submitted_data', array('sid'))
1228
      ->condition('nid', $nid)
1229
      ->condition('cid', $element['#webform_component']['cid'])
1230
      ->condition('data', $element['#value'])
1231 feca1e4a Assos Assos
      // More efficient than using countQuery() for data checks.
1232
      ->range(0, 1);
1233 85ad3d82 Assos Assos
    if ($sid) {
1234
      $query->condition('sid', $sid, '<>');
1235
    }
1236
    $count = $query->execute()->fetchField();
1237
    if ($count) {
1238
      form_error($element, t('The value %value has already been submitted once for the %title field. You may have already submitted this form, or you need to use a different value.', array('%value' => $element['#value'], '%title' => $element['#title'])));
1239
    }
1240
  }
1241
}