Projet

Général

Profil

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

root / drupal7 / sites / all / modules / webform / webform.module @ 7b2d1845

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
        'stores_data' => FALSE,
1117
        'title_inline' => FALSE,
1118
        'wrapper_classes' => FALSE,
1119
      ),
1120
      'file' => 'components/fieldset.inc',
1121
    ),
1122
    'grid' => array(
1123
      'label' => t('Grid'),
1124
      'description' => t('Allows creation of grid questions, denoted by radio buttons.'),
1125
      'features' => array(
1126
        'default_value' => FALSE,
1127
        'title_inline' => FALSE,
1128
        'title_internal' => TRUE,
1129
        'css_classes' => FALSE,
1130
        'conditional' => FALSE,
1131
        'group' => TRUE,
1132
      ),
1133
      'file' => 'components/grid.inc',
1134
    ),
1135
    'hidden' => array(
1136
      'label' => t('Hidden'),
1137
      'description' => t('A field which is not visible to the user, but is recorded with the submission.'),
1138
      'file' => 'components/hidden.inc',
1139
      'features' => array(
1140
        'required' => FALSE,
1141
        'description' => FALSE,
1142
        'email_address' => TRUE,
1143
        'email_name' => TRUE,
1144
        'title_display' => FALSE,
1145
        'private' => FALSE,
1146
        'wrapper_classes' => FALSE,
1147
        'css_classes' => FALSE,
1148
        'conditional_action_set' => TRUE,
1149
      ),
1150
    ),
1151
    'markup' => array(
1152
      'label' => t('Markup'),
1153
      'description' => t('Displays text as HTML in the form; does not render a field.'),
1154
      'features' => array(
1155
        'analysis' => FALSE,
1156
        'csv' => FALSE,
1157
        'default_value' => FALSE,
1158
        'description' => FALSE,
1159
        'email' => FALSE,
1160
        'required' => FALSE,
1161
        'conditional' => FALSE,
1162
        'stores_data' => FALSE,
1163
        'title_display' => FALSE,
1164
        'private' => FALSE,
1165
        'wrapper_classes' => FALSE,
1166
        'css_classes' => FALSE,
1167
        'conditional_action_set' => TRUE,
1168
      ),
1169
      'file' => 'components/markup.inc',
1170
    ),
1171
    'number' => array(
1172
      'label' => t('Number'),
1173
      'description' => t('A numeric input field (either as textfield or select list).'),
1174
      'features' => array(
1175
        'conditional_action_set' => TRUE,
1176
      ),
1177
      'file' => 'components/number.inc',
1178
      'conditional_type' => 'numeric',
1179
    ),
1180
    'pagebreak' => array(
1181
      'label' => t('Page break'),
1182
      'description' => t('Organize forms into multiple pages.'),
1183
      'features' => array(
1184
        'analysis' => FALSE,
1185
        'conditional' => FALSE,
1186
        'csv' => FALSE,
1187
        'default_value' => FALSE,
1188
        'description' => FALSE,
1189
        'private' => FALSE,
1190
        'required' => FALSE,
1191
        'stores_data' => FALSE,
1192
        'title_display' => FALSE,
1193
        'wrapper_classes' => FALSE,
1194
        'css_classes' => FALSE,
1195
      ),
1196
      'file' => 'components/pagebreak.inc',
1197
    ),
1198
    'select' => array(
1199
      'label' => t('Select options'),
1200
      'description' => t('Allows creation of checkboxes, radio buttons, or select menus.'),
1201
      'file' => 'components/select.inc',
1202
      'features' => array(
1203
        'default_value' => FALSE,
1204
        'email_address' => TRUE,
1205
        'email_name' => TRUE,
1206
        'conditional_action_set' => TRUE,
1207
      ),
1208
      'conditional_type' => 'select',
1209
    ),
1210
    'textarea' => array(
1211
      'label' => t('Textarea'),
1212
      'description' => t('A large text area that allows for multiple lines of input.'),
1213
      'file' => 'components/textarea.inc',
1214
      'features' => array(
1215
        'spam_analysis' => TRUE,
1216
        'placeholder' => TRUE,
1217
        'conditional_action_set' => TRUE,
1218
      ),
1219
    ),
1220
    'textfield' => array(
1221
      'label' => t('Textfield'),
1222
      'description' => t('Basic textfield type.'),
1223
      'file' => 'components/textfield.inc',
1224
      'features' => array(
1225
        'email_name' => TRUE,
1226
        'spam_analysis' => TRUE,
1227
        'placeholder' => TRUE,
1228
        'conditional_action_set' => TRUE,
1229
      ),
1230
    ),
1231
    'time' => array(
1232
      'label' => t('Time'),
1233
      'description' => t('Presents the user with hour and minute fields. Optional am/pm fields.'),
1234
      'features' => array(
1235
        'views_range' => TRUE,
1236
        'css_classes' => FALSE,
1237
      ),
1238
      'file' => 'components/time.inc',
1239
      'conditional_type' => 'time',
1240
    ),
1241
  );
1242

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

    
1257
  return $component_info;
1258
}
1259

    
1260
/**
1261
 * Implements hook_webform_conditional_operator_info().
1262
 */
1263
function webform_webform_conditional_operator_info() {
1264
  module_load_include('inc', 'webform', 'includes/webform.conditionals');
1265
  return _webform_conditional_operator_info();
1266
}
1267

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

    
1281
/**
1282
 * Implements hook_webform_select_options_info().
1283
 */
1284
function webform_webform_select_options_info() {
1285
  module_load_include('inc', 'webform', 'includes/webform.options');
1286
  return _webform_options_info();
1287
}
1288

    
1289
/**
1290
 * Implements hook_webform_webform_submission_actions().
1291
 */
1292
function webform_webform_submission_actions($node, $submission) {
1293
  $actions = array();
1294
  $destination = drupal_get_destination();
1295

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

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

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

    
1319
  return $actions;
1320
}
1321

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

    
1338
  foreach ($node->webform['components'] as $cid => $component) {
1339
    if (webform_component_feature($component['type'], 'file_usage')) {
1340
      $has_file_components = TRUE;
1341
      if (!empty($submission->data[$cid])) {
1342
        foreach ($submission->data[$cid] as $key => $value) {
1343
          if (empty($value)) {
1344
            unset($submission->data[$cid][$key]);
1345
          }
1346
          if (strlen($component['extra']['rename'])) {
1347
            $renameable[$cid][] = $value;
1348
          }
1349
        }
1350
        $new_fids = array_merge($new_fids, $submission->data[$cid]);
1351
      }
1352
    }
1353
  }
