Projet

Général

Profil

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

root / drupal7 / sites / all / modules / webform / webform.module @ 01f36513

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
2622
      //      unexpectedly 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 ($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 the item count if it's an array. For
3104
      // radios, FALSE means that no value was submitted, so check that too.
3105
      if ($elements['#required'] && (!count($elements['#value']) || (is_string($elements['#value']) && strlen(trim($elements['#value'])) == 0) || $elements['#value'] === FALSE)) {
3106
        form_error($elements, t('!name field is required.', array('!name' => $elements['#title'])));
3107
      }
3108

    
3109
      // Verify that the value is not longer than #maxlength.
3110
      if (isset($elements['#maxlength']) && drupal_strlen($elements['#value']) > $elements['#maxlength']) {
3111
        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']))));
3112
      }
3113

    
3114
      // Verify that the value is not shorter than #minlength. The value may
3115
      // still be empty (required is a separate validation option).
3116
      if (isset($elements['#minlength'])) {
3117
        $length = drupal_strlen($elements['#value']);
3118
        if ($length > 0 && $length < $elements['#minlength']) {
3119
          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']))));
3120
        }
3121
      }
3122

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

    
3324
  // Inform the submit handlers that a draft will be saved.
3325
  $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());
3326

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

    
3499
  $form_state['redirect'] = $redirect;
3500
}
3501

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

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

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

    
3549
  return $values;
3550
}
3551

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

    
3755
  switch ($element['#title_display']) {
3756
    case 'inline':
3757
      $output .= $description[$above];
3758
      $description[$above] = '';
3759
      // FALL THRU.
3760
    case 'before':
3761
    case 'invisible':
3762
      $output .= ' ' . theme('form_element_label', $variables);
3763
      $output .= ' ' . $description[$above] . $prefix . $element['#children'] . $suffix . "\n";
3764
      break;
3765

    
3766
    case 'after':
3767
      $output .= ' ' . $description[$above] . $prefix . $element['#children'] . $suffix;
3768
      $output .= ' ' . theme('form_element_label', $variables) . "\n";
3769
      break;
3770

    
3771
    case 'none':
3772
    case 'attribute':
3773
      // Output no label and no required marker, only the children.
3774
      $output .= ' ' . $description[$above] . $prefix . $element['#children'] . $suffix . "\n";
3775
      break;
3776
  }
3777

    
3778
  $output .= $description[!$above];
3779
  $output .= "</div>\n";
3780

    
3781
  return $output;
3782
}
3783

    
3784
/**
3785
 * Output a form element in plain text format.
3786
 */
3787
function theme_webform_element_text($variables) {
3788
  $element = $variables['element'];
3789
  $value = $variables['element']['#children'];
3790

    
3791
  $output = '';
3792
  $is_group = webform_component_feature($element['#webform_component']['type'], 'group');
3793

    
3794
  // Output the element title.
3795
  if (isset($element['#title'])) {
3796
    if ($is_group) {
3797
      $output .= '==' . $element['#title'] . '==';
3798
    }
3799
    elseif (!in_array(drupal_substr($element['#title'], -1), array('?', ':', '!', '%', ';', '@'))) {
3800
      $output .= $element['#title'] . ':';
3801
    }
3802
    else {
3803
      $output .= $element['#title'];
3804
    }
3805
  }
3806

    
3807
  // Wrap long values at 65 characters, allowing for a few fieldset indents.
3808
  // It's common courtesy to wrap at 75 characters in e-mails.
3809
  if ($is_group && drupal_strlen($value) > 65) {
3810
    $value = wordwrap($value, 65, "\n");
3811
    $lines = explode("\n", $value);
3812
    foreach ($lines as $key => $line) {
3813
      $lines[$key] = '  ' . $line;
3814
    }
3815
    $value = implode("\n", $lines);
3816
  }
3817

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

    
3821
  // Indent fieldsets.
3822
  if ($is_group) {
3823
    $lines = explode("\n", $output);
3824
    foreach ($lines as $number => $line) {
3825
      if (strlen($line)) {
3826
        $lines[$number] = '  ' . $line;
3827
      }
3828
    }
3829
    $output = implode("\n", $lines);
3830
    $output .= "\n";
3831
  }
3832

    
3833
  if ($output) {
3834
    $output .= "\n";
3835
  }
3836

    
3837
  return $output;
3838
}
3839

    
3840
/**
3841
 * Theme a radio button and another element together.
3842
 *
3843
 * This is used in the e-mail configuration to show a radio button and a text
3844
 * field or select list on the same line.
3845
 */
3846
function theme_webform_inline_radio($variables) {
3847
  $element = $variables['element'];
3848

    
3849
  // Add element's #type and #name as class to aid with JS/CSS selectors.
3850
  $class = array('form-item');
3851
  if (!empty($element['#type'])) {
3852
    $class[] = 'form-type-' . strtr($element['#type'], '_', '-');
3853
  }
3854
  if (!empty($element['#name'])) {
3855
    $class[] = 'form-item-' . strtr($element['#name'], array(' ' => '-', '_' => '-', '[' => '-', ']' => ''));
3856
  }
3857

    
3858
  // Add container-inline to all elements.
3859
  $class[] = 'webform-container-inline';
3860
  if (isset($element['#inline_element']) && isset($variables['element']['#title'])) {
3861
    $variables['element']['#title'] .= ': ';
3862
  }
3863

    
3864
  $output = '<div class="' . implode(' ', $class) . '">' . "\n";
3865
  $output .= ' ' . $element['#children'];
3866
  if (!empty($element['#title'])) {
3867
    $output .= ' ' . theme('webform_inline_radio_label', $variables) . "\n";
3868
  }
3869

    
3870
  if (!empty($element['#description'])) {
3871
    $output .= ' <div class="description">' . $element['#description'] . "</div>\n";
3872
  }
3873

    
3874
  $output .= "</div>\n";
3875

    
3876
  return $output;
3877
}
3878

    
3879
/**
3880
 * Replacement for theme_form_element_label()
3881
 *
3882
 * This varies from theme_element_label in that it allows inline fields such
3883
 * as select and input tags within the label itself.
3884
 */
