Project

General

Profile

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

root / drupal7 / sites / all / modules / webform / webform.module @ 389fb945

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

    
2788
  return $form;
2789
}
2790

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

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

    
2810
  return $form;
2811
}
2812

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

    
3552
  return $values;
3553
}
3554

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

    
3758
  // Determine whether or not this element has form control children. If so and
3759
  // if webform_fieldset_wrap is TRUE, wrap them in a fieldset and use legend
3760
  // instead of label.
3761
  $has_element_children = FALSE;
3762
  if (webform_variable_get('webform_fieldset_wrap')) {
3763
    foreach (array_keys($element) as $key) {
3764
      if (substr($key, 0, 1) !== '#') {
3765
        $has_element_children = TRUE;
3766
        break;
3767
      }
3768
    }
3769
  }
3770

    
3771
  if ($has_element_children) {
3772
    $output .= '<fieldset class="fieldset-invisible">';
3773
  }
3774

    
3775
  switch ($element['#title_display']) {
3776
    case 'inline':
3777
      $output .= $description[$above];
3778
      $description[$above] = '';
3779
    case 'before':
3780
    case 'invisible':
3781
    case 'after':
3782
      if ($has_element_children) {
3783
        $title = '<legend>' . $element['#title'];
3784

    
3785
        if ($element['#required']) {
3786
          $title .= ' ' . theme('form_required_marker', $variables);
3787
        }
3788

    
3789
        $title .= '</legend>';
3790
      }
3791
      else {
3792
        $title = ' ' . theme('form_element_label', $variables);
3793
      }
3794
      break;
3795
  }
3796

    
3797
  $children = ' ' . $description[$above] . $prefix . $element['#children'] . $suffix;
3798
  switch ($element['#title_display']) {
3799
    case 'inline':
3800
    case 'before':
3801
    case 'invisible':
3802
      $output .= $title;
3803
      $output .= $children;
3804
      break;
3805

    
3806
    case 'after':
3807
      $output .= $children;
3808
      $output .= $title;
3809
      break;
3810

    
3811
    case 'none':
3812
    case 'attribute':
3813
      // Output no label and no required marker, only the children.
3814
      $output .= $children;
3815
      break;
3816
  }
3817
  $output .= "\n";
3818

    
3819
  $output .= $description[!$above];
3820

    
3821
  if ($has_element_children) {
3822
    $output .= '</fieldset>';
3823
  }
3824

    
3825
  $output .= "</div>\n";
3826

    
3827
  return $output;
3828
}
3829

    
3830
/**
3831
 * Output a form element in plain text format.
3832
 */
3833
function theme_webform_element_text($variables) {
3834
  $element = $variables['element'];
3835
  $value = $variables['element']['#children'];
3836

    
3837
  $output = '';
3838
  $is_group = webform_component_feature($element['#webform_component']['type'], 'group');
3839

    
3840
  // Output the element title.
3841
  if (isset($element['#title'])) {
3842
    if ($is_group) {
3843
      $output .= '==' . $element['#title'] . '==';
3844
    }
3845
    elseif (!in_array(drupal_substr($element['#title'], -1), array('?', ':', '!', '%', ';', '@'))) {
3846
      $output .= $element['#title'] . ':';
3847
    }
3848
    else {
3849
      $output .= $element['#title'];
3850
    }
3851
  }
3852

    
3853
  // Wrap long values at 65 characters, allowing for a few fieldset indents.
3854
  // It's common courtesy to wrap at 75 characters in e-mails.
3855
  if ($is_group && drupal_strlen($value) > 65) {
3856
    $value = wordwrap($value, 65, "\n");
3857
    $lines = explode("\n", $value);
3858
    foreach ($lines as $key => $line) {
3859
      $lines[$key] = '  ' . $line;
3860
    }
3861
    $value = implode("\n", $lines);
3862
  }
3863

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

    
3867
  // Indent fieldsets.
3868
  if ($is_group) {
3869
    $lines = explode("\n", $output);
3870
    foreach ($lines as $number => $line) {
3871
      if (strlen($line)) {
3872
        $lines[$number] = '  ' . $line;
3873
      }
3874
    }
3875
    $output = implode("\n", $lines);
3876
    $output .= "\n";
3877
  }
3878

    
3879
  if ($output) {
3880
    $output .= "\n";
3881
  }
3882

    
3883
  return $output;
3884
}
3885

    
3886
/**
3887
 * Theme a radio button and another element together.
3888
 *
3889
 * This is used in the e-mail configuration to show a radio button and a text
3890
 * field or select list on the same line.
3891
 */
3892
function theme_webform_inline_radio($variables) {
3893
  $element = $variables['element'];
3894

    
3895
  // Add element's #type and #name as class to aid with JS/CSS selectors.
3896
  $class = array('form-item');
3897
  if (!empty($element['#type'])) {
3898
    $class[] = 'form-type-' . strtr($element['#type'], '_', '-');
3899
  }
3900
  if (!empty($element['#name'])) {
3901
    $class[] = 'form-item-' . strtr($element['#name'], array(' ' => '-', '_' => '-', '[' => '-', ']' => ''));
3902
  }
3903

    
3904
  // Add container-inline to all elements.
3905
  $class[] = 'webform-container-inline';
3906
  if (isset($element['#inline_element']) && isset($variables['element']['#title'])) {
3907
    $variables['element']['#title'] .= ': ';
3908
  }
3909

    
3910
  $output = '<div class="' . implode(' ', $class) . '">' . "\n";
3911
  $output .= ' ' . $element['#children'];
3912
  if (!empty($element['#title'])) {
3913
    $output .= ' ' . theme('webform_inline_radio_label', $variables) . "\n";
3914
  }
3915

    
3916
  if (!empty($element['#description'])) {
3917
    $output .= ' <div class="description">' . $element['#description'] . "</div>\n";
3918
  }
3919

    
3920
  $output .= "</div>\n";
3921

    
3922
  return $output;
3923
}
3924

    
3925
/**
3926
 * Replacement for theme_form_element_label()
3927
 *
3928
 * This varies from theme_element_label in that it allows inline fields such
3929
 * as select and input tags within the label itself.
3930
 */