1354

    
1355
  if ($has_file_components) {
1356
    // If we're updating a submission, build a list of previous files.
1357
    if (isset($submission->sid)) {
1358
      drupal_static_reset('webform_get_submission');
1359
      $old_submission = webform_get_submission($node->nid, $submission->sid);
1360

    
1361
      foreach ($node->webform['components'] as $cid => $component) {
1362
        if (webform_component_feature($component['type'], 'file_usage')) {
1363
          if (!empty($old_submission->data[$cid])) {
1364
            $old_fids = array_merge($old_fids, $old_submission->data[$cid]);
1365
          }
1366
        }
1367
      }
1368
    }
1369

    
1370
    // Only rename files if this is the first time the submission is being saved
1371
    // as finished.
1372
    if ($submission->is_draft || (isset($old_submission) && !$old_submission->is_draft)) {
1373
      $renameable = array();
1374
    }
1375

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

    
1389
/**
1390
 * Implements hook_webform_submission_insert().
1391
 */
1392
function webform_webform_submission_insert($node, $submission) {
1393
  if (isset($submission->file_usage)) {
1394
    webform_component_include('file');
1395
    webform_file_usage_adjust($submission);
1396
    webform_file_rename($node, $submission);
1397
  }
1398
}
1399

    
1400
/**
1401
 * Implements hook_webform_submission_update().
1402
 */
1403
function webform_webform_submission_update($node, $submission) {
1404
  if (isset($submission->file_usage)) {
1405
    webform_component_include('file');
1406
    webform_file_usage_adjust($submission);
1407
    webform_file_rename($node, $submission);
1408
  }
1409
}
1410

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

    
1438
/**
1439
 * Implements hook_file_download().
1440
 *
1441
 * Only allow users with view webform submissions to download files.
1442
 */
1443
function webform_file_download($uri) {
1444
  module_load_include('inc', 'webform', 'includes/webform.submissions');
1445

    
1446
  // Determine whether this file was a webform upload.
1447
  $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();
1448
  if ($row) {
1449
    $file = file_load($row->fid);
1450
  }
1451
  if (!empty($row->sid)) {
1452
    $submissions = webform_get_submissions(array('sid' => $row->sid));
1453
    $submission = reset($submissions);
1454
  }
1455

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

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

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

    
1502
/**
1503
 * Implements hook_node_type_delete().
1504
 */
1505
function webform_node_type_delete($info) {
1506
  variable_del('webform_node_' . $info->type);
1507
}
1508

    
1509
/**
1510
 * Implements hook_node_insert().
1511
 */
1512
function webform_node_insert($node) {
1513
  if (!variable_get('webform_node_' . $node->type, FALSE)) {
1514
    return;
1515
  }
1516

    
1517
  // If added directly through node_save(), set defaults for the node.
1518
  if (!isset($node->webform)) {
1519
    $node->webform = array();
1520
  }
1521

    
1522
  // Ensure values for all defaults are provided. Useful for importing from
1523
  // older versions into newer ones.
1524
  $node->webform += webform_node_defaults();
1525

    
1526
  // Do not make an entry if this node does not have any Webform settings.
1527
  if ($node->webform == webform_node_defaults() && !in_array($node->type, webform_variable_get('webform_node_types_primary'))) {
1528
    return;
1529
  }
1530

    
1531
  module_load_include('inc', 'webform', 'includes/webform.components');
1532
  module_load_include('inc', 'webform', 'includes/webform.conditionals');
1533
  module_load_include('inc', 'webform', 'includes/webform.emails');
1534

    
1535
  // Prepare the record for writing.
1536
  $node->webform['nid'] = $node->nid;
1537
  $webform_record = $node->webform;
1538
  $webform_record['preview_excluded_components'] = implode(',', $webform_record['preview_excluded_components']);
1539

    
1540
  // Insert the webform.
1541
  $node->webform['record_exists'] = (bool) drupal_write_record('webform', $webform_record);
1542

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

    
1552
  // Insert conditionals. Also used with clone.module.
1553
  if (isset($node->webform['conditionals']) && !empty($node->webform['conditionals'])) {
1554
    foreach ($node->webform['conditionals'] as $rgid => $conditional) {
1555
      $conditional['nid'] = $node->nid;
1556
      $conditional['rgid'] = $rgid;
1557
      webform_conditional_insert($conditional);
1558
    }
1559
  }
1560

    
1561
  // Insert emails. Also used with clone.module.
1562
  if (isset($node->webform['emails']) && !empty($node->webform['emails'])) {
1563
    foreach ($node->webform['emails'] as $eid => $email) {
1564
      $email['nid'] = $node->nid;
1565
      webform_email_insert($email);
1566
    }
1567
  }
1568

    
1569
  // Set the per-role submission access control.
1570
  foreach (array_filter($node->webform['roles']) as $rid) {
1571
    db_insert('webform_roles')->fields(array('nid' => $node->nid, 'rid' => $rid))->execute();
1572
  }
1573

    
1574
  // Flush the block cache if creating a block.
1575
  if (module_exists('block') && $node->webform['block']) {
1576
    block_flush_caches();
1577
  }
1578
}
1579

    
1580
/**
1581
 * Implements hook_node_update().
1582
 */
1583
function webform_node_update($node) {
1584
  if (!variable_get('webform_node_' . $node->type, FALSE)) {
1585
    return;
1586
  }
1587

    
1588
  // Check if this node needs a webform record at all. If it matches the
1589
  // defaults, any existing record will be deleted.
1590
  webform_check_record($node);
1591

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

    
1599
  // Prepare the record for writing.
1600
  $node->webform['nid'] = $node->nid;
1601
  $webform_record = $node->webform;
1602
  $webform_record['preview_excluded_components'] = implode(',', $webform_record['preview_excluded_components']);
1603

    
1604
  // Update the webform entry.
1605
  drupal_write_record('webform', $webform_record, array('nid'));
1606

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

    
1610
  if ($original->webform['components'] != $node->webform['components']) {
1611
    module_load_include('inc', 'webform', 'includes/webform.components');
1612

    
1613
    $original_cids = array_keys($original->webform['components']);
1614
    $current_cids = array_keys($node->webform['components']);
1615

    
1616
    $all_cids = array_unique(array_merge($original_cids, $current_cids));
1617
    $deleted_cids = array_diff($original_cids, $current_cids);
1618
    $inserted_cids = array_diff($current_cids, $original_cids);
1619

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

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

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

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

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

    
1667
    $original_eids = array_keys($original->webform['emails']);
1668
    $current_eids = array_keys($node->webform['emails']);
1669

    
1670
    $all_eids = array_unique(array_merge($original_eids, $current_eids));
1671
    $deleted_eids = array_diff($original_eids, $current_eids);
1672
    $inserted_eids = array_diff($current_eids, $original_eids);
1673

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

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

    
1696
  // Flush the block cache if block settings have been changed.
1697
  if (function_exists('block_flush_caches') && $node->webform['block'] != $original->webform['block']) {
1698
    block_flush_caches();
1699
  }
1700
}
1701

    
1702
/**
1703
 * Implements hook_node_delete().
1704
 */
1705
function webform_node_delete($node) {
1706
  if (!variable_get('webform_node_' . $node->type, FALSE)) {
1707
    return;
1708
  }
1709

    
1710
  // Allow components clean up extra data, such as uploaded files.
1711
  module_load_include('inc', 'webform', 'includes/webform.components');
1712
  foreach ($node->webform['components'] as $cid => $component) {
1713
    webform_component_delete($node, $component);
1714
  }
1715

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

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

    
1774
/**
1775
 * Implements hook_node_prepare().
1776
 */
1777
function webform_node_prepare($node) {
1778
  if (variable_get('webform_node_' . $node->type, FALSE) && !isset($node->webform)) {
1779
    $node->webform = webform_node_defaults();
1780
  }
1781
}
1782

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

    
1793
  module_load_include('inc', 'webform', 'includes/webform.components');
1794

    
1795
  // Select all webforms that match these node IDs.
1796
  $result = db_select('webform')
1797
    ->fields('webform')
1798
    ->condition('nid', array_keys($nodes), 'IN')
1799
    ->execute()
1800
    ->fetchAllAssoc('nid', PDO::FETCH_ASSOC);
1801

    
1802
  foreach ($result as $nid => $webform) {
1803
    // Load the basic information for each node.
1804
    $nodes[$nid]->webform = $webform;
1805
    $nodes[$nid]->webform['record_exists'] = TRUE;
1806

    
1807
    // Expand the list of excluded preview components.
1808
    $nodes[$nid]->webform['preview_excluded_components'] = array_filter(explode(',', $webform['preview_excluded_components']));
1809
  }
1810

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

    
1819
    // If a webform record doesn't exist, just return the defaults.
1820
    if (!isset($nodes[$nid]->webform)) {
1821
      $nodes[$nid]->webform = webform_node_defaults();
1822
      continue;
1823
    }
1824

    
1825
    $nodes[$nid]->webform['roles'] = db_select('webform_roles')
1826
      ->fields('webform_roles', array('rid'))
1827
      ->condition('nid', $nid)
1828
      ->execute()
1829
      ->fetchCol();
1830
    $nodes[$nid]->webform['emails'] = db_select('webform_emails')
1831
      ->fields('webform_emails')
1832
      ->condition('nid', $nid)
1833
      ->execute()
1834
      ->fetchAllAssoc('eid', PDO::FETCH_ASSOC);
1835

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

    
1845
    // Load components for each node.
1846
    $nodes[$nid]->webform['components'] = db_select('webform_component')
1847
      ->fields('webform_component')
1848
      ->condition('nid', $nid)
1849
      ->orderBy('weight')
1850
      ->orderBy('name')
1851
      ->execute()
1852
      ->fetchAllAssoc('cid', PDO::FETCH_ASSOC);
1853

    
1854
    // Do a little cleanup on each component.
1855
    foreach ($nodes[$nid]->webform['components'] as $cid => $component) {
1856
      $nodes[$nid]->webform['components'][$cid]['nid'] = $nid;
1857
      $nodes[$nid]->webform['components'][$cid]['extra'] = unserialize($component['extra']);
1858
      webform_component_defaults($nodes[$nid]->webform['components'][$cid]);
1859
    }
1860

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

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

    
1899
/**
1900
 * Implements hook_user_role_delete().
1901
 *
1902
 * Removes references to deleted role from existing webforms.
1903
 */
1904
function webform_user_role_delete($role) {
1905
  db_delete('webform_roles')->condition('rid', $role->rid)->execute();
1906
}
1907

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

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

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

    
1957
/**
1958
 * Submit handler for the webform node form.
1959
 *
1960
 * Redirect the user to the components form on new node inserts. Note that this
1961
 * fires after the hook_submit() function above.
1962
 */
1963
function webform_form_submit($form, &$form_state) {
1964
  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'])));
1965
  $form_state['redirect'] = 'node/' . $form_state['nid'] . '/webform/components';
1966
}
1967

    
1968
/**
1969
 * Implements hook_node_view().
1970
 */
1971
function webform_node_view($node, $view_mode) {
1972
  global $user;
1973

    
1974
  if (!variable_get('webform_node_' . $node->type, FALSE)) {
1975
    return;
1976
  }
1977

    
1978
  // If empty or a new node (during preview) do not display.
1979
  if (empty($node->webform['components']) || empty($node->nid)) {
1980
    return;
1981
  }
1982

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

    
1990
  $submission = FALSE;
1991
  $submission_count = 0;
1992
  $page = node_is_page($node);
1993
  $logging_in = FALSE;
1994
  $total_limit_exceeded = FALSE;
1995
  $user_limit_exceeded = FALSE;
1996
  $closed = FALSE;
1997

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

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

    
2012
  if ($node->webform['status'] == 0) {
2013
    $closed = TRUE;
2014
    $enabled = FALSE;
2015
    $allowed_roles = array();
2016
  }
2017
  else {
2018
    // $enabled set by reference.
2019
    $allowed_roles = _webform_allowed_roles($node, $enabled);
2020
  }
2021

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

    
2030
  // Check if this page is cached or not.
2031
  $cached = drupal_page_is_cacheable();
2032

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

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

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

    
2056
    // Disable the form if the limit is exceeded. The cache is irrelevant for
2057
    // the total submission limit; when it is exceeded for one user, it is
2058
    // exceeded for any other user.
2059
    if (($total_limit_exceeded = webform_submission_total_limit_check($node))) {
2060
      $enabled = FALSE;
2061
    }
2062
  }
2063

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

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

    
2089
  // Remove the surrounding <form> tag if this is a preview.
2090
  if (!empty($node->in_preview)) {
2091
    $form['#type'] = 'markup';
2092
  }
2093

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

    
2109
  // Add the output to the node.
2110
  $node->content['webform'] = array(
2111
    '#theme' => 'webform_view',
2112
    '#node' => $node,
2113
    '#page' => $page,
2114
    '#form' => $form,
2115
    '#enabled' => $enabled,
2116
    '#visible' => $extra_fields['webform']['visible'],
2117
    '#weight' => 10,
2118
  );
2119
}
2120

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

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

    
2172
/**
2173
 * Display a message to a user if they are not allowed to fill out a form.
2174
 *
2175
 * @param array $variables
2176
 *   The variables array.
2177
 */
2178
function theme_webform_view_messages(array $variables) {
2179
  global $user;
2180

    
2181
  $node = $variables['node'];
2182
  $page = $variables['page'];
2183
  $submission_count = $variables['submission_count'];
2184
  $user_limit_exceeded = $variables['user_limit_exceeded'];
2185
  $total_limit_exceeded = $variables['total_limit_exceeded'];
2186
  $allowed_roles = $variables['allowed_roles'];
2187
  $closed = $variables['closed'];
2188
  $cached = $variables['cached'];
2189

    
2190
  $type = 'warning';
2191

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

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

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

    
2252
  if ($page && isset($message)) {
2253
    drupal_set_message($message, $type, FALSE);
2254
  }
2255
}
2256

    
2257
/**
2258
 * Implements hook_mail().
2259
 */
2260
function webform_mail($key, &$message, $params) {
2261
  $message['headers'] = array_merge($message['headers'], $params['headers']);
2262
  $message['subject'] = $params['subject'];
2263
  $message['body'][] = $params['message'];
2264
}
2265

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

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

    
2301
  // Get the node ID from delta.
2302
  $nid = drupal_substr($delta, strrpos($delta, '-') + 1);
2303

    
2304
  // Load node in current language.
2305
  if (module_exists('translation')) {
2306
    global $language;
2307
    if (($translations = translation_node_get_translations($nid)) && (isset($translations[$language->language]))) {
2308
      $nid = $translations[$language->language]->nid;
2309
    }
2310
  }
2311

    
2312
  // The webform node to display in the block.
2313
  $node = node_load($nid);
2314

    
2315
  // Return if user has no access to the webform node.
2316
  if (!node_access('view', $node)) {
2317
    return;
2318
  }
2319

    
2320
  // This is a webform node block.
2321
  $node->webform_block = TRUE;
2322
  $node->webform['confirmation_block'] = $settings['confirmation_block'];
2323

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

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

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

    
2384
  // Add contextual links for the webform node if they aren't already there.
2385
  if (!isset($content['#contextual_links']['node'])) {
2386
    $content['#contextual_links']['node'] = array('node', array($node->nid));
2387
  }
2388

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

    
2399
/**
2400
 * Implements hook_block_configure().
2401
 */
2402
function webform_block_configure($delta = '') {
2403
  $nid = str_replace('client-block-', '', $delta);
2404
  $node = node_load($nid);
2405

    
2406
  // Load the block-specific configuration settings.
2407
  $webform_blocks = webform_variable_get('webform_blocks');
2408
  $settings = isset($webform_blocks[$delta]) ? $webform_blocks[$delta] : array();
2409
  $settings += array(
2410
    'display' => 'form',
2411
    'pages_block' => 1,
2412
    'confirmation_block' => 0,
2413
  );
2414

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

    
2424
  $form = array();
2425
  $form['display'] = array(
2426
    '#type' => 'select',
2427
    '#title' => t('View mode'),
2428
    '#default_value' => $settings['display'],
2429
    '#options' => $view_modes,
2430
    '#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'))),
2431
  );
2432

    
2433
  $form['pages_block'] = array(
2434
    '#type' => 'radios',
2435
    '#title' => t('Multi-page handling'),
2436
    '#options' => array(
2437
      1 => t('Display all pages inside block'),
2438
      0 => t('Redirect to the node page after the first page'),
2439
    ),
2440
    '#default_value' => $settings['pages_block'],
2441
    '#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.'),
2442
  );
