Project

General

Profile

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

root / drupal7 / sites / all / modules / webform / webform.module @ 76bdcd04

1
<?php
2

    
3
/**
4
 * @file
5
 * This module provides a simple way to create forms and questionnaires.
6
 *
7
 * The initial development of this module was sponsored by ÅF Industry AB, Open
8
 * Source City and Karlstad University Library. Continued development sponsored
9
 * by Lullabot.
10
 *
11
 * @author Nathan Haug <nate@lullabot.com>
12
 */
13

    
14
/**
15
 * Constants used in conditional logic.
16
 */
17
define('WEBFORM_CONDITIONAL_EXCLUDE', 0);
18
define('WEBFORM_CONDITIONAL_INCLUDE', 1);
19
define('WEBFORM_CONDITIONAL_SAME_PAGE', 2);
20

    
21
/**
22
 * Implements hook_help().
23
 */
24
function webform_help($section = 'admin/help#webform', $arg = NULL) {
25
  $output = '';
26
  switch ($section) {
27
    case 'admin/config/content/webform':
28
      module_load_include('inc', 'webform', 'includes/webform.admin');
29
      $type_list = webform_admin_type_list();
30
      $output = t('Webform enables nodes to have attached forms and questionnaires.');
31
      if ($type_list) {
32
        $output .= ' ' . t('To add one, create a !types piece of content.', array('!types' => $type_list));
33
      }
34
      else {
35
        $output .= ' <strong>' . t('Webform is currently not enabled on any content types.') . '</strong> ' . t('To use Webform, please enable it on at least one <a href="!url">content type</a>.', array('!url' => url('admin/structure/types')));
36
      }
37
      $output = '<p>' . $output . '</p>';
38
      break;
39

    
40
    case 'admin/content/webform':
41
      $output = '<p>' . t('This page lists all of the content on the site that may have a webform attached to it.') . '</p>';
42
      break;
43

    
44
    case 'admin/help#webform':
45
      module_load_include('inc', 'webform', 'includes/webform.admin');
46
      $types = webform_admin_type_list();
47
      if (empty($types)) {
48
        $types = t('Webform-enabled piece of content');
49
        $types_message = t('Webform is currently not enabled on any content types.') . ' ' . t('Visit the <a href="!url">Webform settings</a> page and enable Webform on at least one content type.', array('!url' => url('admin/config/content/webform')));
50
      }
51
      else {
52
        $types_message = t('Optional: Enable Webform on multiple types by visiting the <a href="!url">Webform settings</a> page.', array('!url' => url('admin/config/content/webform')));
53
      }
54
      $output = t("<p>This module lets you create forms or questionnaires and define their content. Submissions from these forms are stored in the database and optionally also sent by e-mail to a predefined address.</p>
55
      <p>Here is how to create one:</p>
56
      <ul>
57
        <li>!webform-types-message</li>
58
        <li>Go to <a href=\"!create-content\">Create content</a> and add a !types piece of content.</li>
59
        <li>After saving the new content, you will be redirected to the main field list of the form that will be created. Add the fields you would like on your form.</li>
60
        <li>Once finished adding fields, you may want to send e-mails to administrators or back to the user who filled out the form. Click on the <em>Emails</em> sub-tab underneath the <em>Webform</em> tab on the piece of content.</li>
61
        <li>Finally, visit the <em>Form settings</em> sub-tab under the <em>Webform</em> tab to configure remaining configurations options for your form.
62
          <ul>
63
          <li>Add a confirmation message and/or redirect URL that is to be displayed after successful submission.</li>
64
          <li>Set a submission limit.</li>
65
          <li>Determine which roles may submit the form.</li>
66
          <li>Advanced configuration options such as allowing drafts or show users a message indicating how they can edit their submissions.</li>
67
          </ul>
68
        </li>
69
        <li>Your form is now ready for viewing. After receiving submissions, you can check the results users have submitted by visiting the <em>Results</em> tab on the piece of content.</li>
70
      </ul>
71
      <p>Help on adding and configuring the components will be shown after you add your first component.</p>
72
      ", array(
73
        '!webform-types-message' => $types_message,
74
        '!create-content' => url('node/add'),
75
        '!types' => $types,
76
      )
77
      );
78
      break;
79

    
80
    case 'node/%/webform/conditionals':
81
      $output .= '<p>' . t('Conditionals may be used to hide or show certain components (or entire pages!) based on the value of other components.') . '</p>';
82
      break;
83

    
84
    case 'node/%/submission/%/resend':
85
      $output .= '<p>' . t('This form may be used to resend e-mails configured for this webform. Check the e-mails that need to be sent and click <em>Resend e-mails</em> to send these e-mails again.') . '</p>';
86
      break;
87
  }
88

    
89
  return $output;
90
}
91

    
92
/**
93
 * Implements hook_menu().
94
 */
95
function webform_menu() {
96
  $items = array();
97

    
98
  // Submissions listing.
99
  $items['admin/content/webform'] = array(
100
    'title' => 'Webforms',
101
    'page callback' => 'webform_admin_content',
102
    'access callback' => 'user_access',
103
    'access arguments' => array('access all webform results'),
104
    'description' => 'View and edit all the available webforms on your site.',
105
    'file' => 'includes/webform.admin.inc',
106
    'type' => MENU_LOCAL_TASK,
107
  );
108

    
109
  // Admin Settings.
110
  $items['admin/config/content/webform'] = array(
111
    'title' => 'Webform settings',
112
    'page callback' => 'drupal_get_form',
113
    'page arguments' => array('webform_admin_settings'),
114
    'access callback' => 'user_access',
115
    'access arguments' => array('administer site configuration'),
116
    'description' => 'Global configuration of webform functionality.',
117
    'file' => 'includes/webform.admin.inc',
118
    'type' => MENU_NORMAL_ITEM,
119
  );
120

    
121
  // Autocomplete used in Views integration.
122
  $items['webform/autocomplete'] = array(
123
    'title' => 'Webforms',
124
    'page callback' => 'webform_views_autocomplete',
125
    'access arguments' => array('administer views'),
126
    'file' => 'views/webform.views.inc',
127
    'type' => MENU_CALLBACK,
128
  );
129

    
130
  // Node page tabs.
131
  $items['node/%webform_menu/done'] = array(
132
    'title' => 'Webform confirmation',
133
    'page callback' => '_webform_confirmation',
134
    'page arguments' => array(1),
135
    'access callback' => 'webform_confirmation_page_access',
136
    'access arguments' => array(1),
137
    'type' => MENU_CALLBACK,
138
  );
139
  $items['node/%webform_menu/webform'] = array(
140
    'title' => 'Webform',
141
    'page callback' => 'webform_components_page',
142
    'page arguments' => array(1),
143
    'access callback' => 'webform_node_update_access',
144
    'access arguments' => array(1),
145
    'file' => 'includes/webform.components.inc',
146
    'weight' => 1,
147
    'type' => MENU_LOCAL_TASK,
148
    'context' => MENU_CONTEXT_PAGE | MENU_CONTEXT_INLINE,
149
  );
150
  $items['node/%webform_menu/webform/components'] = array(
151
    'title' => 'Form components',
152
    'page callback' => 'webform_components_page',
153
    'page arguments' => array(1),
154
    'access callback' => 'webform_node_update_access',
155
    'access arguments' => array(1),
156
    'file' => 'includes/webform.components.inc',
157
    'weight' => 0,
158
    'type' => MENU_DEFAULT_LOCAL_TASK,
159
  );
160
  $items['node/%webform_menu/webform/conditionals'] = array(
161
    'title' => 'Conditionals',
162
    'page callback' => 'drupal_get_form',
163
    'page arguments' => array('webform_conditionals_form', 1),
164
    'access callback' => 'webform_node_update_access',
165
    'access arguments' => array(1),
166
    'file' => 'includes/webform.conditionals.inc',
167
    'weight' => 1,
168
    'type' => MENU_LOCAL_TASK,
169
  );
170
  $items['node/%webform_menu/webform/configure'] = array(
171
    'title' => 'Form settings',
172
    'page callback' => 'drupal_get_form',
173
    'page arguments' => array('webform_configure_form', 1),
174
    'access callback' => 'webform_node_update_access',
175
    'access arguments' => array(1),
176
    'file' => 'includes/webform.pages.inc',
177
    'weight' => 5,
178
    'type' => MENU_LOCAL_TASK,
179
  );
180

    
181
  // Node e-mail forms.
182
  $items['node/%webform_menu/webform/emails'] = array(
183
    'title' => 'E-mails',
184
    'page callback' => 'drupal_get_form',
185
    'page arguments' => array('webform_emails_form', 1),
186
    'access callback' => 'webform_node_update_access',
187
    'access arguments' => array(1),
188
    'file' => 'includes/webform.emails.inc',
189
    'weight' => 4,
190
    'type' => MENU_LOCAL_TASK,
191
  );
192
  $items['node/%webform_menu/webform/emails/%webform_menu_email'] = array(
193
    'load arguments' => array(1),
194
    'page arguments' => array('webform_email_edit_form', 1, 4),
195
    'access callback' => 'webform_node_update_access',
196
    'access arguments' => array(1),
197
    'file' => 'includes/webform.emails.inc',
198
    'type' => MENU_LOCAL_TASK,
199
  );
200
  $items['node/%webform_menu/webform/emails/%webform_menu_email/clone'] = array(
201
    'load arguments' => array(1),
202
    'page arguments' => array('webform_email_edit_form', 1, 4, TRUE),
203
    'access callback' => 'webform_node_update_access',
204
    'access arguments' => array(1),
205
    'file' => 'includes/webform.emails.inc',
206
    'type' => MENU_LOCAL_TASK,
207
  );
208
  $items['node/%webform_menu/webform/emails/%webform_menu_email/delete'] = array(
209
    'load arguments' => array(1),
210
    'page arguments' => array('webform_email_delete_form', 1, 4),
211
    'access callback' => 'webform_node_update_access',
212
    'access arguments' => array(1),
213
    'type' => MENU_LOCAL_TASK,
214
  );
215

    
216
  // Node component forms.
217
  $items['node/%webform_menu/webform/components/%webform_menu_component'] = array(
218
    'load arguments' => array(1, 5),
219
    'page callback' => 'drupal_get_form',
220
    'page arguments' => array('webform_component_edit_form', 1, 4, FALSE),
221
    'access callback' => 'webform_node_update_access',
222
    'access arguments' => array(1),
223
    'file' => 'includes/webform.components.inc',
224
    'type' => MENU_LOCAL_TASK,
225
  );
226
  $items['node/%webform_menu/webform/components/%webform_menu_component/clone'] = array(
227
    'load arguments' => array(1, 5),
228
    'page callback' => 'drupal_get_form',
229
    'page arguments' => array('webform_component_edit_form', 1, 4, TRUE),
230
    'access callback' => 'webform_node_update_access',
231
    'access arguments' => array(1),
232
    'file' => 'includes/webform.components.inc',
233
    'type' => MENU_LOCAL_TASK,
234
  );
235
  $items['node/%webform_menu/webform/components/%webform_menu_component/delete'] = array(
236
    'load arguments' => array(1, 5),
237
    'page callback' => 'drupal_get_form',
238
    'page arguments' => array('webform_component_delete_form', 1, 4),
239
    'access callback' => 'webform_node_update_access',
240
    'access arguments' => array(1),
241
    'file' => 'includes/webform.components.inc',
242
    'type' => MENU_LOCAL_TASK,
243
  );
244

    
245
  // AJAX callback for loading select list options.
246
  $items['webform/ajax/options/%webform_menu'] = array(
247
    'load arguments' => array(3),
248
    'page callback' => 'webform_select_options_ajax',
249
    'access callback' => 'webform_node_update_access',
250
    'access arguments' => array(3),
251
    'file' => 'components/select.inc',
252
    'type' => MENU_CALLBACK,
253
  );
254

    
255
  // Node webform results.
256
  $items['node/%webform_menu/webform-results'] = array(
257
    'title' => 'Results',
258
    'page callback' => 'webform_results_submissions',
259
    'page arguments' => array(1, FALSE, '50'),
260
    'access callback' => 'webform_results_access',
261
    'access arguments' => array(1),
262
    'file' => 'includes/webform.report.inc',
263
    'weight' => 2,
264
    'type' => MENU_LOCAL_TASK,
265
    'context' => MENU_CONTEXT_PAGE | MENU_CONTEXT_INLINE,
266
  );
267
  $items['node/%webform_menu/webform-results/submissions'] = array(
268
    'title' => 'Submissions',
269
    'page callback' => 'webform_results_submissions',
270
    'page arguments' => array(1, FALSE, '50'),
271
    'access callback' => 'webform_results_access',
272
    'access arguments' => array(1),
273
    'file' => 'includes/webform.report.inc',
274
    'weight' => 4,
275
    'type' => MENU_DEFAULT_LOCAL_TASK,
276
  );
277
  $items['node/%webform_menu/webform-results/analysis'] = array(
278
    'title' => 'Analysis',
279
    'page callback' => 'webform_results_analysis',
280
    'page arguments' => array(1),
281
    'access callback' => 'webform_results_access',
282
    'access arguments' => array(1),
283
    'file' => 'includes/webform.report.inc',
284
    'weight' => 5,
285
    'type' => MENU_LOCAL_TASK,
286
  );
287
  $items['node/%webform_menu/webform-results/analysis/%webform_menu_component'] = array(
288
    'title' => 'Analysis',
289
    'load arguments' => array(1, 4),
290
    'page callback' => 'webform_results_analysis',
291
    'page arguments' => array(1, array(), 4),
292
    'access callback' => 'webform_results_access',
293
    'access arguments' => array(1),
294
    'file' => 'includes/webform.report.inc',
295
    'type' => MENU_LOCAL_TASK,
296
  );
297
  $items['node/%webform_menu/webform-results/analysis/%webform_menu_component/more'] = array(
298
    'title' => 'In-depth analysis',
299
    'type' => MENU_DEFAULT_LOCAL_TASK,
300
  );
301
  $items['node/%webform_menu/webform-results/table'] = array(
302
    'title' => 'Table',
303
    'page callback' => 'webform_results_table',
304
    'page arguments' => array(1, '50'),
305
    'access callback' => 'webform_results_access',
306
    'access arguments' => array(1),
307
    'file' => 'includes/webform.report.inc',
308
    'weight' => 6,
309
    'type' => MENU_LOCAL_TASK,
310
  );
311
  $items['node/%webform_menu/webform-results/download'] = array(
312
    'title' => 'Download',
313
    'page callback' => 'drupal_get_form',
314
    'page arguments' => array('webform_results_download_form', 1),
315
    'access callback' => 'webform_results_access',
316
    'access arguments' => array(1),
317
    'file' => 'includes/webform.report.inc',
318
    'weight' => 7,
319
    'type' => MENU_LOCAL_TASK,
320
  );
321
  $items['node/%webform_menu/webform-results/download-file'] = array(
322
    'title' => 'Download',
323
    'page callback' => 'webform_results_download_callback',
324
    'page arguments' => array(1),
325
    'access callback' => 'webform_results_access',
326
    'access arguments' => array(1),
327
    'file' => 'includes/webform.report.inc',
328
    'type' => MENU_CALLBACK,
329
  );
330
  $items['node/%webform_menu/webform-results/clear'] = array(
331
    'title' => 'Clear',
332
    'page callback' => 'drupal_get_form',
333
    'page arguments' => array('webform_results_clear_form', 1),
334
    'access callback' => 'webform_results_clear_access',
335
    'access arguments' => array(1),
336
    'file' => 'includes/webform.report.inc',
337
    'weight' => 8,
338
    'type' => MENU_LOCAL_TASK,
339
  );
340

    
341
  // Node submissions.
342
  $items['node/%webform_menu/submissions'] = array(
343
    'title' => 'Submissions',
344
    'page callback' => 'webform_results_submissions',
345
    'page arguments' => array(1, TRUE, '50'),
346
    'access callback' => 'webform_submission_access',
347
    'access arguments' => array(1, NULL, 'list'),
348
    'file' => 'includes/webform.report.inc',
349
    'type' => MENU_CALLBACK,
350
  );
351
  $items['node/%webform_menu/submission/%webform_menu_submission'] = array(
352
    'title' => 'Webform submission',
353
    'load arguments' => array(1),
354
    'page callback' => 'webform_submission_page',
355
    'page arguments' => array(1, 3, 'html'),
356
    'title callback' => 'webform_submission_title',
357
    'title arguments' => array(1, 3),
358
    'access callback' => 'webform_submission_access',
359
    'access arguments' => array(1, 3, 'view'),
360
    'file' => 'includes/webform.submissions.inc',
361
    'type' => MENU_CALLBACK,
362
  );
363
  $items['node/%webform_menu/submission/%webform_menu_submission/view'] = array(
364
    'title' => 'View',
365
    'load arguments' => array(1),
366
    'page callback' => 'webform_submission_page',
367
    'page arguments' => array(1, 3, 'html'),
368
    'access callback' => 'webform_submission_access',
369
    'access arguments' => array(1, 3, 'view'),
370
    'weight' => 0,
371
    'file' => 'includes/webform.submissions.inc',
372
    'type' => MENU_DEFAULT_LOCAL_TASK,
373
  );
374
  $items['node/%webform_menu/submission/%webform_menu_submission/edit'] = array(
375
    'title' => 'Edit',
376
    'load arguments' => array(1),
377
    'page callback' => 'webform_submission_page',
378
    'page arguments' => array(1, 3, 'form'),
379
    'access callback' => 'webform_submission_access',
380
    'access arguments' => array(1, 3, 'edit'),
381
    'weight' => 1,
382
    'file' => 'includes/webform.submissions.inc',
383
    'type' => MENU_LOCAL_TASK,
384
  );
385
  $items['node/%webform_menu/submission/%webform_menu_submission/delete'] = array(
386
    'title' => 'Delete',
387
    'load arguments' => array(1),
388
    'page callback' => 'drupal_get_form',
389
    'page arguments' => array('webform_submission_delete_form', 1, 3),
390
    'access callback' => 'webform_submission_access',
391
    'access arguments' => array(1, 3, 'delete'),
392
    'weight' => 2,
393
    'file' => 'includes/webform.submissions.inc',
394
    'type' => MENU_LOCAL_TASK,
395
  );
396
  $items['node/%webform_menu/submission/%webform_menu_submission/resend'] = array(
397
    'title' => 'Resend e-mails',
398
    'load arguments' => array(1),
399
    'page callback' => 'drupal_get_form',
400
    'page arguments' => array('webform_submission_resend', 1, 3),
401
    'access callback' => 'webform_results_access',
402
    'access arguments' => array(1),
403
    'file' => 'includes/webform.submissions.inc',
404
    'type' => MENU_CALLBACK,
405
  );
406

    
407
  // Devel integration for submissions.
408
  if (module_exists('devel')) {
409
    $items['node/%webform_menu/submission/%webform_menu_submission/devel'] = array(
410
      'title' => 'Devel',
411
      'load arguments' => array(1),
412
      'page callback' => 'devel_load_object',
413
      'page arguments' => array('submission', 3),
414
      'access arguments' => array('access devel information'),
415
      'type' => MENU_LOCAL_TASK,
416
      'file' => 'devel.pages.inc',
417
      'file path' => drupal_get_path('module', 'devel'),
418
      'weight' => 100,
419
    );
420
    $items['node/%webform_menu/submission/%webform_menu_submission/devel/load'] = array(
421
      'title' => 'Load',
422
      'type' => MENU_DEFAULT_LOCAL_TASK,
423
    );
424
    if (module_exists('token')) {
425
      $items['node/%webform_menu/submission/%webform_menu_submission/devel/token'] = array(
426
        'title' => 'Tokens',
427
        'load arguments' => array(1),
428
        'page callback' => 'token_devel_token_object',
429
        'page arguments' => array('webform-submission', 3, 'submission'),
430
        'access arguments' => array('access devel information'),
431
        'type' => MENU_LOCAL_TASK,
432
        'file' => 'token.pages.inc',
433
        'file path' => drupal_get_path('module', 'token'),
434
        'weight' => 5,
435
      );
436
    }
437
  }
438

    
439
  return $items;
440
}
441

    
442
/**
443
 * Menu loader callback. Load a webform node if the given nid is a webform.
444
 */
445
function webform_menu_load($nid) {
446
  if (!is_numeric($nid)) {
447
    return FALSE;
448
  }
449
  $node = node_load($nid);
450
  if (!isset($node->type) || !variable_get('webform_node_' . $node->type, FALSE)) {
451
    return FALSE;
452
  }
453
  return $node;
454
}
455

    
456
/**
457
 * Menu LOADERNAME_to_arg callback.
458
 *
459
 * Determines the arguments used to generate a menu link.
460
 *
461
 * This is implemented only to give the webform_localization modules an
462
 * opportunity to link to the orignial webform from the localized one. See
463
 * issue 2097277.
464
 *
465
 * @param string $arg
466
 *   The argument supplied by the caller.
467
 * @param array $map
468
 *   Array of path fragments (for example, array('node','123','edit') for
469
 *   'node/123/edit').
470
 * @param int $index
471
 *   Which element of $map corresponds to $arg.
472
 *
473
 * @return string
474
 *   The $arg, modified as desired.
475
 */
476
function webform_menu_to_arg($arg, array $map, $index) {
477
  return function_exists('webform_localization_webform_menu_to_arg')
478
          ? webform_localization_webform_menu_to_arg($arg, $map, $index)
479
          : $arg;
480
}
481

    
482
/**
483
 * Menu loader callback. Load a webform submission if the given sid is a valid.
484
 */
485
function webform_menu_submission_load($sid, $nid) {
486
  module_load_include('inc', 'webform', 'includes/webform.submissions');
487
  $submission = webform_get_submission($nid, $sid);
488
  return empty($submission) ? FALSE : $submission;
489
}
490

    
491
/**
492
 * Menu loader callback. Load a webform component if the given cid is a valid.
493
 */
494
function webform_menu_component_load($cid, $nid, $type) {
495
  module_load_include('inc', 'webform', 'includes/webform.components');
496
  if ($cid == 'new') {
497
    $components = webform_components();
498
    $component = in_array($type, array_keys($components)) ? array(
499
      'type' => $type,
500
      'nid' => $nid,
501
      'name' => $_GET['name'],
502
      'required' => $_GET['required'],
503
      'pid' => $_GET['pid'],
504
      'weight' => $_GET['weight'],
505
    ) : FALSE;
506
  }
507
  else {
508
    $node = node_load($nid);
509
    $component = isset($node->webform['components'][$cid]) ? $node->webform['components'][$cid] : FALSE;
510
  }
511
  if ($component) {
512
    webform_component_defaults($component);
513
  }
514
  return $component;
515
}
516

    
517
/**
518
 * Menu loader callback. Load a webform e-mail if the given eid is a valid.
519
 */
520
function webform_menu_email_load($eid, $nid) {
521
  module_load_include('inc', 'webform', 'includes/webform.emails');
522
  $node = node_load($nid);
523
  $email = webform_email_load($eid, $nid);
524
  if ($eid == 'new') {
525
    if (isset($_GET['option']) && isset($_GET['email'])) {
526
      $type = $_GET['option'];
527
      if ($type == 'custom') {
528
        $email['email'] = $_GET['email'];
529
      }
530
      elseif ($type == 'component' && isset($node->webform['components'][$_GET['email']])) {
531
        $email['email'] = $_GET['email'];
532
      }
533
    }
534
    if (isset($_GET['status'])) {
535
      $email['status'] = $_GET['status'];
536
    }
537
  }
538

    
539
  return $email;
540
}
541

    
542
/**
543
 * Return the access token for a submission.
544
 *
545
 * @param object $submission
546
 *   The submission object.
547
 *
548
 * @return string
549
 *   The access token for the submission.
550
 */
551
function webform_get_submission_access_token($submission) {
552
  return md5($submission->submitted . $submission->sid . drupal_get_private_key());
553
}
554

    
555
/**
556
 * Access function for confirmation pages.
557
 *
558
 * @param object $node
559
 *   The webform node object.
560
 *
561
 * @return bool
562
 *   Boolean whether the user has access to the confirmation page.
563
 */
564
function webform_confirmation_page_access($node) {
565
  global $user;
566

    
567
  // Make sure SID is a positive integer.
568
  $sid = (!empty($_GET['sid']) && (int) $_GET['sid'] > 0) ? (int) $_GET['sid'] : NULL;
569

    
570
  if ($sid) {
571
    module_load_include('inc', 'webform', 'includes/webform.submissions');
572
    $submission = webform_get_submission($node->nid, $sid);
573
  }
574
  else {
575
    $submission = NULL;
576
  }
577

    
578
  if ($submission) {
579
    // Logged-in users.
580
    if ($user->uid) {
581
      // User's own submission.
582
      if ($submission->uid === $user->uid && node_access('view', $node)) {
583
        return TRUE;
584
      }
585
      // User has results access to this submission.
586
      elseif (webform_submission_access($node, $submission)) {
587
        return TRUE;
588
      }
589
    }
590
    // Anonymous user for their own submission. Hash of submission data must
591
    // match the hash in the query string.
592
    elseif ((int) $user->uid === 0 && (int) $submission->uid === 0) {
593
      $hash_query = !empty($_GET['token']) ? $_GET['token'] : NULL;
594
      $hash = webform_get_submission_access_token($submission);
595
      if ($hash_query === $hash) {
596
        return TRUE;
597
      }
598
    }
599
  }
600
  else {
601
    // No submission exists (such as auto-deleted by another module, such as
602
    // webform_clear), just ensure that the user has access to view the node
603
    // page.
604
    if (node_access('view', $node)) {
605
      return TRUE;
606
    }
607
  }
608

    
609
  return FALSE;
610
}
611

    
612
/**
613
 * Access function for Webform submissions.
614
 *
615
 * @param object $node
616
 *   The webform node object.
617
 * @param object $submission
618
 *   The webform submission object.
619
 * @param object $op
620
 *   The operation to perform. Must be one of view, edit, delete, list.
621
 * @param object $account
622
 *   Optional. A user object or NULL to use the currently logged-in user.
623
 *
624
 * @return bool
625
 *   Boolean whether the user has access to a webform submission.
626
 */
627
function webform_submission_access($node, $submission, $op = 'view', $account = NULL) {
628
  global $user;
629
  $account = isset($account) ? $account : $user;
630

    
631
  $access_all = user_access('access all webform results', $account);
632
  $access_own_submission = isset($submission) && user_access('access own webform submissions', $account) && (($account->uid && $account->uid == $submission->uid) || isset($_SESSION['webform_submission'][$submission->sid]));
633
  $access_node_submissions = user_access('access own webform results', $account) && $account->uid == $node->uid;
634

    
635
  $token_access = $submission && isset($_GET['token']) && $_GET['token'] == webform_get_submission_access_token($submission);
636

    
637
  // If access is granted via a token, then allow subsequent submission access
638
  // for anonymous users.
639
  if (!$user->uid && $token_access) {
640
    $_SESSION['webform_submission'][$submission->sid] = $node->nid;
641
  }
642

    
643
  $general_access = $access_all || $access_own_submission || $access_node_submissions || $token_access;
644

    
645
  // Disable the page cache for anonymous users in this access callback,
646
  // otherwise the "Access denied" page gets cached.
647
  if (!$account->uid && user_access('access own webform submissions', $account)) {
648
    webform_disable_page_cache();
649
  }
650

    
651
  $module_access = count(array_filter(module_invoke_all('webform_submission_access', $node, $submission, $op, $account))) > 0;
652

    
653
  switch ($op) {
654
    case 'view':
655
      return $module_access || $general_access;
656

    
657
    case 'edit':
658
    case 'delete':
659
      return $module_access || (
660
        $general_access && (
661
          user_access($op . ' all webform submissions', $account) || (
662
            user_access($op . ' own webform submissions', $account) &&
663
            $account->uid == $submission->uid
664
          )
665
        )
666
      );
667

    
668
    case 'list':
669
      return $module_access ||
670
        user_access('access all webform results', $account) || (
671
          user_access('access own webform submissions', $account) && (
672
            $account->uid ||
673
            isset($_SESSION['webform_submission'])
674
          )
675
        ) || (
676
          user_access('access own webform results', $account) &&
677
          $account->uid == $node->uid
678
        );
679
  }
680
}
681

    
682
/**
683
 * Menu access callback. Ensure a user both access and node 'view' permission.
684
 */