3885
function theme_webform_inline_radio_label($variables) {
3886
  $element = $variables['element'];
3887
  // This is also used in the installer, pre-database setup.
3888
  $t = get_t();
3889

    
3890
  // If title and required marker are both empty, output no label.
3891
  if ((!isset($element['#title']) || $element['#title'] === '') && empty($element['#required'])) {
3892
    return '';
3893
  }
3894

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

    
3898
  // theme_element_label() does a filter_xss() here, we skip it because we know
3899
  // every use where this theme function is used and we need to allow input and
3900
  // select elements.
3901
  $title = $element['#title'];
3902

    
3903
  $attributes = isset($element['#attributes']) ? $element['#attributes'] : array();
3904

    
3905
  // Style the label as class option to display inline with the element.
3906
  if ($element['#title_display'] == 'after') {
3907
    $attributes['class'][] = 'option';
3908
  }
3909
  // Show label only to screen readers to avoid disruption in visual flows.
3910
  elseif ($element['#title_display'] == 'invisible') {
3911
    $attributes['class'][] = 'element-invisible';
3912
  }
3913

    
3914
  $attributes['class'][] = 'webform-inline-radio';
3915
  if (!empty($element['#id'])) {
3916
    $attributes['for'] = $element['#id'];
3917
  }
3918

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

    
3923
/**
3924
 * Theme the headers when sending an email from webform.
3925
 *
3926
 * @param array $variables
3927
 *   The variables array.
3928
 *
3929
 * @return array
3930
 *   An array of headers to be used when sending a webform email. If headers
3931
 *   for "From", "To", or "Subject" are set, they will take precedence over
3932
 *   the values set in the webform configuration.
3933
 */
3934
function theme_webform_mail_headers(array $variables) {
3935
  $headers = array(
3936
    'X-Mailer' => 'Drupal Webform' . (ini_get('expose_php') ? ' (PHP/' . phpversion() . ')' : ''),
3937
  );
3938
  return $headers;
3939
}
3940

    
3941
/**
3942
 * Check if current user has a draft of this webform, and return the sid.
3943
 */
3944
function _webform_fetch_draft_sid($nid, $uid) {
3945
  // Detect whether a webform draft is being edited. If so, that is the one that
3946
  // should be returned.
3947
  if (isset($_POST['form_id']) && stripos($_POST['form_id'], 'webform_client_form_') === 0 &&
3948
      !empty($_POST['details']['sid']) && empty($_POST['details']['finished'])) {
3949
    // A draft is already being edited.
3950
    $sid = $_POST['details']['sid'];
3951
  }
3952
  else {
3953
    $sid = db_select('webform_submissions')
3954
      ->fields('webform_submissions', array('sid'))
3955
      ->condition('nid', $nid)
3956
      ->condition('uid', $uid)
3957
      ->condition('is_draft', 1)
3958
      ->orderBy('submitted', 'DESC')
3959
      ->execute()
3960
      ->fetchField();
3961

    
3962
    if ($sid) {
3963
      $context = array(
3964
        'nid' => $nid,
3965
        'uid' => $uid,
3966
      );
3967
      drupal_alter('webform_draft', $sid, $context);
3968
    }
3969
  }
3970
  return $sid;
3971
}
3972

    
3973
/**
3974
 * Returns a new or cached WebformConditionals object for the specified node.
3975
 *
3976
 * @param object $node
3977
 *   The loaded webform node.
3978
 *
3979
 * @returns object
3980
 *   Object of type WebformConditionals, possibly with the conditionals already
3981
 *   analyzed for dependencies.
3982
 */
3983
function webform_get_conditional_sorter($node) {
3984
  return WebformConditionals::factory($node);
3985
}
3986

    
3987
/**
3988
 * This function is deprecated! Use webform_replace_tokens() instead.
3989
 *
3990
 * @deprecated
3991
 */
3992
function _webform_filter_values($string, $node = NULL, $submission = NULL, $email = NULL, $strict = TRUE) {
3993
  $output = webform_replace_tokens($string, $node, $submission, $email, $strict);
3994
  return $strict ? webform_filter_xss($output) : $output;
3995
}
3996

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

    
4025
  $token_data = array();
4026
  if ($node) {
4027
    $token_data['node'] = $node;
4028
  }
4029
  if ($submission) {
4030
    $token_data['webform-submission'] = $submission;
4031
  }
4032
  if ($email) {
4033
    $token_data['webform-email'] = $email;
4034
  }
4035
  $clear = is_bool($sanitize);
4036
  $string = token_replace($string, $token_data, array('clear' => $clear, 'sanitize' => $sanitize === TRUE));
4037
  if (!$clear) {
4038
    $string = webform_replace_tokens_clear(check_markup($string, $sanitize));
4039
  }
4040
  return $string;
4041
}
4042

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

    
4072
  $text_tokens = token_scan($text);
4073
  if (empty($text_tokens)) {
4074
    return $text;
4075
  }
4076

    
4077
  $replacements = array();
4078
  foreach ($text_tokens as $type => $tokens) {
4079
    $replacements += array_fill_keys($tokens, '');
4080
  }
4081

    
4082
  $tokens = array_keys($replacements);
4083
  $values = array_values($replacements);
4084

    
4085
  return str_replace($tokens, $values, $text);
4086
}
4087

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

    
4125
/**
4126
 * Replace tokens in descriptions and sanitize according to Webform settings.
4127
 */
4128
function webform_filter_descriptions($string, $node = NULL, $submission = NULL) {
4129
  return strlen($string) == 0 ? '' : webform_filter_xss(webform_replace_tokens($string, $node, $submission));
4130
}
4131

    
4132
/**
4133
 * Deprecated! Use webform_filter_descriptions() instead.
4134
 *
4135
 * @deprecated
4136
 */
4137
function _webform_filter_descriptions($string, $node = NULL, $submission = NULL) {
4138
  return webform_filter_descriptions($string, $node, $submission);
4139
}
4140

    
4141
/**
4142
 * Filter labels for display by running through XSS checks.
4143
 */
4144
function webform_filter_xss($string) {
4145
  static $allowed_tags;
4146
  $allowed_tags = isset($allowed_tags) ? $allowed_tags : webform_variable_get('webform_allowed_tags');
4147
  return filter_xss($string, $allowed_tags);
4148
}
4149

    
4150
/**
4151
 * Deprecated! Use webform_filter_xss() instead!
4152
 *
4153
 * @deprecated
4154
 */
4155
function _webform_filter_xss($string) {
4156
  return webform_filter_xss($string);
4157
}
4158

    
4159
/**
4160
 * Utility function to ensure that a webform record exists in the database.
4161
 *
4162
 * @param object $node
4163
 *   The node object to check if a database entry exists.
4164
 *
4165
 * @return bool
4166
 *   This function should always return TRUE if no errors were encountered,
4167
 *   ensuring that a webform table row has been created. Will return FALSE if
4168
 *   a record does not exist and a new one could not be created.
4169
 */
4170
function webform_ensure_record(&$node) {
4171
  if (!$node->webform['record_exists']) {
4172
    // Even though webform_node_insert() would set this property to TRUE,
4173
    // we set record_exists to trigger a difference from the defaults.
4174
    $node->webform['record_exists'] = TRUE;
4175
    webform_node_insert($node);
4176
  }
4177
  return $node->webform['record_exists'];
4178
}
4179

    
4180
/**
4181
 * Utility function to check if a webform record is necessary in the database.
4182
 *
4183
 * If the node is no longer using any webform settings, this function will
4184
 * delete the settings from the webform table. Note that this function will NOT
4185
 * delete rows from the webform table if the node-type is exclusively used for
4186
 * webforms (per the "webform_node_types_primary" variable).
4187
 *
4188
 * @param object $node
4189
 *   The node object to check if a database entry is still required.
4190
 *
4191
 * @return bool
4192
 *   Returns TRUE if the webform still has a record in the database. Returns
4193
 *   FALSE if the webform does not have a record or if the previously existing
4194
 *   record was just deleted.
4195
 */