2443

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

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

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

    
2500
  // Attach necessary JavaScript and CSS.
2501
  $form['#attached'] = array(
2502
    'css' => array(drupal_get_path('module', 'webform') . '/css/webform.css'),
2503
    'js' => array(drupal_get_path('module', 'webform') . '/js/webform.js'),
2504
  );
2505
  form_load_include($form_state, 'inc', 'webform', 'includes/webform.components');
2506
  form_load_include($form_state, 'inc', 'webform', 'includes/webform.submissions');
2507

    
2508
  // For ajax requests, $form_state['values']['details'] is missing. Restore
2509
  // from storage, if available, for multi-page forms.
2510
  if (empty($form_state['values']['details']) && !empty($form_state['storage']['details'])) {
2511
    $form_state['values']['details'] = $form_state['storage']['details'];
2512
  }
2513

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

    
2520
  $finished = isset($submission->is_draft) ? (!$submission->is_draft) : 0;
2521
  $submit_button_text = $finished
2522
                          ? t('Save')
2523
                          : (empty($node->webform['submit_text']) ? t('Submit') : t($node->webform['submit_text']));
2524

    
2525
  // Bind arguments to $form to make them available in theming and form_alter.
2526
  $form['#node'] = $node;
2527
  $form['#submission'] = $submission;
2528
  $form['#is_draft'] = $submission && $submission->is_draft;
2529
  $form['#filter'] = $filter;
2530

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

    
2534
  // Add a CSS class for all client forms.
2535
  $form['#attributes']['class'][] = 'webform-client-form';
2536
  $form['#attributes']['class'][] = 'webform-client-form-' . $node->nid;
2537

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

    
2544
  $form['#submit'] = array('webform_client_form_pages', 'webform_client_form_submit');
2545
  $form['#validate'] = array('webform_client_form_validate');
2546
  // Add includes for used component types and pre/post validation handlers.
2547
  $form['#process'] = array('webform_client_form_process');
2548

    
2549
  if (is_array($node->webform['components']) && !empty($node->webform['components'])) {
2550
    // Prepare a new form array.
2551
    $form['submitted'] = array(
2552
      '#tree' => TRUE,
2553
    );
2554
    $form['details'] = array(
2555
      '#tree' => TRUE,
2556
    );
2557

    
2558
    // Put the components into a tree structure.
2559
    if (!isset($form_state['storage']['component_tree'])) {
2560
      $form_state['webform']['component_tree'] = array();
2561
      $form_state['webform']['page_count'] = 1;
2562
      $form_state['webform']['page_num'] = 1;
2563
      _webform_components_tree_build($node->webform['components'], $form_state['webform']['component_tree'], 0, $form_state['webform']['page_count']);
2564

    
2565
      // If preview is enabled, increase the page count by one.
2566
      if ($node->webform['preview']) {
2567
        $form_state['webform']['page_count']++;
2568
      }
2569
      $form_state['webform']['preview'] = $node->webform['preview'];
2570

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

    
2587
    // Set the input values based on whether we're editing an existing
2588
    // submission or not.
2589
    $input_values = isset($submission->data) ? $submission->data : array();
2590

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

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

    
2607
    // Generate conditional topological order & report any errors.
2608
    $sorter = webform_get_conditional_sorter($node);
2609
    $sorter->reportErrors();
2610

    
2611
    // Excecute the condtionals on the current input values.
2612
    $input_values = $sorter->executeConditionals($input_values);
2613

    
2614
    // Allow values from other pages to be sent to browser for conditionals.
2615
    $form['#conditional_values'] = $input_values;
2616

    
2617
    // Allow components access to most up-to-date values.
2618
    $form_state['#conditional_values'] = $input_values;
2619

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

    
2645
    // Shorten up our variable names.
2646
    $component_tree = $form_state['webform']['component_tree'];
2647
    $page_count = $form_state['webform']['page_count'];
2648
    $page_num = $form_state['webform']['page_num'];
2649
    $preview = $form_state['webform']['preview'];
2650

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

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

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

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

    
2698
      $form['preview'] = webform_submission_render($node, $preview_submission, NULL, 'html', $node->webform['preview_excluded_components']);
2699
      $form['#attributes']['class'][] = 'preview';
2700
    }
2701

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

    
2728
    // Add process functions to remove the IDs forced upon buttons and wrappers.
2729
    $actions_pre_render = array_merge(element_info_property('actions', '#pre_render', array()), array('webform_pre_render_remove_id'));
2730
    $buttons_pre_render = array_merge(element_info_property('submit', '#pre_render', array()), array('webform_pre_render_remove_id'));
2731

    
2732
    // Add buttons for pages, drafts, and submissions.
2733
    $form['actions'] = array(
2734
      '#type' => 'actions',
2735
      '#weight' => 1000,
2736
      '#pre_render' => $actions_pre_render,
2737
    );
2738

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

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

    
2793
  return $form;
2794
}
2795

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

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

    
2815
  return $form;
2816
}
2817

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

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

    
2866
      // Ensure the component is added as a property.
2867
      $display_element['#webform_component'] = $component;
2868

    
2869
      // Add custom CSS classes to the field and wrapper.
2870
      _webform_component_classes($display_element, $component);
2871

    
2872
      // Allow modules to modify a "display only" webform component.
2873
      drupal_alter('webform_component_display', $display_element, $component);
2874

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

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

    
2900
      // Ensure the component is added as a property.
2901
      $element['#webform_component'] = $component;
2902

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

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

    
2915
      // Add custom CSS classes to the field and wrapper.
2916
      _webform_component_classes($element, $component);
2917

    
2918
      // Allow modules to modify a webform component that is going to be render
2919
      // in a form.
2920
      drupal_alter('webform_component_render', $element, $component);
2921

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

    
2930
  // Disable validation initially on all elements. We manually validate
2931
  // all webform elements in webform_client_form_validate().
2932
  if (isset($parent_fieldset[$component['form_key']])) {
2933
    $parent_fieldset[$component['form_key']]['#validated'] = TRUE;
2934
    $parent_fieldset[$component['form_key']]['#webform_validated'] = FALSE;
2935
  }
2936

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

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

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

    
2970
  // Check that the submissions have not exceeded the total submission limit.
2971
  $total_limit_exceeded = FALSE;
2972
  if ($node->webform['total_submit_limit'] != -1 && !$finished) {
2973
    $total_limit_exceeded = webform_submission_total_limit_check($node);
2974
  }
2975

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

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

    
2995
  // Prevent submission by throwing an error.
2996
  if ((!$allowed_role || $total_limit_exceeded || $user_limit_exceeded || $closed)) {
2997
    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));
2998
    form_set_error('', NULL);
2999
  }
3000
}
3001

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

    
3016
  module_load_include('inc', 'webform', 'includes/webform.submissions');
3017
  $node = $form['#node'];
3018

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

    
3034
  // Run all #element_validate and #required checks. These are skipped initially
3035
  // by setting #validated = TRUE on all components when they are added.
3036
  _webform_client_form_validate($form, $form_state, 'webform_client_form', $input_values);
3037
}
3038

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

    
3080
    // Check for changes in required status made by conditionals.
3081
    $required = $sorter->componentRequired($cid, $page_num);