685
function webform_results_access($node, $account = NULL) {
686
  global $user;
687
  $account = isset($account) ? $account : $user;
688

    
689
  $module_access = count(array_filter(module_invoke_all('webform_results_access', $node, $account))) > 0;
690

    
691
  return node_access('view', $node, $account) && ($module_access || user_access('access all webform results', $account) || (user_access('access own webform results', $account) && $account->uid == $node->uid));
692
}
693

    
694
/**
695
 * Menu access callback.
696
 *
697
 * Ensure a user has both results access and permission to clear submissions.
698
 */
699
function webform_results_clear_access($node, $account = NULL) {
700
  global $user;
701
  $account = isset($account) ? $account : $user;
702

    
703
  $module_access = count(array_filter(module_invoke_all('webform_results_clear_access', $node, $account))) > 0;
704

    
705
  return webform_results_access($node, $account) && ($module_access || user_access('delete all webform submissions', $account));
706
}
707

    
708
/**
709
 * Menu access callback. Ensure a sure has access to update a webform node.
710
 *
711
 * Unlike webform_results_access and webform_results_clear_access, access is
712
 * completely overridden by the any implementation of
713
 * hook_webform_update_access.
714
 *
715
 * If hook_webform_update_access is implemented by one or more other modules,
716
 * the results must be unanimously TRUE for access to be granted; otherwise it
717
 * is denied if even one implementation returns FALSE, regardless of node_access
718
 * and the 'edit webform components' permission. This allows implementors
719
 * complete flexibility.
720
 *
721
 * hook_webform_update_access should return TRUE if access should absolutely
722
 * be granted, FALSE if it should absolutely be denied, or NULL if node_access
723
 * and 'edit webform components' permission should determine access.
724
 *
725
 * @see hook_webform_update_access()
726
 */
727
function webform_node_update_access($node, $account = NULL) {
728
  global $user;
729
  $account = isset($account) ? $account : $user;
730
  $module_access = module_invoke_all('webform_update_access', $node, $account);
731
  return empty($module_access)
732
            ? node_access('update', $node, $account) && user_access('edit webform components')
733
            : count(array_filter($module_access)) == count($module_access);
734
}
735

    
736
/**
737
 * Implements hook_admin_paths().
738
 */
739
function webform_admin_paths() {
740
  if (variable_get('node_admin_theme')) {
741
    return array(
742
      'node/*/webform' => TRUE,
743
      'node/*/webform/*' => TRUE,
744
      'node/*/webform-results' => TRUE,
745
      'node/*/webform-results/*' => TRUE,
746
      'node/*/submission/*' => TRUE,
747
    );
748
  }
749
}
750

    
751
/**
752
 * Implements hook_perm().
753
 */
754
function webform_permission() {
755
  return array(
756
    'access all webform results' => array(
757
      'title' => t('Access all webform results'),
758
      'description' => t('Grants access to the "Results" tab on all webform content. Generally an administrative permission.'),
759
    ),
760
    'access own webform results' => array(
761
      'title' => t('Access own webform results'),
762
      'description' => t('Grants access to the "Results" tab to the author of webform content they have created.'),
763
    ),
764
    'edit all webform submissions' => array(
765
      'title' => t('Edit all webform submissions'),
766
      'description' => t('Allows editing of any webform submission by any user. Generally an administrative permission.'),
767
    ),
768
    'delete all webform submissions' => array(
769
      'title' => t('Delete all webform submissions'),
770
      'description' => t('Allows deleting of any webform submission by any user. Generally an administrative permission.'),
771
    ),
772
    'access own webform submissions' => array(
773
      'title' => t('Access own webform submissions'),
774
    ),
775
    'edit own webform submissions' => array(
776
      'title' => t('Edit own webform submissions'),
777
    ),
778
    'delete own webform submissions' => array(
779
      'title' => t('Delete own webform submissions'),
780
    ),
781
    'edit webform components' => array(
782
      'title' => t('Content authors: access and edit webform components and settings'),
783
      'description' => t('Grants additional access to the webform components and settings to users who can edit the content. Generally an authenticated user permission.'),
784
    ),
785
  );
786
}
787

    
788
/**
789
 * Implements hook_theme().
790
 */
791
function webform_theme() {
792
  $theme = array(
793
    // webform.module.
794
    'webform_view' => array(
795
      'render element' => 'webform',
796
    ),
797
    'webform_view_messages' => array(
798
      'variables' => array(
799
        'node' => NULL,
800
        'page' => NULL,
801
        'submission_count' => NULL,
802
        'user_limit_exceeded' => NULL,
803
        'total_limit_exceeded' => NULL,
804
        'allowed_roles' => NULL,
805
        'closed' => NULL,
806
        'cached' => NULL,
807
      ),
808
    ),
809
    'webform_form' => array(
810
      'render element' => 'form',
811
      'template' => 'templates/webform-form',
812
      'pattern' => 'webform_form_[0-9]+',
813
    ),
814
    'webform_confirmation' => array(
815
      'variables' => array('node' => NULL, 'sid' => NULL),
816
      'template' => 'templates/webform-confirmation',
817
      'pattern' => 'webform_confirmation_[0-9]+',
818
    ),
819
    'webform_element' => array(
820
      'render element' => 'element',
821
    ),
822
    'webform_element_text' => array(
823
      'render element' => 'element',
824
    ),
825
    'webform_inline_radio' => array(
826
      'render element' => 'element',
827
    ),
828
    'webform_inline_radio_label' => array(
829
      'render element' => 'element',
830
    ),
831
    'webform_progressbar' => array(
832
      'variables' => array(
833
        'node' => NULL,
834
        'page_num' => NULL,
835
        'page_count' => NULL,
836
        'page_labels' => array(),
837
      ),
838
      'template' => 'templates/webform-progressbar',
839
    ),
840
    'webform_mail_message' => array(
841
      'variables' => array(
842
        'node' => NULL,
843
        'submission' => NULL,
844
        'email' => NULL,
845
      ),
846
      'template' => 'templates/webform-mail',
847
      'pattern' => 'webform_mail(_[0-9]+)?',
848
    ),
849
    'webform_mail_headers' => array(
850
      'variables' => array(
851
        'node' => NULL,
852
        'submission' => NULL,
853
        'email' => NULL,
854
      ),
855
      'pattern' => 'webform_mail_headers_[0-9]+',
856
    ),
857
    'webform_token_help' => array(
858
      'variables' => array('groups' => array('node')),
859
    ),
860
    // webform.admin.inc.
861
    'webform_admin_settings' => array(
862
      'render element' => 'form',
863
      'file' => 'includes/webform.admin.inc',
864
    ),
865
    'webform_admin_content' => array(
866
      'variables' => array('nodes' => NULL),
867
      'file' => 'includes/webform.admin.inc',
868
    ),
869
    // webform.emails.inc.
870
    'webform_emails_form' => array(
871
      'render element' => 'form',
872
      'file' => 'includes/webform.emails.inc',
873
    ),
874
    'webform_email_component_mapping' => array(
875
      'render element' => 'element',
876
      'file' => 'includes/webform.emails.inc',
877
    ),
878
    'webform_email_add_form' => array(
879
      'render element' => 'form',
880
      'file' => 'includes/webform.emails.inc',
881
    ),
882
    'webform_email_edit_form' => array(
883
      'render element' => 'form',
884
      'file' => 'includes/webform.emails.inc',
885
    ),
886
    // webform.components.inc.
887
    'webform_components_page' => array(
888
      'variables' => array('node' => NULL, 'form' => NULL),
889
      'file' => 'includes/webform.components.inc',
890
    ),
891
    'webform_components_form' => array(
892
      'render element' => 'form',
893
      'file' => 'includes/webform.components.inc',
894
    ),
895
    'webform_component_select' => array(
896
      'render element' => 'element',
897
      'file' => 'includes/webform.components.inc',
898
    ),
899
    // webform.conditionals.inc.
900
    'webform_conditional_groups' => array(
901
      'render element' => 'element',
902
      'file' => 'includes/webform.conditionals.inc',
903
    ),
904
    'webform_conditional_group_row' => array(
905
      'render element' => 'element',
906
      'file' => 'includes/webform.conditionals.inc',
907
    ),
908
    'webform_conditional' => array(
909
      'render element' => 'element',
910
      'file' => 'includes/webform.conditionals.inc',
911
    ),
912
    // webform.pages.inc.
913
    'webform_advanced_redirection_form' => array(
914
      'render element' => 'form',
915
      'file' => 'includes/webform.pages.inc',
916
    ),
917
    'webform_advanced_submit_limit_form' => array(
918
      'render element' => 'form',
919
      'file' => 'includes/webform.pages.inc',
920
    ),
921
    'webform_advanced_total_submit_limit_form' => array(
922
      'render element' => 'form',
923
      'file' => 'includes/webform.pages.inc',
924
    ),
925
    // webform.report.inc.
926
    'webform_results_per_page' => array(
927
      'variables' => array('total_count' => NULL, 'pager_count' => NULL),
928
      'file' => 'includes/webform.report.inc',
929
    ),
930
    'webform_results_submissions_header' => array(
931
      'variables' => array('node' => NULL),
932
      'file' => 'includes/webform.report.inc',
933
    ),
934
    'webform_results_submissions' => array(
935
      'render element' => 'element',
936
      'template' => 'templates/webform-results-submissions',
937
      'file' => 'includes/webform.report.inc',
938
    ),
939
    'webform_results_table_header' => array(
940
      'variables' => array('node' => NULL),
941
      'file' => 'includes/webform.report.inc',
942
    ),
943
    'webform_results_table' => array(
944
      'variables' => array(
945
        'node' => NULL,
946
        'components' => NULL,
947
        'submissions' => NULL,
948
        'total_count' => NULL,
949
        'pager_count' => NULL,
950
      ),
951
      'file' => 'includes/webform.report.inc',
952
    ),
953
    'webform_results_download_range' => array(
954
      'render element' => 'element',
955
      'file' => 'includes/webform.report.inc',
956
    ),
957
    'webform_results_download_select_format' => array(
958
      'render element' => 'element',
959
      'file' => 'includes/webform.report.inc',
960
    ),
961
    'webform_analysis' => array(
962
      'render element' => 'analysis',
963
      'template' => 'templates/webform-analysis',
964
      'file' => 'includes/webform.report.inc',
965
    ),
966
    'webform_analysis_component' => array(
967
      'render element' => 'component_analysis',
968
      'template' => 'templates/webform-analysis-component',
969
      'file' => 'includes/webform.report.inc',
970
    ),
971
    'webform_analysis_component_basic' => array(
972
      'variables' => array('component' => NULL, 'data' => NULL),
973
      'file' => 'includes/webform.report.inc',
974
    ),
975
    // webform.submissions.inc.
976
    'webform_submission' => array(
977
      'render element' => 'renderable',
978
      'template' => 'templates/webform-submission',
979
      'pattern' => 'webform_submission_[0-9]+',
980
      'file' => 'includes/webform.submissions.inc',
981
    ),
982
    'webform_submission_page' => array(
983
      'variables' => array(
984
        'node' => NULL,
985
        'submission' => NULL,
986
        'submission_content' => NULL,
987
        'submission_navigation' => NULL,
988
        'submission_information' => NULL,
989
        'submission_actions' => NULL,
990
        'mode' => NULL,
991
      ),
992
      'template' => 'templates/webform-submission-page',
993
      'file' => 'includes/webform.submissions.inc',
994
    ),
995
    'webform_submission_information' => array(
996
      'variables' => array(
997
        'node' => NULL,
998
        'submission' => NULL,
999
        'mode' => 'display',
1000
      ),
1001
      'template' => 'templates/webform-submission-information',
1002
      'file' => 'includes/webform.submissions.inc',
1003
    ),
1004
    'webform_submission_navigation' => array(
1005
      'variables' => array('node' => NULL, 'submission' => NULL, 'mode' => NULL),
1006
      'template' => 'templates/webform-submission-navigation',
1007
      'file' => 'includes/webform.submissions.inc',
1008
    ),
1009
    'webform_submission_resend' => array(
1010
      'render element' => 'form',
1011
      'file' => 'includes/webform.submissions.inc',
1012
    ),
1013
  );
1014

    
1015
  // Theme functions in all components.
1016
  $components = webform_components(TRUE);
1017
  foreach ($components as $type => $component) {
1018
    if ($theme_additions = webform_component_invoke($type, 'theme')) {
1019
      $theme = array_merge($theme, $theme_additions);
1020
    }
1021
  }
1022
  return $theme;
1023
}
1024

    
1025
/**
1026
 * Implements hook_library().
1027
 */
1028
function webform_library() {
1029
  $module_path = drupal_get_path('module', 'webform');
1030

    
1031
  // Webform administration.
1032
  $libraries['admin'] = array(
1033
    'title' => 'Webform: Administration',
1034
    'website' => 'http://drupal.org/project/webform',
1035
    'version' => '1.0',
1036
    'js' => array(
1037
      $module_path . '/js/webform-admin.js' => array('group' => JS_DEFAULT),
1038
    ),
1039
    'css' => array(
1040
      $module_path . '/css/webform-admin.css' => array('group' => CSS_DEFAULT, 'weight' => 1),
1041
    ),
1042
  );
1043

    
1044
  return $libraries;
1045
}
1046

    
1047
/**
1048
 * Implements hook_element_info().
1049
 */
1050
function webform_element_info() {
1051
  // A few of our components need to be defined here because Drupal does not
1052
  // provide these components natively. Because this hook fires on every page
1053
  // load (even on non-webform pages), we don't put this in the component .inc
1054
  // files because of the unnecessary loading that it would require.
1055
  $elements['webform_time'] = array('#input' => 'TRUE');
1056
  $elements['webform_grid'] = array('#input' => 'TRUE');
1057

    
1058
  $elements['webform_email'] = array(
1059
    '#input' => TRUE,
1060
    '#theme' => 'webform_email',
1061
    '#size' => 60,
1062
  );
1063
  $elements['webform_number'] = array(
1064
    '#input' => TRUE,
1065
    '#theme' => 'webform_number',
1066
    '#min' => NULL,
1067
    '#max' => NULL,
1068
    '#step' => NULL,
1069
  );
1070

    
1071
  $elements['webform_conditional'] = array(
1072
    '#input' => TRUE,
1073
    '#theme' => 'webform_conditional',
1074
    '#default_value' => NULL,
1075
    '#process' => array('webform_conditional_expand'),
1076
  );
1077

    
1078
  return $elements;
1079
}
1080

    
1081
/**
1082
 * Implements hook_webform_component_info().
1083
 */
1084
function webform_webform_component_info() {
1085
  $component_info = array(
1086
    'date' => array(
1087
      'label' => t('Date'),
1088
      'description' => t('Presents month, day, and year fields.'),
1089
      'features' => array(
1090
        'views_range' => TRUE,
1091
        'css_classes' => FALSE,
1092
      ),
1093
      'file' => 'components/date.inc',
1094
      'conditional_type' => 'date',
1095
    ),
1096
    'email' => array(
1097
      'label' => t('E-mail'),
1098
      'description' => t('A special textfield that accepts e-mail addresses.'),
1099
      'file' => 'components/email.inc',
1100
      'features' => array(
1101
        'email_address' => TRUE,
1102
        'spam_analysis' => TRUE,
1103
        'placeholder' => TRUE,
1104
        'conditional_action_set' => TRUE,
1105
      ),
1106
    ),
1107
    'fieldset' => array(
1108
      'label' => t('Fieldset'),
1109
      'description' => t('Fieldsets allow you to organize multiple fields into groups.'),
1110
      'features' => array(
1111
        'csv' => FALSE,
1112
        'default_value' => FALSE,
1113
        'required' => FALSE,
1114
        'conditional' => FALSE,
1115
        'group' => TRUE,
1116
        'title_inline' => FALSE,
1117
        'wrapper_classes' => FALSE,
1118
      ),
1119
      'file' => 'components/fieldset.inc',
1120
    ),
1121
    'grid' => array(
1122
      'label' => t('Grid'),
1123
      'description' => t('Allows creation of grid questions, denoted by radio buttons.'),
1124
      'features' => array(
1125
        'default_value' => FALSE,
1126
        'title_inline' => FALSE,
1127
        'title_internal' => TRUE,
1128
        'css_classes' => FALSE,
1129
        'conditional' => FALSE,
1130
        'group' => TRUE,
1131
      ),
1132
      'file' => 'components/grid.inc',
1133
    ),
1134
    'hidden' => array(
1135
      'label' => t('Hidden'),
1136
      'description' => t('A field which is not visible to the user, but is recorded with the submission.'),
1137
      'file' => 'components/hidden.inc',
1138
      'features' => array(
1139
        'required' => FALSE,
1140
        'description' => FALSE,
1141
        'email_address' => TRUE,
1142
        'email_name' => TRUE,
1143
        'title_display' => FALSE,
1144
        'private' => FALSE,
1145
        'wrapper_classes' => FALSE,
1146
        'css_classes' => FALSE,
1147
        'conditional_action_set' => TRUE,
1148
      ),
1149
    ),
1150
    'markup' => array(
1151
      'label' => t('Markup'),
1152
      'description' => t('Displays text as HTML in the form; does not render a field.'),
1153
      'features' => array(
1154
        'analysis' => FALSE,
1155
        'csv' => FALSE,
1156
        'default_value' => FALSE,
1157
        'description' => FALSE,
1158
        'email' => FALSE,
1159
        'required' => FALSE,
1160
        'conditional' => FALSE,
1161
        'title_display' => FALSE,
1162
        'private' => FALSE,
1163
        'wrapper_classes' => FALSE,
1164
        'css_classes' => FALSE,
1165
        'conditional_action_set' => TRUE,
1166
      ),
1167
      'file' => 'components/markup.inc',
1168
    ),
1169
    'number' => array(
1170
      'label' => t('Number'),
1171
      'description' => t('A numeric input field (either as textfield or select list).'),
1172
      'features' => array(
1173
        'conditional_action_set' => TRUE,
1174
      ),
1175
      'file' => 'components/number.inc',
1176
      'conditional_type' => 'numeric',
1177
    ),
1178
    'pagebreak' => array(
1179
      'label' => t('Page break'),
1180
      'description' => t('Organize forms into multiple pages.'),
1181
      'features' => array(
1182
        'analysis' => FALSE,
1183
        'conditional' => FALSE,
1184
        'csv' => FALSE,
1185
        'default_value' => FALSE,
1186
        'description' => FALSE,
1187
        'private' => FALSE,
1188
        'required' => FALSE,
1189
        'title_display' => FALSE,
1190
        'wrapper_classes' => FALSE,
1191
        'css_classes' => FALSE,
1192
      ),
1193
      'file' => 'components/pagebreak.inc',
1194
    ),
1195
    'select' => array(
1196
      'label' => t('Select options'),
1197
      'description' => t('Allows creation of checkboxes, radio buttons, or select menus.'),
1198
      'file' => 'components/select.inc',
1199
      'features' => array(
1200
        'default_value' => FALSE,
1201
        'email_address' => TRUE,
1202
        'email_name' => TRUE,
1203
        'conditional_action_set' => TRUE,
1204
      ),
1205
      'conditional_type' => 'select',
1206
    ),
1207
    'textarea' => array(
1208
      'label' => t('Textarea'),
1209
      'description' => t('A large text area that allows for multiple lines of input.'),
1210
      'file' => 'components/textarea.inc',
1211
      'features' => array(
1212
        'spam_analysis' => TRUE,
1213
        'placeholder' => TRUE,
1214
        'conditional_action_set' => TRUE,
1215
      ),
1216
    ),
1217
    'textfield' => array(
1218
      'label' => t('Textfield'),
1219
      'description' => t('Basic textfield type.'),
1220
      'file' => 'components/textfield.inc',
1221
      'features' => array(
1222
        'email_name' => TRUE,
1223
        'spam_analysis' => TRUE,
1224
        'placeholder' => TRUE,
1225
        'conditional_action_set' => TRUE,
1226
      ),
1227
    ),
1228
    'time' => array(
1229
      'label' => t('Time'),
1230
      'description' => t('Presents the user with hour and minute fields. Optional am/pm fields.'),
1231
      'features' => array(
1232
        'views_range' => TRUE,
1233
        'css_classes' => FALSE,
1234
      ),
1235
      'file' => 'components/time.inc',
1236
      'conditional_type' => 'time',
1237
    ),
1238
  );
1239

    
1240
  if (module_exists('file')) {
1241
    $component_info['file'] = array(
1242
      'label' => t('File'),
1243
      'description' => t('Allow users to upload files of configurable types.'),
1244
      'features' => array(
1245
        'conditional' => FALSE,
1246
        'default_value' => FALSE,
1247
        'attachment' => TRUE,
1248
      ),
1249
      'file' => 'components/file.inc',
1250
    );
1251
  }
1252

    
1253
  return $component_info;
1254
}
1255

    
1256
/**
1257
 * Implements hook_webform_conditional_operator_info().
1258
 */
1259
function webform_webform_conditional_operator_info() {
1260
  module_load_include('inc', 'webform', 'includes/webform.conditionals');
1261
  return _webform_conditional_operator_info();
1262
}
1263

    
1264
/**
1265
 * Implements hook_forms().
1266
 *
1267
 * All webform_client_form forms share the same form handler.
1268
 */
1269
function webform_forms($form_id) {
1270
  $forms = array();
1271
  if (strpos($form_id, 'webform_client_form_') === 0) {
1272
    $forms[$form_id]['callback'] = 'webform_client_form';
1273
  }
1274
  return $forms;
1275
}
1276

    
1277
/**
1278
 * Implements hook_webform_select_options_info().
1279
 */
1280
function webform_webform_select_options_info() {
1281
  module_load_include('inc', 'webform', 'includes/webform.options');
1282
  return _webform_options_info();
1283
}
1284

    
1285
/**
1286
 * Implements hook_webform_webform_submission_actions().
1287
 */
1288
function webform_webform_submission_actions($node, $submission) {
1289
  $actions = array();
1290
  $destination = drupal_get_destination();
1291

    
1292
  if (module_exists('print_pdf') && user_access('access PDF version')) {
1293
    $actions['printpdf'] = array(
1294
      'title' => t('Download PDF'),
1295
      'href' => 'printpdf/' . $node->nid . '/submission/' . $submission->sid,
1296
      'query' => $destination,
1297
    );
1298
  }
1299

    
1300
  if (module_exists('print') && user_access('access print')) {
1301
    $actions['print'] = array(
1302
      'title' => t('Print'),
1303
      'href' => 'print/' . $node->nid . '/submission/' . $submission->sid,
1304
    );
1305
  }
1306

    
1307
  if (webform_results_access($node) && count($node->webform['emails'])) {
1308
    $actions['resend'] = array(
1309
      'title' => t('Resend e-mails'),
1310
      'href' => 'node/' . $node->nid . '/submission/' . $submission->sid . '/resend',
1311
      'query' => drupal_get_destination(),
1312
    );
1313
  }
1314

    
1315
  return $actions;
1316
}
1317

    
1318
/**
1319
 * Implements hook_webform_submission_presave().
1320
 *
1321
 * We implement our own hook here to facilitate the File component, which needs
1322
 * to clean up manage file usage records and delete files from submissions that
1323
 * have been edited if necessary.
1324
 */
1325
function webform_webform_submission_presave($node, &$submission) {
1326
  // Check if there are any file components in this submission and if any of
1327
  // them currently contain files.
1328
  $has_file_components = FALSE;
1329
  $new_fids = array();
1330
  $old_fids = array();
1331
  $renameable = array();
1332

    
1333
  foreach ($node->webform['components'] as $cid => $component) {
1334
    if ($component['type'] == 'file') {
1335
      $has_file_components = TRUE;
1336
      if (!empty($submission->data[$cid])) {
1337
        foreach ($submission->data[$cid] as $key => $value) {
1338
          if (empty($value)) {
1339
            unset($submission->data[$cid][$key]);
1340
          }
1341
          if (strlen($component['extra']['rename'])) {
1342
            $renameable[$cid][] = $value;
1343
          }
1344
        }
1345
        $new_fids = array_merge($new_fids, $submission->data[$cid]);
1346
      }
1347
    }
1348
  }
1349

    
1350
  if ($has_file_components) {
1351
    // If we're updating a submission, build a list of previous files.
1352
    if (isset($submission->sid)) {
1353
      drupal_static_reset('webform_get_submission');
1354
      $old_submission = webform_get_submission($node->nid, $submission->sid);
1355

    
1356
      foreach ($node->webform['components'] as $cid => $component) {
1357
        if ($component['type'] == 'file') {
1358
          if (!empty($old_submission->data[$cid])) {
1359
            $old_fids = array_merge($old_fids, $old_submission->data[$cid]);
1360
          }
1361
        }
1362
      }
1363
    }
1364

    
1365
    // Only rename files if this is the first time the submission is being saved
1366
    // as finished.
1367
    if ($submission->is_draft || (isset($old_submission) && !$old_submission->is_draft)) {
1368
      $renameable = array();
1369
    }
1370

    
1371
    // Save the list of added or removed files so we can add usage in
1372
    // hook_webform_submission_insert() or _update().
1373
    $submission->file_usage = array(
1374
      // Diff the old against new to determine what files were deleted.
1375
      'deleted_fids' => array_diff($old_fids, $new_fids),
1376
      // Diff the new files against old to determine new uploads.
1377
      'added_fids' => array_diff($new_fids, $old_fids),
1378
      // A list of files which need renaming with tokens.
1379
      'renameable' => $renameable,
1380
    );
1381
  }
1382
}
1383

    
1384
/**
1385
 * Implements hook_webform_submission_insert().
1386
 */
1387
function webform_webform_submission_insert($node, $submission) {
1388
  if (isset($submission->file_usage)) {
1389
    webform_component_include('file');
1390
    webform_file_usage_adjust($submission);
1391
    webform_file_rename($node, $submission);
1392
  }
1393
}
1394

    
1395
/**
1396
 * Implements hook_webform_submission_update().
1397
 */
1398
function webform_webform_submission_update($node, $submission) {
1399
  if (isset($submission->file_usage)) {
1400
    webform_component_include('file');
1401
    webform_file_usage_adjust($submission);
1402
    webform_file_rename($node, $submission);
1403
  }
1404
}
1405

    
1406
/**
1407
 * Implements hook_webform_submission_render_alter().
1408
 */
1409
function webform_webform_submission_render_alter(&$renderable) {
1410
  // If displaying a submission to end-users who are viewing their own
1411
  // submissions (and not through an e-mail), do not show hidden values.
1412
  // This needs to be implemented at the level of the entire submission, since
1413
  // individual components do not get contextual information about where they
1414
  // are being displayed.
1415
  $node = $renderable['#node'];
1416
  $is_admin = webform_results_access($node);
1417
  if (empty($renderable['#email']) && !$is_admin) {
1418
    // Find and hide the display of all hidden components.
1419
    module_load_include('inc', 'webform', 'includes/webform.components');
1420
    foreach ($node->webform['components'] as $cid => $component) {
1421
      if ($component['type'] == 'hidden') {
1422
        $parents = webform_component_parent_keys($node, $component);
1423
        $element = &$renderable;
1424
        foreach ($parents as $pid) {
1425
          $element = &$element[$pid];
1426
        }
1427
        $element['#access'] = FALSE;
1428
      }
1429
    }
1430
  }
1431
}
1432

    
1433
/**
1434
 * Implements hook_file_download().
1435
 *
1436
 * Only allow users with view webform submissions to download files.
1437
 */
1438
function webform_file_download($uri) {
1439
  module_load_include('inc', 'webform', 'includes/webform.submissions');
1440

    
1441
  // Determine whether this file was a webform upload.
1442
  $row = db_query("SELECT fu.id as sid, f.fid FROM {file_managed} f LEFT JOIN {file_usage} fu ON f.fid = fu.fid AND fu.module = :webform AND fu.type = :submission WHERE f.uri = :uri", array('uri' => $uri, ':webform' => 'webform', ':submission' => 'submission'))->fetchObject();
1443
  if ($row) {
1444
    $file = file_load($row->fid);
1445
  }
1446
  if (!empty($row->sid)) {
1447
    $submissions = webform_get_submissions(array('sid' => $row->sid));
1448
    $submission = reset($submissions);
1449
  }
1450

    
1451
  // Grant or deny file access based on access to the submission.
1452
  if (!empty($submission)) {
1453
    $node = node_load($submission->nid);
1454
    if (webform_submission_access($node, $submission)) {
1455
      return file_get_content_headers($file);
1456
    }
1457
    else {
1458
      return -1;
1459
    }
1460
  }
1461
  // Grant access to files uploaded by a user before the submission is saved.
1462
  elseif (!empty($file) && !empty($_SESSION['webform_files'][$file->fid])) {
1463
    return file_get_content_headers($file);
1464
  }
1465

    
1466
  // Ensure we never completely ignore a webform file request.
1467
  if (strpos(file_uri_target($uri), 'webform/') === 0) {
1468
    // The file is not part of a submission or a submission-in-progress (by
1469
    // the current user), however it may be part of a submission-in-progress
1470
    // (or an abandoned submission) by another user. We assume that all files
1471
    // under our enforced directory prefix are in fact webform files, and so
1472
    // we deny access to the file. Abandoned uploads will be deleted by
1473
    // system_cron() in due course.
1474
    return -1;
1475
  }
1476
}
1477

    
1478
/**
1479
 * Return all content type enabled with webform.
1480
 *
1481
 * @return array
1482
 *   An array of node type names.
1483
 */