4196
function webform_check_record(&$node) {
4197
  $webform = $node->webform;
4198
  $webform['record_exists'] = FALSE;
4199
  unset($webform['nid']);
4200

    
4201
  // Don't include empty values in the comparison, this makes it so modules that
4202
  // extend Webform with empty defaults won't affect cleanup of rows.
4203
  $webform = array_filter($webform);
4204
  $defaults = array_filter(webform_node_defaults());
4205
  if ($webform == $defaults && !in_array($node->type, webform_variable_get('webform_node_types_primary'))) {
4206
    webform_node_delete($node);
4207
    $node->webform = webform_node_defaults();
4208
  }
4209
  return $node->webform['record_exists'];
4210
}
4211

    
4212
/**
4213
 * Given a component's form_key and optionally its parent's cid, get its cid(s).
4214
 *
4215
 * @param object $node
4216
 *   A fully loaded webform node object.
4217
 * @param string $form_key
4218
 *   The form key for which to find the cid(s).
4219
 * @param int|null $pid
4220
 *   The cid of the parent component.
4221
 *
4222
 * @return int|int[]
4223
 *   The cid of the component or an array of component ids.
4224
 */
4225
function webform_get_cid($node, $form_key, $pid = NULL) {
4226
  if ($pid === NULL) {
4227
    $cids = array();
4228
    foreach ($node->webform['components'] as $cid => $component) {
4229
      if ((string) $component['form_key'] === (string) $form_key) {
4230
        $cids[] = $cid;
4231
      }
4232
    }
4233
    return $cids;
4234
  }
4235
  else {
4236
    foreach ($node->webform['components'] as $cid => $component) {
4237
      if ((string) $component['form_key'] === (string) $form_key && $component['pid'] == $pid) {
4238
        return $cid;
4239
      }
4240
    }
4241
  }
4242
}
4243

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

    
4282
/**
4283
 * Retrieve a Drupal variable with the appropriate default value.
4284
 */
4285
function webform_variable_get($variable) {
4286
  switch ($variable) {
4287
    case 'webform_blocks':
4288
      $result = variable_get('webform_blocks', array());
4289
      break;
4290

    
4291
    case 'webform_tracking_mode':
4292
      $result = variable_get('webform_tracking_mode', 'cookie');
4293
      break;
4294

    
4295
    case 'webform_allowed_tags':
4296
      $result = variable_get('webform_allowed_tags', array('a', 'em', 'strong', 'code', 'img'));
4297
      break;
4298

    
4299
    case 'webform_email_address_format':
4300
      $result = variable_get('webform_email_address_format', 'long');
4301
      break;
4302

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

    
4307
    case 'webform_default_from_name':
4308
      $result = variable_get('webform_default_from_name', variable_get('site_name', ''));
4309
      break;
4310

    
4311
    case 'webform_default_from_address':
4312
      $result = variable_get('webform_default_from_address', variable_get('site_mail', ini_get('sendmail_from')));
4313
      break;
4314

    
4315
    case 'webform_default_subject':
4316
      $result = variable_get('webform_default_subject', t('Form submission from: [node:title]'));
4317
      break;
4318

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

    
4323
    case 'webform_email_html_capable':
4324
      $result = variable_get('webform_email_html_capable', FALSE);
4325
      break;
4326

    
4327
    case 'webform_default_format':
4328
      $result = variable_get('webform_default_format', 0);
4329
      break;
4330

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

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

    
4339
    case 'webform_node_types':
4340
      $result = webform_node_types();
4341
      break;
4342

    
4343
    case 'webform_node_types_primary':
4344
      $result = variable_get('webform_node_types_primary', array('webform'));
4345
      break;
4346

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

    
4351
    case 'webform_export_format':
4352
      module_load_include('inc', 'webform', 'includes/webform.export');
4353
      $options = webform_export_list();
4354
      $result = variable_get('webform_export_format', 'excel');
4355
      $result = isset($options[$result]) ? $result : key($options);
4356
      break;
4357

    
4358
    case 'webform_csv_delimiter':
4359
      $result = variable_get('webform_csv_delimiter', '\t');
4360
      break;
4361

    
4362
    case 'webform_csv_line_ending':
4363
      $result = variable_get('webform_csv_line_ending', "\n");
4364
      break;
4365

    
4366
    case 'webform_export_wordwrap':
4367
      $result = variable_get('webform_export_wordwrap', 0);
4368
      break;
4369

    
4370
    case 'webform_excel_legacy_exporter':
4371
      $result = variable_get('webform_excel_legacy_exporter', 0);
4372
      break;
4373

    
4374
    case 'webform_progressbar_style':
4375
      $result = variable_get('webform_progressbar_style', array('progressbar_bar', 'progressbar_pagebreak_labels', 'progressbar_include_confirmation'));
4376
      break;
4377

    
4378
    case 'webform_progressbar_label_first':
4379
      $result = variable_get('webform_progressbar_label_first', t('Start'));
4380
      break;
4381

    
4382
    case 'webform_progressbar_label_confirmation':
4383
      $result = variable_get('webform_progressbar_label_confirmation', t('Complete'));
4384
      break;
4385

    
4386
    case 'webform_table':
4387
      $result = variable_get('webform_table', FALSE);
4388
      break;
4389

    
4390
    case 'webform_submission_access_control':
4391
      $result = variable_get('webform_submission_access_control', 1);
4392
      break;
4393

    
4394
    case 'webform_token_access':
4395
      $result = variable_get('webform_token_access', 1);
4396
      break;
4397

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

    
4402
    case 'webform_disabled_components':
4403
      $result = variable_get('webform_disabled_components', array());
4404
      break;
4405
  }
4406
  return $result;
4407
}
4408

    
4409
/**
4410
 * Output the contents of token help used throughout Webform.
4411
 *
4412
 * In earlier versions of Token, a fieldset is used to show all the tokens.
4413
 * Later versions now use a modal dialog that is accessed through a link. If
4414
 * Token module is not available, a message should be displayed.
4415
 */
4416
function theme_webform_token_help($variables) {
4417
  if (!webform_variable_get('webform_token_access')) {
4418
    return '';
4419
  }
4420
  $groups = $variables['groups'];
4421

    
4422
  // Assume dialogs are not supported, show as a fieldset.
4423
  $help = array(
4424
    '#title' => t('Token values'),
4425
    '#type' => 'fieldset',
4426
    '#collapsible' => TRUE,
4427
    '#collapsed' => TRUE,
4428
    '#attributes' => array('class' => array('collapsible', 'collapsed')),
4429
    'help' => array(
4430
      '#markup' => '<p>' . t('This field supports dynamic token values. Common values might be [current-user:mail] or [node:title].') . '</p>',
4431
    ),
4432
    'token_tree' => array(
4433
      '#theme' => 'token_tree',
4434
      '#token_types' => $groups,
4435
    ),
4436
  );
4437

    
4438
  if (!module_exists('token')) {
4439
    // No token module at all. Display a simple suggestion to enable it.
4440
    $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>';
4441
    unset($help['token_tree']);
4442
  }
4443
  else {
4444
    module_load_include('inc', 'token', 'token.pages');
4445
    if (function_exists('token_page_output_tree')) {
4446
      // Token supports dialogs: display simply as a link.
4447
      $help = $help['token_tree'];
4448
      $help['#dialog'] = TRUE;
4449
    }
4450
  }
4451

    
4452
  return render($help);
4453
}
4454

    
4455
/**
4456
 * Convert a name into an identifier.
4457
 *
4458
 * The identifier is safe for machine names, classes, and other ASCII uses.
4459
 */