3082
    if (isset($required)) {
3083
      $elements['#required'] = $required;
3084

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

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

    
3116
      // Verify that the value is not longer than #maxlength.
3117
      if (isset($elements['#maxlength']) && drupal_strlen($elements['#value']) > $elements['#maxlength']) {
3118
        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']))));
3119
      }
3120

    
3121
      // Verify that the value is not shorter than #minlength. The value may
3122
      // still be empty (required is a separate validation option).
3123
      if (isset($elements['#minlength'])) {
3124
        $length = drupal_strlen($elements['#value']);
3125
        if ($length > 0 && $length < $elements['#minlength']) {
3126
          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']))));
3127
        }
3128
      }
3129

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

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

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

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

    
3248
/**
3249
 * Handle the processing of pages and conditional logic.
3250
 */
3251
function webform_client_form_pages($form, &$form_state) {
3252
  $node = node_load($form_state['values']['details']['nid']);
3253

    
3254
  // Multistep forms may not have any components on the first page.
3255
  if (!isset($form_state['values']['submitted'])) {
3256
    $form_state['values']['submitted'] = array();
3257
  }
3258

    
3259
  // Move special settings to storage.
3260
  if (isset($form_state['webform']['component_tree'])) {
3261
    $form_state['storage']['component_tree'] = $form_state['webform']['component_tree'];
3262
    $form_state['storage']['page_count'] = $form_state['webform']['page_count'];
3263
    $form_state['storage']['page_num'] = $form_state['webform']['page_num'];
3264
    $form_state['storage']['preview'] = $form_state['webform']['preview'];
3265
  }
3266

    
3267
  // Flatten trees within the submission.
3268
  $form_state['values']['submitted'] = _webform_client_form_submit_flatten($node, $form_state['values']['submitted']);
3269

    
3270
  // Perform post processing by components.
3271
  _webform_client_form_submit_process($node, $form_state['values']['submitted']);
3272

    
3273
  // Assume the form is completed unless the page logic says otherwise.
3274
  $form_state['webform_completed'] = TRUE;
3275

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

    
3284
  // Execute conditionals on submission values.
3285
  $form_state['values']['submitted'] = webform_get_conditional_sorter($node)->executeConditionals($original_values);
3286

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

    
3292
    // Store values from the current page in the form state storage.
3293
    $form_state['storage']['submitted'] = $form_state['values']['submitted'];
3294

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

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

    
3328
    // The form is done if the page number is greater than the page count.
3329
    $form_state['webform_completed'] = $form_state['storage']['page_num'] > $form_state['storage']['page_count'];
3330
  }
3331

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

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

    
3347
/**
3348
 * Submit handler for saving the form values and sending e-mails.
3349
 */
3350
function webform_client_form_submit($form, &$form_state) {
3351
  module_load_include('inc', 'webform', 'includes/webform.submissions');
3352
  module_load_include('inc', 'webform', 'includes/webform.components');
3353
  global $user;
3354

    
3355
  if (empty($form_state['save_draft']) && empty($form_state['webform_completed'])) {
3356
    return;
3357
  }
3358

    
3359
  $node = $form['#node'];
3360
  $sid = $form_state['values']['details']['sid'] ? (int) $form_state['values']['details']['sid'] : NULL;
3361

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

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

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

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

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

    
3408
  // Save the submission to the database.
3409
  if (!$sid) {
3410
    // No sid was found thus insert it in the dataabase.
3411
    $form_state['values']['details']['sid'] = $sid = webform_submission_insert($node, $submission);
3412
    $form_state['values']['details']['is_new'] = TRUE;
3413

    
3414
    // Save the new details in storage. When ajax calls for file upload/remove,
3415
    // $form_state['values']['details'] is missing. This allows the proper
3416
    // submission to be retrieved in webform_client_form. See #2562703.
3417
    $form_state['storage']['details'] = $form_state['values']['details'];
3418

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

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

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

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

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

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

    
3507
  $form_state['redirect'] = $redirect;
3508
}
3509

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

    
3534
/**
3535
 * Flattens a submitted values back into a single flat array representation.
3536
 */
3537
function _webform_client_form_submit_flatten($node, $fieldset, $parent = 0) {
3538
  $values = array();
3539

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

    
3557
  return $values;
3558
}
3559

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

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

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

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

    
3600
  // Replace tokens.
3601
  module_load_include('inc', 'webform', 'includes/webform.submissions');
3602
  $submission = webform_get_submission($node->nid, $vars['sid']);
3603
  $vars['confirmation_message'] = webform_replace_tokens($confirmation, $node, $submission, NULL, $node->webform['confirmation_format']);
3604

    
3605
  // URL back to form (or same page for in-block confirmations).
3606
  $vars['url'] = empty($node->webform_block)
3607
                    ? url('node/' . $node->nid)
3608
                    : url(current_path(), array('query' => drupal_get_query_parameters()));
3609

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

    
3624
/**
3625
 * Prepare for theming of the webform progressbar.
3626
 */
3627
function template_preprocess_webform_progressbar(&$vars) {
3628
  // Add CSS used by the progress bar.
3629
  drupal_add_css(drupal_get_path('module', 'webform') . '/css/webform.css');
3630

    
3631
  $vars['progressbar_page_number'] = $vars['node']->webform['progressbar_page_number'];
3632
  $vars['progressbar_percent'] = $vars['node']->webform['progressbar_percent'];
3633
  $vars['progressbar_bar'] = $vars['node']->webform['progressbar_bar'];
3634
  $vars['progressbar_pagebreak_labels'] = $vars['node']->webform['progressbar_pagebreak_labels'];
3635
  $vars['progressbar_include_confirmation'] = $vars['node']->webform['progressbar_include_confirmation'];
3636
  $vars['percent'] = ($vars['page_num'] - 1) / ($vars['page_count'] - 1) * 100;
3637
}
3638

    
3639
/**
3640
 * Prepare to theme the contents of e-mails sent by webform.
3641
 */
3642
function template_preprocess_webform_mail_message(&$vars) {
3643
  global $user;
3644

    
3645
  $vars['user'] = $user;
3646
  $vars['ip_address'] = webform_ip_address($vars['node']);
3647
}
3648

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

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

    
3681
/**
3682
 * Implements template_preprocess_THEME_HOOK().
3683
 */
3684
function template_preprocess_webform_element(&$variables) {
3685
  $element = &$variables['element'];
3686

    
3687
  // Ensure defaults.
3688
  $element += array(
3689
    '#title_display' => 'before',
3690
    '#wrapper_attributes' => array(),
3691
  );
3692
  $element['#wrapper_attributes'] += array(
3693
    'class' => array(),
3694
  );
3695

    
3696
  // All elements using this for display only are given the "display" type.
3697
  if (isset($element['#format']) && $element['#format'] == 'html') {
3698
    $type = 'display';
3699
  }
3700
  else {
3701
    $type = ($element['#webform_component']['type'] == 'select' && isset($element['#type'])) ? $element['#type'] : $element['#webform_component']['type'];
3702
  }
3703

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

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

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

    
3729
  // If #title is not set, we don't display any label or required marker.
3730
  if (!isset($element['#title'])) {
3731
    $element['#title_display'] = 'none';
3732
  }
3733

    
3734
  // If an internal title is being used, generate no external title.
3735
  if ($element['#title_display'] == 'internal') {
3736
    $element['#title_display'] = 'none';
3737
  }
3738
}
3739

    
3740
/**
3741
 * Replacement for theme_form_element().
3742
 */
3743
function theme_webform_element($variables) {
3744
  $element = $variables['element'];
3745

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

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

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

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

    
3776
  if ($has_element_children) {
3777
    $output .= '<fieldset class="fieldset-invisible">';
3778
  }
3779

    
3780
  switch ($element['#title_display']) {
3781
    case 'inline':
3782
      $output .= $description[$above];
3783
      $description[$above] = '';
3784
    case 'before':
3785
    case 'invisible':
3786
    case 'after':
3787
      if ($has_element_children) {
3788
        $title = '<legend>' . $element['#title'];
3789

    
3790
        if ($element['#required']) {
3791
          $title .= ' ' . theme('form_required_marker', $variables);
3792
        }
3793

    
3794
        $title .= '</legend>';
3795
      }
3796
      else {
3797
        $title = ' ' . theme('form_element_label', $variables);
3798
      }
3799
      break;
3800
  }
3801

    
3802
  $children = ' ' . $description[$above] . $prefix . $element['#children'] . $suffix;
3803
  switch ($element['#title_display']) {
3804
    case 'inline':
3805
    case 'before':
3806
    case 'invisible':
3807
      $output .= $title;
3808
      $output .= $children;
3809
      break;
3810

    
3811
    case 'after':
3812
      $output .= $children;
3813
      $output .= $title;
3814
      break;
3815

    
3816
    case 'none':
3817
    case 'attribute':
3818
      // Output no label and no required marker, only the children.
3819
      $output .= $children;
3820
      break;
3821
  }
3822
  $output .= "\n";
3823

    
3824
  $output .= $description[!$above];
3825

    
3826
  if ($has_element_children) {
3827
    $output .= '</fieldset>';
3828
  }
3829

    
3830
  $output .= "</div>\n";
3831

    
3832
  return $output;
3833
}
3834

    
3835
/**
3836
 * Output a form element in plain text format.
3837
 */
3838
function theme_webform_element_text($variables) {
3839
  $element = $variables['element'];
3840
  $value = $variables['element']['#children'];
3841

    
3842
  $output = '';
3843
  $is_group = webform_component_feature($element['#webform_component']['type'], 'group');
3844

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

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

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

    
3872
  // Indent fieldsets.
3873
  if ($is_group) {
3874
    $lines = explode("\n", $output);
3875
    foreach ($lines as $number => $line) {
3876
      if (strlen($line)) {
3877
        $lines[$number] = '  ' . $line;
3878
      }
3879
    }
3880
    $output = implode("\n", $lines);
3881
    $output .= "\n";
3882
  }
3883

    
3884
  if ($output) {
3885
    $output .= "\n";
3886
  }
3887

    
3888
  return $output;
3889
}
3890

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

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

    
3909
  // Add container-inline to all elements.
3910
  $class[] = 'webform-container-inline';
3911
  if (isset($element['#inline_element']) && isset($variables['element']['#title'])) {
3912
    $variables['element']['#title'] .= ': ';
3913
  }
3914

    
3915
  $output = '<div class="' . implode(' ', $class) . '">' . "\n";
3916
  $output .= ' ' . $element['#children'];
3917
  if (!empty($element['#title'])) {
3918
    $output .= ' ' . theme('webform_inline_radio_label', $variables) . "\n";
3919
  }
3920

    
3921
  if (!empty($element['#description'])) {
3922
    $output .= ' <div class="description">' . $element['#description'] . "</div>\n";
3923
  }
3924

    
3925
  $output .= "</div>\n";
3926

    
3927
  return $output;
3928
}
3929

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

    
3941
  // If title and required marker are both empty, output no label.
3942
  if ((!isset($element['#title']) || $element['#title'] === '') && empty($element['#required'])) {
3943
    return '';
3944
  }
3945

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

    
3949
  // theme_element_label() does a filter_xss() here, we skip it because we know
3950
  // every use where this theme function is used and we need to allow input and
3951
  // select elements.
3952
  $title = $element['#title'];
3953

    
3954
  $attributes = isset($element['#attributes']) ? $element['#attributes'] : array();
3955

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

    
3965
  $attributes['class'][] = 'webform-inline-radio';
3966
  if (!empty($element['#id'])) {
3967
    $attributes['for'] = $element['#id'];
3968
  }
3969

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

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

    
3992
/**
3993
 * Check if current user has a draft of this webform, and return the sid.
3994
 */
3995
function _webform_fetch_draft_sid($nid, $uid) {
3996
  $q = db_select('webform_submissions')
3997
    ->fields('webform_submissions', array('sid'))
3998
    ->condition('nid', $nid)
3999
    ->condition('uid', $uid)
4000
    ->condition('is_draft', 1)
4001
    ->orderBy('submitted', 'DESC');
4002

    
4003
  // Detect whether a webform draft is being edited. If so, that is the one that
4004
  // should be returned.
4005
  $is_webform = isset($_POST['form_id']) && stripos($_POST['form_id'], 'webform_client_form_') === 0;
4006
  $sid_provided = !empty($_POST['details']['sid']) && is_string($_POST['details']['sid']);
4007
  $not_finished = empty($_POST['details']['finished']);
4008
  if ($is_webform && $sid_provided && $not_finished) {
4009
    // Validate that the sid from $_POST belongs to the current user.
4010
    $q->condition('sid', $_POST['details']['sid']);
4011
    $existing_sid = TRUE;
4012
  }
4013

    
4014
  // Retrieve exisiting draft sid.
4015
  $sid = $q
4016
    ->execute()
4017
    ->fetchField();
4018

    
4019
  // Allow modules to alter the initial choice of sid when there might be more
4020
  // than one.
4021
  if ($sid && empty($existing_sid)) {
4022
    $context = array(
4023
      'nid' => $nid,
4024
      'uid' => $uid,
4025
    );
4026
    drupal_alter('webform_draft', $sid, $context);
4027
  }
4028
  return $sid;
4029
}
4030

    
4031
/**
4032
 * Returns a new or cached WebformConditionals object for the specified node.
4033
 *
4034
 * @param object $node
4035
 *   The loaded webform node.
4036
 *
4037
 * @returns object
4038
 *   Object of type WebformConditionals, possibly with the conditionals already
4039
 *   analyzed for dependencies.
4040
 */
4041
function webform_get_conditional_sorter($node) {
4042
  return WebformConditionals::factory($node);
4043
}
4044

    
4045
/**
4046
 * Wrapper for webform_replace_tokens().
4047
 *
4048
 * @deprecated in webform:7.x-4.0 and is removed from webform:7.x-5.0. Use
4049
 * webform_replace_tokens().
4050
 * @see https://www.drupal.org/project/webform/issues/2038199
4051
 */
4052
function _webform_filter_values($string, $node = NULL, $submission = NULL, $email = NULL, $strict = TRUE) {
4053
  $output = webform_replace_tokens($string, $node, $submission, $email, $strict);
4054
  return $strict ? webform_filter_xss($output) : $output;
4055
}
4056

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

    
4085
  $token_data = array();
4086
  if ($node) {
4087
    $token_data['node'] = $node;
4088
  }
4089
  if ($submission) {
4090
    $token_data['webform-submission'] = $submission;
4091
  }
4092
  if ($email) {
4093
    $token_data['webform-email'] = $email;
4094
  }
4095
  $clear = is_bool($sanitize);
4096
  $string = token_replace($string, $token_data, array('clear' => $clear, 'sanitize' => $sanitize === TRUE));
4097
  if (!$clear) {
4098
    $string = webform_replace_tokens_clear(check_markup($string, $sanitize));
4099
  }
4100
  return $string;
4101
}
4102

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

    
4132
  $text_tokens = token_scan($text);