3931
function theme_webform_inline_radio_label($variables) {
3932
  $element = $variables['element'];
3933
  // This is also used in the installer, pre-database setup.
3934
  $t = get_t();
3935

    
3936
  // If title and required marker are both empty, output no label.
3937
  if ((!isset($element['#title']) || $element['#title'] === '') && empty($element['#required'])) {
3938
    return '';
3939
  }
3940

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

    
3944
  // theme_element_label() does a filter_xss() here, we skip it because we know
3945
  // every use where this theme function is used and we need to allow input and
3946
  // select elements.
3947
  $title = $element['#title'];
3948

    
3949
  $attributes = isset($element['#attributes']) ? $element['#attributes'] : array();
3950

    
3951
  // Style the label as class option to display inline with the element.
3952
  if ($element['#title_display'] == 'after') {
3953
    $attributes['class'][] = 'option';
3954
  }
3955
  // Show label only to screen readers to avoid disruption in visual flows.
3956
  elseif ($element['#title_display'] == 'invisible') {
3957
    $attributes['class'][] = 'element-invisible';
3958
  }
3959

    
3960
  $attributes['class'][] = 'webform-inline-radio';
3961
  if (!empty($element['#id'])) {
3962
    $attributes['for'] = $element['#id'];
3963
  }
3964

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

    
3969
/**
3970
 * Theme the headers when sending an email from webform.
3971
 *
3972
 * @param array $variables
3973
 *   The variables array.
3974
 *
3975
 * @return array
3976
 *   An array of headers to be used when sending a webform email. If headers
3977
 *   for "From", "To", or "Subject" are set, they will take precedence over
3978
 *   the values set in the webform configuration.
3979
 */
3980
function theme_webform_mail_headers(array $variables) {
3981
  $headers = array(
3982
    'X-Mailer' => 'Drupal Webform' . (ini_get('expose_php') ? ' (PHP/' . phpversion() . ')' : ''),
3983
  );
3984
  return $headers;
3985
}
3986

    
3987
/**
3988
 * Check if current user has a draft of this webform, and return the sid.
3989
 */
3990
function _webform_fetch_draft_sid($nid, $uid) {
3991
  // Detect whether a webform draft is being edited. If so, that is the one that
3992
  // should be returned.
3993
  if (isset($_POST['form_id']) && stripos($_POST['form_id'], 'webform_client_form_') === 0 &&
3994
      !empty($_POST['details']['sid']) && empty($_POST['details']['finished'])) {
3995
    // A draft is already being edited.
3996
    $sid = $_POST['details']['sid'];
3997
  }
3998
  else {
3999
    $sid = db_select('webform_submissions')
4000
      ->fields('webform_submissions', array('sid'))
4001
      ->condition('nid', $nid)
4002
      ->condition('uid', $uid)
4003
      ->condition('is_draft', 1)
4004
      ->orderBy('submitted', 'DESC')
4005
      ->execute()
4006
      ->fetchField();
4007

    
4008
    if ($sid) {
4009
      $context = array(
4010
        'nid' => $nid,
4011
        'uid' => $uid,
4012
      );
4013
      drupal_alter('webform_draft', $sid, $context);
4014
    }
4015
  }
4016
  return $sid;
4017
}
4018

    
4019
/**
4020
 * Returns a new or cached WebformConditionals object for the specified node.
4021
 *
4022
 * @param object $node
4023
 *   The loaded webform node.
4024
 *
4025
 * @returns object
4026
 *   Object of type WebformConditionals, possibly with the conditionals already
4027
 *   analyzed for dependencies.
4028
 */
4029
function webform_get_conditional_sorter($node) {
4030
  return WebformConditionals::factory($node);
4031
}
4032

    
4033
/**
4034
 * Wrapper for webform_replace_tokens().
4035
 *
4036
 * @deprecated in webform:7.x-4.0 and is removed from webform:7.x-5.0. Use
4037
 * webform_replace_tokens().
4038
 * @see https://www.drupal.org/project/webform/issues/2038199
4039
 */
4040
function _webform_filter_values($string, $node = NULL, $submission = NULL, $email = NULL, $strict = TRUE) {
4041
  $output = webform_replace_tokens($string, $node, $submission, $email, $strict);
4042
  return $strict ? webform_filter_xss($output) : $output;
4043
}
4044

    
4045
/**
4046
 * Replace tokens with Webform contexts populated.
4047
 *
4048
 * @param $string
4049
 *   The string to have its tokens replaced.
4050
 * @param object $node
4051
 *   If replacing node-level tokens, the node for which tokens will be created.
4052
 * @param $submission
4053
 *   If replacing submission-level tokens, the submission for which tokens will
4054
 *   be created.
4055
 * @param $email
4056
 *   If replacing tokens within the context of an e-mail, the Webform e-mail
4057
 *   settings array.
4058
 * @param $sanitize
4059
 *   Boolean or format name value indicating if the results will be displayed as
4060
 *   HTML output. If FALSE, the contents returned will be unsanitized. This will
4061
 *   also result in all Webform submission tokens being returned as plain-text,
4062
 *   without HTML markup, in preparation for e-mailing or other text-only
4063
 *   purposes (default values, etc.). If TRUE, the tokens only are sanitized by
4064
 *   token_replace. Otherwise $sanitize is the machine name of an import filter
4065
 *   to be used with check_markup().
4066
 */
4067
function webform_replace_tokens($string, $node = NULL, $submission = NULL, $email = NULL, $sanitize = FALSE) {
4068
  // Don't do any filtering if the string is empty.
4069
  if (!strlen(trim($string)) || !webform_variable_get('webform_token_access')) {
4070
    return $string;
4071
  }
4072

    
4073
  $token_data = array();
4074
  if ($node) {
4075
    $token_data['node'] = $node;
4076
  }
4077
  if ($submission) {
4078
    $token_data['webform-submission'] = $submission;
4079
  }
4080
  if ($email) {
4081
    $token_data['webform-email'] = $email;
4082
  }
4083
  $clear = is_bool($sanitize);
4084
  $string = token_replace($string, $token_data, array('clear' => $clear, 'sanitize' => $sanitize === TRUE));
4085
  if (!$clear) {
4086
    $string = webform_replace_tokens_clear(check_markup($string, $sanitize));
4087
  }
4088
  return $string;
4089
}
4090

    
4091
/**
4092
 * Removes tokens from string.
4093
 *
4094
 * Call this function in cases where you need to remove unreplaced tokens but
4095
 * can't call webform_replace_tokens() with the option $clear = TRUE.
4096
 *
4097
 * In some cases the function token_replace() in webform_replace_tokens() needs
4098
 * to be called with the option 'clear' => FALSE, to not remove input filters.
4099
 * For security reasons webform_replace_tokens() is called before
4100
 * check_markup(), where input filters get replaced. Tokens won't be replaced if
4101
 * there is no value provided. These tokens, that is, [current-page:query:*]
4102
 * needs to be removed to not show up in the output.
4103
 *
4104
 * Note: This function was previously named webform_clear_tokens, which
4105
 * conflicted with the webform_clear module, being called as hook_tokens.
4106
 *
4107
 * @param string $text
4108
 *   The text to have its tokens removed.
4109
 *
4110
 * @return mixed|string
4111
 *   Replace tokens with actual value.
4112
 *
4113
 * @see token_replace()
4114
 */
4115
function webform_replace_tokens_clear($text) {
4116
  if (empty($text) || !webform_variable_get('webform_token_access')) {
4117
    return $text;
4118
  }
4119

    
4120
  $text_tokens = token_scan($text);
4121
  if (empty($text_tokens)) {
4122
    return $text;
4123
  }
4124

    
4125
  $replacements = array();
4126
  foreach ($text_tokens as $type => $tokens) {
4127
    $replacements += array_fill_keys($tokens, '');
4128
  }
4129

    
4130
  $tokens = array_keys($replacements);
4131
  $values = array_values($replacements);
4132

    
4133
  return str_replace($tokens, $values, $text);
4134
}
4135

    
4136
/**
4137
 * Replace tokens within a URL, encoding the parts within the query string.
4138
 *
4139
 * @param string $redirect_url
4140
 *   The redirect URL, with everything other than tokens already URL encoded.
4141
 * @param object $node
4142
 *   If replacing node-level tokens, the node for which tokens will be created.
4143
 * @param $submission
4144
 *   If replacing submission-level tokens, the submission for which tokens will
4145
 *   be created.
4146
 *
4147
 * @return array
4148
 *   An array of path and url() options, suitable for a redirect or drupal_goto.
4149
 */
4150
function webform_replace_url_tokens($redirect_url, $node = NULL, $submission = NULL) {
4151
  // Parse the url into its components.
4152
  $parsed_redirect_url = drupal_parse_url($redirect_url);
4153
  // Replace tokens in each component.
4154
  $parsed_redirect_url['path'] = webform_replace_tokens($parsed_redirect_url['path'], $node, $submission);
4155
  if (!empty($parsed_redirect_url['query'])) {
4156
    foreach ($parsed_redirect_url['query'] as $key => $value) {
4157
      $parsed_redirect_url['query'][$key] = trim(webform_replace_tokens($value, $node, $submission));
4158
    }
4159
  }
4160
  $parsed_redirect_url['fragment'] = webform_replace_tokens($parsed_redirect_url['fragment'], $node, $submission);
4161
  // Determine whether the path is internal or external. Paths which contain the
4162
  // site's base url are still considered internal. #webform_external is private
4163
  // to webform.
4164
  $parsed_redirect_url['#webform_external'] = url_is_external($parsed_redirect_url['path']);
4165
  foreach (array(NULL, TRUE, FALSE) as $https) {
4166
    if (stripos($parsed_redirect_url['path'], url('', array('absolute' => TRUE, 'https' => $https))) === 0) {
4167
      $parsed_redirect_url['#webform_external'] = FALSE;
4168
    }
4169
  }
4170
  // Return an array suitable for a form redirect or drupal_goto.
4171
  return array($parsed_redirect_url['path'], $parsed_redirect_url);
4172
}
4173

    
4174
/**
4175
 * Replace tokens in descriptions and sanitize according to Webform settings.
4176
 */
4177
function webform_filter_descriptions($string, $node = NULL, $submission = NULL) {
4178
  return strlen($string) == 0 ? '' : webform_filter_xss(webform_replace_tokens($string, $node, $submission));
4179
}
4180

    
4181
/**
4182
 * Duplicates webform_filter_descriptions().
4183
 *
4184
 * @deprecated in webform:7.x-4.0 and is removed from webform:7.x-5.0. Use
4185
 * webform_filter_descriptions().
4186
 * @see https://www.drupal.org/project/webform/issues/2038199
4187
 */
4188
function _webform_filter_descriptions($string, $node = NULL, $submission = NULL) {
4189
  return webform_filter_descriptions($string, $node, $submission);
4190
}
4191

    
4192
/**
4193
 * Filter labels for display by running through XSS checks.
4194
 */
4195
function webform_filter_xss($string) {
4196
  static $allowed_tags;
4197
  $allowed_tags = isset($allowed_tags) ? $allowed_tags : webform_variable_get('webform_allowed_tags');
4198
  return filter_xss($string, $allowed_tags);
4199
}
4200

    
4201
/**
4202
 * Duplicates webform_filter_xss().
4203
 *
4204
 * @deprecated in webform:7.x-4.0 and is removed from webform:7.x-5.0. Use
4205
 * webform_filter_xss().
4206
 * @see https://www.drupal.org/project/webform/issues/2038199
4207
 */
4208
function _webform_filter_xss($string) {
4209
  return webform_filter_xss($string);
4210
}
4211

    
4212
/**
4213
 * Utility function to ensure that a webform record exists in the database.
4214
 *
4215
 * @param object $node
4216
 *   The node object to check if a database entry exists.
4217
 *
4218
 * @return bool
4219
 *   This function should always return TRUE if no errors were encountered,
4220
 *   ensuring that a webform table row has been created. Will return FALSE if
4221
 *   a record does not exist and a new one could not be created.
4222
 */
4223
function webform_ensure_record(&$node) {
4224
  if (!$node->webform['record_exists']) {
4225
    // Even though webform_node_insert() would set this property to TRUE,
4226
    // we set record_exists to trigger a difference from the defaults.
4227
    $node->webform['record_exists'] = TRUE;
4228
    webform_node_insert($node);
4229
  }
4230
  return $node->webform['record_exists'];
4231
}
4232

    
4233
/**
4234
 * Utility function to check if a webform record is necessary in the database.
4235
 *
4236
 * If the node is no longer using any webform settings, this function will
4237
 * delete the settings from the webform table. Note that this function will NOT
4238
 * delete rows from the webform table if the node-type is exclusively used for
4239
 * webforms (per the "webform_node_types_primary" variable).
4240
 *
4241
 * @param object $node
4242
 *   The node object to check if a database entry is still required.
4243
 *
4244
 * @return bool
4245
 *   Returns TRUE if the webform still has a record in the database. Returns
4246
 *   FALSE if the webform does not have a record or if the previously existing
4247
 *   record was just deleted.
4248
 */
4249
function webform_check_record(&$node) {
4250
  $webform = $node->webform;
4251
  $webform['record_exists'] = FALSE;
4252
  unset($webform['nid']);
4253

    
4254
  // Don't include empty values in the comparison, this makes it so modules that
4255
  // extend Webform with empty defaults won't affect cleanup of rows.
4256
  $webform = array_filter($webform);
4257
  $defaults = array_filter(webform_node_defaults());
4258
  if ($webform == $defaults && !in_array($node->type, webform_variable_get('webform_node_types_primary'))) {
4259
    webform_node_delete($node);
4260
    $node->webform = webform_node_defaults();
4261
  }
4262
  return $node->webform['record_exists'];
4263
}
4264

    
4265
/**
4266
 * Given a component's form_key and optionally its parent's cid, get its cid(s).
4267
 *
4268
 * @param object $node
4269
 *   A fully loaded webform node object.
4270
 * @param string $form_key
4271
 *   The form key for which to find the cid(s).
4272
 * @param int|null $pid
4273
 *   The cid of the parent component.
4274
 *
4275
 * @return int|int[]
4276
 *   The cid of the component or an array of component ids.
4277
 */
4278
function webform_get_cid($node, $form_key, $pid = NULL) {
4279
  if ($pid === NULL) {
4280
    $cids = array();
4281
    foreach ($node->webform['components'] as $cid => $component) {
4282
      if ((string) $component['form_key'] === (string) $form_key) {
4283
        $cids[] = $cid;
4284
      }
4285
    }
4286
    return $cids;
4287
  }
4288
  else {
4289
    foreach ($node->webform['components'] as $cid => $component) {
4290
      if ((string) $component['form_key'] === (string) $form_key && $component['pid'] == $pid) {
4291
        return $cid;
4292
      }
4293
    }
4294
  }
4295
}
4296

    
4297
/**
4298
 * Find the label of a given page based on page breaks.
4299
 *
4300
 * @param object $node
4301
 *   The webform node.
4302
 * @param $form_state
4303
 *   The form's state, if available
4304
 *
4305
 * @return array
4306
 *   An array of all page labels, indexed by page number.
4307
 */
4308
function webform_page_labels($node, $form_state = array()) {
4309
  $page_count = 1;
4310
  $page_labels = array();
4311
  $page_labels[0] = t($node->webform['progressbar_label_first']);
4312
  foreach ($node->webform['components'] as $component) {
4313
    if ($component['type'] == 'pagebreak') {
4314
      if (module_exists('webform_localization')) {
4315
        module_load_include('inc', 'webform_localization', 'includes/webform_localization.i18n');
4316
        $string = webform_localization_i18n_string_name($component['nid'], $component['cid'], '#title');
4317
        $component['name'] = i18n_string($string, $component['name'], array(
4318
          'update' => TRUE,
4319
          'sanitize' => FALSE,
4320
        ));
4321
      }
4322
      $page_labels[$page_count] = $component['name'];
4323
      $page_count++;
4324
    }
4325
  }
4326
  if ($node->webform['preview'] || !empty($form_state['webform']['preview'])) {
4327
    $page_labels[] = $node->webform['preview_title'] ? t($node->webform['preview_title']) : t('Preview');
4328
  }
4329
  if ($node->webform['progressbar_include_confirmation']) {
4330
    $page_labels[] = t($node->webform['progressbar_label_confirmation']);
4331
  }
4332
  return $page_labels;
4333
}
4334

    
4335
/**
4336
 * Retrieve a Drupal variable with the appropriate default value.
4337
 */
4338
function webform_variable_get($variable) {
4339
  switch ($variable) {
4340
    case 'webform_blocks':
4341
      $result = variable_get('webform_blocks', array());
4342
      break;
4343

    
4344
    case 'webform_tracking_mode':
4345
      $result = variable_get('webform_tracking_mode', 'cookie');
4346
      break;
4347

    
4348
    case 'webform_allowed_tags':
4349
      $result = variable_get('webform_allowed_tags', array('a', 'em', 'strong', 'code', 'img'));
4350
      break;
4351

    
4352
    case 'webform_email_address_format':
4353
      $result = variable_get('webform_email_address_format', 'long');
4354
      break;
4355

    
4356
    case 'webform_email_address_individual':
4357
      $result = variable_get('webform_email_address_individual', 0);
4358
      break;
4359

    
4360
    case 'webform_default_from_name':
4361
      $result = variable_get('webform_default_from_name', variable_get('site_name', ''));
4362
      break;
4363

    
4364
    case 'webform_default_from_address':
4365
      $result = variable_get('webform_default_from_address', variable_get('site_mail', ini_get('sendmail_from')));
4366
      break;
4367

    
4368
    case 'webform_default_subject':
4369
      $result = variable_get('webform_default_subject', t('Form submission from: [node:title]'));
4370
      break;
4371

    
4372
    case 'webform_email_replyto':
4373
      $result = variable_get('webform_email_replyto', TRUE);
4374
      break;
4375

    
4376
    case 'webform_email_html_capable':
4377
      $result = variable_get('webform_email_html_capable', FALSE);
4378
      break;
4379

    
4380
    case 'webform_default_format':
4381
      $result = variable_get('webform_default_format', 0);
4382
      break;
4383

    
4384
    case 'webform_format_override':
4385
      $result = variable_get('webform_format_override', 0);
4386
      break;
4387

    
4388
    case 'webform_email_select_max':
4389
      $result = variable_get('webform_email_select_max', 50);
4390
      break;
4391

    
4392
    case 'webform_node_types':
4393
      $result = webform_node_types();
4394
      break;
4395

    
4396
    case 'webform_node_types_primary':
4397
      $result = variable_get('webform_node_types_primary', array('webform'));
4398
      break;
4399

    
4400
    case 'webform_date_type':
4401
      $result = variable_get('webform_date_type', 'medium');
4402
      break;
4403

    
4404
    case 'webform_export_format':
4405
      module_load_include('inc', 'webform', 'includes/webform.export');
4406
      $options = webform_export_list();
4407
      $result = variable_get('webform_export_format', 'excel');
4408
      $result = isset($options[$result]) ? $result : key($options);
4409
      break;
4410

    
4411
    case 'webform_csv_delimiter':
4412
      $result = variable_get('webform_csv_delimiter', '\t');
4413
      break;
4414

    
4415
    case 'webform_csv_line_ending':
4416
      $result = variable_get('webform_csv_line_ending', "\n");
4417
      break;
4418

    
4419
    case 'webform_export_wordwrap':
4420
      $result = variable_get('webform_export_wordwrap', 0);
4421
      break;
4422

    
4423
    case 'webform_excel_legacy_exporter':
4424
      $result = variable_get('webform_excel_legacy_exporter', 0);
4425
      break;
4426

    
4427
    case 'webform_progressbar_style':
4428
      $result = variable_get('webform_progressbar_style', array('progressbar_bar', 'progressbar_pagebreak_labels', 'progressbar_include_confirmation'));
4429
      break;
4430

    
4431
    case 'webform_progressbar_label_first':
4432
      $result = variable_get('webform_progressbar_label_first', t('Start'));
4433
      break;
4434

    
4435
    case 'webform_progressbar_label_confirmation':
4436
      $result = variable_get('webform_progressbar_label_confirmation', t('Complete'));
4437
      break;
4438

    
4439
    case 'webform_table':
4440
      $result = variable_get('webform_table', FALSE);
4441
      break;
4442

    
4443
    case 'webform_submission_access_control':
4444
      $result = variable_get('webform_submission_access_control', 1);
4445
      break;
4446

    
4447
    case 'webform_token_access':
4448
      $result = variable_get('webform_token_access', 1);
4449
      break;
4450

    
4451
    case 'webform_update_batch_size':
4452
      $result = variable_get('webform_update_batch_size', 100);
4453
      break;
4454

    
4455
    case 'webform_disabled_components':
4456
      $result = variable_get('webform_disabled_components', array());
4457
      break;
4458

    
4459
    case 'webform_fieldset_wrap':
4460
      $result = variable_get('webform_fieldset_wrap', FALSE);
4461
      break;
4462
  }
4463
  return $result;
4464
}
4465

    
4466
/**
4467
 * Output the contents of token help used throughout Webform.
4468
 *
4469
 * In earlier versions of Token, a fieldset is used to show all the tokens.
4470
 * Later versions now use a modal dialog that is accessed through a link. If
4471
 * Token module is not available, a message should be displayed.
4472
 */
4473
function theme_webform_token_help($variables) {
4474
  if (!webform_variable_get('webform_token_access')) {
4475
    return '';
4476
  }
4477
  $groups = $variables['groups'];
4478

    
4479
  // Assume dialogs are not supported, show as a fieldset.
4480
  $help = array(
4481
    '#title' => t('Token values'),
4482
    '#type' => 'fieldset',
4483
    '#collapsible' => TRUE,
4484
    '#collapsed' => TRUE,
4485
    '#attributes' => array('class' => array('collapsible', 'collapsed')),
4486
    'help' => array(
4487
      '#markup' => '<p>' . t('This field supports dynamic token values. Common values might be [current-user:mail] or [node:title].') . '</p>',
4488
    ),
4489
    'token_tree' => array(
4490
      '#theme' => 'token_tree',
4491
      '#token_types' => $groups,
4492
    ),
4493
  );
4494

    
4495
  if (!module_exists('token')) {
4496
    // No token module at all. Display a simple suggestion to enable it.
4497
    $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>';
4498
    unset($help['token_tree']);
4499
  }
4500
  else {
4501
    module_load_include('inc', 'token', 'token.pages');
4502
    if (function_exists('token_page_output_tree')) {
4503
      // Token supports dialogs: display simply as a link.
4504
      $help = $help['token_tree'];
4505
      $help['#dialog'] = TRUE;
4506
    }
4507
  }
4508

    
4509
  return render($help);
4510
}
4511

    
4512
/**
4513
 * Convert a name into an identifier.
4514
 *
4515
 * The identifier is safe for machine names, classes, and other ASCII uses.
4516
 */
4517
function _webform_safe_name($name) {
4518
  $new = trim($name);
4519
  $new = _webform_transliterate($new);
4520
  $new = str_replace(array(' ', '-', '/'), array('_', '_', '_'), $new);
4521
  $new = drupal_strtolower($new);
4522
  $new = preg_replace('/[^a-z0-9_]/', '', $new);
4523
  return $new;
4524
}
4525

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

    
4538
/**
4539
 * Given an email address and a name, format an e-mail address.
4540
 *
4541
 * The address can be the cid of a component with multiple values. When $single
4542
 * is true, a single address is return (the first of any multiples). When not
4543
 * true, an array of addresses is returned.
4544
 *
4545
 * Note that multiple names could be used with multiple addresses, but this
4546
 * capability is not currently possible with the webform UI. Separate names
4547
 * are only used with the From address, which is always single.
4548
 *
4549
 * @param $address
4550
 *   The e-mail address.
4551
 * @param $name
4552
 *   The name to be used in the formatted address. If the address contains a
4553
 *   name in 'Some Name <somename@example.com>' format, $name is ignored.
4554
 * @param object $node
4555
 *   The webform node if replacements will be done.
4556
 * @param $submission
4557
 *   The webform submission values if replacements will be done.
4558
 * @param $encode
4559
 *   Encode the text for use in an e-mail.
4560
 * @param $single
4561
 *   Force a single value to be returned, even if a component expands to
4562
 *   multiple addresses. This is useful to ensure a single e-mail will be
4563
 *   returned for the "From" address.
4564
 * @param $format
4565
 *   The e-mail format, defaults to the site-wide setting. May be "short",
4566
 *   "long", or NULL for the system default.
4567
 * @param $mapping
4568
 *   A mapping array to be applied to the address values.
4569
 *
4570
 * @return string|array
4571
 *   The formatted e-mail address -- or addresses (if not $single)
4572
 */
4573
function webform_format_email_address($address, $name, $node = NULL, $submission = NULL, $encode = TRUE, $single = TRUE, $format = NULL, $mapping = NULL) {
4574
  if (!isset($format)) {
4575
    $format = webform_variable_get('webform_email_address_format');
4576
  }
4577

    
4578
  if ($name == 'default') {
4579
    $name = webform_variable_get('webform_default_from_name');
4580
  }
4581
  elseif (is_numeric($name) && isset($node->webform['components'][$name])) {
4582
    if (isset($submission->data[$name])) {
4583
      $component = $node->webform['components'][$name];
4584
      $name = $submission->data[$name];
4585

    
4586
      // Convert the FROM name to be the label of select lists.
4587
      if (webform_component_implements($component['type'], 'options')) {
4588
        $options = webform_component_invoke($component['type'], 'options', $component);
4589
        foreach ($name as &$one_name) {
4590
          $one_name = isset($options[$one_name]) ? $options[$one_name] : $one_name;
4591
        }
4592
        // Drop PHP reference.
4593
        unset($one_name);
4594
      }
4595
    }
4596
    else {
4597
      $name = t('Value of !component', array('!component' => $node->webform['components'][$name]['name']));
4598
    }
4599
  }
4600
  elseif (!isset($name)) {
4601
    $name = '';
4602
  }
4603

    
4604
  if ($address == 'default') {
4605
    $address = webform_variable_get('webform_default_from_address');
4606
  }
4607
  elseif (is_numeric($address) && isset($node->webform['components'][$address])) {
4608
    if (isset($submission->data[$address])) {
4609
      $values = $submission->data[$address];
4610
      $address = array();
4611
      foreach ($values as $value) {
4612
        if (isset($mapping) && isset($mapping[$value])) {
4613
          $value = $mapping[$value];
4614
        }
4615
        $address = array_merge($address, explode(',', $value));
4616
      }
4617
    }
4618
    else {
4619
      $address = t('Value of "!component"', array('!component' => $node->webform['components'][$address]['name']));
4620
    }
4621
  }
4622

    
4623
  // Convert single values to an array to simplify processing.
4624
  $address = is_array($address) ? $address : explode(',', $address);
4625
  $name = is_array($name) ? $name : array($name);
4626
  $name_shortage = count($address) - count($name);
4627
  if ($name_shortage > 0) {
4628
    $name += array_fill(count($name), $name_shortage, $name[0]);
4629
  }
4630

    
4631
  foreach ($address as $key => $individual_address) {
4632
    $individual_address = trim($individual_address);
4633
    $individual_address = webform_replace_tokens($individual_address, $node, $submission);
4634
    $email_parts = webform_parse_email_address($individual_address);
4635
    if ($format == 'long' && !empty($name[$key]) && !strlen($email_parts['name'])) {
4636
      $individual_name = $name[$key];
4637
      $individual_name = webform_replace_tokens($individual_name, $node, $submission);
4638
      if ($encode) {
4639
        $individual_name = mime_header_encode($individual_name);
4640
      }
4641
      $individual_name = trim($individual_name);
4642
      $individual_address = '"' . $individual_name . '" <' . $individual_address . '>';
4643
    }
4644
    $address[$key] = $individual_address;
4645
  }
4646

    
4647
  return $single ? reset($address) : $address;
4648
}
4649

    
4650
/**
4651
 * Validates an email form element.
4652
 *
4653
 * @param string $emails
4654
 *   An email or list of comma-seperated email addresses. Passed by reference.
4655
 *   Empty emails will be eliminated, and mutiple addresses will be seperated
4656
 *   with a comma and space.
4657
 * @param string $form_name
4658
 *   The name of the form element to receive an error, in form_set_error format.
4659
 * @param bool $allow_empty
4660
 *   TRUE if optional. FALSE if required.
4661
 * @param bool $allow_multiple
4662
 *   TRUE if a list of emails is allowed. FALSE if only one.
4663
 * @param bool $allow_tokens
4664
 *   TRUE if any token should be assumed to contain a valid e-mail address.
4665
 * @param string $format
4666
 *   'short', 'long', or NULL (for default) format. Long format has a name and
4667
 *   the address in angle brackets.
4668
 *
4669
 * @return int|bool
4670
 *   The number of valid addresses found, or FALSE for an invalid email found.
4671
 */
4672
function webform_email_validate(&$emails, $form_name, $allow_empty, $allow_multiple, $allow_tokens, $format = NULL) {
4673
  $nr_valid = webform_valid_email_address($emails, $allow_tokens, $format);
4674
  if ($nr_valid === FALSE) {
4675
    form_set_error($form_name, t('The entered e-mail address "@email" does not appear valid.', array('@email' => $emails)));
4676
  }
4677
  elseif ($nr_valid === 0 && !$allow_empty) {
4678
    form_set_error($form_name, t('When adding a new custom e-mail, the e-mail field is required.'));
4679
  }
4680
  elseif ($nr_valid > 1 && !$allow_multiple) {
4681
    form_set_error($form_name, t('Only one e-mail address is allowed.'));
4682
  }
4683
  return $nr_valid;
4684
}
4685

    
4686
/**
4687
 * Validates email address(es) with optional name(s).
4688
 *
4689
 * @param string $emails
4690
 *   An email address, a list of comma-separated email addresses. If all the
4691
 *   addresses are valid, the list of trimmed, non-empty emails is returned by
4692
 *   reference.
4693
 * @param bool $allow_tokens
4694
 *   TRUE if any token should be assumed to contain a valid e-mail address.
4695
 * @param string $format
4696
 *   'short', 'long', or NULL (for default) format. Long format has a name and
4697
 *   the address in angle brackets.
4698
 *
4699
 * @return bool|int
4700
 *   Returns FALSE if an invalid e-mail address was found, 0 if no email
4701
 *   address(es) were found, or the number of valid e-mail addresses found.
4702
 */
4703
function webform_valid_email_address(&$emails, $allow_tokens = FALSE, $format = NULL) {
4704
  $email_array = array_filter(array_map('trim', explode(',', $emails)));
4705
  $count = 0;
4706
  foreach ($email_array as $email) {
4707
    if ($allow_tokens && webform_variable_get('webform_token_access')) {
4708
      foreach (token_scan($email) as $tokens) {
4709
        foreach ($tokens as $token) {
4710
          $email = str_replace($token, 'admin@example.com', $email);
4711
        }
4712
      }
4713
    }
4714
    $matches = webform_parse_email_address($email, $format);
4715
    if (!valid_email_address($matches['address'])) {
4716
      return FALSE;
4717
    }
4718
    $count++;
4719
  }
4720
  $emails = implode(', ', $email_array);
4721
  return $count;
4722
}
4723

    
4724
/**
4725
 * Parses an e-mail address into name and address.
4726
 *
4727
 * @param string $email
4728
 *   The email address to be parsed, with an optional name.
4729
 * @param string $format
4730
 *   'short', 'long', or NULL (for default) format. Long format has a name and
4731
 *   the address in angle brackets.
4732
 *
4733
 * @return array
4734
 *   Associative array indexed by 'name' and 'address'.
4735
 */
4736
function webform_parse_email_address($email, $format = NULL) {
4737
  if (!$format) {
4738
    $format = webform_variable_get('webform_email_address_format');
4739
  }
4740
  if ($format == 'long') {
4741
    // Match e-mails of the form 'My Name <email@domain.com>'.
4742
    preg_match('/^"?([^<]*?)"? *(?:<(.*)>)?$/', $email, $matches);
4743
    if (isset($matches[2]) && strlen($matches[2])) {
4744
      return array(
4745
        'name' => $matches[1],
4746
        'address' => $matches[2],
4747
      );
4748
    }
4749
  }
4750
  return array(
4751
    'name' => '',
4752
    'address' => $email,
4753
  );
4754
}
4755

    
4756
/**
4757
 * Given an email subject, format it with any needed replacements.
4758
 */
4759
function webform_format_email_subject($subject, $node = NULL, $submission = NULL) {
4760
  if ($subject == 'default') {
4761
    $subject = webform_variable_get('webform_default_subject');
4762
  }
4763
  elseif (is_numeric($subject) && isset($node->webform['components'][$subject])) {
4764
    $component = $node->webform['components'][$subject];
4765
    if (isset($submission->data[$subject])) {
4766
      $display_function = '_webform_display_' . $component['type'];
4767
      $value = $submission->data[$subject];
4768

    
4769
      // Convert the value to a clean text representation if possible.
4770
      if (function_exists($display_function)) {
4771
        $display = $display_function($component, $value, 'text');
4772
        $display['#theme_wrappers'] = array();
4773
        $display['#webform_component'] = $component;
4774
        $subject = str_replace("\n", ' ', drupal_render($display));
4775
      }
4776
      else {
4777
        $subject = $value;
4778
      }
4779
    }
4780
    else {
4781
      $subject = t('Value of "!component"', array('!component' => $component['name']));
4782
    }
4783
  }
4784

    
4785
  // Convert arrays to strings (may happen if checkboxes are used as the value).
4786
  if (is_array($subject)) {
4787
    $subject = reset($subject);
4788
  }
4789

    
4790
  return webform_replace_tokens($subject, $node, $submission);
4791
}
4792

    
4793
/**
4794
 * Convert an array of components into a tree.
4795
 */
4796
function _webform_components_tree_build($src, &$tree, $parent, &$page_count) {
4797
  foreach ($src as $cid => $component) {
4798
    if ($component['pid'] == $parent) {
4799
      _webform_components_tree_build($src, $component, $cid, $page_count);
4800
      if ($component['type'] == 'pagebreak') {
4801
        $page_count++;
4802
      }
4803
      $tree['children'][$cid] = $component;
4804
      $tree['children'][$cid]['page_num'] = $page_count;
4805
    }
4806
  }
4807
  return $tree;
4808
}
4809

    
4810
/**
4811
 * Flatten a component tree into a flat list.
4812
 */
4813
function _webform_components_tree_flatten($tree) {
4814
  $components = array();
4815
  foreach ($tree as $cid => $component) {
4816
    if (isset($component['children'])) {
4817
      unset($component['children']);
4818
      $components[$cid] = $component;
4819
      // array_merge() can't be used here because the keys are numeric.
4820
      $children = _webform_components_tree_flatten($tree[$cid]['children']);
4821
      foreach ($children as $ccid => $ccomponent) {
4822
        $components[$ccid] = $ccomponent;
4823
      }
4824
    }
4825
    else {
4826
      $components[$cid] = $component;
4827
    }
4828
  }
4829
  return $components;
4830
}
4831

    
4832
/**
4833
 * Helper for the uasort in webform_tree_sort()
4834
 */
4835
function _webform_components_sort($a, $b) {
4836
  if ($a['weight'] == $b['weight']) {
4837
    return strcasecmp($a['name'], $b['name']);
4838
  }
4839
  return ($a['weight'] < $b['weight']) ? -1 : 1;
4840
}
4841

    
4842
/**
4843
 * Sort each level of a component tree by weight and name.
4844
 */
4845
function _webform_components_tree_sort($tree) {
4846
  if (isset($tree['children']) && is_array($tree['children'])) {
4847
    $children = array();
4848
    uasort($tree['children'], '_webform_components_sort');
4849
    foreach ($tree['children'] as $cid => $component) {
4850
      $children[$cid] = _webform_components_tree_sort($component);
4851
    }
4852
    $tree['children'] = $children;
4853
  }
4854
  return $tree;
4855
}
4856

    
4857
/**
4858
 * Get a list of all available component definitions.
4859
 */
4860
function webform_components($include_disabled = FALSE, $reset = FALSE) {
4861
  static $components, $enabled;
4862

    
4863
  if (!isset($components) || $reset) {
4864
    $components = array();
4865
    $disabled = array_flip(webform_variable_get('webform_disabled_components'));
4866
    foreach (module_implements('webform_component_info') as $module) {
4867
      $module_components = module_invoke($module, 'webform_component_info');
4868
      foreach ($module_components as $type => $info) {
4869
        $module_components[$type]['module'] = $module;
4870
        $module_components[$type]['enabled'] = !array_key_exists($type, $disabled);
4871
      }
4872
      $components += $module_components;
4873
    }
4874
    drupal_alter('webform_component_info', $components);
4875
    uasort($components, function ($a, $b) {
4876
      return strnatcasecmp($a['label'], $b['label']);
4877
    });
4878
    $enabled = array_diff_key($components, $disabled);
4879
  }
4880

    
4881
  return $include_disabled ? $components : $enabled;
4882
}
4883

    
4884
/**
4885
 * Build a list of components suitable for use as select list options.
4886
 */
4887
function webform_component_options($include_disabled = FALSE) {
4888
  $component_info = webform_components($include_disabled);
4889
  $options = array();
4890
  foreach ($component_info as $type => $info) {
4891
    $options[$type] = $info['label'];
4892
  }
4893
  return $options;
4894
}
4895

    
4896
/**
4897
 * Load a component file into memory.
4898
 *
4899
 * @param $component_type
4900
 *   The string machine name of a component.
4901
 */
4902
function webform_component_include($component_type) {
4903
  static $included = array();
4904

    
4905
  // No need to load components that have already been added once.
4906
  if (!isset($included[$component_type])) {
4907
    $components = webform_components(TRUE);
4908
    $included[$component_type] = TRUE;
4909

    
4910
    if (isset($components[$component_type]['file'])) {
4911
      $info = $components[$component_type];
4912
      $pathinfo = pathinfo($info['file']);
4913
      $basename = basename($pathinfo['basename'], '.' . $pathinfo['extension']);
4914
      $path = (!empty($pathinfo['dirname']) ? $pathinfo['dirname'] . '/' : '') . $basename;
4915
      module_load_include($pathinfo['extension'], $info['module'], $path);
4916
    }
4917
  }
4918
}
4919

    
4920
/**
4921
 * Invoke a component callback.
4922
 *
4923
 * @param $type
4924
 *   The component type as a string.
4925
 * @param $callback
4926
 *   The callback to execute.
4927
 * @param ...
4928
 *   Any additional parameters required by the $callback.
4929
 *
4930
 * @return mixed
4931
 *   Return value of the callback on success and FALSE on failure.
4932
 */
4933
function webform_component_invoke($type, $callback) {
4934
  $args = func_get_args();
4935
  $type = array_shift($args);
4936
  $callback = array_shift($args);
4937
  $function = '_webform_' . $callback . '_' . $type;
4938
  webform_component_include($type);
4939
  if (function_exists($function)) {
4940
    return call_user_func_array($function, $args);
4941
  }
4942
}
4943

    
4944
/**
4945
 * Check if a component implements a particular hook.
4946
 *
4947
 * @param $type
4948
 *   The component type as a string.
4949
 * @param $callback
4950
 *   The callback to check.
4951
 *
4952
 * @return bool
4953
 *   Whether or not the hook is implemented.
4954
 */
4955
function webform_component_implements($type, $callback) {
4956
  $function = '_webform_' . $callback . '_' . $type;
4957
  webform_component_include($type);
4958
  return function_exists($function);
4959
}
4960

    
4961
/**
4962
 * Form API #process function to expand a webform conditional element.
4963
 */
4964
function webform_conditional_expand($element) {
4965
  module_load_include('inc', 'webform', 'includes/webform.conditionals');
4966
  return _webform_conditional_expand($element);
4967
}
4968

    
4969
/**
4970
 * Add class and wrapper class attributes to an element.
4971
 */
4972
function _webform_component_classes(&$element, $component) {
4973
  if (isset($component['extra']['css_classes']) && drupal_strlen($component['extra']['css_classes'])) {
4974
    $element['#attributes']['class'] = isset($element['#attributes']['class']) ? $element['#attributes']['class'] : array();
4975
    $element['#attributes']['class'] = array_merge($element['#attributes']['class'], explode(' ', $component['extra']['css_classes']));
4976
  }
4977
  if (isset($component['extra']['wrapper_classes']) && drupal_strlen($component['extra']['wrapper_classes'])) {
4978
    $element['#wrapper_attributes']['class'] = isset($element['#wrapper_attributes']['class']) ? $element['#wrapper_attributes']['class'] : array();
4979
    $element['#wrapper_attributes']['class'] = array_merge($element['#wrapper_attributes']['class'], explode(' ', $component['extra']['wrapper_classes']));
4980
  }
4981
}
4982

    
4983
/**
4984
 * Disable the Drupal page cache.
4985
 */
4986
function webform_disable_page_cache() {
4987
  drupal_page_is_cacheable(FALSE);
4988
}
4989

    
4990
/**
4991
 * Set the necessary breadcrumb for the page we are on.
4992
 *
4993
 * @param object $node
4994
 *   The loaded webform node.
4995
 * @param bool|object $submission
4996
 *   The submission if the current page is viewing or dealing with a submission,
4997
 *   or TRUE to just include the webform node in the breadcrumbs (used for
4998
 *   the submission completion confirmation page), or NULL for no extra
4999
 *   processing.
5000
 */
5001
function webform_set_breadcrumb($node, $submission = NULL) {
5002
  $node_path = "node/{$node->nid}";
5003

    
5004
  // Set the href of the current menu item to be the node's path. This has two
5005
  // effects. The active trail will be to the node's prefered menu tree
5006
  // location, expanding the menu as appropriate. And the breadcrumbs will be
5007
  // set as if the current page were under the node's preferred location.
5008
  // Note that menu_tree_set_path() could be used to set the path for the menu,
5009
  // but it will not affect the breadcrumbs when the webform is not in the
5010
  // default menu.
5011
  menu_set_item(NULL, array('href' => $node_path) + menu_get_item());
5012

    
5013
  if ($submission) {
5014
    $breadcrumb = menu_get_active_breadcrumb();
5015

    
5016
    // Append the node title (or its menu name), in case it isn't in the path
5017
    // already.
5018
    $active_trail = menu_get_active_trail();
5019
    $last_active = end($active_trail);
5020
    $breadcrumb[] = $last_active['href'] === $node_path && !empty($last_active['in_active_trail'])
5021
                      ? l($last_active['title'], $node_path, $last_active['localized_options'])
5022
                      : l($node->title, $node_path);
5023

    
5024
    // Setting the current menu href will cause the submission title and current
5025
    // tab (if not the default tab) to be added to the active path when the
5026
    // webform is in the default location in the menu (node/NID). The title
5027
    // is desirable, but the tab name (for example Edit or Delete) isn't.
5028
    if (preg_match('/href=".*"/', end($breadcrumb), $matches)) {
5029
      foreach ($breadcrumb as $index => $link) {
5030
        if (stripos($link, $matches[0]) !== FALSE) {
5031
          $breadcrumb = array_slice($breadcrumb, 0, $index + 1);
5032
          break;
5033
        }
5034
      }
5035
    }
5036

    
5037
    // If the user is dealing with a submission, then the breadcrumb should
5038
    // be fudged to allow them to return to a likely list of webforms.
5039
    // Note that this isn't necessarily where they came from, but it's the
5040
    // best guess available.
5041
    if (is_object($submission)) {
5042
      if (webform_results_access($node)) {
5043
        $breadcrumb[] = l(t('Webform results'), $node_path . '/webform-results');
5044
      }
5045
      elseif (user_access('access own webform results')) {
5046
        $breadcrumb[] = l(t('Submissions'), $node_path . '/submissions');
5047
      }
5048
    }
5049

    
5050
    drupal_set_breadcrumb($breadcrumb);
5051
  }
5052
}
5053

    
5054
/**
5055
 * Convert an ISO 8601 date or time into an array.
5056
 *
5057
 * This converts full format dates or times. Either a date or time may be
5058
 * provided, in which case only those portions will be returned. Dashes and
5059
 * colons must be used, never implied.
5060
 *
5061
 * Formats:
5062
 * Dates: YYYY-MM-DD
5063
 * Times: HH:MM:SS
5064
 * Datetimes: YYYY-MM-DDTHH:MM:SS
5065
 *
5066
 * @param $string
5067
 *   An ISO 8601 date, time, or datetime.
5068
 * @param $type
5069
 *   If wanting only specific fields back, specify either "date" or "time".
5070
 *   Leaving empty will return an array with both date and time keys, even if
5071
 *   some are empty. Returns an array with the following keys:
5072
 *   - year
5073
 *   - month
5074
 *   - day
5075
 *   - hour (in 24hr notation)
5076
 *   - minute
5077
 *   - second
5078
 *
5079
 * @return array
5080
 *   Date in array formate.
5081
 */
5082
function webform_date_array($string, $type = NULL) {
5083
  $pattern = '/((\d{4}?)-(\d{2}?)-(\d{2}?))?(T?(\d{2}?):(\d{2}?):(\d{2}?))?/';
5084
  $matches = array();
5085
  preg_match($pattern, $string, $matches);
5086
  $matches += array_fill(0, 9, '');
5087

    
5088
  $return = array();
5089

    
5090
  // Check for a date string.
5091
  if ($type == 'date' || !isset($type)) {
5092
    $return['year'] = $matches[2] !== '' ? (int) $matches[2] : '';
5093
    $return['month'] = $matches[3] !== '' ? (int) $matches[3] : '';
5094
    $return['day'] = $matches[4] !== '' ? (int) $matches[4] : '';
5095
  }
5096

    
5097
  // Check for a time string.
5098
  if ($type == 'time' || !isset($type)) {
5099
    $return['hour'] = $matches[6] !== '' ? (int) $matches[6] : '';
5100
    $return['minute'] = $matches[7] !== '' ? (int) $matches[7] : '';
5101
    $return['second'] = $matches[8] !== '' ? (int) $matches[8] : '';
5102
  }
5103

    
5104
  return $return;
5105
}
5106

    
5107
/**
5108
 * Convert an array of a date or time into an ISO 8601 compatible string.
5109
 *
5110
 * @param $array
5111
 *   The array to convert to a date or time string.
5112
 * @param $type
5113
 *   If wanting a specific string format back specify either "date" or "time".
5114
 *   Otherwise a full ISO 8601 date and time string will be returned.
5115
 *
5116
 * @return string
5117
 *   Date in string format
5118
 */
5119
function webform_date_string($array, $type = NULL) {
5120
  $string = '';
5121

    
5122
  if ($type == 'date' || !isset($type)) {
5123
    $string .= empty($array['year']) ? '0000' : sprintf('%04d', $array['year']);
5124
    $string .= '-';
5125
    $string .= empty($array['month']) ? '00' : sprintf('%02d', $array['month']);
5126
    $string .= '-';
5127
    $string .= empty($array['day']) ? '00' : sprintf('%02d', $array['day']);
5128
  }
5129

    
5130
  if (!isset($type)) {
5131
    $string .= 'T';
5132
  }
5133

    
5134
  if ($type == 'time' || !isset($type)) {
5135
    $string .= empty($array['hour']) ? '00' : sprintf('%02d', $array['hour']);
5136
    $string .= ':';
5137
    $string .= empty($array['minute']) ? '00' : sprintf('%02d', $array['minute']);
5138
    $string .= ':';
5139
    $string .= empty($array['second']) ? '00' : sprintf('%02d', $array['second']);
5140
  }
5141

    
5142
  return $string;
5143
}
5144

    
5145
/**
5146
 * Get a date format according to the site settings.
5147
 *
5148
 * @param $type
5149
 *   A choice of 'short', 'medium', 'long' , or other user-defined date formats.
5150
 *   Use NULL for the webform-specific date format choosen in the webform
5151
 *   settings.
5152
 * @param array $exclude
5153
 *   An array containing 'day', 'month', and/or 'year' if they should be
5154
 *   removed from the format.
5155
 *
5156
 * @return string
5157
 *   A date/time format string.
5158
 */
5159
function webform_date_format($type = NULL, array $exclude = array()) {
5160
  static $formats = array();
5161
  $id = $type . ':' . implode('', $exclude);
5162
  if (!isset($formats[$id])) {
5163
    $type_name = $type ? $type : webform_variable_get('webform_date_type');
5164

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

    
5168
    // Date/Time formatting characters
5169
    // WHAT           REQUIRED (at least 1) OPTIONAL (allowed but insufficient)
5170
    // -------------------------------------------------------------------------
5171
    // Day-of-week                          DlNw
5172
    // Day            dj                    Stz
5173
    // Month          FmMn
5174
    // Year           oYy                   L
5175
    //
5176
    //                NOT ALLOWED
5177
    // -------------------------------------------------------------------------
5178
    // Time           aABgGhHisueIOPTZ
5179
    // Special        /.,-: <space>
5180
    //
5181
    // Strip Time and Special characters from the beginning and end of format.
5182
    $date_format = trim($format, 'aABgGhHisueIOPTZ/.,-: ');
5183

    
5184
    // Ensure that a day, month, and year value are present. Use a default
5185
    // format if all the values are not found. This regular expression uses
5186
    // (?= ), the positive lookahead assertion. It asserts that there are some
5187
    // optional characters (.*) followed by one of the day, month, or year
5188
    // characters. Because it is an assertion, it doesn't consume the
5189
    // characters, so the day, month, and year can be in any order.
5190
    if (!preg_match('/(?=.*[dj])(?=.*[FmMn])(?=.*[oYy])/', $date_format)) {
5191
      $date_format = 'm/d/Y';
5192
    }
5193

    
5194
    // Remove any excluded portions.
5195
    $strip = array(
5196
      'day' => 'DlNwdjStz',
5197
      'month' => 'FmMn',
5198
      'year' => 'oYyL',
5199
    );
5200
    foreach ($exclude as $field) {
5201
      // Strip the format and any trailing /.,-: or space.
5202
      $date_format = preg_replace('#[' . $strip[$field] . ']+[/\.,\-: ]*#', '', $date_format);
5203
      $date_format = trim($date_format, '/.,-: ');
5204
    }
5205

    
5206
    $formats[$id] = $date_format;
5207
  }
5208

    
5209
  return $formats[$id];
5210
}
5211

    
5212
/**
5213
 * Return a date in the desired format taking into consideration user timezones.
5214
 */
5215
function webform_strtodate($format, $string, $timezone_name = NULL, $reference_timestamp = NULL) {
5216
  global $user;
5217

    
5218
  // Adjust the time based on the user or site timezone.
5219
  if (variable_get('configurable_timezones', 1) && $timezone_name == 'user' && $user->uid) {
5220
    $timezone_name = isset($GLOBALS['user']->timezone) ? $GLOBALS['user']->timezone : 'UTC';
5221
  }
5222
  // If the timezone is still empty or not set, use the site timezone.
5223
  if (empty($timezone_name) || $timezone_name == 'user') {
5224
    $timezone_name = variable_get('date_default_timezone', 'UTC');
5225
  }
5226

    
5227
  if (!empty($timezone_name) && class_exists('DateTimeZone')) {
5228
    // Suppress errors if encountered during string conversion. Exceptions are
5229
    // only supported for DateTime in PHP 5.3 and higher.
5230
    try {
5231
      @$timezone = new DateTimeZone($timezone_name);
5232
      if (isset($reference_timestamp)) {
5233
        // A reference for relative dates has been provided.
5234
        // 1. Convert the reference timestamp (in UTC) to a DateTime.
5235
        // 2. Set to time zone to the user or system timezone, recreating the
5236
        //    reference time in the appropriate time zone.
5237
        // 3. Set the time to midnight because when a non-referenced relative
5238
        //    date is created without a time, it is created at midnight (0:00).
5239
        // 4. Adjust to the specified relative (or absolute) time.
5240
        @$datetime = new DateTime('@' . $reference_timestamp);
5241
        @$datetime->setTimezone($timezone)
5242
          ->setTime(0, 0, 0)
5243
          ->modify($string);
5244
      }
5245
      else {
5246
        @$datetime = new DateTime($string, $timezone);
5247
      }
5248
      return @$datetime->format($format);
5249
    }
5250
    catch (Exception $e) {
5251
      return '';
5252
    }
5253
  }
5254
  else {
5255
    return date($format, isset($reference_timestamp) ? strtotime($string, $reference_timestamp) : strtotime($string));
5256
  }
5257
}
5258

    
5259
/**
5260
 * Get a timestamp in GMT time, ensuring timezone accuracy.
5261
 */
5262
function webform_strtotime($date) {
5263
  $current_tz = date_default_timezone_get();
5264
  date_default_timezone_set('UTC');
5265
  $timestamp = strtotime($date);
5266
  date_default_timezone_set($current_tz);
5267
  return $timestamp;
5268
}
5269

    
5270
/**
5271
 * Wrapper function for i18n_string() if i18nstrings enabled.
5272
 */
5273
function webform_tt($name, $string, $langcode = NULL, $update = FALSE) {
5274
  if (function_exists('i18n_string')) {
5275
    $options = array(
5276
      'langcode' => $langcode,
5277
      'update' => $update,
5278
    );
5279
    return i18n_string($name, $string, $options);
5280
  }
5281
  else {
5282
    return $string;
5283
  }
5284
}
5285

    
5286
/**
5287
 * Returns an IP Address or anonymized IP Address for confidential webforms.
5288
 */
5289
function webform_ip_address($node) {
5290
  return $node->webform['confidential'] ? t('(unknown)') : ip_address();
5291
}
5292

    
5293
/**
5294
 * Implements hook_views_api().
5295
 */
5296
function webform_views_api() {
5297
  return array(
5298
    'api' => 3.0,
5299
    'path' => drupal_get_path('module', 'webform') . '/views',
5300
  );
5301
}
5302

    
5303
/**
5304
 * Implements hook_views_default_views().
5305
 */
5306
function webform_views_default_views() {
5307
  $path = './' . drupal_get_path('module', 'webform') . '/views/default_views/*.inc';
5308
  $views = array();
5309
  foreach (glob($path) as $views_filename) {
5310
    require_once $views_filename;
5311
  }
5312
  return $views;
5313
}
5314

    
5315
/**
5316
 * Implements hook_field_extra_fields().
5317
 */
5318
function webform_field_extra_fields() {
5319
  $extra = array();
5320
  foreach (webform_node_types() as $type) {
5321
    $extra['node'][$type]['display']['webform'] = array(
5322
      'label' => t('Webform'),
5323
      'description' => t('Webform client form.'),
5324
      'weight' => 10,
5325
    );
5326
  }
5327
  return $extra;
5328
}
5329

    
5330
/**
5331
 * Implements hook_date_views_extra_tables().
5332
 */
5333
function webform_date_views_extra_tables() {
5334
  return array('webform_submissions' => 'webform_submissions');
5335
}
5336

    
5337
/**
5338
 * Returns the next serial number for a given node and increments it.
5339
 *
5340
 * @param int $nid
5341
 *   The nid of the node.
5342
 *
5343
 * @return int
5344
 *   The next value of the serial number.
5345
 */
5346
function _webform_submission_serial_next_value($nid) {
5347
  // Use a transaction with SELECT ... FOR UPDATE to lock the row between
5348
  // the SELECT and the UPDATE, ensuring that multiple Webform submissions
5349
  // at the same time do not have duplicate numbers. FOR UPDATE must be inside
5350
  // a transaction. The return value of db_transaction() must be assigned or the
5351
  // transaction will commit immediately. The transaction will commit when $txn
5352
  // goes out-of-scope.
5353
  $transaction = db_transaction();
5354

    
5355
  // Get the next_serial value.
5356
  $next_serial = db_select('webform', 'w')
5357
    // Only add FOR UPDATE when incrementing.
5358
    ->forUpdate()
5359
    ->fields('w', array('next_serial'))
5360
    ->condition('nid', $nid)
5361
    ->execute()
5362
    ->fetchField();
5363

    
5364
  // $next_serial must be greater than any existing serial number.
5365
  $next_serial = max($next_serial, _webform_submission_serial_next_value_used($nid));
5366

    
5367
  // Increment the next_value.
5368
  db_update('webform')
5369
    ->fields(array('next_serial' => $next_serial + 1))
5370
    ->condition('nid', $nid)
5371
    ->execute();
5372

    
5373
  return $next_serial;
5374
}
5375

    
5376
/**
5377
 * Returns the next submission serial number to be used.
5378
 *
5379
 * This is based on the submissions in the database.
5380
 *
5381
 * @param int $nid
5382
 *   The Node ID of the Webform.
5383
 *
5384
 * @return int
5385
 *   The largest serial number used by a submission plus 1 for the specified
5386
 *   node or 1 when there are no submissions.
5387