4460
function _webform_safe_name($name) {
4461
  $new = trim($name);
4462
  $new = _webform_transliterate($new);
4463
  $new = str_replace(array(' ', '-', '/'), array('_', '_', '_'), $new);
4464
  $new = drupal_strtolower($new);
4465
  $new = preg_replace('/[^a-z0-9_]/', '', $new);
4466
  return $new;
4467
}
4468

    
4469
/**
4470
 * Transliterate common non-English characters to 7-bit ASCII.
4471
 */
4472
function _webform_transliterate($name) {
4473
  // If transliteration is available, use it to convert names to ASCII.
4474
  return function_exists('transliteration_get')
4475
            ? transliteration_get($name, '')
4476
            : str_replace(array('€', 'ƒ', 'Š', 'Ž', 'š', 'ž', 'Ÿ', '¢', '¥', 'µ', 'À', 'Á', 'Â', 'Ã', 'Ä', 'Å', 'Ç', 'È', 'É', 'Ê', 'Ë', 'Ì', 'Í', 'Î', 'Ï', 'Ñ', 'Ò', 'Ó', 'Ô', 'Õ', 'Ö', 'Ø', 'Ù', 'Ú', 'Û', 'Ü', 'Ý', 'à', 'á', 'â', 'ã', 'ä', 'å', 'ç', 'è', 'é', 'ê', 'ë', 'ì', 'í', 'î', 'ï', 'ñ', 'ò', 'ó', 'ô', 'õ', 'ö', 'ø', 'ù', 'ú', 'û', 'ü', 'ý', 'ÿ', 'Œ', 'œ', 'Æ', 'Ð', 'Þ', 'ß', 'æ', 'ð', 'þ'),
4477
                          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'),
4478
                          $name);
4479
}
4480

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

    
4521
  if ($name == 'default') {
4522
    $name = webform_variable_get('webform_default_from_name');
4523
  }
4524
  elseif (is_numeric($name) && isset($node->webform['components'][$name])) {
4525
    if (isset($submission->data[$name])) {
4526
      $component = $node->webform['components'][$name];
4527
      $name = $submission->data[$name];
4528

    
4529
      // Convert the FROM name to be the label of select lists.
4530
      if (webform_component_implements($component['type'], 'options')) {
4531
        $options = webform_component_invoke($component['type'], 'options', $component);
4532
        foreach ($name as &$one_name) {
4533
          $one_name = isset($options[$one_name]) ? $options[$one_name] : $one_name;
4534
        }
4535
        // Drop PHP reference.
4536
        unset($one_name);
4537
      }
4538
    }
4539
    else {
4540
      $name = t('Value of !component', array('!component' => $node->webform['components'][$name]['name']));
4541
    }
4542
  }
4543
  elseif (!isset($name)) {
4544
    $name = '';
4545
  }
4546

    
4547
  if ($address == 'default') {
4548
    $address = webform_variable_get('webform_default_from_address');
4549
  }
4550
  elseif (is_numeric($address) && isset($node->webform['components'][$address])) {
4551
    if (isset($submission->data[$address])) {
4552
      $values = $submission->data[$address];
4553
      $address = array();
4554
      foreach ($values as $value) {
4555
        if (isset($mapping) && isset($mapping[$value])) {
4556
          $value = $mapping[$value];
4557
        }
4558
        $address = array_merge($address, explode(',', $value));
4559
      }
4560
    }
4561
    else {
4562
      $address = t('Value of "!component"', array('!component' => $node->webform['components'][$address]['name']));
4563
    }
4564
  }
4565

    
4566
  // Convert single values to an array to simplify processing.
4567
  $address = is_array($address) ? $address : explode(',', $address);
4568
  $name = is_array($name) ? $name : array($name);
4569
  $name_shortage = count($address) - count($name);
4570
  if ($name_shortage > 0) {
4571
    $name += array_fill(count($name), $name_shortage, $name[0]);
4572
  }
4573

    
4574
  foreach ($address as $key => $individual_address) {
4575
    $individual_address = webform_replace_tokens($individual_address, $node, $submission);
4576
    $email_parts = webform_parse_email_address($individual_address);
4577
    if ($format == 'long' && !empty($name[$key]) && !strlen($email_parts['name'])) {
4578
      $individual_name = $name[$key];
4579
      $individual_name = webform_replace_tokens($individual_name, $node, $submission);
4580
      if ($encode) {
4581
        $individual_name = mime_header_encode($individual_name);
4582
      }
4583
      $individual_name = trim($individual_name);
4584
      $individual_address = '"' . $individual_name . '" <' . $individual_address . '>';
4585
    }
4586
    $address[$key] = $individual_address;
4587
  }
4588

    
4589
  return $single ? reset($address) : $address;
4590
}
4591

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

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

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

    
4698
/**
4699
 * Given an email subject, format it with any needed replacements.
4700
 */
4701
function webform_format_email_subject($subject, $node = NULL, $submission = NULL) {
4702
  if ($subject == 'default') {
4703
    $subject = webform_variable_get('webform_default_subject');
4704
  }
4705
  elseif (is_numeric($subject) && isset($node->webform['components'][$subject])) {
4706
    $component = $node->webform['components'][$subject];
4707
    if (isset($submission->data[$subject])) {
4708
      $display_function = '_webform_display_' . $component['type'];
4709
      $value = $submission->data[$subject];
4710

    
4711
      // Convert the value to a clean text representation if possible.
4712
      if (function_exists($display_function)) {
4713
        $display = $display_function($component, $value, 'text');
4714
        $display['#theme_wrappers'] = array();
4715
        $display['#webform_component'] = $component;
4716
        $subject = str_replace("\n", ' ', drupal_render($display));
4717
      }
4718
      else {
4719
        $subject = $value;
4720
      }
4721
    }
4722
    else {
4723
      $subject = t('Value of "!component"', array('!component' => $component['name']));
4724
    }
4725
  }
4726

    
4727
  // Convert arrays to strings (may happen if checkboxes are used as the value).
4728
  if (is_array($subject)) {
4729
    $subject = reset($subject);
4730
  }
4731

    
4732
  return webform_replace_tokens($subject, $node, $submission);
4733
}
4734

    
4735
/**
4736
 * Convert an array of components into a tree.
4737
 */