4133
  if (empty($text_tokens)) {
4134
    return $text;
4135
  }
4136

    
4137
  $replacements = array();
4138
  foreach ($text_tokens as $type => $tokens) {
4139
    $replacements += array_fill_keys($tokens, '');
4140
  }
4141

    
4142
  $tokens = array_keys($replacements);
4143
  $values = array_values($replacements);
4144

    
4145
  return str_replace($tokens, $values, $text);
4146
}
4147

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

    
4186
/**
4187
 * Replace tokens in descriptions and sanitize according to Webform settings.
4188
 */
4189
function webform_filter_descriptions($string, $node = NULL, $submission = NULL) {
4190
  return strlen($string) == 0 ? '' : webform_filter_xss(webform_replace_tokens($string, $node, $submission));
4191
}
4192

    
4193
/**
4194
 * Duplicates webform_filter_descriptions().
4195
 *
4196
 * @deprecated in webform:7.x-4.0 and is removed from webform:7.x-5.0. Use
4197
 * webform_filter_descriptions().
4198
 * @see https://www.drupal.org/project/webform/issues/2038199
4199
 */
4200
function _webform_filter_descriptions($string, $node = NULL, $submission = NULL) {
4201
  return webform_filter_descriptions($string, $node, $submission);
4202
}
4203

    
4204
/**
4205
 * Filter labels for display by running through XSS checks.
4206
 */
4207
function webform_filter_xss($string) {
4208
  static $allowed_tags;
4209
  $allowed_tags = isset($allowed_tags) ? $allowed_tags : webform_variable_get('webform_allowed_tags');
4210
  return filter_xss($string, $allowed_tags);
4211
}
4212

    
4213
/**
4214
 * Duplicates webform_filter_xss().
4215
 *
4216
 * @deprecated in webform:7.x-4.0 and is removed from webform:7.x-5.0. Use
4217
 * webform_filter_xss().
4218
 * @see https://www.drupal.org/project/webform/issues/2038199
4219
 */
4220
function _webform_filter_xss($string) {
4221
  return webform_filter_xss($string);
4222
}
4223

    
4224
/**
4225
 * Utility function to ensure that a webform record exists in the database.
4226
 *
4227
 * @param object $node
4228
 *   The node object to check if a database entry exists.
4229
 *
4230
 * @return bool
4231
 *   This function should always return TRUE if no errors were encountered,
4232
 *   ensuring that a webform table row has been created. Will return FALSE if
4233
 *   a record does not exist and a new one could not be created.
4234
 */
4235
function webform_ensure_record(&$node) {
4236
  if (!$node->webform['record_exists']) {
4237
    // Even though webform_node_insert() would set this property to TRUE,
4238
    // we set record_exists to trigger a difference from the defaults.
4239
    $node->webform['record_exists'] = TRUE;
4240
    webform_node_insert($node);
4241
  }
4242
  return $node->webform['record_exists'];
4243
}
4244

    
4245
/**
4246
 * Utility function to check if a webform record is necessary in the database.
4247
 *
4248
 * If the node is no longer using any webform settings, this function will
4249
 * delete the settings from the webform table. Note that this function will NOT
4250
 * delete rows from the webform table if the node-type is exclusively used for
4251
 * webforms (per the "webform_node_types_primary" variable).
4252
 *
4253
 * @param object $node
4254
 *   The node object to check if a database entry is still required.
4255
 *
4256
 * @return bool
4257
 *   Returns TRUE if the webform still has a record in the database. Returns
4258
 *   FALSE if the webform does not have a record or if the previously existing
4259
 *   record was just deleted.
4260
 */
4261
function webform_check_record(&$node) {
4262
  $webform = $node->webform;
4263
  $webform['record_exists'] = FALSE;
4264
  unset($webform['nid']);
4265

    
4266
  // Don't include empty values in the comparison, this makes it so modules that
4267
  // extend Webform with empty defaults won't affect cleanup of rows.
4268
  $webform = array_filter($webform);
4269
  $defaults = array_filter(webform_node_defaults());
4270
  if ($webform == $defaults && !in_array($node->type, webform_variable_get('webform_node_types_primary'))) {
4271
    webform_node_delete($node);
4272
    $node->webform = webform_node_defaults();
4273
  }
4274
  return $node->webform['record_exists'];
4275
}
4276

    
4277
/**
4278
 * Given a component's form_key and optionally its parent's cid, get its cid(s).
4279
 *
4280
 * @param object $node
4281
 *   A fully loaded webform node object.
4282
 * @param string $form_key
4283
 *   The form key for which to find the cid(s).
4284
 * @param int|null $pid
4285
 *   The cid of the parent component.
4286
 *
4287
 * @return int|int[]
4288
 *   The cid of the component or an array of component ids.
4289
 */
4290
function webform_get_cid($node, $form_key, $pid = NULL) {
4291
  if ($pid === NULL) {
4292
    $cids = array();
4293
    foreach ($node->webform['components'] as $cid => $component) {
4294
      if ((string) $component['form_key'] === (string) $form_key) {
4295
        $cids[] = $cid;
4296
      }
4297
    }
4298
    return $cids;
4299
  }
4300
  else {
4301
    foreach ($node->webform['components'] as $cid => $component) {
4302
      if ((string) $component['form_key'] === (string) $form_key && $component['pid'] == $pid) {
4303
        return $cid;
4304
      }
4305
    }
4306
  }
4307
}
4308

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

    
4347
/**
4348
 * Retrieve a Drupal variable with the appropriate default value.
4349
 */
4350
function webform_variable_get($variable) {
4351
  switch ($variable) {
4352
    case 'webform_blocks':
4353
      $result = variable_get('webform_blocks', array());
4354
      break;
4355

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

    
4360
    case 'webform_allowed_tags':
4361
      $result = variable_get('webform_allowed_tags', array('a', 'em', 'strong', 'code', 'img'));
4362
      break;
4363

    
4364
    case 'webform_email_address_format':
4365
      $result = variable_get('webform_email_address_format', 'long');
4366
      break;
4367

    
4368
    case 'webform_email_address_individual':
4369
      $result = variable_get('webform_email_address_individual', 0);
4370
      break;
4371

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

    
4376
    case 'webform_default_from_address':
4377
      $result = variable_get('webform_default_from_address', variable_get('site_mail', ini_get('sendmail_from')));
4378
      break;
4379

    
4380
    case 'webform_default_subject':
4381
      $result = variable_get('webform_default_subject', t('Form submission from: [node:title]'));
4382
      break;
4383

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

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

    
4392
    case 'webform_default_format':
4393
      $result = variable_get('webform_default_format', 0);
4394
      break;
4395

    
4396
    case 'webform_format_override':
4397
      $result = variable_get('webform_format_override', 0);
4398
      break;
4399

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

    
4404
    case 'webform_node_types':
4405
      $result = webform_node_types();
4406
      break;
4407

    
4408
    case 'webform_node_types_primary':
4409
      $result = variable_get('webform_node_types_primary', array('webform'));
4410
      break;
4411

    
4412
    case 'webform_date_type':
4413
      $result = variable_get('webform_date_type', 'medium');
4414
      break;
4415

    
4416
    case 'webform_export_format':
4417
      module_load_include('inc', 'webform', 'includes/webform.export');
4418
      $options = webform_export_list();
4419
      $result = variable_get('webform_export_format', 'excel');
4420
      $result = isset($options[$result]) ? $result : key($options);
4421
      break;
4422

    
4423
    case 'webform_csv_delimiter':
4424
      $result = variable_get('webform_csv_delimiter', '\t');
4425
      break;
4426

    
4427
    case 'webform_csv_line_ending':
4428
      $result = variable_get('webform_csv_line_ending', "\n");
4429
      break;
4430

    
4431
    case 'webform_export_wordwrap':
4432
      $result = variable_get('webform_export_wordwrap', 0);
4433
      break;
4434

    
4435
    case 'webform_excel_legacy_exporter':
4436
      $result = variable_get('webform_excel_legacy_exporter', 0);
4437
      break;
4438

    
4439
    case 'webform_progressbar_style':
4440
      $result = variable_get('webform_progressbar_style', array('progressbar_bar', 'progressbar_pagebreak_labels', 'progressbar_include_confirmation'));
4441
      break;
4442

    
4443
    case 'webform_progressbar_label_first':
4444
      $result = variable_get('webform_progressbar_label_first', t('Start'));
4445
      break;
4446

    
4447
    case 'webform_progressbar_label_confirmation':
4448
      $result = variable_get('webform_progressbar_label_confirmation', t('Complete'));
4449
      break;
4450

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

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

    
4459
    case 'webform_token_access':
4460
      $result = variable_get('webform_token_access', 1);
4461
      break;
4462

    
4463
    case 'webform_update_batch_size':
4464
      $result = variable_get('webform_update_batch_size', 100);
4465
      break;
4466

    
4467
    case 'webform_disabled_components':
4468
      $result = variable_get('webform_disabled_components', array());
4469
      break;
4470

    
4471
    case 'webform_fieldset_wrap':
4472
      $result = variable_get('webform_fieldset_wrap', FALSE);
4473
      break;
4474
  }
4475
  return $result;
4476
}
4477

    
4478
/**
4479
 * Output the contents of token help used throughout Webform.
4480
 *
4481
 * In earlier versions of Token, a fieldset is used to show all the tokens.
4482
 * Later versions now use a modal dialog that is accessed through a link. If
4483
 * Token module is not available, a message should be displayed.
4484
 */
4485
function theme_webform_token_help($variables) {
4486
  if (!webform_variable_get('webform_token_access')) {
4487
    return '';
4488
  }
4489
  $groups = $variables['groups'];
4490

    
4491
  // Assume dialogs are not supported, show as a fieldset.
4492
  $help = array(
4493
    '#title' => t('Token values'),
4494
    '#type' => 'fieldset',
4495
    '#collapsible' => TRUE,
4496
    '#collapsed' => TRUE,
4497
    '#attributes' => array('class' => array('collapsible', 'collapsed')),
4498
    'help' => array(
4499
      '#markup' => '<p>' . t('This field supports dynamic token values. Common values might be [current-user:mail] or [node:title].') . '</p>',
4500
    ),
4501
    'token_tree' => array(
4502
      '#theme' => 'token_tree',
4503
      '#token_types' => $groups,
4504
    ),
4505
  );
4506

    
4507
  if (!module_exists('token')) {
4508
    // No token module at all. Display a simple suggestion to enable it.
4509
    $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>';
4510
    unset($help['token_tree']);
4511
  }
4512
  else {
4513
    module_load_include('inc', 'token', 'token.pages');
4514
    if (function_exists('token_page_output_tree')) {
4515
      // Token supports dialogs: display simply as a link.
4516
      $help = $help['token_tree'];
4517
      $help['#dialog'] = TRUE;
4518
    }
4519
  }
4520

    
4521
  return render($help);
4522
}
4523

    
4524
/**
4525
 * Convert a name into an identifier.
4526
 *
4527
 * The identifier is safe for machine names, classes, and other ASCII uses.
4528
 */