1484
function webform_node_types() {
1485
  $types = &drupal_static(__FUNCTION__, NULL);
1486
  if (!isset($types)) {
1487
    $types = array();
1488
    foreach (node_type_get_names() as $type => $name) {
1489
      if (variable_get('webform_node_' . $type, FALSE)) {
1490
        $types[] = $type;
1491
      }
1492
    }
1493
  }
1494
  return $types;
1495
}
1496

    
1497
/**
1498
 * Implements hook_node_type_delete().
1499
 */
1500
function webform_node_type_delete($info) {
1501
  variable_del('webform_node_' . $info->type);
1502
}
1503

    
1504
/**
1505
 * Implements hook_node_insert().
1506
 */
1507
function webform_node_insert($node) {
1508
  if (!variable_get('webform_node_' . $node->type, FALSE)) {
1509
    return;
1510
  }
1511

    
1512
  // If added directly through node_save(), set defaults for the node.
1513
  if (!isset($node->webform)) {
1514
    $node->webform = array();
1515
  }
1516

    
1517
  // Ensure values for all defaults are provided. Useful for importing from
1518
  // older versions into newer ones.
1519
  $node->webform += webform_node_defaults();
1520

    
1521
  // Do not make an entry if this node does not have any Webform settings.
1522
  if ($node->webform == webform_node_defaults() && !in_array($node->type, webform_variable_get('webform_node_types_primary'))) {
1523
    return;
1524
  }
1525

    
1526
  module_load_include('inc', 'webform', 'includes/webform.components');
1527
  module_load_include('inc', 'webform', 'includes/webform.conditionals');
1528
  module_load_include('inc', 'webform', 'includes/webform.emails');
1529

    
1530
  // Prepare the record for writing.
1531
  $node->webform['nid'] = $node->nid;
1532
  $webform_record = $node->webform;
1533
  $webform_record['preview_excluded_components'] = implode(',', $webform_record['preview_excluded_components']);
1534

    
1535
  // Insert the webform.
1536
  $node->webform['record_exists'] = (bool) drupal_write_record('webform', $webform_record);
1537

    
1538
  // Insert the components into the database. Used with clone.module.
1539
  if (isset($node->webform['components']) && !empty($node->webform['components'])) {
1540
    foreach ($node->webform['components'] as $cid => $component) {
1541
      // Required for clone.module.
1542
      $component['nid'] = $node->nid;
1543
      webform_component_insert($component);
1544
    }
1545
  }
1546

    
1547
  // Insert conditionals. Also used with clone.module.
1548
  if (isset($node->webform['conditionals']) && !empty($node->webform['conditionals'])) {
1549
    foreach ($node->webform['conditionals'] as $rgid => $conditional) {
1550
      $conditional['nid'] = $node->nid;
1551
      $conditional['rgid'] = $rgid;
1552
      webform_conditional_insert($conditional);
1553
    }
1554
  }
1555

    
1556
  // Insert emails. Also used with clone.module.
1557
  if (isset($node->webform['emails']) && !empty($node->webform['emails'])) {
1558
    foreach ($node->webform['emails'] as $eid => $email) {
1559
      $email['nid'] = $node->nid;
1560
      webform_email_insert($email);
1561
    }
1562
  }
1563

    
1564
  // Set the per-role submission access control.
1565
  foreach (array_filter($node->webform['roles']) as $rid) {
1566
    db_insert('webform_roles')->fields(array('nid' => $node->nid, 'rid' => $rid))->execute();
1567
  }
1568

    
1569
  // Flush the block cache if creating a block.
1570
  if (function_exists('block_flush_caches') && $node->webform['block']) {
1571
    block_flush_caches();
1572
  }
1573
}
1574

    
1575
/**
1576
 * Implements hook_node_update().
1577
 */
1578
function webform_node_update($node) {
1579
  if (!variable_get('webform_node_' . $node->type, FALSE)) {
1580
    return;
1581
  }
1582

    
1583
  // Check if this node needs a webform record at all. If it matches the
1584
  // defaults, any existing record will be deleted.
1585
  webform_check_record($node);
1586

    
1587
  // If a webform row doesn't even exist, we can assume it needs to be inserted.
1588
  // If the the webform matches the defaults, no row will be inserted.
1589
  if (!$node->webform['record_exists']) {
1590
    webform_node_insert($node);
1591
    return;
1592
  }
1593

    
1594
  // Prepare the record for writing.
1595
  $node->webform['nid'] = $node->nid;
1596
  $webform_record = $node->webform;
1597
  $webform_record['preview_excluded_components'] = implode(',', $webform_record['preview_excluded_components']);
1598

    
1599
  // Update the webform entry.
1600
  drupal_write_record('webform', $webform_record, array('nid'));
1601

    
1602
  // Compare the webform components and don't do anything if it's not needed.
1603
  $original = $node->original;
1604

    
1605
  if ($original->webform['components'] != $node->webform['components']) {
1606
    module_load_include('inc', 'webform', 'includes/webform.components');
1607

    
1608
    $original_cids = array_keys($original->webform['components']);
1609
    $current_cids = array_keys($node->webform['components']);
1610

    
1611
    $all_cids = array_unique(array_merge($original_cids, $current_cids));
1612
    $deleted_cids = array_diff($original_cids, $current_cids);
1613
    $inserted_cids = array_diff($current_cids, $original_cids);
1614

    
1615
    foreach ($all_cids as $cid) {
1616
      $node->webform['components'][$cid]['nid'] = $node->nid;
1617
      if (in_array($cid, $inserted_cids)) {
1618
        webform_component_insert($node->webform['components'][$cid]);
1619
      }
1620
      elseif (in_array($cid, $deleted_cids)) {
1621
        // Delete components only after all updates have been processed.
1622
      }
1623
      elseif ($node->webform['components'][$cid] != $original->webform['components'][$cid]) {
1624
        webform_component_update($node->webform['components'][$cid]);
1625
      }
1626
    }
1627

    
1628
    // Delete components now that any parent changes have been saved. When
1629
    // components are moved and deleted in one operation in FormBuilder, this
1630
    // ensures that only the current children are deleted.
1631
    foreach ($deleted_cids as $cid) {
1632
      webform_component_delete($node, $original->webform['components'][$cid]);
1633
    }
1634
  }
1635

    
1636
  // Compare the webform conditionals and don't do anything if it's not needed.
1637
  if ($original->webform['conditionals'] != $node->webform['conditionals']) {
1638
    module_load_include('inc', 'webform', 'includes/webform.conditionals');
1639

    
1640
    // Conditionals don't have unique site-wide IDs or configuration, so our
1641
    // update here is a bit more aggressive than for components and e-mails.
1642
    // Delete any conditionals no longer in the webform or that have changed.
1643
    foreach ($original->webform['conditionals'] as $rgid => $conditional) {
1644
      if (!isset($node->webform['conditionals'][$rgid]) || $conditional != $node->webform['conditionals'][$rgid]) {
1645
        webform_conditional_delete($node, $conditional);
1646
      }
1647
    }
1648
    // Insert any conditionals not in the original or that have changed.
1649
    foreach ($node->webform['conditionals'] as $rgid => $conditional) {
1650
      $conditional['nid'] = $node->nid;
1651
      $conditional['rgid'] = $rgid;
1652
      if (!isset($original->webform['conditionals'][$rgid]) || $original->webform['conditionals'][$rgid] != $conditional) {
1653
        webform_conditional_insert($conditional);
1654
      }
1655
    }
1656
  }
1657

    
1658
  // Compare the webform e-mails and don't do anything if it's not needed.
1659
  if ($original->webform['emails'] != $node->webform['emails']) {
1660
    module_load_include('inc', 'webform', 'includes/webform.emails');
1661

    
1662
    $original_eids = array_keys($original->webform['emails']);
1663
    $current_eids = array_keys($node->webform['emails']);
1664

    
1665
    $all_eids = array_unique(array_merge($original_eids, $current_eids));
1666
    $deleted_eids = array_diff($original_eids, $current_eids);
1667
    $inserted_eids = array_diff($current_eids, $original_eids);
1668

    
1669
    foreach ($all_eids as $eid) {
1670
      $node->webform['emails'][$eid]['nid'] = $node->nid;
1671
      if (in_array($eid, $inserted_eids)) {
1672
        webform_email_insert($node->webform['emails'][$eid]);
1673
      }
1674
      elseif (in_array($eid, $deleted_eids)) {
1675
        webform_email_delete($node, $original->webform['emails'][$eid]);
1676
      }
1677
      elseif ($node->webform['emails'][$eid] != $original->webform['emails'][$eid]) {
1678
        webform_email_update($node->webform['emails'][$eid]);
1679
      }
1680
    }
1681
  }
1682

    
1683
  // Just delete and re-insert roles if they've changed.
1684
  if ($original->webform['roles'] != $node->webform['roles']) {
1685
    db_delete('webform_roles')->condition('nid', $node->nid)->execute();
1686
    foreach (array_filter($node->webform['roles']) as $rid) {
1687
      db_insert('webform_roles')->fields(array('nid' => $node->nid, 'rid' => $rid))->execute();
1688
    }
1689
  }
1690

    
1691
  // Flush the block cache if block settings have been changed.
1692
  if (function_exists('block_flush_caches') && $node->webform['block'] != $original->webform['block']) {
1693
    block_flush_caches();
1694
  }
1695
}
1696

    
1697
/**
1698
 * Implements hook_node_delete().
1699
 */
1700
function webform_node_delete($node) {
1701
  if (!variable_get('webform_node_' . $node->type, FALSE)) {
1702
    return;
1703
  }
1704

    
1705
  // Allow components clean up extra data, such as uploaded files.
1706
  module_load_include('inc', 'webform', 'includes/webform.components');
1707
  foreach ($node->webform['components'] as $cid => $component) {
1708
    webform_component_delete($node, $component);
1709
  }
1710

    
1711
  // Remove any trace of webform data from the database.
1712
  db_delete('webform')->condition('nid', $node->nid)->execute();
1713
  db_delete('webform_component')->condition('nid', $node->nid)->execute();
1714
  db_delete('webform_conditional')->condition('nid', $node->nid)->execute();
1715
  db_delete('webform_conditional_rules')->condition('nid', $node->nid)->execute();
1716
  db_delete('webform_conditional_actions')->condition('nid', $node->nid)->execute();
1717
  db_delete('webform_emails')->condition('nid', $node->nid)->execute();
1718
  db_delete('webform_roles')->condition('nid', $node->nid)->execute();
1719
  db_delete('webform_submissions')->condition('nid', $node->nid)->execute();
1720
  db_delete('webform_submitted_data')->condition('nid', $node->nid)->execute();
1721
  db_delete('webform_last_download')->condition('nid', $node->nid)->execute();
1722
}
1723

    
1724
/**
1725
 * Default settings for a newly created webform node.
1726
 */
1727
function webform_node_defaults() {
1728
  $progress_bar_defaults = webform_variable_get('webform_progressbar_style');
1729
  $defaults = array(
1730
    'confirmation' => '',
1731
    'confirmation_format' => NULL,
1732
    'redirect_url' => '<confirmation>',
1733
    'block' => '0',
1734
    'allow_draft' => '0',
1735
    'auto_save' => '0',
1736
    'confidential' => '0',
1737
    'submit_notice' => '1',
1738
    'submit_text' => '',
1739
    'next_serial' => 1,
1740
    'submit_limit' => '-1',
1741
    'submit_interval' => '-1',
1742
    'total_submit_limit' => '-1',
1743
    'total_submit_interval' => '-1',
1744
    'progressbar_page_number' => in_array('progressbar_page_number', $progress_bar_defaults) ? '1' : '0',
1745
    'progressbar_percent' => in_array('progressbar_percent', $progress_bar_defaults) ? '1' : '0',
1746
    'progressbar_bar' => in_array('progressbar_bar', $progress_bar_defaults) ? '1' : '0',
1747
    'progressbar_pagebreak_labels' => in_array('progressbar_pagebreak_labels', $progress_bar_defaults) ? '1' : '0',
1748
    'progressbar_include_confirmation' => in_array('progressbar_include_confirmation', $progress_bar_defaults) ? '1' : '0',
1749
    'progressbar_label_first' => webform_variable_get('webform_progressbar_label_first'),
1750
    'progressbar_label_confirmation' => webform_variable_get('webform_progressbar_label_confirmation'),
1751
    'preview' => 0,
1752
    'preview_next_button_label' => '',
1753
    'preview_prev_button_label' => '',
1754
    'preview_title' => '',
1755
    'preview_message' => '',
1756
    'preview_message_format' => NULL,
1757
    'preview_excluded_components' => array(),
1758
    'status' => '1',
1759
    'record_exists' => FALSE,
1760
    'roles' => array('1', '2'),
1761
    'emails' => array(),
1762
    'components' => array(),
1763
    'conditionals' => array(),
1764
  );
1765
  drupal_alter('webform_node_defaults', $defaults);
1766
  return $defaults;
1767
}
1768

    
1769
/**
1770
 * Implements hook_node_prepare().
1771
 */
1772
function webform_node_prepare($node) {
1773
  if (variable_get('webform_node_' . $node->type, FALSE) && !isset($node->webform)) {
1774
    $node->webform = webform_node_defaults();
1775
  }
1776
}
1777

    
1778
/**
1779
 * Implements hook_node_load().
1780
 */
1781
function webform_node_load($nodes, $types) {
1782
  // Quick check to see if we need to do anything at all for these nodes.
1783
  $webform_types = webform_node_types();
1784
  if (count(array_intersect($types, $webform_types)) == 0) {
1785
    return;
1786
  }
1787

    
1788
  module_load_include('inc', 'webform', 'includes/webform.components');
1789

    
1790
  // Select all webforms that match these node IDs.
1791
  $result = db_select('webform')
1792
    ->fields('webform')
1793
    ->condition('nid', array_keys($nodes), 'IN')
1794
    ->execute()
1795
    ->fetchAllAssoc('nid', PDO::FETCH_ASSOC);
1796

    
1797
  foreach ($result as $nid => $webform) {
1798
    // Load the basic information for each node.
1799
    $nodes[$nid]->webform = $webform;
1800
    $nodes[$nid]->webform['record_exists'] = TRUE;
1801

    
1802
    // Expand the list of excluded preview components.
1803
    $nodes[$nid]->webform['preview_excluded_components'] = array_filter(explode(',', $webform['preview_excluded_components']));
1804
  }
1805

    
1806
  // Load the components, emails, and defaults for all webform-enabled nodes.
1807
  // @todo: Increase efficiency here by pulling in all information all at once
1808
  // instead of individual queries.
1809
  foreach ($nodes as $nid => $node) {
1810
    if (!in_array($node->type, $webform_types)) {
1811
      continue;
1812
    }
1813

    
1814
    // If a webform record doesn't exist, just return the defaults.
1815
    if (!isset($nodes[$nid]->webform)) {
1816
      $nodes[$nid]->webform = webform_node_defaults();
1817
      continue;
1818
    }
1819

    
1820
    $nodes[$nid]->webform['roles'] = db_select('webform_roles')
1821
      ->fields('webform_roles', array('rid'))
1822
      ->condition('nid', $nid)
1823
      ->execute()
1824
      ->fetchCol();
1825
    $nodes[$nid]->webform['emails'] = db_select('webform_emails')
1826
      ->fields('webform_emails')
1827
      ->condition('nid', $nid)
1828
      ->execute()
1829
      ->fetchAllAssoc('eid', PDO::FETCH_ASSOC);
1830

    
1831
    // Unserialize the mappings and excluded component list for e-mails.
1832
    foreach ($nodes[$nid]->webform['emails'] as $eid => $email) {
1833
      $nodes[$nid]->webform['emails'][$eid]['excluded_components'] = array_filter(explode(',', $email['excluded_components']));
1834
      $nodes[$nid]->webform['emails'][$eid]['extra'] = unserialize($email['extra']);
1835
      if (webform_variable_get('webform_format_override')) {
1836
        $nodes[$nid]->webform['emails'][$eid]['html'] = webform_variable_get('webform_default_format');
1837
      }
1838
    }
1839

    
1840
    // Load components for each node.
1841
    $nodes[$nid]->webform['components'] = db_select('webform_component')
1842
      ->fields('webform_component')
1843
      ->condition('nid', $nid)
1844
      ->orderBy('weight')
1845
      ->orderBy('name')
1846
      ->execute()
1847
      ->fetchAllAssoc('cid', PDO::FETCH_ASSOC);
1848

    
1849
    // Do a little cleanup on each component.
1850
    foreach ($nodes[$nid]->webform['components'] as $cid => $component) {
1851
      $nodes[$nid]->webform['components'][$cid]['nid'] = $nid;
1852
      $nodes[$nid]->webform['components'][$cid]['extra'] = unserialize($component['extra']);
1853
      webform_component_defaults($nodes[$nid]->webform['components'][$cid]);
1854
    }
1855

    
1856
    // Organize the components into a fieldset-based order.
1857
    if (!empty($nodes[$nid]->webform['components'])) {
1858
      $component_tree = array();
1859
      $page_count = 1;
1860
      _webform_components_tree_build($nodes[$nid]->webform['components'], $component_tree, 0, $page_count);
1861
      $nodes[$nid]->webform['components'] = _webform_components_tree_flatten($component_tree['children']);
1862
    }
1863

    
1864
    // Load all the conditional information, if any.
1865
    $nodes[$nid]->webform['conditionals'] = db_select('webform_conditional')
1866
      ->fields('webform_conditional')
1867
      ->condition('nid', $nid)
1868
      ->orderBy('weight')
1869
      ->execute()
1870
      ->fetchAllAssoc('rgid', PDO::FETCH_ASSOC);
1871
    if ($nodes[$nid]->webform['conditionals']) {
1872
      $rules = db_select('webform_conditional_rules')
1873
        ->fields('webform_conditional_rules')
1874
        ->condition('nid', $nid)
1875
        ->orderBy('rgid')
1876
        ->orderBy('rid')
1877
        ->execute();
1878
      foreach ($rules as $rule) {
1879
        $nodes[$nid]->webform['conditionals'][$rule->rgid]['rules'][$rule->rid] = (array) $rule;
1880
      }
1881
      $actions = db_select('webform_conditional_actions')
1882
        ->fields('webform_conditional_actions')
1883
        ->condition('nid', $nid)
1884
        ->orderBy('rgid')
1885
        ->orderBy('aid')
1886
        ->execute();
1887
      foreach ($actions as $action) {
1888
        $nodes[$nid]->webform['conditionals'][$action->rgid]['actions'][$action->aid] = (array) $action;
1889
      }
1890
    }
1891
  }
1892
}
1893

    
1894
/**
1895
 * Implements hook_user_role_delete().
1896
 *
1897
 * Removes references to deleted role from existing webforms.
1898
 */
1899
function webform_user_role_delete($role) {
1900
  db_delete('webform_roles')->condition('rid', $role->rid)->execute();
1901
}
1902

    
1903
/**
1904
 * Implements hook_form_alter().
1905
 */
1906
function webform_form_alter(&$form, $form_state, $form_id) {
1907
  if (isset($form['#node']->type) && $form_id == $form['#node']->type . '_node_form' && variable_get('webform_node_' . $form['#node']->type, FALSE)) {
1908
    $node = $form['#node'];
1909
    // Preserve all Webform options currently set on the node.
1910
    $form['webform'] = array(
1911
      '#type' => 'value',
1912
      '#value' => $node->webform,
1913
    );
1914

    
1915
    // If a new node, redirect the user to the components form after save.
1916
    if (empty($node->nid) && in_array($node->type, webform_variable_get('webform_node_types_primary'))) {
1917
      $form['actions']['submit']['#submit'][] = 'webform_form_submit';
1918
    }
1919
  }
1920
}
1921

    
1922
/**
1923
 * Implements hook_form_BASE_FORM_ID_alter().
1924
 */
1925
function webform_form_node_type_form_alter(&$form, $form_state) {
1926
  if (isset($form['type'])) {
1927
    $form['webform'] = array(
1928
      '#title' => t('Webform'),
1929
      '#type' => 'fieldset',
1930
      '#collapsible' => TRUE,
1931
      '#collapsed' => TRUE,
1932
      '#group' => 'additional_settings',
1933
      '#weight' => 10,
1934
      '#attached' => array(
1935
        'js' => array(drupal_get_path('module', 'webform') . '/js/node-type-form.js'),
1936
      ),
1937
    );
1938
    $form['webform']['webform_node'] = array(
1939
      '#type' => 'checkbox',
1940
      '#title' => t('Enable webform functionality'),
1941
      '#description' => t('Allows a form to be attached to content. This will add tabs for "Webform" and "Results" on all content of this type.'),
1942
      '#weight' => 0,
1943
      '#default_value' => variable_get('webform_node_' . $form['#node_type']->type, FALSE),
1944
      '#attributes' => array(
1945
        'data-enabled-description' => t('Enabled'),
1946
        'data-disabled-description' => t('Disabled'),
1947
      ),
1948
    );
1949
  }
1950
}
1951

    
1952
/**
1953
 * Submit handler for the webform node form.
1954
 *
1955
 * Redirect the user to the components form on new node inserts. Note that this
1956
 * fires after the hook_submit() function above.
1957
 */
1958
function webform_form_submit($form, &$form_state) {
1959
  drupal_set_message(t('The new webform %title has been created. Add new fields to your webform with the form below.', array('%title' => $form_state['values']['title'])));
1960
  $form_state['redirect'] = 'node/' . $form_state['nid'] . '/webform/components';
1961
}
1962

    
1963
/**
1964
 * Implements hook_node_view().
1965
 */
1966
function webform_node_view($node, $view_mode) {
1967
  global $user;
1968

    
1969
  if (!variable_get('webform_node_' . $node->type, FALSE)) {
1970
    return;
1971
  }
1972

    
1973
  // If empty or a new node (during preview) do not display.
1974
  if (empty($node->webform['components']) || empty($node->nid)) {
1975
    return;
1976
  }
1977

    
1978
  // If the webform is not set to display in this view mode, return early.
1979
  // View mode of 'form' is exempted to allow blocks and views to force display.
1980
  $extra_fields = field_extra_fields_get_display('node', $node->type, $view_mode);
1981
  if ($view_mode != 'form' && empty($extra_fields['webform']['visible'])) {
1982
    return;
1983
  }
1984

    
1985
  $submission = FALSE;
1986
  $submission_count = 0;
1987
  $page = node_is_page($node);
1988
  $logging_in = FALSE;
1989
  $total_limit_exceeded = FALSE;
1990
  $user_limit_exceeded = FALSE;
1991
  $closed = FALSE;
1992

    
1993
  // If a teaser, tell the form to load subsequent pages on the node page. A
1994
  // special exception is made for this view mode only.
1995
  if ($view_mode == 'teaser' && !isset($node->webform['action'])) {
1996
    $query = array_diff_key($_GET, array('q' => ''));
1997
    $node->webform['action'] = url('node/' . $node->nid, array('query' => $query));
1998
  }
1999

    
2000
  // When logging in using a form on the same page as a webform node, suppress
2001
  // output messages so that they don't show up after the user has logged in.
2002
  // See http://drupal.org/node/239343.
2003
  if (isset($_POST['op']) && isset($_POST['name']) && isset($_POST['pass'])) {
2004
    $logging_in = TRUE;
2005
  }
2006

    
2007
  if ($node->webform['status'] == 0) {
2008
    $closed = TRUE;
2009
    $enabled = FALSE;
2010
    $allowed_roles = array();
2011
  }
2012
  else {
2013
    // $enabled set by reference.
2014
    $allowed_roles = _webform_allowed_roles($node, $enabled);
2015
  }
2016

    
2017
  // Get a count of previous submissions by this user. Note that the
2018
  // webform_submission_access() function may disable the page cache for
2019
  // anonymous users if they are allowed to edit their own submissions!
2020
  if ($page && webform_submission_access($node, NULL, 'list')) {
2021
    module_load_include('inc', 'webform', 'includes/webform.submissions');
2022
    $submission_count = webform_get_submission_count($node->nid, $user->uid);
2023
  }
2024

    
2025
  // Check if this page is cached or not.
2026
  $cached = drupal_page_is_cacheable();
2027

    
2028
  // Check if the user can add another submission based on the individual
2029
  // submission limit.
2030
  // -1: Submissions are never throttled.
2031
  if ($node->webform['submit_limit'] != -1) {
2032
    module_load_include('inc', 'webform', 'includes/webform.submissions');
2033

    
2034
    // Disable the form if the limit is exceeded and page cache is not active.
2035
    // This prevents one anonymous user from generated a disabled webform page
2036
    // for the cache, which would be shown to other anonymous users who have not
2037
    // exceeded the limit.
2038
    // Cached should be checked first to avoid the expensive limit check on
2039
    // cached requests.
2040
    if (!$cached && ($user_limit_exceeded = webform_submission_user_limit_check($node))) {
2041
      $enabled = FALSE;
2042
    }
2043
  }
2044

    
2045
  // Check if the user can add another submission if there is a limit on total
2046
  // submissions.
2047
  // -1: Submissions are never throttled.
2048
  if ($node->webform['total_submit_limit'] != -1) {
2049
    module_load_include('inc', 'webform', 'includes/webform.submissions');
2050

    
2051
    // Disable the form if the limit is exceeded. The cache is irrelevant for
2052
    // the total submission limit; when it is exceeded for one user, it is
2053
    // exceeded for any other user.
2054
    if (($total_limit_exceeded = webform_submission_total_limit_check($node))) {
2055
      $enabled = FALSE;
2056
    }
2057
  }
2058

    
2059
  // Check if this user has a draft for this webform.
2060
  $resume_draft = FALSE;
2061
  if (($node->webform['allow_draft'] || $node->webform['auto_save']) && $user->uid != 0) {
2062
    // Draft found - display form with draft data for further editing.
2063
    if ($draft_sid = _webform_fetch_draft_sid($node->nid, $user->uid)) {
2064
      module_load_include('inc', 'webform', 'includes/webform.submissions');
2065
      $submission = webform_get_submission($node->nid, $draft_sid);
2066
      $enabled = TRUE;
2067
      $resume_draft = TRUE;
2068
    }
2069
  }
2070

    
2071
  // Avoid building the same form twice on the same page request (which can
2072
  // happen if the webform is displayed in a panel or block) because this
2073
  // causes multistep forms to build incorrectly the second time.
2074
  $cached_forms = &drupal_static(__FUNCTION__, array());
2075
  if (isset($cached_forms[$node->nid])) {
2076
    $form = $cached_forms[$node->nid];
2077
  }
2078
  // If this is the first time, generate the form array.
2079
  else {
2080
    $form = drupal_get_form('webform_client_form_' . $node->nid, $node, $submission, $resume_draft);
2081
    $cached_forms[$node->nid] = $form;
2082
  }
2083

    
2084
  // Remove the surrounding <form> tag if this is a preview.
2085
  if (!empty($node->in_preview)) {
2086
    $form['#type'] = 'markup';
2087
  }
2088

    
2089
  // Print out messages for the webform.
2090
  if (empty($node->in_preview) && !isset($node->webform_block) && !$logging_in) {
2091
    theme('webform_view_messages', array(
2092
      'node' => $node,
2093
      'page' => $page,
2094
      'submission_count' => $submission_count,
2095
      'user_limit_exceeded' => $user_limit_exceeded,
2096
      'total_limit_exceeded' => $total_limit_exceeded,
2097
      'allowed_roles' => $allowed_roles,
2098
      'closed' => $closed,
2099
      'cached' => $cached,
2100
    )
2101
    );
2102
  }
2103

    
2104
  // Add the output to the node.
2105
  $node->content['webform'] = array(
2106
    '#theme' => 'webform_view',
2107
    '#node' => $node,
2108
    '#page' => $page,
2109
    '#form' => $form,
2110
    '#enabled' => $enabled,
2111
    '#visible' => $extra_fields['webform']['visible'],
2112
    '#weight' => 10,
2113
  );
2114
}
2115

    
2116
/**
2117
 * Helper. Generates an array of allowed roles.
2118
 *
2119
 * @param object $node
2120
 *   The loaded node object containing a webform.
2121
 * @param bool $user_is_allowed
2122
 *   Reference to boolean to be set to whether the current user is allowed.
2123
 *
2124
 * @return array
2125
 *   Associative array of allowed roles indexed by the role id with a boolean
2126
 *   value indicating if the current user has this role.
2127
 */