4738
function _webform_components_tree_build($src, &$tree, $parent, &$page_count) {
4739
  foreach ($src as $cid => $component) {
4740
    if ($component['pid'] == $parent) {
4741
      _webform_components_tree_build($src, $component, $cid, $page_count);
4742
      if ($component['type'] == 'pagebreak') {
4743
        $page_count++;
4744
      }
4745
      $tree['children'][$cid] = $component;
4746
      $tree['children'][$cid]['page_num'] = $page_count;
4747
    }
4748
  }
4749
  return $tree;
4750
}
4751

    
4752
/**
4753
 * Flatten a component tree into a flat list.
4754
 */
4755
function _webform_components_tree_flatten($tree) {
4756
  $components = array();
4757
  foreach ($tree as $cid => $component) {
4758
    if (isset($component['children'])) {
4759
      unset($component['children']);
4760
      $components[$cid] = $component;
4761
      // array_merge() can't be used here because the keys are numeric.
4762
      $children = _webform_components_tree_flatten($tree[$cid]['children']);
4763
      foreach ($children as $ccid => $ccomponent) {
4764
        $components[$ccid] = $ccomponent;
4765
      }
4766
    }
4767
    else {
4768
      $components[$cid] = $component;
4769
    }
4770
  }
4771
  return $components;
4772
}
4773

    
4774
/**
4775
 * Helper for the uasort in webform_tree_sort()
4776
 */
4777
function _webform_components_sort($a, $b) {
4778
  if ($a['weight'] == $b['weight']) {
4779
    return strcasecmp($a['name'], $b['name']);
4780
  }
4781
  return ($a['weight'] < $b['weight']) ? -1 : 1;
4782
}
4783

    
4784
/**
4785
 * Sort each level of a component tree by weight and name.
4786
 */
4787
function _webform_components_tree_sort($tree) {
4788
  if (isset($tree['children']) && is_array($tree['children'])) {
4789
    $children = array();
4790
    uasort($tree['children'], '_webform_components_sort');
4791
    foreach ($tree['children'] as $cid => $component) {
4792
      $children[$cid] = _webform_components_tree_sort($component);
4793
    }
4794
    $tree['children'] = $children;
4795
  }
4796
  return $tree;
4797
}
4798

    
4799
/**
4800
 * Get a list of all available component definitions.
4801
 */
4802
function webform_components($include_disabled = FALSE, $reset = FALSE) {
4803
  static $components, $enabled;
4804

    
4805
  if (!isset($components) || $reset) {
4806
    $components = array();
4807
    $disabled = array_flip(webform_variable_get('webform_disabled_components'));
4808
    foreach (module_implements('webform_component_info') as $module) {
4809
      $module_components = module_invoke($module, 'webform_component_info');
4810
      foreach ($module_components as $type => $info) {
4811
        $module_components[$type]['module'] = $module;
4812
        $module_components[$type]['enabled'] = !array_key_exists($type, $disabled);
4813
      }
4814
      $components += $module_components;
4815
    }
4816
    drupal_alter('webform_component_info', $components);
4817
    uasort($components, function ($a, $b) {
4818
      return strnatcasecmp($a['label'], $b['label']);
4819
    });
4820
    $enabled = array_diff_key($components, $disabled);
4821
  }
4822

    
4823
  return $include_disabled ? $components : $enabled;
4824
}
4825

    
4826
/**
4827
 * Build a list of components suitable for use as select list options.
4828
 */
4829
function webform_component_options($include_disabled = FALSE) {
4830
  $component_info = webform_components($include_disabled);
4831
  $options = array();
4832
  foreach ($component_info as $type => $info) {
4833
    $options[$type] = $info['label'];
4834
  }
4835
  return $options;
4836
}
4837

    
4838
/**
4839
 * Load a component file into memory.
4840
 *
4841
 * @param $component_type
4842
 *   The string machine name of a component.
4843
 */
4844
function webform_component_include($component_type) {
4845
  static $included = array();
4846

    
4847
  // No need to load components that have already been added once.
4848
  if (!isset($included[$component_type])) {
4849
    $components = webform_components(TRUE);
4850
    $included[$component_type] = TRUE;
4851

    
4852
    if (isset($components[$component_type]['file'])) {
4853
      $info = $components[$component_type];
4854
      $pathinfo = pathinfo($info['file']);
4855
      $basename = basename($pathinfo['basename'], '.' . $pathinfo['extension']);
4856
      $path = (!empty($pathinfo['dirname']) ? $pathinfo['dirname'] . '/' : '') . $basename;
4857
      module_load_include($pathinfo['extension'], $info['module'], $path);
4858
    }
4859
  }
4860
}
4861

    
4862
/**
4863
 * Invoke a component callback.
4864
 *
4865
 * @param $type
4866
 *   The component type as a string.
4867
 * @param $callback
4868
 *   The callback to execute.
4869
 * @param ...
4870
 *   Any additional parameters required by the $callback.
4871
 *
4872
 * @return mixed
4873
 *   Return value of the callback on success and FALSE on failure.
4874
 */
4875
function webform_component_invoke($type, $callback) {
4876
  $args = func_get_args();
4877
  $type = array_shift($args);
4878
  $callback = array_shift($args);
4879
  $function = '_webform_' . $callback . '_' . $type;
4880
  webform_component_include($type);
4881
  if (function_exists($function)) {
4882
    return call_user_func_array($function, $args);
4883
  }
4884
}
4885

    
4886
/**
4887
 * Check if a component implements a particular hook.
4888
 *
4889
 * @param $type
4890
 *   The component type as a string.
4891
 * @param $callback
4892
 *   The callback to check.
4893
 *
4894
 * @return bool
4895
 *   Whether or not the hook is implemented.
4896
 */
4897
function webform_component_implements($type, $callback) {
4898
  $function = '_webform_' . $callback . '_' . $type;
4899
  webform_component_include($type);
4900
  return function_exists($function);
4901
}
4902

    
4903
/**
4904
 * Form API #process function to expand a webform conditional element.
4905
 */
4906
function webform_conditional_expand($element) {
4907
  module_load_include('inc', 'webform', 'includes/webform.conditionals');
4908
  return _webform_conditional_expand($element);
4909
}
4910

    
4911
/**
4912
 * Add class and wrapper class attributes to an element.
4913
 */
4914
function _webform_component_classes(&$element, $component) {
4915
  if (isset($component['extra']['css_classes']) && drupal_strlen($component['extra']['css_classes'])) {
4916
    $element['#attributes']['class'] = isset($element['#attributes']['class']) ? $element['#attributes']['class'] : array();
4917
    $element['#attributes']['class'] = array_merge($element['#attributes']['class'], explode(' ', $component['extra']['css_classes']));
4918
  }
4919
  if (isset($component['extra']['wrapper_classes']) && drupal_strlen($component['extra']['wrapper_classes'])) {
4920
    $element['#wrapper_attributes']['class'] = isset($element['#wrapper_attributes']['class']) ? $element['#wrapper_attributes']['class'] : array();
4921
    $element['#wrapper_attributes']['class'] = array_merge($element['#wrapper_attributes']['class'], explode(' ', $component['extra']['wrapper_classes']));
4922
  }
4923
}
4924

    
4925
/**
4926
 * Disable the Drupal page cache.
4927
 */