4529
function _webform_safe_name($name) {
4530
  $new = trim($name);
4531
  $new = _webform_transliterate($new);
4532
  $new = str_replace(array(' ', '-', '/'), array('_', '_', '_'), $new);
4533
  $new = drupal_strtolower($new);
4534
  $new = preg_replace('/[^a-z0-9_]/', '', $new);
4535
  return $new;
4536
}
4537

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

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

    
4590
  if ($name == 'default') {
4591
    $name = webform_variable_get('webform_default_from_name');
4592
  }
4593
  elseif (is_numeric($name) && isset($node->webform['components'][$name])) {
4594
    if (isset($submission->data[$name])) {
4595
      $component = $node->webform['components'][$name];
4596
      $name = $submission->data[$name];
4597

    
4598
      // Convert the FROM name to be the label of select lists.
4599
      if (webform_component_implements($component['type'], 'options')) {
4600
        $options = webform_component_invoke($component['type'], 'options', $component);
4601
        foreach ($name as &$one_name) {
4602
          $one_name = isset($options[$one_name]) ? $options[$one_name] : $one_name;
4603
        }
4604
        // Drop PHP reference.
4605
        unset($one_name);
4606
      }
4607
    }
4608
    else {
4609
      $name = t('Value of !component', array('!component' => $node->webform['components'][$name]['name']));
4610
    }
4611
  }
4612
  elseif (!isset($name)) {
4613
    $name = '';
4614
  }
4615

    
4616
  if ($address == 'default') {
4617
    $address = webform_variable_get('webform_default_from_address');
4618
  }
4619
  elseif (is_numeric($address) && isset($node->webform['components'][$address])) {
4620
    if (isset($submission->data[$address])) {
4621
      $values = $submission->data[$address];
4622
      $address = array();
4623
      foreach ($values as $value) {
4624
        if (isset($mapping) && isset($mapping[$value])) {
4625
          $value = $mapping[$value];
4626
        }
4627
        $address = array_merge($address, explode(',', $value));
4628
      }
4629
    }
4630
    else {
4631
      $address = t('Value of "!component"', array('!component' => $node->webform['components'][$address]['name']));
4632
    }
4633
  }
4634

    
4635
  // Convert single values to an array to simplify processing.
4636
  $address = is_array($address) ? $address : explode(',', $address);
4637
  $name = is_array($name) ? $name : array($name);
4638
  $name_shortage = count($address) - count($name);
4639
  if ($name_shortage > 0) {
4640
    $name += array_fill(count($name), $name_shortage, $name[0]);
4641
  }
4642

    
4643
  foreach ($address as $key => $individual_address) {
4644
    $individual_address = trim($individual_address);
4645
    $individual_address = webform_replace_tokens($individual_address, $node, $submission);
4646
    $email_parts = webform_parse_email_address($individual_address);
4647
    if ($format == 'long' && !empty($name[$key]) && !strlen($email_parts['name'])) {
4648
      $individual_name = $name[$key];
4649
      $individual_name = webform_replace_tokens($individual_name, $node, $submission);
4650
      if ($encode) {
4651
        $individual_name = mime_header_encode($individual_name);
4652
      }
4653
      $individual_name = trim($individual_name);
4654
      $individual_address = '"' . $individual_name . '" <' . $individual_address . '>';
4655
    }
4656
    $address[$key] = $individual_address;
4657
  }
4658

    
4659
  return $single ? reset($address) : $address;
4660
}
4661

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

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

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

    
4768
/**
4769
 * Given an email subject, format it with any needed replacements.
4770
 */
4771
function webform_format_email_subject($subject, $node = NULL, $submission = NULL) {
4772
  if ($subject == 'default') {
4773
    $subject = webform_variable_get('webform_default_subject');
4774
  }
4775
  elseif (is_numeric($subject) && isset($node->webform['components'][$subject])) {
4776
    $component = $node->webform['components'][$subject];
4777
    if (isset($submission->data[$subject])) {
4778
      $display_function = '_webform_display_' . $component['type'];
4779
      $value = $submission->data[$subject];
4780

    
4781
      // Convert the value to a clean text representation if possible.
4782
      if (function_exists($display_function)) {
4783
        $display = $display_function($component, $value, 'text');
4784
        $display['#theme_wrappers'] = array();
4785
        $display['#webform_component'] = $component;
4786
        $subject = str_replace("\n", ' ', drupal_render($display));
4787
      }
4788
      else {
4789
        $subject = $value;
4790
      }
4791
    }
4792
    else {
4793
      $subject = t('Value of "!component"', array('!component' => $component['name']));
4794
    }
4795
  }
4796

    
4797
  // Convert arrays to strings (may happen if checkboxes are used as the value).
4798
  if (is_array($subject)) {
4799
    $subject = reset($subject);
4800
  }
4801

    
4802
  return webform_replace_tokens($subject, $node, $submission);
4803
}
4804

    
4805
/**
4806
 * Convert an array of components into a tree.
4807
 */
4808
function _webform_components_tree_build($src, &$tree, $parent, &$page_count) {
4809
  foreach ($src as $cid => $component) {
4810
    if ($component['pid'] == $parent) {
4811
      _webform_components_tree_build($src, $component, $cid, $page_count);
4812
      if ($component['type'] == 'pagebreak') {
4813
        $page_count++;
4814
      }
4815
      $tree['children'][$cid] = $component;
4816
      $tree['children'][$cid]['page_num'] = $page_count;
4817
    }
4818
  }
4819
  return $tree;
4820
}
4821

    
4822
/**
4823
 * Flatten a component tree into a flat list.
4824
 */
4825
function _webform_components_tree_flatten($tree) {
4826
  $components = array();
4827
  foreach ($tree as $cid => $component) {
4828
    if (isset($component['children'])) {
4829
      unset($component['children']);
4830
      $components[$cid] = $component;
4831
      // array_merge() can't be used here because the keys are numeric.
4832
      $children = _webform_components_tree_flatten($tree[$cid]['children']);
4833
      foreach ($children as $ccid => $ccomponent) {
4834
        $components[$ccid] = $ccomponent;
4835
      }
4836
    }
4837
    else {
4838
      $components[$cid] = $component;
4839
    }
4840
  }
4841
  return $components;
4842
}
4843

    
4844
/**
4845
 * Helper for the uasort in webform_tree_sort()
4846
 */
4847
function _webform_components_sort($a, $b) {
4848
  if ($a['weight'] == $b['weight']) {
4849
    return strcasecmp($a['name'], $b['name']);
4850
  }
4851
  return ($a['weight'] < $b['weight']) ? -1 : 1;
4852
}
4853

    
4854
/**
4855
 * Sort each level of a component tree by weight and name.
4856
 */
4857
function _webform_components_tree_sort($tree) {
4858
  if (isset($tree['children']) && is_array($tree['children'])) {
4859
    $children = array();
4860
    uasort($tree['children'], '_webform_components_sort');
4861
    foreach ($tree['children'] as $cid => $component) {
4862
      $children[$cid] = _webform_components_tree_sort($component);
4863
    }
4864
    $tree['children'] = $children;
4865
  }
4866
  return $tree;
4867
}
4868

    
4869
/**
4870
 * Get a list of all available component definitions.
4871
 */
4872
function webform_components($include_disabled = FALSE, $reset = FALSE) {
4873
  static $components, $enabled;
4874

    
4875
  if (!isset($components) || $reset) {
4876
    $components = array();
4877
    $disabled = array_flip(webform_variable_get('webform_disabled_components'));
4878
    foreach (module_implements('webform_component_info') as $module) {
4879
      $module_components = module_invoke($module, 'webform_component_info');
4880
      foreach ($module_components as $type => $info) {
4881
        $module_components[$type]['module'] = $module;
4882
        $module_components[$type]['enabled'] = !array_key_exists($type, $disabled);
4883
      }
4884
      $components += $module_components;
4885
    }
4886
    drupal_alter('webform_component_info', $components);
4887
    uasort($components, function ($a, $b) {
4888
      return strnatcasecmp($a['label'], $b['label']);
4889
    });
4890
    $enabled = array_diff_key($components, $disabled);
4891
  }
4892

    
4893
  return $include_disabled ? $components : $enabled;
4894
}
4895

    
4896
/**
4897
 * Build a list of components suitable for use as select list options.
4898
 */
4899
function webform_component_options($include_disabled = FALSE) {
4900
  $component_info = webform_components($include_disabled);
4901
  $options = array();
4902
  foreach ($component_info as $type => $info) {
4903
    $options[$type] = $info['label'];
4904
  }
4905
  return $options;
4906
}
4907

    
4908
/**
4909
 * Load a component file into memory.
4910
 *
4911
 * @param $component_type
4912
 *   The string machine name of a component.
4913
 */
4914
function webform_component_include($component_type) {
4915
  static $included = array();
4916

    
4917
  // No need to load components that have already been added once.
4918
  if (!isset($included[$component_type])) {
4919
    $components = webform_components(TRUE);
4920
    $included[$component_type] = TRUE;
4921

    
4922
    if (isset($components[$component_type]['file'])) {
4923
      $info = $components[$component_type];
4924
      $pathinfo = pathinfo($info['file']);
4925
      $basename = basename($pathinfo['basename'], '.' . $pathinfo['extension']);
4926
      $path = (!empty($pathinfo['dirname']) ? $pathinfo['dirname'] . '/' : '') . $basename;
4927
      module_load_include($pathinfo['extension'], $info['module'], $path);
4928
    }
4929
  }
4930
}
4931

    
4932
/**
4933
 * Invoke a component callback.
4934
 *
4935
 * @param $type
4936
 *   The component type as a string.
4937
 * @param $callback
4938
 *   The callback to execute.
4939
 * @param ...
4940
 *   Any additional parameters required by the $callback.
4941
 *
4942
 * @return mixed
4943
 *   Return value of the callback on success and FALSE on failure.
4944
 */
4945
function webform_component_invoke($type, $callback) {
4946
  $args = func_get_args();
4947
  $type = array_shift($args);
4948
  $callback = array_shift($args);
4949
  $function = '_webform_' . $callback . '_' . $type;
4950
  webform_component_include($type);
4951
  if (function_exists($function)) {
4952
    return call_user_func_array($function, $args);
4953
  }
4954
}
4955

    
4956
/**
4957
 * Check if a component implements a particular hook.
4958
 *
4959
 * @param $type
4960
 *   The component type as a string.
4961
 * @param $callback
4962
 *   The callback to check.
4963
 *
4964
 * @return bool
4965
 *   Whether or not the hook is implemented.
4966
 */