2128
function _webform_allowed_roles($node, &$user_is_allowed) {
2129
  global $user;
2130
  if ($node->webform['confidential']) {
2131
    // Confidential webform may only be submitted anonymously, including uid 1.
2132
    $user_is_allowed = user_is_anonymous();
2133
    $allowed_roles = array(DRUPAL_ANONYMOUS_RID => $user_is_allowed);
2134
  }
2135
  elseif (webform_variable_get('webform_submission_access_control')) {
2136
    // Check if the user's role can submit this webform.
2137
    $allowed_roles = array();
2138
    foreach ($node->webform['roles'] as $rid) {
2139
      $allowed_roles[$rid] = isset($user->roles[$rid]);
2140
    }
2141
    $user_is_allowed = $user->uid == 1 || array_search(TRUE, $allowed_roles);
2142
  }
2143
  else {
2144
    // If not using Webform submission access control, allow all roles.
2145
    $user_is_allowed = TRUE;
2146
    $allowed_roles = array_fill_keys(array_keys(user_roles()), TRUE);
2147
  }
2148
  return $allowed_roles;
2149
}
2150

    
2151
/**
2152
 * Output the Webform into the node content.
2153
 *
2154
 * @param array $variables
2155
 *   The variables array.
2156
 *
2157
 * @return string
2158
 *   The rendered Webform.
2159
 */
2160
function theme_webform_view(array $variables) {
2161
  // Only show the form if this user is allowed access.
2162
  if ($variables['webform']['#enabled']) {
2163
    return drupal_render($variables['webform']['#form']);
2164
  }
2165
}
2166

    
2167
/**
2168
 * Display a message to a user if they are not allowed to fill out a form.
2169
 *
2170
 * @param array $variables
2171
 *   The variables array.
2172
 */
2173
function theme_webform_view_messages(array $variables) {
2174
  global $user;
2175

    
2176
  $node = $variables['node'];
2177
  $page = $variables['page'];
2178
  $submission_count = $variables['submission_count'];
2179
  $user_limit_exceeded = $variables['user_limit_exceeded'];
2180
  $total_limit_exceeded = $variables['total_limit_exceeded'];
2181
  $allowed_roles = $variables['allowed_roles'];
2182
  $closed = $variables['closed'];
2183
  $cached = $variables['cached'];
2184

    
2185
  $type = 'warning';
2186

    
2187
  if ($closed) {
2188
    $message = t('Submissions for this form are closed.');
2189
  }
2190
  elseif ($node->webform['confidential'] && user_is_logged_in()) {
2191
    $message = t('This form is confidential. You must <a href="!url">Log out</a> to submit it.', array('!url' => url('/user/logout', array('query' => array('destination' => request_uri())))));
2192
  }
2193
  // If open and not allowed to submit the form, give an explanation.
2194
  elseif (array_search(TRUE, $allowed_roles) === FALSE && $user->uid != 1) {
2195
    if (empty($allowed_roles)) {
2196
      // No roles are allowed to submit the form.
2197
      $message = t('Submissions for this form are closed.');
2198
    }
2199
    elseif ($user->uid == 0) {
2200
      // The user is anonymous, so (at least) needs to log in to view the form.
2201
      $login = url('user/login', array('query' => drupal_get_destination()));
2202
      $register = url('user/register', array('query' => drupal_get_destination()));
2203
      if (variable_get('user_register', 1) == 0) {
2204
        $message = t('You must <a href="!login">login</a> to view this form.', array('!login' => $login));
2205
      }
2206
      else {
2207
        $message = t('You must <a href="!login">login</a> or <a href="!register">register</a> to view this form.', array('!login' => $login, '!register' => $register));
2208
      }
2209
    }
2210
    else {
2211
      // The user must be some other role to submit.
2212
      $message = t('You do not have permission to view this form.');
2213
      $type = 'error';
2214
    }
2215
  }
2216

    
2217
  // If the user has exceeded the limit of submissions, explain the limit.
2218
  elseif ($user_limit_exceeded && !$cached) {
2219
    if ($node->webform['submit_interval'] == -1 && $node->webform['submit_limit'] > 1) {
2220
      $message = t('You have submitted this form the maximum number of times (@count).', array('@count' => $node->webform['submit_limit']));
2221
    }
2222
    elseif ($node->webform['submit_interval'] == -1 && $node->webform['submit_limit'] == 1) {
2223
      $message = t('You have already submitted this form.');
2224
    }
2225
    else {
2226
      $message = t('You may not submit another entry at this time.');
2227
    }
2228
  }
2229
  elseif ($total_limit_exceeded && !$cached) {
2230
    if ($node->webform['total_submit_interval'] == -1 && $node->webform['total_submit_limit'] > 1) {
2231
      $message = t('This form has received the maximum number of entries.');
2232
    }
2233
    else {
2234
      $message = t('You may not submit another entry at this time.');
2235
    }
2236
  }
2237

    
2238
  // If the user has submitted before, give them a link to their submissions.
2239
  if ($submission_count > 0 && $node->webform['submit_notice'] == 1 && !$cached) {
2240
    if (empty($message)) {
2241
      $message = t('You have already submitted this form.');
2242
      $type = 'status';
2243
    }
2244
    $message .= ' ' . t('<a href="!url">View your previous submissions</a>.', array('!url' => url('node/' . $node->nid . '/submissions')));
2245
  }
2246

    
2247
  if ($page && isset($message)) {
2248
    drupal_set_message($message, $type, FALSE);
2249
  }
2250
}
2251

    
2252
/**
2253
 * Implements hook_mail().
2254
 */
2255
function webform_mail($key, &$message, $params) {
2256
  $message['headers'] = array_merge($message['headers'], $params['headers']);
2257
  $message['subject'] = $params['subject'];
2258
  $message['body'][] = $params['message'];
2259
}
2260

    
2261
/**
2262
 * Implements hook_block_info().
2263
 */
2264
function webform_block_info() {
2265
  $blocks = array();
2266
  $webform_node_types = webform_node_types();
2267
  if (!empty($webform_node_types)) {
2268
    $query = db_select('webform', 'w')->fields('w')->fields('n', array('title'));
2269
    $query->leftJoin('node', 'n', 'w.nid = n.nid');
2270
    $query->condition('w.block', 1);
2271
    $query->condition('n.type', $webform_node_types, 'IN');
2272
    $result = $query->execute();
2273
    foreach ($result as $data) {
2274
      $blocks['client-block-' . $data->nid] = array(
2275
        'info' => t('Webform: !title', array('!title' => $data->title)),
2276
        'cache' => DRUPAL_NO_CACHE,
2277
      );
2278
    }
2279
  }
2280
  return $blocks;
2281
}
2282

    
2283
/**
2284
 * Implements hook_block_view().
2285
 */
2286
function webform_block_view($delta = '') {
2287
  // Load the block-specific configuration settings.
2288
  $webform_blocks = webform_variable_get('webform_blocks');
2289
  $settings = isset($webform_blocks[$delta]) ? $webform_blocks[$delta] : array();
2290
  $settings += array(
2291
    'display' => 'form',
2292
    'pages_block' => 1,
2293
    'confirmation_block' => 0,
2294
  );
2295

    
2296
  // Get the node ID from delta.
2297
  $nid = drupal_substr($delta, strrpos($delta, '-') + 1);
2298

    
2299
  // Load node in current language.
2300
  if (module_exists('translation')) {
2301
    global $language;
2302
    if (($translations = translation_node_get_translations($nid)) && (isset($translations[$language->language]))) {
2303
      $nid = $translations[$language->language]->nid;
2304
    }
2305
  }
2306

    
2307
  // The webform node to display in the block.
2308
  $node = node_load($nid);
2309

    
2310
  // Return if user has no access to the webform node.
2311
  if (!node_access('view', $node)) {
2312
    return;
2313
  }
2314

    
2315
  // This is a webform node block.
2316
  $node->webform_block = TRUE;
2317
  $node->webform['confirmation_block'] = $settings['confirmation_block'];
2318

    
2319
  // If not displaying pages in the block, set the #action property on the form.
2320
  if ($settings['pages_block']) {
2321
    $node->webform['action'] = FALSE;
2322
  }
2323
  else {
2324
    $query = array_diff_key($_GET, array('q' => ''));
2325
    $node->webform['action'] = url('node/' . $node->nid, array('query' => $query));
2326
  }
2327

    
2328
  // Generate the content of the block based on display settings.
2329
  $content = array();
2330
  if ($settings['display'] == 'form') {
2331
    webform_node_view($node, 'form');
2332
    if (isset($node->content['webform'])) {
2333
      $content = $node->content['webform'];
2334
      if (!$node->content['webform']['#visible']) {
2335
        // If the webform form is only shown in a block and not as within the
2336
        // node, remove the content from the node.
2337
        unset($node->content['webform']);
2338
      }
2339
    }
2340
  }
2341
  else {
2342
    $content = node_view($node, $settings['display']);
2343
  }
2344

    
2345
  // Check for an in-block confirmation message.
2346
  if (isset($_SESSION['webform_confirmation'][$nid])) {
2347
    if ($_SESSION['webform_confirmation'][$nid]['confirmation_page']) {
2348
      // Replace form with confirmation page.
2349
      $content = array(
2350
        '#theme' => array('webform_confirmation_' . $node->nid, 'webform_confirmation'),
2351
        '#node' => $node,
2352
        '#sid' => $_SESSION['webform_confirmation'][$nid]['sid'],
2353
      );
2354
    }
2355
    elseif (strlen(trim(strip_tags($node->webform['confirmation'])))) {
2356
      // Display confirmation link drupal status messages, but in the block.
2357
      $message = webform_replace_tokens($node->webform['confirmation'],
2358
                                        $node,
2359
                                        webform_get_submission($nid, $_SESSION['webform_confirmation'][$nid]['confirmation_page']),
2360
                                        NULL,
2361
                                        $node->webform['confirmation_format']);
2362
      $content = array(
2363
        'confirmation_message' => array(
2364
          '#markup' => "<div class=\"messages status webform-confirmation\">\n" .
2365
          '<h2 class="element-invisible">' . t('Status message') . "</h2>\n" .
2366
          $message .
2367
          "</div>\n",
2368
          '#weight' => -1,
2369
        ),
2370
        'webform_view' => $content,
2371
      );
2372
    }
2373
    unset($_SESSION['webform_confirmation'][$nid]);
2374
    if (empty($_SESSION['webform_confirmation'])) {
2375
      unset($_SESSION['webform_confirmation']);
2376
    }
2377
  }
2378

    
2379
  // Add contextual links for the webform node if they aren't already there.
2380
  if (!isset($content['#contextual_links']['node'])) {
2381
    $content['#contextual_links']['node'] = array('node', array($node->nid));
2382
  }
2383

    
2384
  // Create the block, using the node title for the block title.
2385
  // Note that we render the content immediately here rather than passing back
2386
  // a renderable so that if the block is empty it is hidden.
2387
  $block = array(
2388
    'subject' => check_plain($node->title),
2389
    'content' => $content,
2390
  );
2391
  return $block;
2392
}
2393

    
2394
/**
2395
 * Implements hook_block_configure().
2396
 */
2397
function webform_block_configure($delta = '') {
2398
  $nid = str_replace('client-block-', '', $delta);
2399
  $node = node_load($nid);
2400

    
2401
  // Load the block-specific configuration settings.
2402
  $webform_blocks = webform_variable_get('webform_blocks');
2403
  $settings = isset($webform_blocks[$delta]) ? $webform_blocks[$delta] : array();
2404
  $settings += array(
2405
    'display' => 'form',
2406
    'pages_block' => 1,
2407
    'confirmation_block' => 0,
2408
  );
2409

    
2410
  // Build a list of view modes for this node.
2411
  $entity_info = entity_get_info('node');
2412
  $view_modes = array(
2413
    'form' => t('Form only'),
2414
  );
2415
  foreach ($entity_info['view modes'] as $view_mode_key => $view_mode_info) {
2416
    $view_modes[$view_mode_key] = $view_mode_info['label'];
2417
  }
2418

    
2419
  $form = array();
2420
  $form['display'] = array(
2421
    '#type' => 'select',
2422
    '#title' => t('View mode'),
2423
    '#default_value' => $settings['display'],
2424
    '#options' => $view_modes,
2425
    '#description' => t('The view mode determines how much of the webform to show within the block. You may <a href="!view_modes">customize different view modes</a> (other than the "Form only" mode) or even create new custom view modes if either the <a href="http://drupal.org/project/entity_view_mode">Entity view modes</a> or <a href="http://drupal.org/project/ds">Display Suite</a> modules are installed.', array('!view_modes' => url('admin/structure/types/manage/' . $node->type . '/display'))),
2426
  );
2427

    
2428
  $form['pages_block'] = array(
2429
    '#type' => 'radios',
2430
    '#title' => t('Multi-page handling'),
2431
    '#options' => array(
2432
      1 => t('Display all pages inside block'),
2433
      0 => t('Redirect to the node page after the first page'),
2434
    ),
2435
    '#default_value' => $settings['pages_block'],
2436
    '#description' => t('If your webform has multiple pages, you may change the behavior of the "Next" button. This will also affect where validation messages show up after an error.'),
2437
  );
2438

    
2439
  $form['confirmation_block'] = array(
2440
    '#type' => 'radios',
2441
    '#title' => t('Confirmation message'),
2442
    '#options' => array(
2443
      0 => t('Display as configured in the webform'),
2444
      1 => t('Display the confirmation page in the block on the same page (no redirect)'),
2445
    ),
2446
    '#default_value' => $settings['confirmation_block'],
2447
    '#description' => t("This setting overrides the webform's configuration and redirection location settings when the webform is submitted via this block."),
2448
  );
2449
  return $form;
2450
}
2451

    
2452
/**
2453
 * Implements hook_block_save().
2454
 */
2455
function webform_block_save($delta = '', $edit = array()) {
2456
  // Load the previously defined block-specific configuration settings.
2457
  $settings = webform_variable_get('webform_blocks');
2458
  // Build the settings array.
2459
  $new_settings[$delta] = array(
2460
    'display' => $edit['display'],
2461
    'pages_block' => $edit['pages_block'],
2462
    'confirmation_block' => $edit['confirmation_block'],
2463
  );
2464
  // We store settings for multiple blocks in just one variable
2465
  // so we merge the existing settings with the new ones before save.
2466
  variable_set('webform_blocks', array_merge($settings, $new_settings));
2467
}
2468

    
2469
/**
2470
 * Client form generation function.
2471
 *
2472
 * If this is displaying an existing submission, pass in the $submission
2473
 * variable with the contents of the submission to be displayed.
2474
 *
2475
 * @param $form
2476
 *   The current form array (always empty).
2477
 * @param $form_state
2478
 *   The current form values of a submission, used in multipage webforms.
2479
 * @param object $node
2480
 *   The current webform node.
2481
 * @param $submission
2482
 *   An object containing information about the form submission if we're
2483
 *   displaying a result.
2484
 * @param $resume_draft
2485
 *   Optional. Set to TRUE when resuming a draft and skipping past previously-
2486
 *   validated pages is desired.
2487
 * @param $filter
2488
 *   Whether or not to filter the contents of descriptions and values when
2489
 *   building the form. Values need to be unfiltered to be editable by
2490
 *   Form Builder.
2491
 */
2492
function webform_client_form($form, &$form_state, $node, $submission = FALSE, $resume_draft = FALSE, $filter = TRUE) {
2493
  global $user;
2494

    
2495
  // Attach necessary JavaScript and CSS.
2496
  $form['#attached'] = array(
2497
    'css' => array(drupal_get_path('module', 'webform') . '/css/webform.css'),
2498
    'js' => array(drupal_get_path('module', 'webform') . '/js/webform.js'),
2499
  );
2500
  form_load_include($form_state, 'inc', 'webform', 'includes/webform.components');
2501
  form_load_include($form_state, 'inc', 'webform', 'includes/webform.submissions');
2502

    
2503
  // For ajax requests, $form_state['values']['details'] is missing. Restore
2504
  // from storage, if available, for multi-page forms.
2505
  if (empty($form_state['values']['details']) && !empty($form_state['storage']['details'])) {
2506
    $form_state['values']['details'] = $form_state['storage']['details'];
2507
  }
2508

    
2509
  // If in a multi-step form, a submission ID may be specified in form state.
2510
  // Load this submission. This allows anonymous users to use auto-save.
2511
  if (empty($submission) && !empty($form_state['values']['details']['sid'])) {
2512
    $submission = webform_get_submission($node->nid, $form_state['values']['details']['sid']);
2513
  }
2514

    
2515
  $finished = isset($submission->is_draft) ? (!$submission->is_draft) : 0;
2516
  $submit_button_text = $finished
2517
                          ? t('Save')
2518
                          : (empty($node->webform['submit_text']) ? t('Submit') : t($node->webform['submit_text']));
2519

    
2520
  // Bind arguments to $form to make them available in theming and form_alter.
2521
  $form['#node'] = $node;
2522
  $form['#submission'] = $submission;
2523
  $form['#is_draft'] = $submission && $submission->is_draft;
2524
  $form['#filter'] = $filter;
2525

    
2526
  // Add a theme function for this form.
2527
  $form['#theme'] = array('webform_form_' . $node->nid, 'webform_form');
2528

    
2529
  // Add a CSS class for all client forms.
2530
  $form['#attributes']['class'][] = 'webform-client-form';
2531
  $form['#attributes']['class'][] = 'webform-client-form-' . $node->nid;
2532

    
2533
  // Sometimes when displaying a webform as a teaser or block, a custom action
2534
  // property is set to direct the user to the node page.
2535
  if (!empty($node->webform['action'])) {
2536
    $form['#action'] = $node->webform['action'];
2537
  }
2538

    
2539
  $form['#submit'] = array('webform_client_form_pages', 'webform_client_form_submit');
2540
  $form['#validate'] = array('webform_client_form_validate');
2541
  // Add includes for used component types and pre/post validation handlers.
2542
  $form['#process'] = array('webform_client_form_process');
2543

    
2544
  if (is_array($node->webform['components']) && !empty($node->webform['components'])) {
2545
    // Prepare a new form array.
2546
    $form['submitted'] = array(
2547
      '#tree' => TRUE,
2548
    );
2549
    $form['details'] = array(
2550
      '#tree' => TRUE,
2551
    );
2552

    
2553
    // Put the components into a tree structure.
2554
    if (!isset($form_state['storage']['component_tree'])) {
2555
      $form_state['webform']['component_tree'] = array();
2556
      $form_state['webform']['page_count'] = 1;
2557
      $form_state['webform']['page_num'] = 1;
2558
      _webform_components_tree_build($node->webform['components'], $form_state['webform']['component_tree'], 0, $form_state['webform']['page_count']);
2559

    
2560
      // If preview is enabled, increase the page count by one.
2561
      if ($node->webform['preview']) {
2562
        $form_state['webform']['page_count']++;
2563
      }
2564
      $form_state['webform']['preview'] = $node->webform['preview'];
2565

    
2566
      // If this is the first time this draft has been restore and presented to
2567
      // the user, let them know that they are looking at a draft, rather than
2568
      // a new form. This applies to the node view page, but not to a submission
2569
      // edit page (where they presummably know what submission they are
2570
      // editing).
2571
      if ($resume_draft && empty($form_state['input'])) {
2572
        drupal_set_message(t('A partially-completed form was found. Please complete the remaining portions.'));
2573
      }
2574
    }
2575
    else {
2576
      $form_state['webform']['component_tree'] = $form_state['storage']['component_tree'];
2577
      $form_state['webform']['page_count'] = $form_state['storage']['page_count'];
2578
      $form_state['webform']['page_num'] = $form_state['storage']['page_num'];
2579
      $form_state['webform']['preview'] = $form_state['storage']['preview'];
2580
    }
2581

    
2582
    // Set the input values based on whether we're editing an existing
2583
    // submission or not.
2584
    $input_values = isset($submission->data) ? $submission->data : array();
2585

    
2586
    // Form state storage override any default submission information. Convert
2587
    // the value structure to always be an array, matching $submission->data.
2588
    if (isset($form_state['storage']['submitted'])) {
2589
      foreach ($form_state['storage']['submitted'] as $cid => $data) {
2590
        $input_values[$cid] = is_array($data) ? $data : array($data);
2591
      }
2592
    }
2593

    
2594
    // Form state values override any default submission information. Convert
2595
    // the value structure to always be an array, matching $submission->data.
2596
    if (isset($form_state['values']['submitted'])) {
2597
      foreach ($form_state['values']['submitted'] as $cid => $data) {
2598
        $input_values[$cid] = is_array($data) ? $data : array($data);
2599
      }
2600
    }
2601

    
2602
    // Generate conditional topological order & report any errors.
2603
    $sorter = webform_get_conditional_sorter($node);
2604
    $sorter->reportErrors();
2605

    
2606
    // Excecute the condtionals on the current input values.
2607
    $input_values = $sorter->executeConditionals($input_values);
2608

    
2609
    // Allow values from other pages to be sent to browser for conditionals.
2610
    $form['#conditional_values'] = $input_values;
2611

    
2612
    // Allow components access to most up-to-date values.
2613
    $form_state['#conditional_values'] = $input_values;
2614

    
2615
    // For resuming a previous draft, find the next page after the last
2616
    // validated page.
2617
    if (!isset($form_state['storage']['page_num']) && $submission && $submission->is_draft && $submission->highest_valid_page) {
2618
      // Find the:
2619
      // 1. previous/next non-empty page, or
2620
      // 2. the preview page, or
2621
      // 3. the preview page, forcing its display if the form would unexpectedly
2622
      //    submit, or
2623
      // 4. page 1 even if empty, if no other previous page would be shown.
2624
      $form_state['webform']['page_num'] = $submission->highest_valid_page;
2625
      do {
2626
        $form_state['webform']['page_num']++;
2627
      } while (!webform_get_conditional_sorter($node)->pageVisibility($form_state['webform']['page_num']));
2628
      if (!$form_state['webform']['preview'] && $form_state['webform']['page_num'] == $form_state['webform']['page_count'] + (int) !$form_state['webform']['preview']) {
2629
        // Force a preview to avert an unintended submission via Next.
2630
        $form_state['webform']['preview'] = TRUE;
2631
        $form_state['webform']['page_count']++;
2632
      }
2633
      // The form hasn't been submitted (ever) and the preview code will expect
2634
      // $form_state['values']['submitted'] to be set from a previous
2635
      // submission, so provide these values here.
2636
      $form_state['values']['submitted'] = $input_values;
2637
      $form_state['storage']['submitted'] = $input_values;
2638
    }
2639

    
2640
    // Shorten up our variable names.
2641
    $component_tree = $form_state['webform']['component_tree'];
2642
    $page_count = $form_state['webform']['page_count'];
2643
    $page_num = $form_state['webform']['page_num'];
2644
    $preview = $form_state['webform']['preview'];
2645

    
2646
    if ($node->webform['progressbar_include_confirmation'] || $page_count > 1) {
2647
      $page_labels = webform_page_labels($node, $form_state);
2648
      $form['progressbar'] = array(
2649
        '#theme' => 'webform_progressbar',
2650
        '#node' => $node,
2651
        '#page_num' => $page_num,
2652
        '#page_count' => count($page_labels),
2653
        '#page_labels' => $page_labels,
2654
        '#weight' => -100,
2655
      );
2656
    }
2657

    
2658
    // Check whether a previous submission was truncated. The length of the
2659
    // client form is not estimated before submission because a) the
2660
    // determination may not be accurate for some webform components and b) the
2661
    // error will be apparent upon submission.
2662
    webform_input_vars_check($form, $form_state, 'submitted');
2663

    
2664
    // Recursively add components to the form. The unfiltered version of the
2665
    // form (typically used in Form Builder), includes all components.
2666
    foreach ($component_tree['children'] as $cid => $component) {
2667
      if ($component['type'] == 'pagebreak') {
2668
        $next_page_labels[$component['page_num'] - 1] = !empty($component['extra']['next_page_label']) ? t($component['extra']['next_page_label']) : t('Next Page >');
2669
        $prev_page_labels[$component['page_num']] = !empty($component['extra']['prev_page_label']) ? t($component['extra']['prev_page_label']) : t('< Previous Page');
2670
      }
2671
      if (!$filter || $sorter->componentVisibility($cid, $page_num)) {
2672
        $component_value = isset($input_values[$cid]) ? $input_values[$cid] : NULL;
2673
        _webform_client_form_add_component($node, $component, $component_value, $form['submitted'], $form, $input_values, 'form', $page_num, $filter);
2674
      }
2675
    }
2676
    if ($preview) {
2677
      $next_page_labels[$page_count - 1] = $node->webform['preview_next_button_label'] ? t($node->webform['preview_next_button_label']) : t('Preview');
2678
      $prev_page_labels[$page_count] = $node->webform['preview_prev_button_label'] ? t($node->webform['preview_prev_button_label']) : t('< Previous');
2679
    }
2680

    
2681
    // Add the preview if needed.
2682
    if ($preview && $page_num === $page_count) {
2683
      $preview_submission = webform_submission_create($node, $user, $form_state, TRUE, $submission);
2684
      $preview_message = $node->webform['preview_message'];
2685
      if (strlen(trim(strip_tags($preview_message))) === 0) {
2686
        $preview_message = t('Please review your submission. Your submission is not complete until you press the "!button" button!', array('!button' => $submit_button_text));
2687
      }
2688
      $form['preview_message'] = array(
2689
        '#type' => 'markup',
2690
        '#markup' => webform_replace_tokens($preview_message, $node, $preview_submission, NULL, $node->webform['preview_message_format']),
2691
      );
2692

    
2693
      $form['preview'] = webform_submission_render($node, $preview_submission, NULL, 'html', $node->webform['preview_excluded_components']);
2694
      $form['#attributes']['class'][] = 'preview';
2695
    }
2696

    
2697
    // These form details help managing data upon submission.
2698
    $form['details']['nid'] = array(
2699
      '#type' => 'value',
2700
      '#value' => $node->nid,
2701
    );
2702
    $form['details']['sid'] = array(
2703
      '#type' => 'hidden',
2704
      '#value' => isset($submission->sid) ? $submission->sid : NULL,
2705
    );
2706
    $form['details']['uid'] = array(
2707
      '#type' => 'value',
2708
      '#value' => isset($submission->uid) ? $submission->uid : $user->uid,
2709
    );
2710
    $form['details']['page_num'] = array(
2711
      '#type' => 'hidden',
2712
      '#value' => $page_num,
2713
    );
2714
    $form['details']['page_count'] = array(
2715
      '#type' => 'hidden',
2716
      '#value' => $page_count,
2717
    );
2718
    $form['details']['finished'] = array(
2719
      '#type' => 'hidden',
2720
      '#value' => $finished,
2721
    );
2722

    
2723
    // Add process functions to remove the IDs forced upon buttons and wrappers.
2724
    $actions_pre_render = array_merge(element_info_property('actions', '#pre_render', array()), array('webform_pre_render_remove_id'));
2725
    $buttons_pre_render = array_merge(element_info_property('submit', '#pre_render', array()), array('webform_pre_render_remove_id'));
2726

    
2727
    // Add buttons for pages, drafts, and submissions.
2728
    $form['actions'] = array(
2729
      '#type' => 'actions',
2730
      '#weight' => 1000,
2731
      '#pre_render' => $actions_pre_render,
2732
    );
2733

    
2734
    // Add the draft button.
2735
    if ($node->webform['allow_draft'] && (empty($submission) || $submission->is_draft) && $user->uid != 0) {
2736
      $form['actions']['draft'] = array(
2737
        '#type' => 'submit',
2738
        '#value' => t('Save Draft'),
2739
        '#weight' => -2,
2740
        // Prevalidation only; no element validation for Save Draft.
2741
        '#validate' => array('webform_client_form_prevalidate'),
2742
        '#attributes' => array(
2743
          'formnovalidate' => 'formnovalidate',
2744
          'class' => array('webform-draft'),
2745
        ),
2746
        '#pre_render' => $buttons_pre_render,
2747
      );
2748
    }
2749

    
2750
    // Add the submit button(s).
2751
    if ($page_num > 1) {
2752
      $form['actions']['previous'] = array(
2753
        '#type' => 'submit',
2754
        '#value' => $prev_page_labels[$page_num],
2755
        '#weight' => 5,
2756
        '#validate' => array(),
2757
        '#attributes' => array(
2758
          'formnovalidate' => 'formnovalidate',
2759
          'class' => array('webform-previous'),
2760
        ),
2761
        '#pre_render' => $buttons_pre_render,
2762
      );
2763
    }
2764
    if ($page_num == $page_count) {
2765
      $form['actions']['submit'] = array(
2766
        '#type' => 'submit',
2767
        '#value' => $submit_button_text,
2768
        '#weight' => 10,
2769
        '#attributes' => array(
2770
          'class' => array('webform-submit', 'button-primary'),
2771
        ),
2772
        '#pre_render' => $buttons_pre_render,
2773
      );
2774
    }
2775
    elseif ($page_num < $page_count) {
2776
      $form['actions']['next'] = array(
2777
        '#type' => 'submit',
2778
        '#value' => $next_page_labels[$page_num],
2779
        '#weight' => 10,
2780
        '#attributes' => array(
2781
          'class' => array('webform-next', 'button-primary'),
2782
        ),
2783
        '#pre_render' => $buttons_pre_render,
2784
      );
2785
    }
2786
  }
2787

    
2788
  return $form;
2789
}
2790

    
2791
/**
2792
 * Process function for webform_client_form().
2793
 *
2794
 * Include all the enabled components for this form to ensure availability.
2795
 * Also adds the pre- and post-validators to ensure that hook_form_alters don't
2796
 * add their validation functions in the wrong order.
2797
 */