4928
function webform_disable_page_cache() {
4929
  drupal_page_is_cacheable(FALSE);
4930
}
4931

    
4932
/**
4933
 * Set the necessary breadcrumb for the page we are on.
4934
 *
4935
 * @param object $node
4936
 *   The loaded webform node.
4937
 * @param bool|object $submission
4938
 *   The submission if the current page is viewing or dealing with a submission,
4939
 *   or TRUE to just include the webform node in the breadcrumbs (used for
4940
 *   the submission completion confirmation page), or NULL for no extra
4941
 *   processing.
4942
 */
4943
function webform_set_breadcrumb($node, $submission = NULL) {
4944
  $node_path = "node/{$node->nid}";
4945

    
4946
  // Set the href of the current menu item to be the node's path. This has two
4947
  // effects. The active trail will be to the node's prefered menu tree
4948
  // location, expanding the menu as appropriate. And the breadcrumbs will be
4949
  // set as if the current page were under the node's preferred location.
4950
  // Note that menu_tree_set_path() could be used to set the path for the menu,
4951
  // but it will not affect the breadcrumbs when the webform is not in the
4952
  // default menu.
4953
  menu_set_item(NULL, array('href' => $node_path) + menu_get_item());
4954

    
4955
  if ($submission) {
4956
    $breadcrumb = menu_get_active_breadcrumb();
4957

    
4958
    // Append the node title (or its menu name), in case it isn't in the path already.
4959
    $active_trail = menu_get_active_trail();
4960
    $last_active = end($active_trail);
4961
    $breadcrumb[] = $last_active['href'] === $node_path && !empty($last_active['in_active_trail'])
4962
                      ? l($last_active['title'], $node_path, $last_active['localized_options'])
4963
                      : l($node->title, $node_path);
4964

    
4965
    // Setting the current menu href will cause the submission title and current
4966
    // tab (if not the default tab) to be added to the active path when the
4967
    // webform is in the default location in the menu (node/NID). The title
4968
    // is desirable, but the tab name (for example Edit or Delete) isn't.
4969
    if (preg_match('/href=".*"/', end($breadcrumb), $matches)) {
4970
      foreach ($breadcrumb as $index => $link) {
4971
        if (stripos($link, $matches[0]) !== FALSE) {
4972
          $breadcrumb = array_slice($breadcrumb, 0, $index + 1);
4973
          break;
4974
        }
4975
      }
4976
    }
4977

    
4978
    // If the user is dealing with a submission, then the breadcrumb should
4979
    // be fudged to allow them to return to a likely list of webforms.
4980
    // Note that this isn't necessarily where they came from, but it's the
4981
    // best guess available.
4982
    if (is_object($submission)) {
4983
      if (webform_results_access($node)) {
4984
        $breadcrumb[] = l(t('Webform results'), $node_path . '/webform-results');
4985
      }
4986
      elseif (user_access('access own webform results')) {
4987
        $breadcrumb[] = l(t('Submissions'), $node_path . '/submissions');
4988
      }
4989
    }
4990

    
4991
    drupal_set_breadcrumb($breadcrumb);
4992
  }
4993
}
4994

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

    
5029
  $return = array();
5030

    
5031
  // Check for a date string.
5032
  if ($type == 'date' || !isset($type)) {
5033
    $return['year'] = $matches[2] !== '' ? (int) $matches[2] : '';
5034
    $return['month'] = $matches[3] !== '' ? (int) $matches[3] : '';
5035
    $return['day'] = $matches[4] !== '' ? (int) $matches[4] : '';
5036
  }
5037

    
5038
  // Check for a time string.
5039
  if ($type == 'time' || !isset($type)) {
5040
    $return['hour'] = $matches[6] !== '' ? (int) $matches[6] : '';
5041
    $return['minute'] = $matches[7] !== '' ? (int) $matches[7] : '';
5042
    $return['second'] = $matches[8] !== '' ? (int) $matches[8] : '';
5043
  }
5044

    
5045
  return $return;
5046
}
5047

    
5048
/**
5049
 * Convert an array of a date or time into an ISO 8601 compatible string.
5050
 *
5051
 * @param $array
5052
 *   The array to convert to a date or time string.
5053
 * @param $type
5054
 *   If wanting a specific string format back specify either "date" or "time".
5055
 *   Otherwise a full ISO 8601 date and time string will be returned.
5056
 *
5057
 * @return string
5058
 *   Date in string format
5059
 */
5060
function webform_date_string($array, $type = NULL) {
5061
  $string = '';
5062

    
5063
  if ($type == 'date' || !isset($type)) {
5064
    $string .= empty($array['year']) ? '0000' : sprintf('%04d', $array['year']);
5065
    $string .= '-';
5066
    $string .= empty($array['month']) ? '00' : sprintf('%02d', $array['month']);
5067
    $string .= '-';
5068
    $string .= empty($array['day']) ? '00' : sprintf('%02d', $array['day']);
5069
  }
5070

    
5071
  if (!isset($type)) {
5072
    $string .= 'T';
5073
  }
5074

    
5075
  if ($type == 'time' || !isset($type)) {
5076
    $string .= empty($array['hour']) ? '00' : sprintf('%02d', $array['hour']);
5077
    $string .= ':';
5078
    $string .= empty($array['minute']) ? '00' : sprintf('%02d', $array['minute']);
5079
    $string .= ':';
5080
    $string .= empty($array['second']) ? '00' : sprintf('%02d', $array['second']);
5081
  }
5082

    
5083
  return $string;
5084
}
5085

    
5086
/**
5087
 * Get a date format according to the site settings.
5088
 *
5089
 * @param $type
5090
 *   A choice of 'short', 'medium', 'long' , or other user-defined date formats.
5091
 *   Use NULL for the webform-specific date format choosen in the webform
5092
 *   settings.
5093
 * @param array $exclude
5094
 *   An array containing 'day', 'month', and/or 'year' if they should be
5095
 *   removed from the format.
5096
 *
5097
 * @return string
5098
 *   A date/time format string.
5099
 */