4967
function webform_component_implements($type, $callback) {
4968
  $function = '_webform_' . $callback . '_' . $type;
4969
  webform_component_include($type);
4970
  return function_exists($function);
4971
}
4972

    
4973
/**
4974
 * Form API #process function to expand a webform conditional element.
4975
 */
4976
function webform_conditional_expand($element) {
4977
  module_load_include('inc', 'webform', 'includes/webform.conditionals');
4978
  return _webform_conditional_expand($element);
4979
}
4980

    
4981
/**
4982
 * Add class and wrapper class attributes to an element.
4983
 */
4984
function _webform_component_classes(&$element, $component) {
4985
  if (isset($component['extra']['css_classes']) && drupal_strlen($component['extra']['css_classes'])) {
4986
    $element['#attributes']['class'] = isset($element['#attributes']['class']) ? $element['#attributes']['class'] : array();
4987
    $element['#attributes']['class'] = array_merge($element['#attributes']['class'], explode(' ', $component['extra']['css_classes']));
4988
  }
4989
  if (isset($component['extra']['wrapper_classes']) && drupal_strlen($component['extra']['wrapper_classes'])) {
4990
    $element['#wrapper_attributes']['class'] = isset($element['#wrapper_attributes']['class']) ? $element['#wrapper_attributes']['class'] : array();
4991
    $element['#wrapper_attributes']['class'] = array_merge($element['#wrapper_attributes']['class'], explode(' ', $component['extra']['wrapper_classes']));
4992
  }
4993
}
4994

    
4995
/**
4996
 * Disable the Drupal page cache.
4997
 */
4998
function webform_disable_page_cache() {
4999
  drupal_page_is_cacheable(FALSE);
5000
}
5001

    
5002
/**
5003
 * Set the necessary breadcrumb for the page we are on.
5004
 *
5005
 * @param object $node
5006
 *   The loaded webform node.
5007
 * @param bool|object $submission
5008
 *   The submission if the current page is viewing or dealing with a submission,
5009
 *   or TRUE to just include the webform node in the breadcrumbs (used for
5010
 *   the submission completion confirmation page), or NULL for no extra
5011
 *   processing.
5012
 */
5013
function webform_set_breadcrumb($node, $submission = NULL) {
5014
  $node_path = "node/{$node->nid}";
5015

    
5016
  // Set the href of the current menu item to be the node's path. This has two
5017
  // effects. The active trail will be to the node's prefered menu tree
5018
  // location, expanding the menu as appropriate. And the breadcrumbs will be
5019
  // set as if the current page were under the node's preferred location.
5020
  // Note that menu_tree_set_path() could be used to set the path for the menu,
5021
  // but it will not affect the breadcrumbs when the webform is not in the
5022
  // default menu.
5023
  menu_set_item(NULL, array('href' => $node_path) + menu_get_item());
5024

    
5025
  if ($submission) {
5026
    $breadcrumb = menu_get_active_breadcrumb();
5027

    
5028
    // Append the node title (or its menu name), in case it isn't in the path
5029
    // already.
5030
    $active_trail = menu_get_active_trail();
5031
    $last_active = end($active_trail);
5032
    $breadcrumb[] = $last_active['href'] === $node_path && !empty($last_active['in_active_trail'])
5033
                      ? l($last_active['title'], $node_path, $last_active['localized_options'])
5034
                      : l($node->title, $node_path);
5035

    
5036
    // Setting the current menu href will cause the submission title and current
5037
    // tab (if not the default tab) to be added to the active path when the
5038
    // webform is in the default location in the menu (node/NID). The title
5039
    // is desirable, but the tab name (for example Edit or Delete) isn't.
5040
    if (preg_match('/href=".*"/', end($breadcrumb), $matches)) {
5041
      foreach ($breadcrumb as $index => $link) {
5042
        if (stripos($link, $matches[0]) !== FALSE) {
5043
          $breadcrumb = array_slice($breadcrumb, 0, $index + 1);
5044
          break;
5045
        }
5046
      }
5047
    }
5048

    
5049
    // If the user is dealing with a submission, then the breadcrumb should
5050
    // be fudged to allow them to return to a likely list of webforms.
5051
    // Note that this isn't necessarily where they came from, but it's the
5052
    // best guess available.
5053
    if (is_object($submission)) {
5054
      if (webform_results_access($node)) {
5055
        $breadcrumb[] = l(t('Webform results'), $node_path . '/webform-results');
5056
      }
5057
      elseif (user_access('access own webform results')) {
5058
        $breadcrumb[] = l(t('Submissions'), $node_path . '/submissions');
5059
      }
5060
    }
5061

    
5062
    drupal_set_breadcrumb($breadcrumb);
5063
  }
5064
}
5065

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

    
5100
  $return = array();
5101

    
5102
  // Check for a date string.
5103
  if ($type == 'date' || !isset($type)) {
5104
    $return['year'] = $matches[2] !== '' ? (int) $matches[2] : '';
5105
    $return['month'] = $matches[3] !== '' ? (int) $matches[3] : '';
5106
    $return['day'] = $matches[4] !== '' ? (int) $matches[4] : '';
5107
  }
5108

    
5109
  // Check for a time string.
5110
  if ($type == 'time' || !isset($type)) {
5111
    $return['hour'] = $matches[6] !== '' ? (int) $matches[6] : '';
5112
    $return['minute'] = $matches[7] !== '' ? (int) $matches[7] : '';
5113
    $return['second'] = $matches[8] !== '' ? (int) $matches[8] : '';
5114
  }
5115

    
5116
  return $return;
5117
}
5118

    
5119
/**
5120
 * Convert an array of a date or time into an ISO 8601 compatible string.
5121
 *
5122
 * @param $array
5123
 *   The array to convert to a date or time string.
5124
 * @param $type
5125
 *   If wanting a specific string format back specify either "date" or "time".
5126
 *   Otherwise a full ISO 8601 date and time string will be returned.
5127
 *
5128
 * @return string
5129
 *   Date in string format
5130
 */
5131
function webform_date_string($array, $type = NULL) {
5132
  $string = '';
5133

    
5134
  if ($type == 'date' || !isset($type)) {
5135
    $string .= empty($array['year']) ? '0000' : sprintf('%04d', $array['year']);
5136
    $string .= '-';
5137
    $string .= empty($array['month']) ? '00' : sprintf('%02d', $array['month']);
5138
    $string .= '-';
5139
    $string .= empty($array['day']) ? '00' : sprintf('%02d', $array['day']);
5140
  }
5141

    
5142
  if (!isset($type)) {
5143
    $string .= 'T';
5144
  }
5145

    
5146
  if ($type == 'time' || !isset($type)) {
5147
    $string .= empty($array['hour']) ? '00' : sprintf('%02d', $array['hour']);
5148
    $string .= ':';
5149
    $string .= empty($array['minute']) ? '00' : sprintf('%02d', $array['minute']);
5150
    $string .= ':';
5151
    $string .= empty($array['second']) ? '00' : sprintf('%02d', $array['second']);
5152
  }
5153

    
5154
  return $string;
5155
}
5156

    
5157
/**
5158
 * Get a date format according to the site settings.
5159
 *
5160
 * @param $type
5161
 *   A choice of 'short', 'medium', 'long' , or other user-defined date formats.
5162
 *   Use NULL for the webform-specific date format choosen in the webform
5163
 *   settings.
5164
 * @param array $exclude
5165
 *   An array containing 'day', 'month', and/or 'year' if they should be
5166
 *   removed from the format.
5167
 *
5168
 * @return string
5169
 *   A date/time format string.
5170
 */
5171
function webform_date_format($type = NULL, array $exclude = array()) {
5172
  static $formats = array();
5173
  $id = $type . ':' . implode('', $exclude);
5174
  if (!isset($formats[$id])) {
5175
    $type_name = $type ? $type : webform_variable_get('webform_date_type');
5176

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

    
5180
    // Date/Time formatting characters
5181
    // WHAT           REQUIRED (at least 1) OPTIONAL (allowed but insufficient)
5182
    // -------------------------------------------------------------------------
5183
    // Day-of-week                          DlNw
5184
    // Day            dj                    Stz
5185
    // Month          FmMn
5186
    // Year           oYy                   L
5187
    //
5188
    //                NOT ALLOWED
5189
    // -------------------------------------------------------------------------
5190
    // Time           aABgGhHisueIOPTZ
5191
    // Special        /.,-: <space>
5192
    //
5193
    // Strip Time and Special characters from the beginning and end of format.
5194
    $date_format = trim($format, 'aABgGhHisueIOPTZ/.,-: ');
5195

    
5196
    // Ensure that a day, month, and year value are present. Use a default
5197
    // format if all the values are not found. This regular expression uses
5198
    // (?= ), the positive lookahead assertion. It asserts that there are some
5199
    // optional characters (.*) followed by one of the day, month, or year
5200
    // characters. Because it is an assertion, it doesn't consume the
5201
    // characters, so the day, month, and year can be in any order.
5202
    if (!preg_match('/(?=.*[dj])(?=.*[FmMn])(?=.*[oYy])/', $date_format)) {
5203
      $date_format = 'm/d/Y';
5204
    }
5205

    
5206
    // Remove any excluded portions.
5207
    $strip = array(
5208
      'day' => 'DlNwdjStz',
5209
      'month' => 'FmMn',
5210
      'year' => 'oYyL',
5211
    );
5212
    foreach ($exclude as $field) {
5213
      // Strip the format and any trailing /.,-: or space.
5214
      $date_format = preg_replace('#[' . $strip[$field] . ']+[/\.,\-: ]*#', '', $date_format);
5215
      $date_format = trim($date_format, '/.,-: ');
5216
    }
5217

    
5218
    $formats[$id] = $date_format;
5219
  }
5220

    
5221
  return $formats[$id];
5222
}
5223

    
5224
/**
5225
 * Return a date in the desired format taking into consideration user timezones.
5226
 */