2798
function webform_client_form_process($form, $form_state) {
2799
  $components = webform_components();
2800
  foreach ($components as $component_type => $component) {
2801
    webform_component_include($component_type);
2802
  }
2803

    
2804
  // Add the post validation to end of validators. Do this first on the off
2805
  // chance that an _alter function has unset form['#validate'].
2806
  $form['#validate'][] = 'webform_client_form_postvalidate';
2807
  // Add the pre-validator to the front of the list to run first.
2808
  array_unshift($form['#validate'], 'webform_client_form_prevalidate');
2809

    
2810
  return $form;
2811
}
2812

    
2813
/**
2814
 * Add a component to a renderable array. Called recursively for fieldsets.
2815
 *
2816
 * This function assists in the building of the client form, as well as the
2817
 * display of results, and the text of e-mails.
2818
 *
2819
 * @param object $node
2820
 *   The current webform node.
2821
 * @param $component
2822
 *   The component to be added to the form.
2823
 * @param $component_value
2824
 *   The components current value if known.
2825
 * @param $parent_fieldset
2826
 *   The fieldset to which this element will be added.
2827
 * @param $form
2828
 *   The entire form array.
2829
 * @param $input_values
2830
 *   All the values for this form, keyed by the component IDs. This may be
2831
 *   pulled from $form_state['values']['submitted'] or $submission->data.
2832
 *   These values are used to check if the component should be displayed
2833
 *   conditionally.
2834
 * @param $format
2835
 *   The format the form should be displayed as. May be one of the following:
2836
 *   - form: Show as an editable form.
2837
 *   - html: Show as HTML results.
2838
 *   - text: Show as plain text.
2839
 * @param $page_num
2840
 *   The page number. Defaults to 0.
2841
 * @param $filter
2842
 *   Whether the form element properties should be filtered. Only set to FALSE
2843
 *   if needing the raw properties for editing.
2844
 *
2845
 * @see webform_client_form()
2846
 * @see webform_submission_render()
2847
 */
2848
function _webform_client_form_add_component($node, $component, $component_value, &$parent_fieldset, &$form, $input_values, $format = 'form', $page_num = 0, $filter = TRUE) {
2849
  $cid = $component['cid'];
2850
  $component_access = empty($component['extra']['private']) || webform_results_access($node);
2851

    
2852
  // Load with submission information if necessary.
2853
  if ($format != 'form') {
2854
    // This component is display only.
2855
    $data = empty($input_values[$cid]) ? NULL : $input_values[$cid];
2856
    if ($display_element = webform_component_invoke($component['type'], 'display', $component, $data, $format, $form['#submission'])) {
2857
      // Set access based on the private property.
2858
      $display_element += array('#access' => TRUE);
2859
      $display_element['#access'] = $display_element['#access'] && $component_access;
2860

    
2861
      // Ensure the component is added as a property.
2862
      $display_element['#webform_component'] = $component;
2863

    
2864
      // Add custom CSS classes to the field and wrapper.
2865
      _webform_component_classes($display_element, $component);
2866

    
2867
      // Allow modules to modify a "display only" webform component.
2868
      drupal_alter('webform_component_display', $display_element, $component);
2869

    
2870
      // The form_builder() function usually adds #parents and #id for us, but
2871
      // because these are not marked for #input, we need to add them manually.
2872
      if (!isset($display_element['#parents'])) {
2873
        $parents = isset($parent_fieldset['#parents']) ? $parent_fieldset['#parents'] : array('submitted');
2874
        $parents[] = $component['form_key'];
2875
        $display_element['#parents'] = $parents;
2876
      }
2877
      if (!isset($display_element['#id'])) {
2878
        $display_element['#id'] = drupal_clean_css_identifier('edit-' . implode('-', $display_element['#parents']));
2879
      }
2880

    
2881
      // Add the element into the proper parent in the display.
2882
      $parent_fieldset[$component['form_key']] = $display_element;
2883
    }
2884
  }
2885
  // Show the component only on its form page, or if building an unfiltered
2886
  // version of the form (such as for Form Builder).
2887
  elseif ($component['page_num'] == $page_num || $filter == FALSE) {
2888
    // Add this user-defined field to the form (with all the values that are
2889
    // always available).
2890
    if ($element = webform_component_invoke($component['type'], 'render', $component, $component_value, $filter, $form['#submission'])) {
2891
      // Set access based on the private property.
2892
      $element += array('#access' => TRUE);
2893
      $element['#access'] = $element['#access'] && $component_access;
2894

    
2895
      // Ensure the component is added as a property.
2896
      $element['#webform_component'] = $component;
2897

    
2898
      // The 'private' option is in most components, but it's not a real
2899
      // property. Add it for Form Builder compatibility.
2900
      if (webform_component_feature($component['type'], 'private')) {
2901
        $element['#webform_private'] = $component['extra']['private'];
2902
      }
2903

    
2904
      // The 'placeholder' option is in some components, but it's not a real
2905
      // property. Add it for Form Builder compatibility.
2906
      if (webform_component_feature($component['type'], 'placeholder')) {
2907
        $element['#webform_placeholder'] = $component['extra']['placeholder'];
2908
      }
2909

    
2910
      // Add custom CSS classes to the field and wrapper.
2911
      _webform_component_classes($element, $component);
2912

    
2913
      // Allow modules to modify a webform component that is going to be render
2914
      // in a form.
2915
      drupal_alter('webform_component_render', $element, $component);
2916

    
2917
      // Add the element into the proper parent in the form.
2918
      $parent_fieldset[$component['form_key']] = $element;
2919
    }
2920
    else {
2921
      drupal_set_message(t('The webform component @type is not able to be displayed', array('@type' => $component['type'])));
2922
    }
2923
  }
2924

    
2925
  // Disable validation initially on all elements. We manually validate
2926
  // all webform elements in webform_client_form_validate().
2927
  if (isset($parent_fieldset[$component['form_key']])) {
2928
    $parent_fieldset[$component['form_key']]['#validated'] = TRUE;
2929
    $parent_fieldset[$component['form_key']]['#webform_validated'] = FALSE;
2930
  }
2931

    
2932
  if (isset($component['children']) && is_array($component['children'])) {
2933
    $sorter = webform_get_conditional_sorter($node);
2934
    foreach ($component['children'] as $scid => $subcomponent) {
2935
      $subcomponent_value = isset($input_values[$scid]) ? $input_values[$scid] : NULL;
2936
      // Include if always shown, or for forms, also if currently hidden but
2937
      // might be shown due to conditionals.
2938
      $visibility = $sorter->componentVisibility($scid, $subcomponent['page_num']);
2939
      if ($visibility == WebformConditionals::componentShown || ($format == 'form' && $visibility) || !$filter) {
2940
        _webform_client_form_add_component($node, $subcomponent, $subcomponent_value, $parent_fieldset[$component['form_key']], $form, $input_values, $format, $page_num, $filter);
2941
      }
2942
    }
2943
  }
2944
}
2945

    
2946
/**
2947
 * Validates that the form can still be submitted, saved as draft, or edited.
2948
 *
2949
 * Because forms may be submitted from cache or the webform changed while the
2950
 * submission is in progress, the conditions to allow the form are re-checked
2951
 * upon form submission.
2952
 */
2953
function webform_client_form_prevalidate($form, &$form_state) {
2954
  // Refresh the node in case it changed since the form was build and retrieved
2955
  // from cache.
2956
  $node = $form['#node'] = node_load($form['#node']->nid);
2957
  $finished = $form_state['values']['details']['finished'];
2958

    
2959
  // Check if the user is allowed to submit based on role. This check is
2960
  // repeated here to ensure the user is still logged in at the time of
2961
  // submission, otherwise a stale form in another window may be allowed.
2962
  // $allowed_role set by reference.
2963
  $allowed_roles = _webform_allowed_roles($node, $allowed_role);
2964

    
2965
  // Check that the submissions have not exceeded the total submission limit.
2966
  $total_limit_exceeded = FALSE;
2967
  if ($node->webform['total_submit_limit'] != -1 && !$finished) {
2968
    $total_limit_exceeded = webform_submission_total_limit_check($node);
2969
  }
2970

    
2971
  // Check that the user has not exceeded the submission limit.
2972
  // This usually will only apply to anonymous users when the page cache is
2973
  // enabled, because they may submit the form even if they do not have access.
2974
  $user_limit_exceeded = FALSE;
2975
  if ($node->webform['submit_limit'] != -1 && !$finished) {
2976
    $user_limit_exceeded = webform_submission_user_limit_check($node);
2977
  }
2978

    
2979
  // Check that the form is still open at time of submission.
2980
  // See https://www.drupal.org/node/2317273
2981
  // Consider the webform closed when it's status is closed AND either there
2982
  // is no submission yet (hence isn't being edited) or the user isn't an admin.
2983
  // Another way to consider this is that the form is open when its status is
2984
  // open OR there is a submission and the user is an admin.
2985
  $closed = empty($node->webform['status']) && (
2986
    empty($form['#submission']) ||
2987
    !user_access('edit all webform submissions')
2988
  );
2989

    
2990
  // Prevent submission by throwing an error.
2991
  if ((!$allowed_role || $total_limit_exceeded || $user_limit_exceeded || $closed)) {
2992
    theme('webform_view_messages', array('node' => $node, 'page' => 1, 'submission_count' => 0, 'user_limit_exceeded' => $user_limit_exceeded, 'total_limit_exceeded' => $total_limit_exceeded, 'allowed_roles' => $allowed_roles, 'closed' => $closed, 'cached' => FALSE));
2993
    form_set_error('', NULL);
2994
  }
2995
}
2996

    
2997
/**
2998
 * Form API #validate handler for the webform_client_form() form.
2999
 */
3000
function webform_client_form_validate($form, &$form_state) {
3001
  if (($errors = form_get_errors()) && array_key_exists('', $errors)) {
3002
    // Prevalidation failed. The form cannot be submitted. Do not attemp futher
3003
    // validation.
3004
    return;
3005
  }
3006
  if ($form_state['webform']['preview'] && $form_state['webform']['page_count'] === $form_state['webform']['page_num']) {
3007
    // Form has already passed validation and is on the preview page.
3008
    return;
3009
  }
3010

    
3011
  module_load_include('inc', 'webform', 'includes/webform.submissions');
3012
  $node = $form['#node'];
3013

    
3014
  // Assemble an array of all past and new input values that will determine if
3015
  // certain elements need validation at all.
3016
  if (!empty($node->webform['conditionals'])) {
3017
    $input_values = isset($form_state['storage']['submitted']) ? $form_state['storage']['submitted'] : array();
3018
    $new_values = isset($form_state['values']['submitted']) ? _webform_client_form_submit_flatten($form['#node'], $form_state['values']['submitted']) : array();
3019
    foreach ($new_values as $cid => $values) {
3020
      $input_values[$cid] = $values;
3021
    }
3022
    // Ensure that all conditionally-hidden values are removed.
3023
    $input_values = webform_get_conditional_sorter($node)->executeConditionals($input_values, $form_state['webform']['page_num']);
3024
  }
3025
  else {
3026
    $input_values = NULL;
3027
  }
3028

    
3029
  // Run all #element_validate and #required checks. These are skipped initially
3030
  // by setting #validated = TRUE on all components when they are added.
3031
  _webform_client_form_validate($form, $form_state, 'webform_client_form', $input_values);
3032
}
3033

    
3034
/**
3035
 * Recursive validation function to trigger normal Drupal validation.
3036
 *
3037
 * This function imitates _form_validate in Drupal's form.inc, only it sets
3038
 * a different property to ensure that validation has occurred.
3039
 */
3040
function _webform_client_form_validate(&$elements, &$form_state, $form_id = NULL, $input_values = NULL) {
3041
  if (isset($input_values) && isset($elements['#webform_component'])) {
3042
    $sorter = webform_get_conditional_sorter($form_state['complete form']['#node']);
3043
    $cid = $elements['#webform_component']['cid'];
3044
    $page_num = $form_state['values']['details']['page_num'];
3045
    // Webform-specific enhancements:
3046
    // 1. Only validate the field if it was used in this submission.
3047
    //    This both skips validation on the field and sets the value of the
3048
    //    field to NULL, preventing any dangerous input. Short-circuit
3049
    //    validation for a hidden component (hidden by rules dependent upon
3050
    //    component on previous pages), or a component this is dependent upon
3051
    //    values on the current page, but is hidden based upon their current
3052
    //    values.
3053
    // 2. Only validate if the field has not been set by conditionals.
3054
    //    The user will be unable to fix the validation without surmising the
3055
    //    logic and changing the trigger for the conditional. Also, it isn't
3056
    //    possible to set $element['#value'] without component-specific
3057
    //    knowledge of how the data is stored because $input_values is already
3058
    //    webform-normalized to contain values in arrays.
3059
    if ($sorter->componentVisibility($cid, $page_num) != WebformConditionals::componentShown) {
3060
      form_set_value($elements, NULL, $form_state);
3061
      return;
3062
    }
3063
    if ($sorter->componentSet($cid, $page_num)) {
3064
      $component = $elements['#webform_component'];
3065
      $value = $input_values[$cid];
3066
      $value = is_array($value) ? $value[0] : $value;
3067
      // webform_component_invoke cannot be called with reference arguments.
3068
      // Call directly.
3069
      // webform_component_invoke($component['type'], 'action_set', $component,
3070
      // $elements, $form_state, $value);.
3071
      $function = '_webform_action_set_' . $component['type'];
3072
      $function($component, $elements, $form_state, $value);
3073
    }
3074

    
3075
    // Check for changes in required status made by conditionals.
3076
    $required = $sorter->componentRequired($cid, $page_num);
3077
    if (isset($required)) {
3078
      $elements['#required'] = $required;
3079

    
3080
      // Some components, for example, grids, have nested sub-elements. Extend
3081
      // required to any sub-components.
3082
      foreach (element_children($elements) as $key) {
3083
        if (isset($elements[$key]) && $elements[$key] && !isset($elements[$key]['#webform_component'])) {
3084
          // Child is *not* a component.
3085
          $elements[$key]['#required'] = $required;
3086
        }
3087
      }
3088
    }
3089
  }
3090

    
3091
  // Recurse through all children.
3092
  foreach (element_children($elements) as $key) {
3093
    if (isset($elements[$key]) && $elements[$key]) {
3094
      _webform_client_form_validate($elements[$key], $form_state, NULL, $input_values);
3095
    }
3096
  }
3097
  // Validate the current input.
3098
  if (isset($elements['#webform_validated']) && !$elements['#webform_validated']) {
3099
    if (isset($elements['#needs_validation'])) {
3100
      // Make sure a value is passed when the field is required.
3101
      // A simple call to empty() will not cut it here as some fields, like
3102
      // checkboxes, can return a valid value of 0. Instead, check the
3103
      // length if it's a string, and if it's an array whether it is empty. For
3104
      // radios, FALSE means that no value was submitted, so check that too.
3105
      $value_is_empty_string = is_string($elements['#value']) && strlen(trim($elements['#value'])) === 0;
3106
      $value_is_empty_array = is_array($elements['#value']) && !$elements['#value'];
3107
      if ($elements['#required'] && ($value_is_empty_string || $value_is_empty_array || $elements['#value'] === FALSE || $elements['#value'] === NULL)) {
3108
        form_error($elements, t('!name field is required.', array('!name' => $elements['#title'])));
3109
      }
3110

    
3111
      // Verify that the value is not longer than #maxlength.
3112
      if (isset($elements['#maxlength']) && drupal_strlen($elements['#value']) > $elements['#maxlength']) {
3113
        form_error($elements, t('!name cannot be longer than %max characters but is currently %length characters long.', array('!name' => empty($elements['#title']) ? $elements['#parents'][0] : $elements['#title'], '%max' => $elements['#maxlength'], '%length' => drupal_strlen($elements['#value']))));
3114
      }
3115

    
3116
      // Verify that the value is not shorter than #minlength. The value may
3117
      // still be empty (required is a separate validation option).
3118
      if (isset($elements['#minlength'])) {
3119
        $length = drupal_strlen($elements['#value']);
3120
        if ($length > 0 && $length < $elements['#minlength']) {
3121
          form_error($elements, t('!name cannot be shorter than %min characters but is currently %length characters long.', array('!name' => empty($elements['#title']) ? $elements['#parents'][0] : $elements['#title'], '%min' => $elements['#minlength'], '%length' => drupal_strlen($elements['#value']))));
3122
        }
3123
      }
3124

    
3125
      if (isset($elements['#options']) && isset($elements['#value'])) {
3126
        if ($elements['#type'] == 'select') {
3127
          $options = form_options_flatten($elements['#options']);
3128
        }
3129
        else {
3130
          $options = $elements['#options'];
3131
        }
3132
        if (is_array($elements['#value'])) {
3133
          $value = $elements['#type'] == 'checkboxes' ? array_keys(array_filter($elements['#value'])) : $elements['#value'];
3134
          foreach ($value as $v) {
3135
            if (!isset($options[$v])) {
3136
              form_error($elements, t('An illegal choice has been detected. Please contact the site administrator.'));
3137
              watchdog('form', 'Illegal choice %choice in !name element.', array('%choice' => $v, '!name' => empty($elements['#title']) ? $elements['#parents'][0] : $elements['#title']), WATCHDOG_ERROR);
3138
            }
3139
          }
3140
        }
3141
        elseif ($elements['#value'] !== '' && !isset($options[$elements['#value']])) {
3142
          form_error($elements, t('An illegal choice has been detected. Please contact the site administrator.'));
3143
          watchdog('form', 'Illegal choice %choice in %name element.', array('%choice' => $elements['#value'], '%name' => empty($elements['#title']) ? $elements['#parents'][0] : $elements['#title']), WATCHDOG_ERROR);
3144
        }
3145
      }
3146
    }
3147

    
3148
    // Call user-defined form level validators.
3149
    if (isset($form_id)) {
3150
      form_execute_handlers('validate', $elements, $form_state);
3151
    }
3152
    // Call any element-specific validators. These must act on the element
3153
    // #value data.
3154
    elseif (isset($elements['#element_validate'])) {
3155
      foreach ($elements['#element_validate'] as $function) {
3156
        if (is_callable($function)) {
3157
          $function($elements, $form_state, $form_state['complete form']);
3158
        }
3159
      }
3160
    }
3161
    $elements['#webform_validated'] = TRUE;
3162
  }
3163
}
3164

    
3165
/**
3166
 * Saves submissions that fail validation as drafts.
3167
 *
3168
 * When a user attempts to submit an unfinished form and auto-save is allowed,
3169
 * automatically save the form as a draft to allow the user to complete the
3170
 * form later. This prevents the common failure of a user trying to submit a
3171
 * form and not noticing validation errors. The user then leaves the page
3172
 * without realizing that the form hasn't been submitted.
3173
 *
3174
 * THEORY OF OPERATION:
3175
 * The Drupal 7 Form API lacks an easy way to rebuild the form in the event of
3176
 * validation errors. The operations is thus:
3177
 *
3178
 * 1) The form is first displayed. If it is an existing draft,
3179
 *    webform_client_form will generated a form to edit the draft submission.
3180
 *    Otherwise it creates a form for a new, empty submission. As usual.
3181
 * 2) The submit button is pressed. The form is retrieved from cache or is
3182
 *    recreated by webform_client_form. The values from the $_POST are merged in
3183
 *    and the validation routines are called. As usual.
3184
 * 3) The postvalidation routine, below, detects that validation errors should
3185
 *    be autosaved and calls the submit handlers on a copy of the form and
3186
 *    form_state. This creates the submission, or saves to the existing
3187
 *    submission. The original form and form_state are not modified (yet).
3188
 * 4) If a new submission was created, the form and form_state are updated with
3189
 *    the newly-created sid of the submission, which is returned to the
3190
 *    browser in the hidden field [details][sid]. The form is set to not be
3191
 *    cached, and any existing cached copy is cleared to force step 5. The form
3192
 *    is presented with validation errors as usual.
3193
 * 5) When the form is submitted again, the form must be rebuilt because it is
3194
 *    not in the cache. The existing draft detection in _webform_fetch_draft_sid
3195
 *    detects that a webform draft is being submitted, and uses its sid in
3196
 *    preference to any other stored draft sid in the database. In the event
3197
 *    that multiple drafts are being implemented by another module, this ensures
3198
 *    that the correct draft is edited.
3199
 * 6) Repeat from step 2 until the form is abandoned (leaving the draft) or
3200
 *    successfully submitted.
3201
 */
3202
function webform_client_form_postvalidate(&$form, &$form_state) {
3203
  $errors = form_get_errors();
3204
  $nid = $form_state['values']['details']['nid'];
3205
  $node = node_load($nid);
3206
  if (user_is_logged_in() &&
3207
      $errors && !array_key_exists('', $errors) &&
3208
      $node->webform['auto_save'] &&
3209
      !$form_state['values']['details']['finished'] &&
3210
      !empty($form_state['values']['op'])) {
3211
    // Validation errors are present, prevalidation succeeded (for example
3212
    // submission limits are ok), auto-save is enabled, this form isn't finished
3213
    // (this is, is or soon will be a draft) and a button was pushed (not ajax).
3214
    //
3215
    // Process submission on a copy of the form and form_state to prevent the
3216
    // submission handlers from making unintended changes. Use a button that
3217
    // isn't Save Draft, Next Page, Submit, etc to avoid triggering any
3218
    // unwanted side effects.
3219
    $submit_form = $form;
3220
    $submit_form_state = $form_state;
3221
    $submit_form_state['values']['op'] = '__AUTOSAVE__';
3222
    form_execute_handlers('submit', $submit_form, $submit_form_state);
3223
    $sid = $submit_form_state['values']['details']['sid'];
3224
    if ($sid != $form_state['values']['details']['sid']) {
3225
      // A new submission was created. Update the form and form_state as if it
3226
      // has been submitted with the new sid. This causes the Form API to
3227
      // render the form with new sid.
3228
      $form_state['values']['details']['sid'] = $sid;
3229
      $form_state['input']['details']['sid'] = $sid;
3230
      $form['details']['sid']['#value'] = $sid;
3231

    
3232
      // Prevent the form from being cached, forcing it to be rebuilt from the
3233
      // form definition function, which will honor the new sid.
3234
      $form_state['no_cache'] = TRUE;
3235
      if (!empty($form_state['values']['form_build_id'])) {
3236
        cache_clear_all('form_' . $form_state['values']['form_build_id'], 'cache_form');
3237
        cache_clear_all('form_state_' . $form_state['values']['form_build_id'], 'cache_form');
3238
      }
3239
    }
3240
  }
3241
}
3242

    
3243
/**
3244
 * Handle the processing of pages and conditional logic.
3245
 */
3246
function webform_client_form_pages($form, &$form_state) {
3247
  $node = node_load($form_state['values']['details']['nid']);
3248

    
3249
  // Multistep forms may not have any components on the first page.
3250
  if (!isset($form_state['values']['submitted'])) {
3251
    $form_state['values']['submitted'] = array();
3252
  }
3253

    
3254
  // Move special settings to storage.
3255
  if (isset($form_state['webform']['component_tree'])) {
3256
    $form_state['storage']['component_tree'] = $form_state['webform']['component_tree'];
3257
    $form_state['storage']['page_count'] = $form_state['webform']['page_count'];
3258
    $form_state['storage']['page_num'] = $form_state['webform']['page_num'];
3259
    $form_state['storage']['preview'] = $form_state['webform']['preview'];
3260
  }
3261

    
3262
  // Flatten trees within the submission.
3263
  $form_state['values']['submitted'] = _webform_client_form_submit_flatten($node, $form_state['values']['submitted']);
3264

    
3265
  // Perform post processing by components.
3266
  _webform_client_form_submit_process($node, $form_state['values']['submitted']);
3267

    
3268
  // Assume the form is completed unless the page logic says otherwise.
3269
  $form_state['webform_completed'] = TRUE;
3270

    
3271
  // Merge any stored submission data for multistep forms.
3272
  $original_values = is_array($form_state['values']['submitted']) ? $form_state['values']['submitted'] : array();
3273
  if (isset($form_state['storage']['submitted'])) {
3274
    // Array + operator keeps all elements of left operand and discards any duplicate elements in right operand.
3275
    $original_values += $form_state['storage']['submitted'];
3276
  }
3277

    
3278
  // Execute conditionals on submission values.
3279
  $form_state['values']['submitted'] = webform_get_conditional_sorter($node)->executeConditionals($original_values);
3280

    
3281
  // Check for a multi-page form that is not yet complete.
3282
  $submit_op = !empty($form['actions']['submit']['#value']) ? $form['actions']['submit']['#value'] : t('Submit');
3283
  $draft_op = !empty($form['actions']['draft']['#value']) ? $form['actions']['draft']['#value'] : t('Save Draft');
3284
  if (!in_array($form_state['values']['op'], array($submit_op, $draft_op, '__AUTOSAVE__'))) {
3285

    
3286
    // Store values from the current page in the form state storage.
3287
    $form_state['storage']['submitted'] = $form_state['values']['submitted'];
3288

    
3289
    // Set the page number.
3290
    if (!isset($form_state['storage']['page_num'])) {
3291
      $form_state['storage']['page_num'] = 1;
3292
    }
3293
    if (end($form_state['clicked_button']['#parents']) == 'next') {
3294
      $forward = 1;
3295
    }
3296
    elseif (end($form_state['clicked_button']['#parents']) == 'previous') {
3297
      $forward = -1;
3298
    }
3299
    $current_page = $form_state['storage']['page_num'];
3300

    
3301
    if (isset($forward)) {
3302
      // Find the:
3303
      // 1. previous/next non-empty page, or
3304
      // 2. the preview page, or
3305
      // 3. the preview page, forcing its display if the form would unexpectedly
3306
      //    submit, or
3307
      // 4. page 1 even if empty, if no other previous page would be shown.
3308
      $preview_page_num = $form_state['storage']['page_count'] + (int) !$form_state['webform']['preview'];
3309
      $page_num = $current_page;
3310
      do {
3311
        $page_num += $forward;
3312
      } while (!webform_get_conditional_sorter($node)->pageVisibility($page_num));
3313
      if (!$form_state['webform']['preview'] && $page_num == $preview_page_num) {
3314
        // Force a preview to avert an unintended submission via Next.
3315
        $form_state['webform']['preview'] = TRUE;
3316
        $form_state['storage']['preview'] = TRUE;
3317
        $form_state['storage']['page_count']++;
3318
      }
3319
      $form_state['storage']['page_num'] = $page_num;
3320
    }
3321

    
3322
    // The form is done if the page number is greater than the page count.
3323
    $form_state['webform_completed'] = $form_state['storage']['page_num'] > $form_state['storage']['page_count'];
3324
  }
3325

    
3326
  // Inform the submit handlers that a draft will be saved.
3327
  $form_state['save_draft'] = in_array($form_state['values']['op'], array($draft_op, '__AUTOSAVE__')) || ($node->webform['auto_save'] && !$form_state['values']['details']['finished'] && !$form_state['webform_completed'] && user_is_logged_in());
3328

    
3329
  // Determine what we need to do on the next page.
3330
  if (!empty($form_state['save_draft']) || !$form_state['webform_completed']) {
3331
    // Rebuild the form and display the current (on drafts) or next page.
3332
    $form_state['rebuild'] = TRUE;
3333
  }
3334
  else {
3335
    // Remove the form state storage now that we're done with the pages.
3336
    $form_state['rebuild'] = FALSE;
3337
    unset($form_state['storage']);
3338
  }
3339
}
3340

    
3341
/**
3342
 * Submit handler for saving the form values and sending e-mails.
3343
 */