5100
function webform_date_format($type = NULL, array $exclude = array()) {
5101
  static $formats = array();
5102
  $id = $type . ':' . implode('', $exclude);
5103
  if (!isset($formats[$id])) {
5104
    $type_name = $type ? $type : webform_variable_get('webform_date_type');
5105

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

    
5109
    // Date/Time formatting characters
5110
    // WHAT           REQUIRED (at least 1) OPTIONAL (allowed but insufficient)
5111
    // ---------------------------------------------------------------------------
5112
    // Day-of-week                          DlNw
5113
    // Day            dj                    Stz
5114
    // Month          FmMn
5115
    // Year           oYy                   L
5116
    //
5117
    //                NOT ALLOWED
5118
    // --------------------------------------------------------------------------
5119
    // Time           aABgGhHisueIOPTZ
5120
    // Special        /.,-: <space>
5121
    //
5122
    // Strip Time and Special characters from the beginning and end of format.
5123
    $date_format = trim($format, 'aABgGhHisueIOPTZ/.,-: ');
5124

    
5125
    // Ensure that a day, month, and year value are present. Use a default
5126
    // format if all the values are not found. This regular expression uses
5127
    // (?= ), the positive lookahead assertion. It asserts that there are some
5128
    // optional characters (.*) followed by one of the day, month, or year
5129
    // characters. Because it is an assertion, it doesn't consume the
5130
    // characters, so the day, month, and year can be in any order.
5131
    if (!preg_match('/(?=.*[dj])(?=.*[FmMn])(?=.*[oYy])/', $date_format)) {
5132
      $date_format = 'm/d/Y';
5133
    }
5134

    
5135
    // Remove any excluded portions.
5136
    $strip = array(
5137
      'day' => 'DlNwdjStz',
5138
      'month' => 'FmMn',
5139
      'year' => 'oYyL',
5140
    );
5141
    foreach ($exclude as $field) {
5142
      // Strip the format and any trailing /.,-: or space.
5143
      $date_format = preg_replace('#[' . $strip[$field] . ']+[/\.,\-: ]*#', '', $date_format);
5144
      $date_format = trim($date_format, '/.,-: ');
5145
    }
5146

    
5147
    $formats[$id] = $date_format;
5148
  }
5149

    
5150
  return $formats[$id];
5151
}
5152

    
5153
/**
5154
 * Return a date in the desired format taking into consideration user timezones.
5155
 */