5227
function webform_strtodate($format, $string, $timezone_name = NULL, $reference_timestamp = NULL) {
5228
  global $user;
5229

    
5230
  // Adjust the time based on the user or site timezone.
5231
  if (variable_get('configurable_timezones', 1) && $timezone_name == 'user' && $user->uid) {
5232
    $timezone_name = isset($GLOBALS['user']->timezone) ? $GLOBALS['user']->timezone : 'UTC';
5233
  }
5234
  // If the timezone is still empty or not set, use the site timezone.
5235
  if (empty($timezone_name) || $timezone_name == 'user') {
5236
    $timezone_name = variable_get('date_default_timezone', 'UTC');
5237
  }
5238

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

    
5271
/**
5272
 * Get a timestamp in GMT time, ensuring timezone accuracy.
5273
 */
5274
function webform_strtotime($date) {
5275
  $current_tz = date_default_timezone_get();
5276
  date_default_timezone_set('UTC');
5277
  $timestamp = strtotime($date);
5278
  date_default_timezone_set($current_tz);
5279
  return $timestamp;
5280
}
5281

    
5282
/**
5283
 * Wrapper function for i18n_string() if i18nstrings enabled.
5284
 */
5285
function webform_tt($name, $string, $langcode = NULL, $update = FALSE) {
5286
  if (function_exists('i18n_string')) {
5287
    $options = array(
5288
      'langcode' => $langcode,
5289
      'update' => $update,
5290
    );
5291
    return i18n_string($name, $string, $options);
5292
  }
5293
  else {
5294
    return $string;
5295
  }
5296
}
5297

    
5298
/**
5299
 * Returns an IP Address or anonymized IP Address for confidential webforms.
5300
 */
5301
function webform_ip_address($node) {
5302
  return $node->webform['confidential'] ? t('(unknown)') : ip_address();
5303
}
5304

    
5305
/**
5306
 * Implements hook_views_api().
5307
 */
5308
function webform_views_api() {
5309
  return array(
5310
    'api' => 3.0,
5311
    'path' => drupal_get_path('module', 'webform') . '/views',
5312
  );
5313
}
5314

    
5315
/**
5316
 * Implements hook_views_default_views().
5317
 */
5318
function webform_views_default_views() {
5319
  $path = './' . drupal_get_path('module', 'webform') . '/views/default_views/*.inc';
5320
  $views = array();
5321
  foreach (glob($path) as $views_filename) {
5322
    require_once $views_filename;
5323
  }
5324
  return $views;
5325
}
5326

    
5327
/**
5328
 * Implements hook_field_extra_fields().
5329
 */
5330
function webform_field_extra_fields() {
5331
  $extra = array();
5332
  foreach (webform_node_types() as $type) {
5333
    $extra['node'][$type]['display']['webform'] = array(
5334
      'label' => t('Webform'),
5335
      'description' => t('Webform client form.'),
5336
      'weight' => 10,
5337
    );
5338
  }
5339
  return $extra;
5340
}
5341

    
5342
/**
5343
 * Implements hook_date_views_extra_tables().
5344
 */
5345
function webform_date_views_extra_tables() {
5346
  return array('webform_submissions' => 'webform_submissions');
5347
}
5348

    
5349
/**
5350
 * Returns the next serial number for a given node and increments it.
5351
 *
5352
 * Return the serial number to be used in the next submission and saves the
5353
 * number after that as the subsequent serial number.
5354
 *
5355
 * @param int $nid
5356
 *   The nid of the node.
5357
 *
5358
 * @return int
5359
 *   The next value of the serial number.
5360
 */
5361
function _webform_submission_serial_next_value($nid) {
5362
  // Use a transaction with SELECT ... FOR UPDATE to lock the row between
5363
  // the SELECT and the UPDATE, ensuring that multiple Webform submissions
5364
  // at the same time do not have duplicate numbers. FOR UPDATE must be inside
5365
  // a transaction. The return value of db_transaction() must be assigned or the
5366
  // transaction will commit immediately. The transaction will commit when
5367
  // $transaction goes out-of-scope.
5368
  $transaction = db_transaction();
5369

    
5370
  // Get the value stored in this webform as the next_serial value.
5371
  $next_serial = db_select('webform', 'w')
5372
    // Only add FOR UPDATE when incrementing.
5373
    ->forUpdate()
5374
    ->fields('w', array('next_serial'))
5375
    ->condition('nid', $nid)
5376
    ->execute()
5377
    ->fetchField();
5378

    
5379
  // The value must be greater than $next_serial and any existing serial number.
5380
  $next_serial = max($next_serial, _webform_submission_serial_next_value_used($nid));
5381

    
5382
  // Store the value after $next_value for use in the subsequent submission.
5383
  db_update('webform')
5384
    ->fields(array('next_serial' => $next_serial + 1))
5385
    ->condition('nid', $nid)
5386
    ->execute();
5387

    
5388
  return $next_serial;
5389
}
5390

    
5391
/**
5392
 * Returns the next submission serial number to be used.
5393
 *
5394
 * This is based on the submissions in the database.
5395
 *
5396
 * @param int $nid
5397
 *   The Node ID of the Webform.
5398
 *
5399
 * @return int
5400
 *   The largest serial number used by a submission plus 1 for the specified
5401
 *   node or 1 when there are no submissions.
5402
 */
5403
function _webform_submission_serial_next_value_used($nid) {
5404
  $max_serial = db_select('webform_submissions');
5405
  $max_serial->addExpression('MAX(serial)');
5406
  $max_serial = $max_serial
5407
    ->condition('nid', $nid)
5408
    ->execute()
5409
    ->fetchField();
5410
  // $max_serial will be a numeric string or NULL.
5411
  return $max_serial + 1;
5412
}
5413

    
5414
/**
5415
 * Alter the node before saving a clone.
5416
 *
5417
 * @param object $node
5418
 *   Reference to the fully loaded node object being saved (the clone) that
5419
 *   can be altered as needed.
5420
 * @param array $context
5421
 *   An array of context describing the clone operation. The keys are:
5422
 *   - 'method' : Can be either 'prepopulate' or 'save-edit'.
5423
 *   - 'original_node' : The original fully loaded node object being cloned.
5424
 *
5425
 * @see clone_node_save()
5426
 * @see drupal_alter()
5427
 */
5428
function webform_clone_node_alter(&$node, array $context) {
5429
  if (isset($node->webform)) {
5430
    $defaults = webform_node_defaults();
5431
    $node->webform['next_serial'] = $defaults['next_serial'];
5432
  }
5433
}
5434

    
5435
/**
5436
 * Check if the last form submission exceeded the servers max_input_vars limit.
5437
 *
5438
 * Optionally preflight the current form to be returned in this request.
5439
 *
5440
 * @param array $form
5441
 *   Reference to the form, which will be changed if $parent_key is set.
5442
 * @param array $form_state
5443
 *   Form's state or NULL for no form state check.
5444
 * @param string $detect_key
5445
 *   A key that will always be present in the posted data when an actual form
5446
 *   submission has been made.
5447
 * @param string $parent_key
5448
 *   Omit to not preflight the form, or the array key for the parent of where
5449
 *   the preflight warning should be inserted into the form.
5450
 */
5451
function webform_input_vars_check(array &$form, array $form_state, $detect_key, $parent_key = NULL) {
5452
  if (isset($parent_key)) {
5453
    $form['#pre_render'] = array('webform_pre_render_input_vars');
5454
    $form['#input_var_waring_parent'] = $parent_key;
5455
  }
5456
  if (!empty($form_state['input']) && array_key_exists($detect_key, $form_state['input']) && !array_key_exists('form_id', $form_state['input'])) {
5457
    // A form was submitted with POST, but the form_id was missing. The most
5458
    // likely cause of this is that the POST was truncated because PHP exceeded
5459
    // its max_input_vars limit.
5460
    $subs = array(
5461
      '@count' => webform_count_terminals($_POST),
5462
      '@limit' => (int) ini_get('max_input_vars'),
5463
    );
5464
    drupal_set_message(user_access('administer site configuration')
5465
                          ? t('This form could not be submitted because $_POST was truncated to @count input vars.  PHP max_input_vars is @limit and needs to be increased.', $subs)
5466
                          : t('This form could not be submitted because it exceeds the server configuration. Contact the administrator.'),
5467
                      'error');
5468
    watchdog('webform',
5469
      'POST truncated to @count input vars. PHP max_input_vars is @limit. Increase max_input_vars.',
5470
      $subs,
5471
      WATCHDOG_ERROR);
5472
  }
5473
}
5474

    
5475
/**
5476
 * Checks the number of input form elements on this page.
5477
 *
5478
 * This ensures that the PHP max_input_vars limit is not exceeded.
5479
 *
5480
 * Install this function as a #pre_render function.
5481
 */
5482
function webform_pre_render_input_vars($element) {
5483
  // Determine the limit on input vars for this server configuration.
5484
  $limit = ini_get('max_input_vars');
5485
  if ($limit) {
5486
    // Estimate the number of input vars needed to see if the PHP limit has been
5487
    // exceeded. Additional input_vars: op.
5488
    $count = 1 + webform_count_input_vars($element);
5489
    if ($count > $limit * 0.95) {
5490
      $subs = array(
5491
        '@count' => $count,
5492
        '@limit' => $limit,
5493
      );
5494
      $warning = array(
5495
        '#markup' => '<div class="messages warning">' .
5496
        (user_access('administer site configuration')
5497
          ? t('This form contains @count input elements. PHP max_input_vars is @limit and should be increased.', $subs)
5498
          : t('This form may be too long to work properly. Contact the administrator.'))
5499
        . '</div>',
5500
        '#weight' => -1,
5501
      );
5502
      if ($element['#input_var_waring_parent']) {
5503
        $element[$element['#input_var_waring_parent']]['input_vars_warning'] = $warning;
5504
      }
5505
      else {
5506
        $element['input_vars_warning'] = $warning;
5507
      }
5508
      watchdog('webform',
5509
        'Page contains @count input elements but PHP max_input_vars is only @limit. Increase max_input_vars.',
5510
        $subs,
5511
        WATCHDOG_ERROR);
5512
    }
5513
  }
5514
  return $element;
5515
}
5516

    
5517
/**
5518
 * Counts the number of input form elements.
5519
 *
5520
 * Note that this is somewhat imprecise. The number of input vars returned in
5521
 * $_POST can vary with the form element. For example, a multiple-select
5522
 * listbox returns one input var for each selection actually made.
5523
 *
5524
 * The primary use for this count is for the conditionals page, where only
5525
 * select, textfield, hidden, and token elements are used. If a more accurate
5526
 * count for webform_client_form is needed, a mechanism to predict the number
5527
 * of input elements for each component type and each component instance would
5528
 * be needed.
5529
 *
5530
 * @param array $element
5531
 *   The form whose elements should be counted.
5532
 *
5533
 * @return int
5534
 *   The number of elements in the form that will result in $_POST entries.
5535
 */
5536
function webform_count_input_vars(array $element) {
5537
  static $input_types = array(
5538
    'checkbox' => 1,
5539
    'date' => 1,
5540
    'file' => 1,
5541
    'managed_file' => 1,
5542
    'password' => 1,
5543
    'password_confirm' => 1,
5544
    'radios' => 1,
5545
    'select' => 1,
5546
    'textfield' => 1,
5547
    'textarea' => 1,
5548
    'token' => 1,
5549
    'weight' => 1,
5550
    'hidden' => 1,
5551
    'value' => 1,
5552
    'webform_email' => 1,
5553
    'webform_number' => 1,
5554
  );
5555
  $children = array_intersect_key($element, array_flip(element_children($element)));
5556
  return $children
5557
    ? array_reduce($children, function ($carry, $item) {
5558
      return $carry + webform_count_input_vars($item);
5559
    }, 0)
5560
    : (isset($element['#type']) && isset($input_types[$element['#type']]) ? $input_types[$element['#type']] : 0);
5561
}
5562

    
5563
/**
5564
 * Counts terminals in an array.
5565
 *
5566
 * Useful for counting how many input_vars were returned in $_POST.
5567
 *
5568
 * @param $a
5569
 *   Array or array element to be counted
5570
 *
5571
 * @return int
5572
 *   Number of non-array elements within $a.
5573
 */
5574
function webform_count_terminals($a) {
5575
  return is_array($a)
5576
    ? array_reduce($a, function ($carry, $item) {
5577
      return $carry + webform_count_terminals($item);
5578
    }, 0)
5579
    : 1;
5580
}