3344
function webform_client_form_submit($form, &$form_state) {
3345
  module_load_include('inc', 'webform', 'includes/webform.submissions');
3346
  module_load_include('inc', 'webform', 'includes/webform.components');
3347
  global $user;
3348

    
3349
  if (empty($form_state['save_draft']) && empty($form_state['webform_completed'])) {
3350
    return;
3351
  }
3352

    
3353
  $node = $form['#node'];
3354
  $sid = $form_state['values']['details']['sid'] ? (int) $form_state['values']['details']['sid'] : NULL;
3355

    
3356
  // Check if user is submitting as a draft.
3357
  $is_draft = (int) !empty($form_state['save_draft']);
3358

    
3359
  // To maintain time and user information, load the existing submission.
3360
  // If a draft is deleted while a user is working on completing it, $sid will
3361
  // exist, but webform_get_submission() will not find the draft. So, make a new
3362
  // submission.
3363
  if ($sid && $submission = webform_get_submission($node->webform['nid'], $sid)) {
3364
    // Store original data on object for use in update hook.
3365
    $submission->original = clone $submission;
3366

    
3367
    // Merge with new submission data. The + operator maintains numeric keys.
3368
    // This maintains existing data with just-submitted data when a user resumes
3369
    // a submission previously saved as a draft. Remove any existing data on
3370
    // this and previous pages. If components are hidden, they may be in the
3371
    // $submission->data but absent entirely from $new_data.
3372
    $page_map = webform_get_conditional_sorter($node)->getPageMap();
3373
    for ($page_nr = 1; $page_nr <= $form_state['webform']['page_num']; $page_nr++) {
3374
      $submission->data = array_diff_key($submission->data, $page_map[$page_nr]);
3375
    }
3376
    $submission->data = webform_submission_data($node, $form_state['values']['submitted']) + $submission->data;
3377
  }
3378
  else {
3379
    // Create a new submission object.
3380
    $submission = webform_submission_create($node, $user, $form_state);
3381
    // Since this is a new submission, a new sid is needed.
3382
    $sid = NULL;
3383
  }
3384

    
3385
  // Save draft state, and for drafts, save the current page (if clicking next)
3386
  // or the previous page (if not) as the last valid page.
3387
  $submission->is_draft = $is_draft;
3388
  $submission->highest_valid_page = 0;
3389
  if ($is_draft) {
3390
    $submission->highest_valid_page = end($form_state['clicked_button']['#parents']) == 'next' && $form_state['values']['op'] != '__AUTOSAVE__'
3391
      ? $form_state['webform']['page_num']
3392
      : $form_state['webform']['page_num'] - 1;
3393
  }
3394

    
3395
  // If there is no data to be saved (such as on a multipage form with no fields
3396
  // on the first page), process no further. Submissions with no data cannot
3397
  // be loaded from the database as efficiently, so we don't save them at all.
3398
  if (empty($submission->data)) {
3399
    return;
3400
  }
3401

    
3402
  // Save the submission to the database.
3403
  if (!$sid) {
3404
    // No sid was found thus insert it in the dataabase.
3405
    $form_state['values']['details']['sid'] = $sid = webform_submission_insert($node, $submission);
3406
    $form_state['values']['details']['is_new'] = TRUE;
3407

    
3408
    // Save the new details in storage. When ajax calls for file upload/remove,
3409
    // $form_state['values']['details'] is missing. This allows the proper
3410
    // submission to be retrieved in webform_client_form. See #2562703.
3411
    $form_state['storage']['details'] = $form_state['values']['details'];
3412

    
3413
    // Set a cookie including the server's submission time. The cookie expires
3414
    // in the length of the interval plus a day to compensate for timezones.
3415
    $tracking_mode = webform_variable_get('webform_tracking_mode');
3416
    if ($tracking_mode === 'cookie' || $tracking_mode === 'strict') {
3417
      $cookie_name = 'webform-' . $node->nid;
3418
      $time = REQUEST_TIME;
3419
      $params = session_get_cookie_params();
3420
      setcookie($cookie_name . '[' . $time . ']', $time, $time + $node->webform['submit_interval'] + 86400, $params['path'], $params['domain'], $params['secure'], $params['httponly']);
3421
    }
3422

    
3423
    // Save session information about this submission for anonymous users,
3424
    // allowing them to access or edit their submissions.
3425
    if (!$user->uid && user_access('access own webform submissions')) {
3426
      $_SESSION['webform_submission'][$sid] = $node->nid;
3427
    }
3428
  }
3429
  else {
3430
    // Sid was found thus update the existing sid in the database.
3431
    webform_submission_update($node, $submission);
3432
    $form_state['values']['details']['is_new'] = FALSE;
3433
  }
3434

    
3435
  // Check if this form is sending an email.
3436
  if (!$is_draft && !$form_state['values']['details']['finished']) {
3437
    drupal_static_reset('webform_get_submission');
3438
    $submission = webform_get_submission($node->webform['nid'], $sid);
3439
    webform_submission_send_mail($node, $submission);
3440
  }
3441

    
3442
  // Strip out empty tags added by WYSIWYG editors if needed.
3443
  $message = strlen(trim(strip_tags($node->webform['confirmation']))) > 0;
3444

    
3445
  // Check confirmation and redirect_url fields.
3446
  $redirect = NULL;
3447
  $redirect_url = trim($node->webform['redirect_url']);
3448
  if (isset($form['actions']['draft']['#value']) && $form_state['values']['op'] == $form['actions']['draft']['#value']) {
3449
    $message = t('Submission saved. You may return to this form later and it will restore the current values.');
3450
  }
3451
  elseif ($is_draft) {
3452
    // No redirect needed. No confirmation message needed.
3453
    $message = FALSE;
3454
  }
3455
  elseif (!empty($form_state['values']['details']['finished'])) {
3456
    $message = t('Submission updated.');
3457
    $redirect = "node/{$node->nid}/submission/$sid";
3458
  }
3459
  elseif (!empty($node->webform['confirmation_block'])) {
3460
    $message = FALSE;
3461
    // Webform was submitted in a block and the confirmation message is to be
3462
    // displayed in the block.
3463
    $_SESSION['webform_confirmation'][$node->nid] = array(
3464
      'sid' => $sid,
3465
      'confirmation_page' => $redirect_url == '<confirmation>',
3466
    );
3467
    drupal_page_is_cacheable(FALSE);
3468
  }
3469
  elseif ($redirect_url == '<none>') {
3470
    // No redirect needed. Show a confirmatin message if there is one.
3471
  }
3472
  elseif ($redirect_url == '<confirmation>') {
3473
    // No confirmation message needed because it will be shown on the
3474
    // confirmation page.
3475
    $message = FALSE;
3476
    $query = array('sid' => $sid);
3477
    if ((int) $user->uid === 0) {
3478
      $query['token'] = webform_get_submission_access_token($submission);
3479
    }
3480
    $redirect = array('node/' . $node->nid . '/done', array('query' => $query));
3481
  }
3482
  else {
3483
    // Clean up the redirect URL, filter it for tokens and detect external
3484
    // domains. If the redirect is to an external URL, then don't show the
3485
    // confirmation message.
3486
    $redirect = webform_replace_url_tokens($redirect_url, $node, $submission);
3487
    if ($redirect[1]['#webform_external']) {
3488
      $message = FALSE;
3489
    }
3490
  }
3491

    
3492
  // Show a message if manually set.
3493
  if (is_string($message)) {
3494
    drupal_set_message($message);
3495
  }
3496
  // If redirecting and we have a confirmation message, show it as a message.
3497
  elseif ($message) {
3498
    drupal_set_message(webform_replace_tokens($node->webform['confirmation'], $node, $submission, NULL, $node->webform['confirmation_format']));
3499
  }
3500

    
3501
  $form_state['redirect'] = $redirect;
3502
}
3503

    
3504
/**
3505
 * Post processes the submission tree with any updates from components.
3506
 *
3507
 * @param object $node
3508
 *   The full webform node.
3509
 * @param $form_values
3510
 *   The form values for the form.
3511
 * @param $types
3512
 *   Optional. Specific types to perform processing.
3513
 * @param $parent
3514
 *   Internal use. The current parent CID whose children are being processed.
3515
 */
3516
function _webform_client_form_submit_process($node, &$form_values) {
3517
  foreach ($form_values as $cid => $value) {
3518
    if (isset($node->webform['components'][$cid])) {
3519
      // Call the component process submission function.
3520
      $component = $node->webform['components'][$cid];
3521
      if ((!isset($types) || in_array($component['type'], $types)) && webform_component_implements($component['type'], 'submit')) {
3522
        $form_values[$cid] = webform_component_invoke($component['type'], 'submit', $component, $form_values[$cid]);
3523
      }
3524
    }
3525
  }
3526
}
3527

    
3528
/**
3529
 * Flattens a submitted values back into a single flat array representation.
3530
 */
3531
function _webform_client_form_submit_flatten($node, $fieldset, $parent = 0) {
3532
  $values = array();
3533

    
3534
  if (is_array($fieldset)) {
3535
    foreach ($fieldset as $form_key => $value) {
3536
      if ($cid = webform_get_cid($node, $form_key, $parent)) {
3537
        if (is_array($value) && webform_component_feature($node->webform['components'][$cid]['type'], 'group')) {
3538
          $values += _webform_client_form_submit_flatten($node, $value, $cid);
3539
        }
3540
        else {
3541
          $values[$cid] = $value;
3542
        }
3543
      }
3544
      else {
3545
        // This $form_key must belong to the parent. For example, a grid.
3546
        $values[$parent][$form_key] = $value;
3547
      }
3548
    }
3549
  }
3550

    
3551
  return $values;
3552
}
3553

    
3554
/**
3555
 * Prints the confirmation message after a successful submission.
3556
 */
3557
function _webform_confirmation($node) {
3558
  drupal_set_title($node->title);
3559
  webform_set_breadcrumb($node, TRUE);
3560
  $sid = isset($_GET['sid']) ? $_GET['sid'] : NULL;
3561
  return theme(array('webform_confirmation_' . $node->nid, 'webform_confirmation'), array('node' => $node, 'sid' => $sid));
3562
}
3563

    
3564
/**
3565
 * Prepare for theming of the webform form.
3566
 */
3567
function template_preprocess_webform_form(&$vars) {
3568
  if (isset($vars['form']['details']['nid']['#value'])) {
3569
    $vars['nid'] = $vars['form']['details']['nid']['#value'];
3570
  }
3571
  elseif (isset($vars['form']['submission']['#value'])) {
3572
    $vars['nid'] = $vars['form']['submission']['#value']->nid;
3573
  }
3574

    
3575
  if (!empty($vars['form']['#node']->webform['conditionals']) && empty($vars['form']['preview'])) {
3576
    module_load_include('inc', 'webform', 'includes/webform.conditionals');
3577
    $submission_data = isset($vars['form']['#conditional_values']) ? $vars['form']['#conditional_values'] : array();
3578
    $settings = webform_conditional_prepare_javascript($vars['form']['#node'],
3579
      $submission_data,
3580
      $vars['form']['details']['page_num']['#value']);
3581
    drupal_add_js(array('webform' => array('conditionals' => array('webform-client-form-' . $vars['nid'] => $settings))), 'setting');
3582
  }
3583
}
3584

    
3585
/**
3586
 * Prepare for theming of the webform submission confirmation.
3587
 */
3588
function template_preprocess_webform_confirmation(&$vars) {
3589
  $node = $vars['node'];
3590
  // Strip out empty tags added by WYSIWYG editors if needed.
3591
  $confirmation = $node->webform['confirmation'];
3592
  $confirmation = strlen(trim(strip_tags($confirmation))) ? $confirmation : '';
3593

    
3594
  // Replace tokens.
3595
  module_load_include('inc', 'webform', 'includes/webform.submissions');
3596
  $submission = webform_get_submission($node->nid, $vars['sid']);
3597
  $vars['confirmation_message'] = webform_replace_tokens($confirmation, $node, $submission, NULL, $node->webform['confirmation_format']);
3598

    
3599
  // URL back to form (or same page for in-block confirmations).
3600
  $vars['url'] = empty($node->webform_block)
3601
                    ? url('node/' . $node->nid)
3602
                    : url(current_path(), array('query' => drupal_get_query_parameters()));
3603

    
3604
  // Progress bar.
3605
  $vars['progressbar'] = '';
3606
  if ($node->webform['progressbar_include_confirmation']) {
3607
    $page_labels = webform_page_labels($node);
3608
    $page_count = count($page_labels);
3609
    $vars['progressbar'] = theme('webform_progressbar', array(
3610
      'node' => $node,
3611
      'page_num' => $page_count,
3612
      'page_count' => $page_count,
3613
      'page_labels' => $page_labels,
3614
    ));
3615
  }
3616
}
3617

    
3618
/**
3619
 * Prepare for theming of the webform progressbar.
3620
 */
3621
function template_preprocess_webform_progressbar(&$vars) {
3622
  // Add CSS used by the progress bar.
3623
  drupal_add_css(drupal_get_path('module', 'webform') . '/css/webform.css');
3624

    
3625
  $vars['progressbar_page_number'] = $vars['node']->webform['progressbar_page_number'];
3626
  $vars['progressbar_percent'] = $vars['node']->webform['progressbar_percent'];
3627
  $vars['progressbar_bar'] = $vars['node']->webform['progressbar_bar'];
3628
  $vars['progressbar_pagebreak_labels'] = $vars['node']->webform['progressbar_pagebreak_labels'];
3629
  $vars['progressbar_include_confirmation'] = $vars['node']->webform['progressbar_include_confirmation'];
3630
  $vars['percent'] = ($vars['page_num'] - 1) / ($vars['page_count'] - 1) * 100;
3631
}
3632

    
3633
/**
3634
 * Prepare to theme the contents of e-mails sent by webform.
3635
 */
3636
function template_preprocess_webform_mail_message(&$vars) {
3637
  global $user;
3638

    
3639
  $vars['user'] = $user;
3640
  $vars['ip_address'] = webform_ip_address($vars['node']);
3641
}
3642

    
3643
/**
3644
 * A Form API #pre_render function. Sets display based on #title_display.
3645
 *
3646
 * This function is used regularly in D6 for all elements, but specifically for
3647
 * fieldsets in D7, which don't support #title_display natively.
3648
 */
3649
function webform_element_title_display($element) {
3650
  if (isset($element['#title_display']) && strcmp($element['#title_display'], 'none') === 0) {
3651
    $element['#title'] = NULL;
3652
  }
3653
  return $element;
3654
}
3655

    
3656
/**
3657
 * A Form API #pre_render function that removes the ID from an element.
3658
 *
3659
 * Drupal forcibly adds IDs to all form elements, including those that do not
3660
 * need them for any reason, such as the actions wrapper or submit buttons. We
3661
 * use this process function wherever we wish to remove an ID from an element.
3662
 * Because #states and #ajax require IDs, they are only removed if the states
3663
 * and ajax arrays are empty.
3664
 */
3665
function webform_pre_render_remove_id($element) {
3666
  if (empty($element['#states']) && empty($element['#ajax'])) {
3667
    $element['#id'] = NULL;
3668
    // Removing array parents is required to prevent theme_container from adding
3669
    // an empty ID attribute.
3670
    $element['#array_parents'] = NULL;
3671
  }
3672
  return $element;
3673
}
3674

    
3675
/**
3676
 * Implements template_preprocess_THEME_HOOK().
3677
 */
3678
function template_preprocess_webform_element(&$variables) {
3679
  $element = &$variables['element'];
3680

    
3681
  // Ensure defaults.
3682
  $element += array(
3683
    '#title_display' => 'before',
3684
    '#wrapper_attributes' => array(),
3685
  );
3686
  $element['#wrapper_attributes'] += array(
3687
    'class' => array(),
3688
  );
3689

    
3690
  // All elements using this for display only are given the "display" type.
3691
  if (isset($element['#format']) && $element['#format'] == 'html') {
3692
    $type = 'display';
3693
  }
3694
  else {
3695
    $type = ($element['#webform_component']['type'] == 'select' && isset($element['#type'])) ? $element['#type'] : $element['#webform_component']['type'];
3696
  }
3697

    
3698
  // Convert the parents array into a string, excluding the "submitted" wrapper.
3699
  $nested_level = $element['#parents'][0] == 'submitted' ? 1 : 0;
3700
  $parents = str_replace('_', '-', implode('--', array_slice($element['#parents'], $nested_level)));
3701

    
3702
  // Build up a list of classes to apply on the element wrapper.
3703
  $wrapper_classes = array(
3704
    'form-item',
3705
    'webform-component',
3706
    'webform-component-' . str_replace('_', '-', $type),
3707
    'webform-component--' . $parents,
3708
  );
3709
  if (isset($element['#title_display']) && strcmp($element['#title_display'], 'inline') === 0) {
3710
    $wrapper_classes[] = 'webform-container-inline';
3711
  }
3712
  $element['#wrapper_attributes']['class'] = array_merge($element['#wrapper_attributes']['class'], $wrapper_classes);
3713

    
3714
  // If #title_display is none, set it to invisible instead - none only used if
3715
  // we have no title at all to use.
3716
  if ($element['#title_display'] == 'none') {
3717
    $element['#title_display'] = 'invisible';
3718
    if (empty($element['#attributes']['title']) && !empty($element['#title'])) {
3719
      $element['#attributes']['title'] = $element['#title'];
3720
    }
3721
  }
3722

    
3723
  // If #title is not set, we don't display any label or required marker.
3724
  if (!isset($element['#title'])) {
3725
    $element['#title_display'] = 'none';
3726
  }
3727

    
3728
  // If an internal title is being used, generate no external title.
3729
  if ($element['#title_display'] == 'internal') {
3730
    $element['#title_display'] = 'none';
3731
  }
3732
}
3733

    
3734
/**
3735
 * Replacement for theme_form_element().
3736
 */
3737
function theme_webform_element($variables) {
3738
  $element = $variables['element'];
3739

    
3740
  $output = '<div ' . drupal_attributes($element['#wrapper_attributes']) . '>' . "\n";
3741
  $prefix = isset($element['#field_prefix']) ? '<span class="field-prefix">' . webform_filter_xss($element['#field_prefix']) . '</span> ' : '';
3742
  $suffix = isset($element['#field_suffix']) ? ' <span class="field-suffix">' . webform_filter_xss($element['#field_suffix']) . '</span>' : '';
3743

    
3744
  // Generate description for above or below the field.
3745
  $above = !empty($element['#webform_component']['extra']['description_above']);
3746
  $description = array(
3747
    FALSE => '',
3748
    TRUE => !empty($element['#description']) ? ' <div class="description">' . $element['#description'] . "</div>\n" : '',
3749
  );
3750

    
3751
  // If #children does not contain an element with a matching @id, do not
3752
  // include @for in the label.
3753
  if (isset($variables['element']['#id']) && strpos($element['#children'], ' id="' . $variables['element']['#id'] . '"') === FALSE) {
3754
    $variables['element']['#id'] = NULL;
3755
  }
3756

    
3757
  switch ($element['#title_display']) {
3758
    case 'inline':
3759
      $output .= $description[$above];
3760
      $description[$above] = '';
3761
    case 'before':
3762
    case 'invisible':
3763
    case 'after':
3764
      $title = ' ' . theme('form_element_label', $variables);
3765
      break;
3766
  }
3767

    
3768
  $children = ' ' . $description[$above] . $prefix . $element['#children'] . $suffix;
3769
  switch ($element['#title_display']) {
3770
    case 'inline':
3771
    case 'before':
3772
    case 'invisible':
3773
      $output .= $title;
3774
      $output .= $children;
3775
      break;
3776

    
3777
    case 'after':
3778
      $output .= $children;
3779
      $output .= $title;
3780
      break;
3781

    
3782
    case 'none':
3783
    case 'attribute':
3784
      // Output no label and no required marker, only the children.
3785
      $output .= $children;
3786
      break;
3787
  }
3788
  $output .= "\n";
3789

    
3790
  $output .= $description[!$above];
3791
  $output .= "</div>\n";
3792

    
3793
  return $output;
3794
}
3795

    
3796
/**
3797
 * Output a form element in plain text format.
3798
 */
3799
function theme_webform_element_text($variables) {
3800
  $element = $variables['element'];
3801
  $value = $variables['element']['#children'];
3802

    
3803
  $output = '';
3804
  $is_group = webform_component_feature($element['#webform_component']['type'], 'group');
3805

    
3806
  // Output the element title.
3807
  if (isset($element['#title'])) {
3808
    if ($is_group) {
3809
      $output .= '==' . $element['#title'] . '==';
3810
    }
3811
    elseif (!in_array(drupal_substr($element['#title'], -1), array('?', ':', '!', '%', ';', '@'))) {
3812
      $output .= $element['#title'] . ':';
3813
    }
3814
    else {
3815
      $output .= $element['#title'];
3816
    }
3817
  }
3818

    
3819
  // Wrap long values at 65 characters, allowing for a few fieldset indents.
3820
  // It's common courtesy to wrap at 75 characters in e-mails.
3821
  if ($is_group && drupal_strlen($value) > 65) {
3822
    $value = wordwrap($value, 65, "\n");
3823
    $lines = explode("\n", $value);
3824
    foreach ($lines as $key => $line) {
3825
      $lines[$key] = '  ' . $line;
3826
    }
3827
    $value = implode("\n", $lines);
3828
  }
3829

    
3830
  // Add the value to the output. Add a newline before the response if needed.
3831
  $output .= (strpos($value, "\n") === FALSE ? ' ' : "\n") . $value;
3832

    
3833
  // Indent fieldsets.
3834
  if ($is_group) {
3835
    $lines = explode("\n", $output);
3836
    foreach ($lines as $number => $line) {
3837
      if (strlen($line)) {
3838
        $lines[$number] = '  ' . $line;
3839
      }
3840
    }
3841
    $output = implode("\n", $lines);
3842
    $output .= "\n";
3843
  }
3844

    
3845
  if ($output) {
3846
    $output .= "\n";
3847
  }
3848

    
3849
  return $output;
3850
}
3851

    
3852
/**
3853
 * Theme a radio button and another element together.
3854
 *
3855
 * This is used in the e-mail configuration to show a radio button and a text
3856
 * field or select list on the same line.
3857
 */
3858
function theme_webform_inline_radio($variables) {
3859
  $element = $variables['element'];
3860

    
3861
  // Add element's #type and #name as class to aid with JS/CSS selectors.
3862
  $class = array('form-item');
3863
  if (!empty($element['#type'])) {
3864
    $class[] = 'form-type-' . strtr($element['#type'], '_', '-');
3865
  }
3866
  if (!empty($element['#name'])) {
3867
    $class[] = 'form-item-' . strtr($element['#name'], array(' ' => '-', '_' => '-', '[' => '-', ']' => ''));
3868
  }
3869

    
3870
  // Add container-inline to all elements.
3871
  $class[] = 'webform-container-inline';
3872
  if (isset($element['#inline_element']) && isset($variables['element']['#title'])) {
3873
    $variables['element']['#title'] .= ': ';
3874
  }
3875

    
3876
  $output = '<div class="' . implode(' ', $class) . '">' . "\n";
3877
  $output .= ' ' . $element['#children'];
3878
  if (!empty($element['#title'])) {
3879
    $output .= ' ' . theme('webform_inline_radio_label', $variables) . "\n";
3880
  }
3881

    
3882
  if (!empty($element['#description'])) {
3883
    $output .= ' <div class="description">' . $element['#description'] . "</div>\n";
3884
  }
3885

    
3886
  $output .= "</div>\n";
3887

    
3888
  return $output;
3889
}
3890

    
3891
/**
3892
 * Replacement for theme_form_element_label()
3893
 *
3894
 * This varies from theme_element_label in that it allows inline fields such
3895
 * as select and input tags within the label itself.
3896
 */
3897
function theme_webform_inline_radio_label($variables) {
3898
  $element = $variables['element'];
3899
  // This is also used in the installer, pre-database setup.
3900
  $t = get_t();
3901

    
3902
  // If title and required marker are both empty, output no label.
3903
  if ((!isset($element['#title']) || $element['#title'] === '') && empty($element['#required'])) {
3904
    return '';
3905
  }
3906

    
3907
  // If the element is required, a required marker is appended to the label.
3908
  $required = !empty($element['#required']) ? theme('form_required_marker', array('element' => $element)) : '';
3909

    
3910
  // theme_element_label() does a filter_xss() here, we skip it because we know
3911
  // every use where this theme function is used and we need to allow input and
3912
  // select elements.
3913
  $title = $element['#title'];
3914

    
3915
  $attributes = isset($element['#attributes']) ? $element['#attributes'] : array();
3916

    
3917
  // Style the label as class option to display inline with the element.
3918
  if ($element['#title_display'] == 'after') {
3919
    $attributes['class'][] = 'option';
3920
  }
3921
  // Show label only to screen readers to avoid disruption in visual flows.
3922
  elseif ($element['#title_display'] == 'invisible') {
3923
    $attributes['class'][] = 'element-invisible';
3924
  }
3925

    
3926
  $attributes['class'][] = 'webform-inline-radio';
3927
  if (!empty($element['#id'])) {
3928
    $attributes['for'] = $element['#id'];
3929
  }
3930

    
3931
  // The leading whitespace helps visually separate fields from inline labels.
3932
  return ' <label' . drupal_attributes($attributes) . '>' . $t('!title !required', array('!title' => $title, '!required' => $required)) . "</label>\n";
3933
}
3934

    
3935
/**
3936
 * Theme the headers when sending an email from webform.
3937
 *
3938
 * @param array $variables
3939
 *   The variables array.
3940
 *
3941
 * @return array
3942
 *   An array of headers to be used when sending a webform email. If headers
3943
 *   for "From", "To", or "Subject" are set, they will take precedence over
3944
 *   the values set in the webform configuration.
3945
 */
3946
function theme_webform_mail_headers(array $variables) {
3947
  $headers = array(
3948
    'X-Mailer' => 'Drupal Webform' . (ini_get('expose_php') ? ' (PHP/' . phpversion() . ')' : ''),
3949
  );
3950
  return $headers;
3951
}
3952

    
3953
/**
3954
 * Check if current user has a draft of this webform, and return the sid.
3955
 */
3956
function _webform_fetch_draft_sid($nid, $uid) {
3957
  // Detect whether a webform draft is being edited. If so, that is the one that
3958
  // should be returned.
3959
  if (isset($_POST['form_id']) && stripos($_POST['form_id'], 'webform_client_form_') === 0 &&
3960
      !empty($_POST['details']['sid']) && empty($_POST['details']['finished'])) {
3961
    // A draft is already being edited.
3962
    $sid = $_POST['details']['sid'];
3963
  }
3964
  else {
3965
    $sid = db_select('webform_submissions')
3966
      ->fields('webform_submissions', array('sid'))
3967
      ->condition('nid', $nid)
3968
      ->condition('uid', $uid)
3969
      ->condition('is_draft', 1)
3970
      ->orderBy('submitted', 'DESC')
3971
      ->execute()
3972
      ->fetchField();
3973

    
3974
    if ($sid) {
3975
      $context = array(
3976
        'nid' => $nid,
3977
        'uid' => $uid,
3978
      );
3979
      drupal_alter('webform_draft', $sid, $context);
3980
    }
3981
  }
3982
  return $sid;
3983
}
3984

    
3985
/**
3986
 * Returns a new or cached WebformConditionals object for the specified node.
3987
 *
3988
 * @param object $node
3989
 *   The loaded webform node.
3990
 *
3991
 * @returns object
3992
 *   Object of type WebformConditionals, possibly with the conditionals already
3993
 *   analyzed for dependencies.
3994
 */