5156
function webform_strtodate($format, $string, $timezone_name = NULL, $reference_timestamp = NULL) {
5157
  global $user;
5158

    
5159
  // Adjust the time based on the user or site timezone.
5160
  if (variable_get('configurable_timezones', 1) && $timezone_name == 'user' && $user->uid) {
5161
    $timezone_name = isset($GLOBALS['user']->timezone) ? $GLOBALS['user']->timezone : 'UTC';
5162
  }
5163
  // If the timezone is still empty or not set, use the site timezone.
5164
  if (empty($timezone_name) || $timezone_name == 'user') {
5165
    $timezone_name = variable_get('date_default_timezone', 'UTC');
5166
  }
5167

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

    
5200
/**
5201
 * Get a timestamp in GMT time, ensuring timezone accuracy.
5202
 */
5203
function webform_strtotime($date) {
5204
  $current_tz = date_default_timezone_get();
5205
  date_default_timezone_set('UTC');
5206
  $timestamp = strtotime($date);
5207
  date_default_timezone_set($current_tz);
5208
  return $timestamp;
5209
}
5210

    
5211
/**
5212
 * Wrapper function for i18n_string() if i18nstrings enabled.
5213
 */
5214
function webform_tt($name, $string, $langcode = NULL, $update = FALSE) {
5215
  if (function_exists('i18n_string')) {
5216
    $options = array(
5217
      'langcode' => $langcode,
5218
      'update' => $update,
5219
    );
5220
    return i18n_string($name, $string, $options);
5221
  }
5222
  else {
5223
    return $string;
5224
  }
5225
}
5226

    
5227
/**
5228
 * Returns an IP Address or anonymized IP Address for confidential webforms.
5229
 */
5230
function webform_ip_address($node) {
5231
  return $node->webform['confidential'] ? t('(unknown)') : ip_address();
5232
}
5233

    
5234
/**
5235
 * Implements hook_views_api().
5236
 */
5237
function webform_views_api() {
5238
  return array(
5239
    'api' => 3.0,
5240
    'path' => drupal_get_path('module', 'webform') . '/views',
5241
  );
5242
}
5243

    
5244
/**
5245
 * Implements hook_views_default_views().
5246
 */
5247
function webform_views_default_views() {
5248
  $path = './' . drupal_get_path('module', 'webform') . '/views/default_views/*.inc';
5249
  $views = array();
5250
  foreach (glob($path) as $views_filename) {
5251
    require_once $views_filename;
5252
  }
5253
  return $views;
5254
}
5255

    
5256
/**
5257
 * Implements hook_field_extra_fields().
5258
 */
5259
function webform_field_extra_fields() {
5260
  $extra = array();
5261
  foreach (webform_node_types() as $type) {
5262
    $extra['node'][$type]['display']['webform'] = array(
5263
      'label' => t('Webform'),
5264
      'description' => t('Webform client form.'),
5265
      'weight' => 10,
5266
    );
5267
  }
5268
  return $extra;
5269
}
5270

    
5271
/**
5272
 * Implements hook_date_views_extra_tables().
5273
 */
5274
function webform_date_views_extra_tables() {
5275
  return array('webform_submissions' => 'webform_submissions');
5276
}
5277

    
5278
/**
5279
 * Returns the next serial number for a given node and increments it.
5280
 *
5281
 * @param int $nid
5282
 *   The nid of the node.
5283
 *
5284
 * @return int
5285
 *   The next value of the serial number.
5286
 */
5287
function _webform_submission_serial_next_value($nid) {
5288
  // Use a transaction with SELECT ... FOR UPDATE to lock the row between
5289
  // the SELECT and the UPDATE, ensuring that multiple Webform submissions
5290
  // at the same time do not have duplicate numbers. FOR UPDATE must be inside
5291
  // a transaction. The return value of db_transaction() must be assigned or the
5292
  // transaction will commit immediately. The transaction will commit when $txn
5293
  // goes out-of-scope.
5294
  $txn = db_transaction();
5295

    
5296
  // Get the next_serial value.
5297
  $next_serial = db_select('webform', 'w')
5298
    // Only add FOR UPDATE when incrementing.
5299
    ->forUpdate()
5300
    ->fields('w', array('next_serial'))
5301
    ->condition('nid', $nid)
5302
    ->execute()
5303
    ->fetchField();
5304

    
5305
  // $next_serial must be greater than any existing serial number.
5306
  $next_serial = max($next_serial, _webform_submission_serial_next_value_used($nid));
5307

    
5308
  // Increment the next_value.
5309
  db_update('webform')
5310
    ->fields(array('next_serial' => $next_serial + 1))
5311
    ->condition('nid', $nid)
5312
    ->execute();
5313

    
5314
  return $next_serial;
5315
}
5316

    
5317
/**
5318
 * Returns the next submission serial number to be used.
5319
 *
5320
 * This is based on the submissions in the database.
5321
 *
5322
 * @param int $nid
5323
 *   The Node ID of the Webform.
5324
 *
5325
 * @return int
5326
 *   The largest serial number used by a submission plus 1 for the specified
5327
 *   node or 1 when there are no submissions.
5328
 */
5329
function _webform_submission_serial_next_value_used($nid) {
5330
  $max_serial = db_select('webform_submissions');
5331
  $max_serial->addExpression('MAX(serial)');
5332
  $max_serial = $max_serial
5333
    ->condition('nid', $nid)
5334
    ->execute()
5335
    ->fetchField();
5336
  // $max_serial will be a numeric string or NULL.
5337
  return $max_serial + 1;
5338
}
5339

    
5340
/**
5341
 * Alter the node before saving a clone.
5342
 *
5343
 * @param object $node
5344
 *   Reference to the fully loaded node object being saved (the clone) that
5345
 *   can be altered as needed.
5346
 * @param array $context
5347
 *   An array of context describing the clone operation. The keys are:
5348
 *   - 'method' : Can be either 'prepopulate' or 'save-edit'.
5349
 *   - 'original_node' : The original fully loaded node object being cloned.
5350
 *
5351
 * @see clone_node_save()
5352
 * @see drupal_alter()
5353
 */
5354
function webform_clone_node_alter(&$node, array $context) {
5355
  if (isset($node->webform)) {
5356
    $defaults = webform_node_defaults();
5357
    $node->webform['next_serial'] = $defaults['next_serial'];
5358
  }
5359
}
5360

    
5361
/**
5362
 * Check if the last form submission exceeded the servers max_input_vars limit.
5363
 *
5364
 * Optionally preflight the current form to be returned in this request.
5365
 *
5366
 * @param array $form
5367
 *   Reference to the form, which will be changed if $parent_key is set.
5368
 * @param array $form_state
5369
 *   Form's state or NULL for no form state check.
5370
 * @param string $detect_key
5371
 *   A key that will always be present in the posted data when an actual form
5372
 *   submission has been made.
5373
 * @param string $parent_key
5374
 *   Omit to not preflight the form, or the array key for the parent of where
5375
 *   the preflight warning should be inserted into the form.
5376
 */
5377
function webform_input_vars_check(array &$form, array $form_state, $detect_key, $parent_key = NULL) {
5378
  if (isset($parent_key)) {
5379
    $form['#pre_render'] = array('webform_pre_render_input_vars');
5380
    $form['#input_var_waring_parent'] = $parent_key;
5381
  }
5382
  if (!empty($form_state['input']) && array_key_exists($detect_key, $form_state['input']) && !array_key_exists('form_id', $form_state['input'])) {
5383
    // A form was submitted with POST, but the form_id was missing. The most likely cause of this
5384
    // is that the POST was truncated because PHP exceeded its max_input_vars limit.
5385
    $subs = array(
5386
      '@count' => webform_count_terminals($_POST),
5387
      '@limit' => (int) ini_get('max_input_vars'),
5388
    );
5389
    drupal_set_message(user_access('administer site configuration')
5390
                          ? t('This form could not be submitted because $_POST was truncated to @count input vars.  PHP max_input_vars is @limit and needs to be increased.', $subs)
5391
                          : t('This form could not be submitted because it exceeds the server configuration. Contact the administrator.'),
5392
                      'error');
5393
    watchdog('webform',
5394
      'POST truncated to @count input vars. PHP max_input_vars is @limit. Increase max_input_vars.',
5395
      $subs,
5396
      WATCHDOG_ERROR);
5397
  }
5398
}
5399

    
5400
/**
5401
 * Checks the number of input form elements on this page.
5402
 *
5403
 * This ensures that the PHP max_input_vars limit is not exceeded.
5404
 *
5405
 * Install this function as a #pre_render function.
5406
 */
5407
function webform_pre_render_input_vars($element) {
5408
  // Determine the limit on input vars for this server configuration.
5409
  $limit = ini_get('max_input_vars');
5410
  if ($limit) {
5411
    // Estimate the number of input vars needed to see if the PHP limit has been exceeded.
5412
    // Additional input_vars: op.
5413
    $count = 1 + webform_count_input_vars($element);
5414
    if ($count > $limit * 0.95) {
5415
      $subs = array(
5416
        '@count' => $count,
5417
        '@limit' => $limit,
5418
      );
5419
      $warning = array(
5420
        '#markup' => '<div class="messages warning">' .
5421
        (user_access('administer site configuration')
5422
          ? t('This form contains @count input elements. PHP max_input_vars is @limit and should be increased.', $subs)
5423
          : t('This form may be too long to work properly. Contact the administrator.'))
5424
        . '</div>',
5425
        '#weight' => -1,
5426
      );
5427
      if ($element['#input_var_waring_parent']) {
5428
        $element[$element['#input_var_waring_parent']]['input_vars_warning'] = $warning;
5429
      }
5430
      else {
5431
        $element['input_vars_warning'] = $warning;
5432
      }
5433
      watchdog('webform',
5434
        'Page contains @count input elements but PHP max_input_vars is only @limit. Increase max_input_vars.',
5435
        $subs,
5436
        WATCHDOG_ERROR);
5437
    }
5438
  }
5439
  return $element;
5440
}
5441

    
5442
/**
5443
 * Counts the number of input form elements.
5444
 *
5445
 * Note that this is somewhat imprecise. The number of input vars returned in
5446
 * $_POST can vary with the form element. For example, a multiple-select
5447
 * listbox returns one input var for each selection actually made.
5448
 *
5449
 * The primary use for this count is for the conditionals page, where only
5450
 * select, textfield, hidden, and token elements are used. If a more accurate
5451
 * count for webform_client_form is needed, a mechanism to predict the number
5452
 * of input elements for each component type and each component instance would
5453
 * be needed.
5454
 *
5455
 * @param array $element
5456
 *   The form whose elements should be counted.
5457
 *
5458
 * @return int
5459
 *   The number of elements in the form that will result in $_POST entries.
5460
 */
5461
function webform_count_input_vars(array $element) {
5462
  static $input_types = array(
5463
    'checkbox' => 1,
5464
    'date' => 1,
5465
    'file' => 1,
5466
    'managed_file' => 1,
5467
    'password' => 1,
5468
    'password_confirm' => 1,
5469
    'radios' => 1,
5470
    'select' => 1,
5471
    'textfield' => 1,
5472
    'textarea' => 1,
5473
    'token' => 1,
5474
    'weight' => 1,
5475
    'hidden' => 1,
5476
    'value' => 1,
5477
    'webform_email' => 1,
5478
    'webform_number' => 1,
5479
  );
5480
  $children = array_intersect_key($element, array_flip(element_children($element)));
5481
  return $children
5482
    ? array_reduce($children, function ($carry, $item) {
5483
      return $carry + webform_count_input_vars($item);
5484
    }, 0)
5485
    : (isset($element['#type']) && isset($input_types[$element['#type']]) ? $input_types[$element['#type']] : 0);
5486
}
5487

    
5488
/**
5489
 * Counts terminals in an array.
5490
 *
5491
 * Useful for counting how many input_vars were returned in $_POST.
5492
 *
5493
 * @param $a
5494
 *   Array or array element to be counted
5495
 *
5496
 * @return int
5497
 *   Number of non-array elements within $a.
5498
 */
5499
function webform_count_terminals($a) {
5500
  return is_array($a)
5501
    ? array_reduce($a, function ($carry, $item) {
5502
      return $carry + webform_count_terminals($item);
5503
    }, 0)
5504
    : 1;
5505
}