3995
function webform_get_conditional_sorter($node) {
3996
  return WebformConditionals::factory($node);
3997
}
3998

    
3999
/**
4000
 * This function is deprecated! Use webform_replace_tokens() instead.
4001
 *
4002
 * @deprecated
4003
 */
4004
function _webform_filter_values($string, $node = NULL, $submission = NULL, $email = NULL, $strict = TRUE) {
4005
  $output = webform_replace_tokens($string, $node, $submission, $email, $strict);
4006
  return $strict ? webform_filter_xss($output) : $output;
4007
}
4008

    
4009
/**
4010
 * Replace tokens with Webform contexts populated.
4011
 *
4012
 * @param $string
4013
 *   The string to have its tokens replaced.
4014
 * @param object $node
4015
 *   If replacing node-level tokens, the node for which tokens will be created.
4016
 * @param $submission
4017
 *   If replacing submission-level tokens, the submission for which tokens will
4018
 *   be created.
4019
 * @param $email
4020
 *   If replacing tokens within the context of an e-mail, the Webform e-mail
4021
 *   settings array.
4022
 * @param $sanitize
4023
 *   Boolean or format name value indicating if the results will be displayed as
4024
 *   HTML output. If FALSE, the contents returned will be unsanitized. This will
4025
 *   also result in all Webform submission tokens being returned as plain-text,
4026
 *   without HTML markup, in preparation for e-mailing or other text-only
4027
 *   purposes (default values, etc.). If TRUE, the tokens only are sanitized by
4028
 *   token_replace. Otherwise $sanitize is the machine name of an import filter
4029
 *   to be used with check_markup().
4030
 */
4031
function webform_replace_tokens($string, $node = NULL, $submission = NULL, $email = NULL, $sanitize = FALSE) {
4032
  // Don't do any filtering if the string is empty.
4033
  if (!strlen(trim($string)) || !webform_variable_get('webform_token_access')) {
4034
    return $string;
4035
  }
4036

    
4037
  $token_data = array();
4038
  if ($node) {
4039
    $token_data['node'] = $node;
4040
  }
4041
  if ($submission) {
4042
    $token_data['webform-submission'] = $submission;
4043
  }
4044
  if ($email) {
4045
    $token_data['webform-email'] = $email;
4046
  }
4047
  $clear = is_bool($sanitize);
4048
  $string = token_replace($string, $token_data, array('clear' => $clear, 'sanitize' => $sanitize === TRUE));
4049
  if (!$clear) {
4050
    $string = webform_replace_tokens_clear(check_markup($string, $sanitize));
4051
  }
4052
  return $string;
4053
}
4054

    
4055
/**
4056
 * Removes tokens from string.
4057
 *
4058
 * Call this function in cases where you need to remove unreplaced tokens but
4059
 * can't call webform_replace_tokens() with the option $clear = TRUE.
4060
 *
4061
 * In some cases the function token_replace() in webform_replace_tokens() needs
4062
 * to be called with the option 'clear' => FALSE, to not remove input filters.
4063
 * For security reasons webform_replace_tokens() is called before
4064
 * check_markup(), where input filters get replaced. Tokens won't be replaced if
4065
 * there is no value provided. These tokens, that is, [current-page:query:*]
4066
 * needs to be removed to not show up in the output.
4067
 *
4068
 * Note: This function was previously named webform_clear_tokens, which
4069
 * conflicted with the webform_clear module, being called as hook_tokens.
4070
 *
4071
 * @param string $text
4072
 *   The text to have its tokens removed.
4073
 *
4074
 * @return mixed|string
4075
 *   Replace tokens with actual value.
4076
 *
4077
 * @see token_replace()
4078
 */
4079
function webform_replace_tokens_clear($text) {
4080
  if (empty($text) || !webform_variable_get('webform_token_access')) {
4081
    return $text;
4082
  }
4083

    
4084
  $text_tokens = token_scan($text);
4085
  if (empty($text_tokens)) {
4086
    return $text;
4087
  }
4088

    
4089
  $replacements = array();
4090
  foreach ($text_tokens as $type => $tokens) {
4091
    $replacements += array_fill_keys($tokens, '');
4092
  }
4093

    
4094
  $tokens = array_keys($replacements);
4095
  $values = array_values($replacements);
4096

    
4097
  return str_replace($tokens, $values, $text);
4098
}
4099

    
4100
/**
4101
 * Replace tokens within a URL, encoding the parts within the query string.
4102
 *
4103
 * @param string $redirect_url
4104
 *   The redirect URL, with everything other than tokens already URL encoded.
4105
 * @param object $node
4106
 *   If replacing node-level tokens, the node for which tokens will be created.
4107
 * @param $submission
4108
 *   If replacing submission-level tokens, the submission for which tokens will
4109
 *   be created.
4110
 *
4111
 * @return array
4112
 *   An array of path and url() options, suitable for a redirect or drupal_goto.
4113
 */
4114
function webform_replace_url_tokens($redirect_url, $node = NULL, $submission = NULL) {
4115
  // Parse the url into its components.
4116
  $parsed_redirect_url = drupal_parse_url($redirect_url);
4117
  // Replace tokens in each component.
4118
  $parsed_redirect_url['path'] = webform_replace_tokens($parsed_redirect_url['path'], $node, $submission);
4119
  if (!empty($parsed_redirect_url['query'])) {
4120
    foreach ($parsed_redirect_url['query'] as $key => $value) {
4121
      $parsed_redirect_url['query'][$key] = trim(webform_replace_tokens($value, $node, $submission));
4122
    }
4123
  }
4124
  $parsed_redirect_url['fragment'] = webform_replace_tokens($parsed_redirect_url['fragment'], $node, $submission);
4125
  // Determine whether the path is internal or external. Paths which contain the site's
4126
  // base url are still considered internal. #webform_external is private to webform.
4127
  $parsed_redirect_url['#webform_external'] = url_is_external($parsed_redirect_url['path']);
4128
  foreach (array(NULL, TRUE, FALSE) as $https) {
4129
    if (stripos($parsed_redirect_url['path'], url('', array('absolute' => TRUE, 'https' => $https))) === 0) {
4130
      $parsed_redirect_url['#webform_external'] = FALSE;
4131
    }
4132
  }
4133
  // Return an array suitable for a form redirect or drupal_goto.
4134
  return array($parsed_redirect_url['path'], $parsed_redirect_url);
4135
}
4136

    
4137
/**
4138
 * Replace tokens in descriptions and sanitize according to Webform settings.
4139
 */
4140
function webform_filter_descriptions($string, $node = NULL, $submission = NULL) {
4141
  return strlen($string) == 0 ? '' : webform_filter_xss(webform_replace_tokens($string, $node, $submission));
4142
}
4143

    
4144
/**
4145
 * Deprecated! Use webform_filter_descriptions() instead.
4146
 *
4147
 * @deprecated
4148
 */
4149
function _webform_filter_descriptions($string, $node = NULL, $submission = NULL) {
4150
  return webform_filter_descriptions($string, $node, $submission);
4151
}
4152

    
4153
/**
4154
 * Filter labels for display by running through XSS checks.
4155
 */
4156
function webform_filter_xss($string) {
4157
  static $allowed_tags;
4158
  $allowed_tags = isset($allowed_tags) ? $allowed_tags : webform_variable_get('webform_allowed_tags');
4159
  return filter_xss($string, $allowed_tags);
4160
}
4161

    
4162
/**
4163
 * Deprecated! Use webform_filter_xss() instead!
4164
 *
4165
 * @deprecated
4166
 */
4167
function _webform_filter_xss($string) {
4168
  return webform_filter_xss($string);
4169
}
4170

    
4171
/**
4172
 * Utility function to ensure that a webform record exists in the database.
4173
 *
4174
 * @param object $node
4175
 *   The node object to check if a database entry exists.
4176
 *
4177
 * @return bool
4178
 *   This function should always return TRUE if no errors were encountered,
4179
 *   ensuring that a webform table row has been created. Will return FALSE if
4180
 *   a record does not exist and a new one could not be created.
4181
 */
4182
function webform_ensure_record(&$node) {
4183
  if (!$node->webform['record_exists']) {
4184
    // Even though webform_node_insert() would set this property to TRUE,
4185
    // we set record_exists to trigger a difference from the defaults.
4186
    $node->webform['record_exists'] = TRUE;
4187
    webform_node_insert($node);
4188
  }
4189
  return $node->webform['record_exists'];
4190
}
4191

    
4192
/**
4193
 * Utility function to check if a webform record is necessary in the database.
4194
 *
4195
 * If the node is no longer using any webform settings, this function will
4196
 * delete the settings from the webform table. Note that this function will NOT
4197
 * delete rows from the webform table if the node-type is exclusively used for
4198
 * webforms (per the "webform_node_types_primary" variable).
4199
 *
4200
 * @param object $node
4201
 *   The node object to check if a database entry is still required.
4202
 *
4203
 * @return bool
4204
 *   Returns TRUE if the webform still has a record in the database. Returns
4205
 *   FALSE if the webform does not have a record or if the previously existing
4206
 *   record was just deleted.
4207
 */
4208
function webform_check_record(&$node) {
4209
  $webform = $node->webform;
4210
  $webform['record_exists'] = FALSE;
4211
  unset($webform['nid']);
4212

    
4213
  // Don't include empty values in the comparison, this makes it so modules that
4214
  // extend Webform with empty defaults won't affect cleanup of rows.
4215
  $webform = array_filter($webform);
4216
  $defaults = array_filter(webform_node_defaults());
4217
  if ($webform == $defaults && !in_array($node->type, webform_variable_get('webform_node_types_primary'))) {
4218
    webform_node_delete($node);
4219
    $node->webform = webform_node_defaults();
4220
  }
4221
  return $node->webform['record_exists'];
4222
}
4223

    
4224
/**
4225
 * Given a component's form_key and optionally its parent's cid, get its cid(s).
4226
 *
4227
 * @param object $node
4228
 *   A fully loaded webform node object.
4229
 * @param string $form_key
4230
 *   The form key for which to find the cid(s).
4231
 * @param int|null $pid
4232
 *   The cid of the parent component.
4233
 *
4234
 * @return int|int[]
4235
 *   The cid of the component or an array of component ids.
4236
 */
4237
function webform_get_cid($node, $form_key, $pid = NULL) {
4238
  if ($pid === NULL) {
4239
    $cids = array();
4240
    foreach ($node->webform['components'] as $cid => $component) {
4241
      if ((string) $component['form_key'] === (string) $form_key) {
4242
        $cids[] = $cid;
4243
      }
4244
    }
4245
    return $cids;
4246
  }
4247
  else {
4248
    foreach ($node->webform['components'] as $cid => $component) {
4249
      if ((string) $component['form_key'] === (string) $form_key && $component['pid'] == $pid) {
4250
        return $cid;
4251
      }
4252
    }
4253
  }
4254
}
4255

    
4256
/**
4257
 * Find the label of a given page based on page breaks.
4258
 *
4259
 * @param object $node
4260
 *   The webform node.
4261
 * @param $form_state
4262
 *   The form's state, if available
4263
 *
4264
 * @return array
4265
 *   An array of all page labels, indexed by page number.
4266
 */
4267
function webform_page_labels($node, $form_state = array()) {
4268
  $page_count = 1;
4269
  $page_labels = array();
4270
  $page_labels[0] = t($node->webform['progressbar_label_first']);
4271
  foreach ($node->webform['components'] as $component) {
4272
    if ($component['type'] == 'pagebreak') {
4273
      if (module_exists('webform_localization')) {
4274
        module_load_include('inc', 'webform_localization', 'includes/webform_localization.i18n');
4275
        $string = webform_localization_i18n_string_name($component['nid'], $component['cid'], '#title');
4276
        $component['name'] = i18n_string($string, $component['name'], array(
4277
          'update' => TRUE,
4278
          'sanitize' => FALSE,
4279
        ));
4280
      }
4281
      $page_labels[$page_count] = $component['name'];
4282
      $page_count++;
4283
    }
4284
  }
4285
  if ($node->webform['preview'] || !empty($form_state['webform']['preview'])) {
4286
    $page_labels[] = $node->webform['preview_title'] ? t($node->webform['preview_title']) : t('Preview');
4287
  }
4288
  if ($node->webform['progressbar_include_confirmation']) {
4289
    $page_labels[] = t($node->webform['progressbar_label_confirmation']);
4290
  }
4291
  return $page_labels;
4292
}
4293

    
4294
/**
4295
 * Retrieve a Drupal variable with the appropriate default value.
4296
 */
4297
function webform_variable_get($variable) {
4298
  switch ($variable) {
4299
    case 'webform_blocks':
4300
      $result = variable_get('webform_blocks', array());
4301
      break;
4302

    
4303
    case 'webform_tracking_mode':
4304
      $result = variable_get('webform_tracking_mode', 'cookie');
4305
      break;
4306

    
4307
    case 'webform_allowed_tags':
4308
      $result = variable_get('webform_allowed_tags', array('a', 'em', 'strong', 'code', 'img'));
4309
      break;
4310

    
4311
    case 'webform_email_address_format':
4312
      $result = variable_get('webform_email_address_format', 'long');
4313
      break;
4314

    
4315
    case 'webform_email_address_individual':
4316
      $result = variable_get('webform_email_address_individual', 0);
4317
      break;
4318

    
4319
    case 'webform_default_from_name':
4320
      $result = variable_get('webform_default_from_name', variable_get('site_name', ''));
4321
      break;
4322

    
4323
    case 'webform_default_from_address':
4324
      $result = variable_get('webform_default_from_address', variable_get('site_mail', ini_get('sendmail_from')));
4325
      break;
4326

    
4327
    case 'webform_default_subject':
4328
      $result = variable_get('webform_default_subject', t('Form submission from: [node:title]'));
4329
      break;
4330

    
4331
    case 'webform_email_replyto':
4332
      $result = variable_get('webform_email_replyto', TRUE);
4333
      break;
4334

    
4335
    case 'webform_email_html_capable':
4336
      $result = variable_get('webform_email_html_capable', FALSE);
4337
      break;
4338

    
4339
    case 'webform_default_format':
4340
      $result = variable_get('webform_default_format', 0);
4341
      break;
4342

    
4343
    case 'webform_format_override':
4344
      $result = variable_get('webform_format_override', 0);
4345
      break;
4346

    
4347
    case 'webform_email_select_max':
4348
      $result = variable_get('webform_email_select_max', 50);
4349
      break;
4350

    
4351
    case 'webform_node_types':
4352
      $result = webform_node_types();
4353
      break;
4354

    
4355
    case 'webform_node_types_primary':
4356
      $result = variable_get('webform_node_types_primary', array('webform'));
4357
      break;
4358

    
4359
    case 'webform_date_type':
4360
      $result = variable_get('webform_date_type', 'medium');
4361
      break;
4362

    
4363
    case 'webform_export_format':
4364
      module_load_include('inc', 'webform', 'includes/webform.export');
4365
      $options = webform_export_list();
4366
      $result = variable_get('webform_export_format', 'excel');
4367
      $result = isset($options[$result]) ? $result : key($options);
4368
      break;
4369

    
4370
    case 'webform_csv_delimiter':
4371
      $result = variable_get('webform_csv_delimiter', '\t');
4372
      break;
4373

    
4374
    case 'webform_csv_line_ending':
4375
      $result = variable_get('webform_csv_line_ending', "\n");
4376
      break;
4377

    
4378
    case 'webform_export_wordwrap':
4379
      $result = variable_get('webform_export_wordwrap', 0);
4380
      break;
4381

    
4382
    case 'webform_excel_legacy_exporter':
4383
      $result = variable_get('webform_excel_legacy_exporter', 0);
4384
      break;
4385

    
4386
    case 'webform_progressbar_style':
4387
      $result = variable_get('webform_progressbar_style', array('progressbar_bar', 'progressbar_pagebreak_labels', 'progressbar_include_confirmation'));
4388
      break;
4389

    
4390
    case 'webform_progressbar_label_first':
4391
      $result = variable_get('webform_progressbar_label_first', t('Start'));
4392
      break;
4393

    
4394
    case 'webform_progressbar_label_confirmation':
4395
      $result = variable_get('webform_progressbar_label_confirmation', t('Complete'));
4396
      break;
4397

    
4398
    case 'webform_table':
4399
      $result = variable_get('webform_table', FALSE);
4400
      break;
4401

    
4402
    case 'webform_submission_access_control':
4403
      $result = variable_get('webform_submission_access_control', 1);
4404
      break;
4405

    
4406
    case 'webform_token_access':
4407
      $result = variable_get('webform_token_access', 1);
4408
      break;
4409

    
4410
    case 'webform_update_batch_size':
4411
      $result = variable_get('webform_update_batch_size', 100);
4412
      break;
4413

    
4414
    case 'webform_disabled_components':
4415
      $result = variable_get('webform_disabled_components', array());
4416
      break;
4417
  }
4418
  return $result;
4419
}
4420

    
4421
/**
4422
 * Output the contents of token help used throughout Webform.
4423
 *
4424
 * In earlier versions of Token, a fieldset is used to show all the tokens.
4425
 * Later versions now use a modal dialog that is accessed through a link. If
4426
 * Token module is not available, a message should be displayed.
4427
 */
4428
function theme_webform_token_help($variables) {
4429
  if (!webform_variable_get('webform_token_access')) {
4430
    return '';
4431
  }
4432
  $groups = $variables['groups'];
4433

    
4434
  // Assume dialogs are not supported, show as a fieldset.
4435
  $help = array(
4436
    '#title' => t('Token values'),
4437
    '#type' => 'fieldset',
4438
    '#collapsible' => TRUE,
4439
    '#collapsed' => TRUE,
4440
    '#attributes' => array('class' => array('collapsible', 'collapsed')),
4441
    'help' => array(
4442
      '#markup' => '<p>' . t('This field supports dynamic token values. Common values might be [current-user:mail] or [node:title].') . '</p>',
4443
    ),
4444
    'token_tree' => array(
4445
      '#theme' => 'token_tree',
4446
      '#token_types' => $groups,
4447
    ),
4448
  );
4449

    
4450
  if (!module_exists('token')) {
4451
    // No token module at all. Display a simple suggestion to enable it.
4452
    $help['help']['#markup'] .= '<p>' . t('A full listing of tokens may be listed here by installing the <a href="http://drupal.org/project/token">Token module</a>.') . '</p>';
4453
    unset($help['token_tree']);
4454
  }
4455
  else {
4456
    module_load_include('inc', 'token', 'token.pages');
4457
    if (function_exists('token_page_output_tree')) {
4458
      // Token supports dialogs: display simply as a link.
4459
      $help = $help['token_tree'];
4460
      $help['#dialog'] = TRUE;
4461
    }
4462
  }
4463

    
4464
  return render($help);
4465
}
4466

    
4467
/**
4468
 * Convert a name into an identifier.
4469
 *
4470
 * The identifier is safe for machine names, classes, and other ASCII uses.
4471
 */
4472
function _webform_safe_name($name) {
4473
  $new = trim($name);
4474
  $new = _webform_transliterate($new);
4475
  $new = str_replace(array(' ', '-', '/'), array('_', '_', '_'), $new);
4476
  $new = drupal_strtolower($new);
4477
  $new = preg_replace('/[^a-z0-9_]/', '', $new);
4478
  return $new;
4479
}
4480

    
4481
/**
4482
 * Transliterate common non-English characters to 7-bit ASCII.
4483
 */
4484
function _webform_transliterate($name) {
4485
  // If transliteration is available, use it to convert names to ASCII.
4486
  return function_exists('transliteration_get')
4487
            ? transliteration_get($name, '')
4488
            : str_replace(array('€', 'ƒ', 'Š', 'Ž', 'š', 'ž', 'Ÿ', '¢', '¥', 'µ', 'À', 'Á', 'Â', 'Ã', 'Ä', 'Å', 'Ç', 'È', 'É', 'Ê', 'Ë', 'Ì', 'Í', 'Î', 'Ï', 'Ñ', 'Ò', 'Ó', 'Ô', 'Õ', 'Ö', 'Ø', 'Ù', 'Ú', 'Û', 'Ü', 'Ý', 'à', 'á', 'â', 'ã', 'ä', 'å', 'ç', 'è', 'é', 'ê', 'ë', 'ì', 'í', 'î', 'ï', 'ñ', 'ò', 'ó', 'ô', 'õ', 'ö', 'ø', 'ù', 'ú', 'û', 'ü', 'ý', 'ÿ', 'Œ', 'œ', 'Æ', 'Ð', 'Þ', 'ß', 'æ', 'ð', 'þ'),
4489
                          array('E', 'f', 'S', 'Z', 's', 'z', 'Y', 'c', 'Y', 'u', 'A', 'A', 'A', 'A', 'A', 'A', 'C', 'E', 'E', 'E', 'E', 'I', 'I', 'I', 'I', 'N', 'O', 'O', 'O', 'O', 'O', 'O', 'U', 'U', 'U', 'U', 'Y', 'a', 'a', 'a', 'a', 'a', 'a', 'c', 'e', 'e', 'e', 'e', 'i', 'i', 'i', 'i', 'n', 'o', 'o', 'o', 'o', 'o', 'o', 'u', 'u', 'u', 'u', 'y', 'y', 'OE', 'oe', 'AE', 'DH', 'TH', 'ss', 'ae', 'dh', 'th'),
4490
                          $name);
4491
}
4492

    
4493
/**
4494
 * Given an email address and a name, format an e-mail address.
4495
 *
4496
 * The address can be the cid of a component with multiple values. When $single
4497
 * is true, a single address is return (the first of any multiples). When not
4498
 * true, an array of addresses is returned.
4499
 *
4500
 * Note that multiple names could be used with multiple addresses, but this
4501
 * capability is not currently possible with the webform UI. Separate names
4502
 * are only used with the From address, which is always single.
4503
 *
4504
 * @param $address
4505
 *   The e-mail address.
4506
 * @param $name
4507
 *   The name to be used in the formatted address. If the address contains a
4508
 *   name in 'Some Name <somename@example.com>' format, $name is ignored.
4509
 * @param object $node
4510
 *   The webform node if replacements will be done.
4511
 * @param $submission
4512
 *   The webform submission values if replacements will be done.
4513
 * @param $encode
4514
 *   Encode the text for use in an e-mail.
4515
 * @param $single
4516
 *   Force a single value to be returned, even if a component expands to
4517
 *   multiple addresses. This is useful to ensure a single e-mail will be
4518
 *   returned for the "From" address.
4519
 * @param $format
4520
 *   The e-mail format, defaults to the site-wide setting. May be "short",
4521
 *   "long", or NULL for the system default.
4522
 * @param $mapping
4523
 *   A mapping array to be applied to the address values.
4524
 *
4525
 * @return string|array
4526
 *   The formatted e-mail address -- or addresses (if not $single)
4527
 */
4528
function webform_format_email_address($address, $name, $node = NULL, $submission = NULL, $encode = TRUE, $single = TRUE, $format = NULL, $mapping = NULL) {
4529
  if (!isset($format)) {
4530
    $format = webform_variable_get('webform_email_address_format');
4531
  }
4532

    
4533
  if ($name == 'default') {
4534
    $name = webform_variable_get('webform_default_from_name');
4535
  }
4536
  elseif (is_numeric($name) && isset($node->webform['components'][$name])) {
4537
    if (isset($submission->data[$name])) {
4538
      $component = $node->webform['components'][$name];
4539
      $name = $submission->data[$name];
4540

    
4541
      // Convert the FROM name to be the label of select lists.
4542
      if (webform_component_implements($component['type'], 'options')) {
4543
        $options = webform_component_invoke($component['type'], 'options', $component);
4544
        foreach ($name as &$one_name) {
4545
          $one_name = isset($options[$one_name]) ? $options[$one_name] : $one_name;
4546
        }
4547
        // Drop PHP reference.
4548
        unset($one_name);
4549
      }
4550
    }
4551
    else {
4552
      $name = t('Value of !component', array('!component' => $node->webform['components'][$name]['name']));
4553
    }
4554
  }
4555
  elseif (!isset($name)) {
4556
    $name = '';
4557
  }
4558

    
4559
  if ($address == 'default') {
4560
    $address = webform_variable_get('webform_default_from_address');
4561
  }
4562
  elseif (is_numeric($address) && isset($node->webform['components'][$address])) {
4563
    if (isset($submission->data[$address])) {
4564
      $values = $submission->data[$address];
4565
      $address = array();
4566
      foreach ($values as $value) {
4567
        if (isset($mapping) && isset($mapping[$value])) {
4568
          $value = $mapping[$value];
4569
        }
4570
        $address = array_merge($address, explode(',', $value));
4571
      }
4572
    }
4573
    else {
4574
      $address = t('Value of "!component"', array('!component' => $node->webform['components'][$address]['name']));
4575
    }
4576
  }
4577

    
4578
  // Convert single values to an array to simplify processing.
4579
  $address = is_array($address) ? $address : explode(',', $address);
4580
  $name = is_array($name) ? $name : array($name);
4581
  $name_shortage = count($address) - count($name);
4582
  if ($name_shortage > 0) {
4583
    $name += array_fill(count($name), $name_shortage, $name[0]);
4584
  }
4585

    
4586
  foreach ($address as $key => $individual_address) {
4587
    $individual_address = webform_replace_tokens($individual_address, $node, $submission);
4588
    $email_parts = webform_parse_email_address($individual_address);
4589
    if ($format == 'long' && !empty($name[$key]) && !strlen($email_parts['name'])) {
4590
      $individual_name = $name[$key];
4591
      $individual_name = webform_replace_tokens($individual_name, $node, $submission);
4592
      if ($encode) {
4593
        $individual_name = mime_header_encode($individual_name);
4594
      }
4595
      $individual_name = trim($individual_name);
4596
      $individual_address = '"' . $individual_name . '" <' . $individual_address . '>';
4597
    }
4598
    $address[$key] = $individual_address;
4599
  }
4600

    
4601
  return $single ? reset($address) : $address;
4602
}
4603

    
4604
/**
4605
 * Validates an email form element.
4606
 *
4607
 * @param string $emails
4608
 *   An email or list of comma-seperated email addresses. Passed by reference.
4609
 *   Empty emails will be eliminated, and mutiple addresses will be seperated
4610
 *   with a comma and space.
4611
 * @param string $form_name
4612
 *   The name of the form element to receive an error, in form_set_error format.
4613
 * @param bool $allow_empty
4614
 *   TRUE if optional. FALSE if required.
4615
 * @param bool $allow_multiple
4616
 *   TRUE if a list of emails is allowed. FALSE if only one.
4617
 * @param bool $allow_tokens
4618
 *   TRUE if any token should be assumed to contain a valid e-mail address.
4619
 * @param string $format
4620
 *   'short', 'long', or NULL (for default) format. Long format has a name and
4621
 *   the address in angle brackets.
4622
 *
4623
 * @return int|bool
4624
 *   The number of valid addresses found, or FALSE for an invalid email found.
4625
 */
4626
function webform_email_validate(&$emails, $form_name, $allow_empty, $allow_multiple, $allow_tokens, $format = NULL) {
4627
  $nr_valid = webform_valid_email_address($emails, $allow_tokens, $format);
4628
  if ($nr_valid === FALSE) {
4629
    form_set_error($form_name, t('The entered e-mail address "@email" does not appear valid.', array('@email' => $emails)));
4630
  }
4631
  elseif ($nr_valid === 0 && !$allow_empty) {
4632
    form_set_error($form_name, t('When adding a new custom e-mail, the e-mail field is required.'));
4633
  }
4634
  elseif ($nr_valid > 1 && !$allow_multiple) {
4635
    form_set_error($form_name, t('Only one e-mail address is allowed.'));
4636
  }
4637
  return $nr_valid;
4638
}
4639

    
4640
/**
4641
 * Validates email address(es) with optional name(s).
4642
 *
4643
 * @param string $emails
4644
 *   An email address, a list of comma-separated email addresses. If all the
4645
 *   addresses are valid, the list of trimmed, non-empty emails is returned by
4646
 *   reference.
4647
 * @param bool $allow_tokens
4648
 *   TRUE if any token should be assumed to contain a valid e-mail address.
4649
 * @param string $format
4650
 *   'short', 'long', or NULL (for default) format. Long format has a name and
4651
 *   the address in angle brackets.
4652
 *
4653
 * @return bool|int
4654
 *   Returns FALSE if an invalid e-mail address was found, 0 if no email
4655
 *   address(es) were found, or the number of valid e-mail addresses found.
4656
 */
4657
function webform_valid_email_address(&$emails, $allow_tokens = FALSE, $format = NULL) {
4658
  $email_array = array_filter(array_map('trim', explode(',', $emails)));
4659
  $count = 0;
4660
  foreach ($email_array as $email) {
4661
    if ($allow_tokens && webform_variable_get('webform_token_access')) {
4662
      foreach (token_scan($email) as $tokens) {
4663
        foreach ($tokens as $token) {
4664
          $email = str_replace($token, 'admin@example.com', $email);
4665
        }
4666
      }
4667
    }
4668
    $matches = webform_parse_email_address($email, $format);
4669
    if (!valid_email_address($matches['address'])) {
4670
      return FALSE;
4671
    }
4672
    $count++;
4673
  }
4674
  $emails = implode(', ', $email_array);
4675
  return $count;
4676
}
4677

    
4678
/**
4679
 * Parses an e-mail address into name and address.
4680
 *
4681
 * @param string $email
4682
 *   The email address to be parsed, with an optional name.
4683
 * @param string $format
4684
 *   'short', 'long', or NULL (for default) format. Long format has a name and
4685
 *   the address in angle brackets.
4686
 *
4687
 * @return array
4688
 *   Associative array indexed by 'name' and 'address'.
4689
 */
4690
function webform_parse_email_address($email, $format = NULL) {
4691
  if (!$format) {
4692
    $format = webform_variable_get('webform_email_address_format');
4693
  }
4694
  if ($format == 'long') {
4695
    // Match e-mails of the form 'My Name <email@domain.com>'.
4696
    preg_match('/^"?([^<]*?)"? *(?:<(.*)>)?$/', $email, $matches);
4697
    if (isset($matches[2]) && strlen($matches[2])) {
4698
      return array(
4699
        'name' => $matches[1],
4700
        'address' => $matches[2],
4701
      );
4702
    }
4703
  }
4704
  return array(
4705
    'name' => '',
4706
    'address' => $email,
4707
  );
4708
}
4709

    
4710
/**
4711
 * Given an email subject, format it with any needed replacements.
4712
 */
4713
function webform_format_email_subject($subject, $node = NULL, $submission = NULL) {
4714
  if ($subject == 'default') {
4715
    $subject = webform_variable_get('webform_default_subject');
4716
  }
4717
  elseif (is_numeric($subject) && isset($node->webform['components'][$subject])) {
4718
    $component = $node->webform['components'][$subject];
4719
    if (isset($submission->data[$subject])) {
4720
      $display_function = '_webform_display_' . $component['type'];
4721
      $value = $submission->data[$subject];
4722

    
4723
      // Convert the value to a clean text representation if possible.
4724
      if (function_exists($display_function)) {
4725
        $display = $display_function($component, $value, 'text');
4726
        $display['#theme_wrappers'] = array();
4727
        $display['#webform_component'] = $component;
4728
        $subject = str_replace("\n", ' ', drupal_render($display));
4729
      }
4730
      else {
4731
        $subject = $value;
4732
      }
4733
    }
4734
    else {
4735
      $subject = t('Value of "!component"', array('!component' => $component['name']));
4736
    }
4737
  }
4738

    
4739
  // Convert arrays to strings (may happen if checkboxes are used as the value).
4740
  if (is_array($subject)) {
4741
    $subject = reset($subject);
4742
  }
4743

    
4744
  return webform_replace_tokens($subject, $node, $submission);
4745
}
4746

    
4747
/**
4748
 * Convert an array of components into a tree.
4749
 */
4750
function _webform_components_tree_build($src, &$tree, $parent, &$page_count) {
4751
  foreach ($src as $cid => $component) {
4752
    if ($component['pid'] == $parent) {
4753
      _webform_components_tree_build($src, $component, $cid, $page_count);
4754
      if ($component['type'] == 'pagebreak') {
4755
        $page_count++;
4756
      }
4757
      $tree['children'][$cid] = $component;
4758
      $tree['children'][$cid]['page_num'] = $page_count;
4759
    }
4760
  }
4761
  return $tree;
4762
}
4763

    
4764
/**
4765
 * Flatten a component tree into a flat list.
4766
 */
4767
function _webform_components_tree_flatten($tree) {
4768
  $components = array();
4769
  foreach ($tree as $cid => $component) {
4770
    if (isset($component['children'])) {
4771
      unset($component['children']);
4772
      $components[$cid] = $component;
4773
      // array_merge() can't be used here because the keys are numeric.
4774
      $children = _webform_components_tree_flatten($tree[$cid]['children']);
4775
      foreach ($children as $ccid => $ccomponent) {
4776
        $components[$ccid] = $ccomponent;
4777
      }
4778
    }
4779
    else {
4780
      $components[$cid] = $component;
4781
    }
4782
  }
4783
  return $components;
4784
}
4785

    
4786
/**
4787
 * Helper for the uasort in webform_tree_sort()
4788
 */
4789
function _webform_components_sort($a, $b) {
4790
  if ($a['weight'] == $b['weight']) {
4791
    return strcasecmp($a['name'], $b['name']);
4792
  }
4793
  return ($a['weight'] < $b['weight']) ? -1 : 1;
4794
}
4795

    
4796
/**
4797
 * Sort each level of a component tree by weight and name.
4798
 */
4799
function _webform_components_tree_sort($tree) {
4800
  if (isset($tree['children']) && is_array($tree['children'])) {
4801
    $children = array();
4802
    uasort($tree['children'], '_webform_components_sort');
4803
    foreach ($tree['children'] as $cid => $component) {
4804
      $children[$cid] = _webform_components_tree_sort($component);
4805
    }
4806
    $tree['children'] = $children;
4807
  }
4808
  return $tree;
4809
}
4810

    
4811
/**
4812
 * Get a list of all available component definitions.
4813
 */
4814
function webform_components($include_disabled = FALSE, $reset = FALSE) {
4815
  static $components, $enabled;
4816

    
4817
  if (!isset($components) || $reset) {
4818
    $components = array();
4819
    $disabled = array_flip(webform_variable_get('webform_disabled_components'));
4820
    foreach (module_implements('webform_component_info') as $module) {
4821
      $module_components = module_invoke($module, 'webform_component_info');
4822
      foreach ($module_components as $type => $info) {
4823
        $module_components[$type]['module'] = $module;
4824
        $module_components[$type]['enabled'] = !array_key_exists($type, $disabled);
4825
      }
4826
      $components += $module_components;
4827
    }
4828
    drupal_alter('webform_component_info', $components);
4829
    uasort($components, function ($a, $b) {
4830
      return strnatcasecmp($a['label'], $b['label']);
4831
    });
4832
    $enabled = array_diff_key($components, $disabled);
4833
  }
4834

    
4835
  return $include_disabled ? $components : $enabled;
4836
}
4837

    
4838
/**
4839
 * Build a list of components suitable for use as select list options.
4840
 */
4841
function webform_component_options($include_disabled = FALSE) {
4842
  $component_info = webform_components($include_disabled);
4843
  $options = array();
4844
  foreach ($component_info as $type => $info) {
4845
    $options[$type] = $info['label'];
4846
  }
4847
  return $options;
4848
}
4849

    
4850
/**
4851
 * Load a component file into memory.
4852
 *
4853
 * @param $component_type
4854
 *   The string machine name of a component.
4855
 */
4856
function webform_component_include($component_type) {
4857
  static $included = array();
4858

    
4859
  // No need to load components that have already been added once.
4860
  if (!isset($included[$component_type])) {
4861
    $components = webform_components(TRUE);
4862
    $included[$component_type] = TRUE;
4863

    
4864
    if (isset($components[$component_type]['file'])) {
4865
      $info = $components[$component_type];
4866
      $pathinfo = pathinfo($info['file']);
4867
      $basename = basename($pathinfo['basename'], '.' . $pathinfo['extension']);
4868
      $path = (!empty($pathinfo['dirname']) ? $pathinfo['dirname'] . '/' : '') . $basename;
4869
      module_load_include($pathinfo['extension'], $info['module'], $path);
4870
    }
4871
  }
4872
}
4873

    
4874
/**
4875
 * Invoke a component callback.
4876
 *
4877
 * @param $type
4878
 *   The component type as a string.
4879
 * @param $callback
4880
 *   The callback to execute.
4881
 * @param ...
4882
 *   Any additional parameters required by the $callback.
4883
 *
4884
 * @return mixed
4885
 *   Return value of the callback on success and FALSE on failure.
4886
 */
4887
function webform_component_invoke($type, $callback) {
4888
  $args = func_get_args();
4889
  $type = array_shift($args);
4890
  $callback = array_shift($args);
4891
  $function = '_webform_' . $callback . '_' . $type;
4892
  webform_component_include($type);
4893
  if (function_exists($function)) {
4894
    return call_user_func_array($function, $args);
4895
  }
4896
}
4897

    
4898
/**
4899
 * Check if a component implements a particular hook.
4900
 *
4901
 * @param $type
4902
 *   The component type as a string.
4903
 * @param $callback
4904
 *   The callback to check.
4905
 *
4906
 * @return bool
4907
 *   Whether or not the hook is implemented.
4908
 */
4909
function webform_component_implements($type, $callback) {
4910
  $function = '_webform_' . $callback . '_' . $type;
4911
  webform_component_include($type);
4912
  return function_exists($function);
4913
}
4914

    
4915
/**
4916
 * Form API #process function to expand a webform conditional element.
4917
 */
4918
function webform_conditional_expand($element) {
4919
  module_load_include('inc', 'webform', 'includes/webform.conditionals');
4920
  return _webform_conditional_expand($element);
4921
}
4922

    
4923
/**
4924
 * Add class and wrapper class attributes to an element.
4925
 */
4926
function _webform_component_classes(&$element, $component) {
4927
  if (isset($component['extra']['css_classes']) && drupal_strlen($component['extra']['css_classes'])) {
4928
    $element['#attributes']['class'] = isset($element['#attributes']['class']) ? $element['#attributes']['class'] : array();
4929
    $element['#attributes']['class'] = array_merge($element['#attributes']['class'], explode(' ', $component['extra']['css_classes']));
4930
  }
4931
  if (isset($component['extra']['wrapper_classes']) && drupal_strlen($component['extra']['wrapper_classes'])) {
4932
    $element['#wrapper_attributes']['class'] = isset($element['#wrapper_attributes']['class']) ? $element['#wrapper_attributes']['class'] : array();
4933
    $element['#wrapper_attributes']['class'] = array_merge($element['#wrapper_attributes']['class'], explode(' ', $component['extra']['wrapper_classes']));
4934
  }
4935
}
4936

    
4937
/**
4938
 * Disable the Drupal page cache.
4939
 */
4940
function webform_disable_page_cache() {
4941
  drupal_page_is_cacheable(FALSE);
4942
}
4943

    
4944
/**
4945
 * Set the necessary breadcrumb for the page we are on.
4946
 *
4947
 * @param object $node
4948
 *   The loaded webform node.
4949
 * @param bool|object $submission
4950
 *   The submission if the current page is viewing or dealing with a submission,
4951
 *   or TRUE to just include the webform node in the breadcrumbs (used for
4952
 *   the submission completion confirmation page), or NULL for no extra
4953
 *   processing.
4954
 */
4955
function webform_set_breadcrumb($node, $submission = NULL) {
4956
  $node_path = "node/{$node->nid}";
4957

    
4958
  // Set the href of the current menu item to be the node's path. This has two
4959
  // effects. The active trail will be to the node's prefered menu tree
4960
  // location, expanding the menu as appropriate. And the breadcrumbs will be
4961
  // set as if the current page were under the node's preferred location.
4962
  // Note that menu_tree_set_path() could be used to set the path for the menu,
4963
  // but it will not affect the breadcrumbs when the webform is not in the
4964
  // default menu.
4965
  menu_set_item(NULL, array('href' => $node_path) + menu_get_item());
4966

    
4967
  if ($submission) {
4968
    $breadcrumb = menu_get_active_breadcrumb();
4969

    
4970
    // Append the node title (or its menu name), in case it isn't in the path already.
4971
    $active_trail = menu_get_active_trail();
4972
    $last_active = end($active_trail);
4973
    $breadcrumb[] = $last_active['href'] === $node_path && !empty($last_active['in_active_trail'])
4974
                      ? l($last_active['title'], $node_path, $last_active['localized_options'])
4975
                      : l($node->title, $node_path);
4976

    
4977
    // Setting the current menu href will cause the submission title and current
4978
    // tab (if not the default tab) to be added to the active path when the
4979
    // webform is in the default location in the menu (node/NID). The title
4980
    // is desirable, but the tab name (for example Edit or Delete) isn't.
4981
    if (preg_match('/href=".*"/', end($breadcrumb), $matches)) {
4982
      foreach ($breadcrumb as $index => $link) {
4983
        if (stripos($link, $matches[0]) !== FALSE) {
4984
          $breadcrumb = array_slice($breadcrumb, 0, $index + 1);
4985
          break;
4986
        }
4987
      }
4988
    }
4989

    
4990
    // If the user is dealing with a submission, then the breadcrumb should
4991
    // be fudged to allow them to return to a likely list of webforms.
4992
    // Note that this isn't necessarily where they came from, but it's the
4993
    // best guess available.
4994
    if (is_object($submission)) {
4995
      if (webform_results_access($node)) {
4996
        $breadcrumb[] = l(t('Webform results'), $node_path . '/webform-results');
4997
      }
4998
      elseif (user_access('access own webform results')) {
4999
        $breadcrumb[] = l(t('Submissions'), $node_path . '/submissions');
5000
      }
5001
    }
5002

    
5003
    drupal_set_breadcrumb($breadcrumb);
5004
  }
5005
}
5006

    
5007
/**
5008
 * Convert an ISO 8601 date or time into an array.
5009
 *
5010
 * This converts full format dates or times. Either a date or time may be
5011
 * provided, in which case only those portions will be returned. Dashes and
5012
 * colons must be used, never implied.
5013
 *
5014
 * Formats:
5015
 * Dates: YYYY-MM-DD
5016
 * Times: HH:MM:SS
5017
 * Datetimes: YYYY-MM-DDTHH:MM:SS
5018
 *
5019
 * @param $string
5020
 *   An ISO 8601 date, time, or datetime.
5021
 * @param $type
5022
 *   If wanting only specific fields back, specify either "date" or "time".
5023
 *   Leaving empty will return an array with both date and time keys, even if
5024
 *   some are empty. Returns an array with the following keys:
5025
 *   - year
5026
 *   - month
5027
 *   - day
5028
 *   - hour (in 24hr notation)
5029
 *   - minute
5030
 *   - second
5031
 *
5032
 * @return array
5033
 *   Date in array formate.
5034
 */
5035
function webform_date_array($string, $type = NULL) {
5036
  $pattern = '/((\d{4}?)-(\d{2}?)-(\d{2}?))?(T?(\d{2}?):(\d{2}?):(\d{2}?))?/';
5037
  $matches = array();
5038
  preg_match($pattern, $string, $matches);
5039
  $matches += array_fill(0, 9, '');
5040

    
5041
  $return = array();
5042

    
5043
  // Check for a date string.
5044
  if ($type == 'date' || !isset($type)) {
5045
    $return['year'] = $matches[2] !== '' ? (int) $matches[2] : '';
5046
    $return['month'] = $matches[3] !== '' ? (int) $matches[3] : '';
5047
    $return['day'] = $matches[4] !== '' ? (int) $matches[4] : '';
5048
  }
5049

    
5050
  // Check for a time string.
5051
  if ($type == 'time' || !isset($type)) {
5052
    $return['hour'] = $matches[6] !== '' ? (int) $matches[6] : '';
5053
    $return['minute'] = $matches[7] !== '' ? (int) $matches[7] : '';
5054
    $return['second'] = $matches[8] !== '' ? (int) $matches[8] : '';
5055
  }
5056

    
5057
  return $return;
5058
}
5059

    
5060
/**
5061
 * Convert an array of a date or time into an ISO 8601 compatible string.
5062
 *
5063
 * @param $array
5064
 *   The array to convert to a date or time string.
5065
 * @param $type
5066
 *   If wanting a specific string format back specify either "date" or "time".
5067
 *   Otherwise a full ISO 8601 date and time string will be returned.
5068
 *
5069
 * @return string
5070
 *   Date in string format
5071
 */
5072
function webform_date_string($array, $type = NULL) {
5073
  $string = '';
5074

    
5075
  if ($type == 'date' || !isset($type)) {
5076
    $string .= empty($array['year']) ? '0000' : sprintf('%04d', $array['year']);
5077
    $string .= '-';
5078
    $string .= empty($array['month']) ? '00' : sprintf('%02d', $array['month']);
5079
    $string .= '-';
5080
    $string .= empty($array['day']) ? '00' : sprintf('%02d', $array['day']);
5081
  }
5082

    
5083
  if (!isset($type)) {
5084
    $string .= 'T';
5085
  }
5086

    
5087
  if ($type == 'time' || !isset($type)) {
5088
    $string .= empty($array['hour']) ? '00' : sprintf('%02d', $array['hour']);
5089
    $string .= ':';
5090
    $string .= empty($array['minute']) ? '00' : sprintf('%02d', $array['minute']);
5091
    $string .= ':';
5092
    $string .= empty($array['second']) ? '00' : sprintf('%02d', $array['second']);
5093
  }
5094

    
5095
  return $string;
5096
}
5097

    
5098
/**
5099
 * Get a date format according to the site settings.
5100
 *
5101
 * @param $type
5102
 *   A choice of 'short', 'medium', 'long' , or other user-defined date formats.
5103
 *   Use NULL for the webform-specific date format choosen in the webform
5104
 *   settings.
5105
 * @param array $exclude
5106
 *   An array containing 'day', 'month', and/or 'year' if they should be
5107
 *   removed from the format.
5108
 *
5109
 * @return string
5110
 *   A date/time format string.
5111
 */
5112
function webform_date_format($type = NULL, array $exclude = array()) {
5113
  static $formats = array();
5114
  $id = $type . ':' . implode('', $exclude);
5115
  if (!isset($formats[$id])) {
5116
    $type_name = $type ? $type : webform_variable_get('webform_date_type');
5117

    
5118
    // Format date according to site's given format.
5119
    $format = variable_get('date_format_' . $type_name, 'D, m/d/Y');
5120

    
5121
    // Date/Time formatting characters
5122
    // WHAT           REQUIRED (at least 1) OPTIONAL (allowed but insufficient)
5123
    // ---------------------------------------------------------------------------
5124
    // Day-of-week                          DlNw
5125
    // Day            dj                    Stz
5126
    // Month          FmMn
5127
    // Year           oYy                   L
5128
    //
5129
    //                NOT ALLOWED
5130
    // --------------------------------------------------------------------------
5131
    // Time           aABgGhHisueIOPTZ
5132
    // Special        /.,-: <space>
5133
    //
5134
    // Strip Time and Special characters from the beginning and end of format.
5135
    $date_format = trim($format, 'aABgGhHisueIOPTZ/.,-: ');
5136

    
5137
    // Ensure that a day, month, and year value are present. Use a default
5138
    // format if all the values are not found. This regular expression uses
5139
    // (?= ), the positive lookahead assertion. It asserts that there are some
5140
    // optional characters (.*) followed by one of the day, month, or year
5141
    // characters. Because it is an assertion, it doesn't consume the
5142
    // characters, so the day, month, and year can be in any order.
5143
    if (!preg_match('/(?=.*[dj])(?=.*[FmMn])(?=.*[oYy])/', $date_format)) {
5144
      $date_format = 'm/d/Y';
5145
    }
5146

    
5147
    // Remove any excluded portions.
5148
    $strip = array(
5149
      'day' => 'DlNwdjStz',
5150
      'month' => 'FmMn',
5151
      'year' => 'oYyL',
5152
    );
5153
    foreach ($exclude as $field) {
5154
      // Strip the format and any trailing /.,-: or space.
5155
      $date_format = preg_replace('#[' . $strip[$field] . ']+[/\.,\-: ]*#', '', $date_format);
5156
      $date_format = trim($date_format, '/.,-: ');
5157
    }
5158

    
5159
    $formats[$id] = $date_format;
5160
  }
5161

    
5162
  return $formats[$id];
5163
}
5164

    
5165
/**
5166
 * Return a date in the desired format taking into consideration user timezones.
5167
 */
5168
function webform_strtodate($format, $string, $timezone_name = NULL, $reference_timestamp = NULL) {
5169
  global $user;
5170

    
5171
  // Adjust the time based on the user or site timezone.
5172
  if (variable_get('configurable_timezones', 1) && $timezone_name == 'user' && $user->uid) {
5173
    $timezone_name = isset($GLOBALS['user']->timezone) ? $GLOBALS['user']->timezone : 'UTC';
5174
  }
5175
  // If the timezone is still empty or not set, use the site timezone.
5176
  if (empty($timezone_name) || $timezone_name == 'user') {
5177
    $timezone_name = variable_get('date_default_timezone', 'UTC');
5178
  }
5179

    
5180
  if (!empty($timezone_name) && class_exists('DateTimeZone')) {
5181
    // Suppress errors if encountered during string conversion. Exceptions are
5182
    // only supported for DateTime in PHP 5.3 and higher.
5183
    try {
5184
      @$timezone = new DateTimeZone($timezone_name);
5185
      if (isset($reference_timestamp)) {
5186
        // A reference for relative dates has been provided.
5187
        // 1. Convert the reference timestamp (in UTC) to a DateTime.
5188
        // 2. Set to time zone to the user or system timezone, recreating the
5189
        //    reference time in the appropriate time zone.
5190
        // 3. Set the time to midnight because when a non-referenced relative
5191
        //    date is created without a time, it is created at midnight (0:00).
5192
        // 4. Adjust to the specified relative (or absolute) time.
5193
        @$datetime = new DateTime('@' . $reference_timestamp);
5194
        @$datetime->setTimezone($timezone)
5195
          ->setTime(0, 0, 0)
5196
          ->modify($string);
5197
      }
5198
      else {
5199
        @$datetime = new DateTime($string, $timezone);
5200
      }
5201
      return @$datetime->format($format);
5202
    }
5203
    catch (Exception $e) {
5204
      return '';
5205
    }
5206
  }
5207
  else {
5208
    return date($format, isset($reference_timestamp) ? strtotime($string, $reference_timestamp) : strtotime($string));
5209
  }
5210
}
5211

    
5212
/**
5213
 * Get a timestamp in GMT time, ensuring timezone accuracy.
5214
 */
5215
function webform_strtotime($date) {
5216
  $current_tz = date_default_timezone_get();
5217
  date_default_timezone_set('UTC');
5218
  $timestamp = strtotime($date);
5219
  date_default_timezone_set($current_tz);
5220
  return $timestamp;
5221
}
5222

    
5223
/**
5224
 * Wrapper function for i18n_string() if i18nstrings enabled.
5225
 */
5226
function webform_tt($name, $string, $langcode = NULL, $update = FALSE) {
5227
  if (function_exists('i18n_string')) {
5228
    $options = array(
5229
      'langcode' => $langcode,
5230
      'update' => $update,
5231
    );
5232
    return i18n_string($name, $string, $options);
5233
  }
5234
  else {
5235
    return $string;
5236
  }
5237
}
5238

    
5239
/**
5240
 * Returns an IP Address or anonymized IP Address for confidential webforms.
5241
 */
5242
function webform_ip_address($node) {
5243
  return $node->webform['confidential'] ? t('(unknown)') : ip_address();
5244
}
5245

    
5246
/**
5247
 * Implements hook_views_api().
5248
 */
5249
function webform_views_api() {
5250
  return array(
5251
    'api' => 3.0,
5252
    'path' => drupal_get_path('module', 'webform') . '/views',
5253
  );
5254
}
5255

    
5256
/**
5257
 * Implements hook_views_default_views().
5258
 */
5259
function webform_views_default_views() {
5260
  $path = './' . drupal_get_path('module', 'webform') . '/views/default_views/*.inc';
5261
  $views = array();
5262
  foreach (glob($path) as $views_filename) {
5263
    require_once $views_filename;
5264
  }
5265
  return $views;
5266
}
5267

    
5268
/**
5269
 * Implements hook_field_extra_fields().
5270
 */
5271
function webform_field_extra_fields() {
5272
  $extra = array();
5273
  foreach (webform_node_types() as $type) {
5274
    $extra['node'][$type]['display']['webform'] = array(
5275
      'label' => t('Webform'),
5276
      'description' => t('Webform client form.'),
5277
      'weight' => 10,
5278
    );
5279
  }
5280
  return $extra;
5281
}
5282

    
5283
/**
5284
 * Implements hook_date_views_extra_tables().
5285
 */
5286
function webform_date_views_extra_tables() {
5287
  return array('webform_submissions' => 'webform_submissions');
5288
}
5289

    
5290
/**
5291
 * Returns the next serial number for a given node and increments it.
5292
 *
5293
 * @param int $nid
5294
 *   The nid of the node.
5295
 *
5296
 * @return int
5297
 *   The next value of the serial number.
5298
 */
5299
function _webform_submission_serial_next_value($nid) {
5300
  // Use a transaction with SELECT ... FOR UPDATE to lock the row between
5301
  // the SELECT and the UPDATE, ensuring that multiple Webform submissions
5302
  // at the same time do not have duplicate numbers. FOR UPDATE must be inside
5303
  // a transaction. The return value of db_transaction() must be assigned or the
5304
  // transaction will commit immediately. The transaction will commit when $txn
5305
  // goes out-of-scope.
5306
  $txn = db_transaction();
5307

    
5308
  // Get the next_serial value.
5309
  $next_serial = db_select('webform', 'w')
5310
    // Only add FOR UPDATE when incrementing.
5311
    ->forUpdate()
5312
    ->fields('w', array('next_serial'))
5313
    ->condition('nid', $nid)
5314
    ->execute()
5315
    ->fetchField();
5316

    
5317
  // $next_serial must be greater than any existing serial number.
5318
  $next_serial = max($next_serial, _webform_submission_serial_next_value_used($nid));
5319

    
5320
  // Increment the next_value.
5321
  db_update('webform')
5322
    ->fields(array('next_serial' => $next_serial + 1))
5323
    ->condition('nid', $nid)
5324
    ->execute();
5325

    
5326
  return $next_serial;
5327
}
5328

    
5329
/**
5330
 * Returns the next submission serial number to be used.
5331
 *
5332
 * This is based on the submissions in the database.
5333
 *
5334
 * @param int $nid
5335
 *   The Node ID of the Webform.
5336
 *
5337
 * @return int
5338
 *   The largest serial number used by a submission plus 1 for the specified
5339
 *   node or 1 when there are no submissions.
5340
 */
5341
function _webform_submission_serial_next_value_used($nid) {
5342
  $max_serial = db_select('webform_submissions');
5343
  $max_serial->addExpression('MAX(serial)');
5344
  $max_serial = $max_serial
5345
    ->condition('nid', $nid)
5346
    ->execute()
5347
    ->fetchField();
5348
  // $max_serial will be a numeric string or NULL.
5349
  return $max_serial + 1;
5350
}
5351

    
5352
/**
5353
 * Alter the node before saving a clone.
5354
 *
5355
 * @param object $node
5356
 *   Reference to the fully loaded node object being saved (the clone) that
5357
 *   can be altered as needed.
5358
 * @param array $context
5359
 *   An array of context describing the clone operation. The keys are:
5360
 *   - 'method' : Can be either 'prepopulate' or 'save-edit'.
5361
 *   - 'original_node' : The original fully loaded node object being cloned.
5362
 *
5363
 * @see clone_node_save()
5364
 * @see drupal_alter()
5365
 */
5366
function webform_clone_node_alter(&$node, array $context) {
5367
  if (isset($node->webform)) {
5368
    $defaults = webform_node_defaults();
5369
    $node->webform['next_serial'] = $defaults['next_serial'];
5370
  }
5371
}
5372

    
5373
/**
5374
 * Check if the last form submission exceeded the servers max_input_vars limit.
5375
 *
5376
 * Optionally preflight the current form to be returned in this request.
5377
 *
5378
 * @param array $form
5379
 *   Reference to the form, which will be changed if $parent_key is set.
5380
 * @param array $form_state
5381
 *   Form's state or NULL for no form state check.
5382
 * @param string $detect_key
5383
 *   A key that will always be present in the posted data when an actual form
5384
 *   submission has been made.
5385
 * @param string $parent_key
5386
 *   Omit to not preflight the form, or