Projet

Général

Profil

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

root / drupal7 / sites / all / modules / webform / webform.module @ 8c72e82a

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

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

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

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

    
538
  return $email;
539
}
540

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

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

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

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

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

    
608
  return FALSE;
609
}
610

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

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

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

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

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

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

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

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

    
656
    case 'edit':
657
      return $module_access || ($general_access && (user_access('edit all webform submissions', $account) || (user_access('edit own webform submissions', $account) && $account->uid == $submission->uid)));
658

    
659
    case 'delete':
660
      return $module_access || ($general_access && (user_access('delete all webform submissions', $account) || (user_access('delete own webform submissions', $account) && $account->uid == $submission->uid)));
661

    
662
    case 'list':
663
      return $module_access || user_access('access all webform results', $account) || (user_access('access own webform submissions', $account) && ($account->uid || isset($_SESSION['webform_submission']))) || (user_access('access own webform results', $account) && $account->uid == $node->uid);
664
  }
665
}
666

    
667
/**
668
 * Menu access callback. Ensure a user both access and node 'view' permission.
669
 */
670
function webform_results_access($node, $account = NULL) {
671
  global $user;
672
  $account = isset($account) ? $account : $user;
673

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

    
676
  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));
677
}
678

    
679
/**
680
 * Menu access callback. Ensure a user has both results access and permission
681
 * to clear submissions.
682
 */
683
function webform_results_clear_access($node, $account = NULL) {
684
  global $user;
685
  $account = isset($account) ? $account : $user;
686

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

    
689
  return webform_results_access($node, $account) && ($module_access || user_access('delete all webform submissions', $account));
690
}
691

    
692
/**
693
 * Menu access callback. Ensure a sure has access to update a webform node.
694
 *
695
 * Unlike webform_results_access and webform_results_clear_access, access is
696
 * completely overridden by the any implmentation of hook_webform_update_access.
697
 *
698
 * If hook_webform_update_access is implemented by one or more other modules,
699
 * the results must be unanimously TRUE for access to be granted; otherwise it
700
 * is denied if even one implementation returns FALSE, regardless of node_access
701
 * and the 'edit webform components' permission. This allows implementors
702
 * complete flexibility.
703
 *
704
 * hook_webform_update_access should return TRUE if access should absolutely
705
 * be granted, FALSE if it should absolutely be denied, or NULL if node_access
706
 * and 'edit webform components' permission should determine access.
707
 *
708
 * @see hook_webform_update_access()
709
 */
710
function webform_node_update_access($node, $account = NULL) {
711
  global $user;
712
  $account = isset($account) ? $account : $user;
713
  $module_access = module_invoke_all('webform_update_access', $node, $account);
714
  return empty($module_access)
715
            ? node_access('update', $node, $account) && user_access('edit webform components')
716
            : count(array_filter($module_access)) == count($module_access);
717
}
718

    
719
/**
720
 * Implements hook_admin_paths().
721
 */
722
function webform_admin_paths() {
723
  if (variable_get('node_admin_theme')) {
724
    return array(
725
      'node/*/webform' => TRUE,
726
      'node/*/webform/*' => TRUE,
727
      'node/*/webform-results' => TRUE,
728
      'node/*/webform-results/*' => TRUE,
729
      'node/*/submission/*' => TRUE,
730
    );
731
  }
732
}
733

    
734
/**
735
 * Implements hook_perm().
736
 */
737
function webform_permission() {
738
  return array(
739
    'access all webform results' => array(
740
      'title' => t('Access all webform results'),
741
      'description' => t('Grants access to the "Results" tab on all webform content. Generally an administrative permission.'),
742
    ),
743
    'access own webform results' => array(
744
      'title' => t('Access own webform results'),
745
      'description' => t('Grants access to the "Results" tab to the author of webform content they have created.'),
746
    ),
747
    'edit all webform submissions' => array(
748
      'title' => t('Edit all webform submissions'),
749
      'description' => t('Allows editing of any webform submission by any user. Generally an administrative permission.'),
750
    ),
751
    'delete all webform submissions' => array(
752
      'title' => t('Delete all webform submissions'),
753
      'description' => t('Allows deleting of any webform submission by any user. Generally an administrative permission.'),
754
    ),
755
    'access own webform submissions' => array(
756
      'title' => t('Access own webform submissions'),
757
    ),
758
    'edit own webform submissions' => array(
759
      'title' => t('Edit own webform submissions'),
760
    ),
761
    'delete own webform submissions' => array(
762
      'title' => t('Delete own webform submissions'),
763
    ),
764
    'edit webform components' => array(
765
      'title' => t('Content authors: access and edit webform components and settings'),
766
      'description' => t('Grants additional access to the webform components and settings to users who can edit the content. Generally an authenticated user permission.'),
767
    ),
768
  );
769
}
770

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

    
998
  // Theme functions in all components.
999
  $components = webform_components(TRUE);
1000
  foreach ($components as $type => $component) {
1001
    if ($theme_additions = webform_component_invoke($type, 'theme')) {
1002
      $theme = array_merge($theme, $theme_additions);
1003
    }
1004
  }
1005
  return $theme;
1006
}
1007

    
1008
/**
1009
 * Implements hook_library().
1010
 */
1011
function webform_library() {
1012
  $module_path = drupal_get_path('module', 'webform');
1013

    
1014
  // Webform administration.
1015
  $libraries['admin'] = array(
1016
    'title' => 'Webform: Administration',
1017
    'website' => 'http://drupal.org/project/webform',
1018
    'version' => '1.0',
1019
    'js' => array(
1020
      $module_path . '/js/webform-admin.js' => array('group' => JS_DEFAULT),
1021
    ),
1022
    'css' => array(
1023
      $module_path . '/css/webform-admin.css' => array('group' => CSS_DEFAULT, 'weight' => 1),
1024
    ),
1025
  );
1026

    
1027
  return $libraries;
1028
}
1029

    
1030
/**
1031
 * Implements hook_element_info().
1032
 */
1033
function webform_element_info() {
1034
  // A few of our components need to be defined here because Drupal does not
1035
  // provide these components natively. Because this hook fires on every page
1036
  // load (even on non-webform pages), we don't put this in the component .inc
1037
  // files because of the unnecessary loading that it would require.
1038
  $elements['webform_time'] = array('#input' => 'TRUE');
1039
  $elements['webform_grid'] = array('#input' => 'TRUE');
1040

    
1041
  $elements['webform_email'] = array(
1042
    '#input' => TRUE,
1043
    '#theme' => 'webform_email',
1044
    '#size' => 60,
1045
  );
1046
  $elements['webform_number'] = array(
1047
    '#input' => TRUE,
1048
    '#theme' => 'webform_number',
1049
    '#min' => NULL,
1050
    '#max' => NULL,
1051
    '#step' => NULL,
1052
  );
1053

    
1054
  $elements['webform_conditional'] = array(
1055
    '#input' => TRUE,
1056
    '#theme' => 'webform_conditional',
1057
    '#default_value' => NULL,
1058
    '#process' => array('webform_conditional_expand'),
1059
  );
1060

    
1061
  return $elements;
1062
}
1063

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

    
1223
  if (module_exists('file')) {
1224
    $component_info['file'] = array(
1225
      'label' => t('File'),
1226
      'description' => t('Allow users to upload files of configurable types.'),
1227
      'features' => array(
1228
        'conditional' => FALSE,
1229
        'default_value' => FALSE,
1230
        'attachment' => TRUE,
1231
      ),
1232
      'file' => 'components/file.inc',
1233
    );
1234
  }
1235

    
1236
  return $component_info;
1237
}
1238

    
1239
/**
1240
 * Implements hook_webform_conditional_operator_info().
1241
 */
1242
function webform_webform_conditional_operator_info() {
1243
  module_load_include('inc', 'webform', 'includes/webform.conditionals');
1244
  return _webform_conditional_operator_info();
1245
}
1246

    
1247
/**
1248
 * Implements hook_forms().
1249
 *
1250
 * All webform_client_form forms share the same form handler.
1251
 */
1252
function webform_forms($form_id) {
1253
  $forms = array();
1254
  if (strpos($form_id, 'webform_client_form_') === 0) {
1255
    $forms[$form_id]['callback'] = 'webform_client_form';
1256
  }
1257
  return $forms;
1258
}
1259

    
1260
/**
1261
 * Implements hook_webform_select_options_info().
1262
 */
1263
function webform_webform_select_options_info() {
1264
  module_load_include('inc', 'webform', 'includes/webform.options');
1265
  return _webform_options_info();
1266
}
1267

    
1268
/**
1269
 * Implements hook_webform_webform_submission_actions().
1270
 */
1271
function webform_webform_submission_actions($node, $submission) {
1272
  $actions = array();
1273
  $destination = drupal_get_destination();
1274

    
1275
  if (module_exists('print_pdf') && user_access('access PDF version')) {
1276
    $actions['printpdf'] = array(
1277
      'title' => t('Download PDF'),
1278
      'href' => 'printpdf/' . $node->nid . '/submission/' . $submission->sid,
1279
      'query' => $destination,
1280
    );
1281
  }
1282

    
1283
  if (module_exists('print') && user_access('access print')) {
1284
    $actions['print'] = array(
1285
      'title' => t('Print'),
1286
      'href' => 'print/' . $node->nid . '/submission/' . $submission->sid,
1287
    );
1288
  }
1289

    
1290
  if (webform_results_access($node) && count($node->webform['emails'])) {
1291
    $actions['resend'] = array(
1292
      'title' => t('Resend e-mails'),
1293
      'href' => 'node/' . $node->nid . '/submission/' . $submission->sid . '/resend',
1294
      'query' => drupal_get_destination(),
1295
    );
1296
  }
1297

    
1298
  return $actions;
1299
}
1300

    
1301
/**
1302
 * Implements hook_webform_submission_presave().
1303
 *
1304
 * We implement our own hook here to facilitate the File component, which needs
1305
 * to clean up manage file usage records and delete files from submissions that
1306
 * have been edited if necessary.
1307
 */
1308
function webform_webform_submission_presave($node, &$submission) {
1309
  // Check if there are any file components in this submission and if any of
1310
  // them currently contain files.
1311
  $has_file_components = FALSE;
1312
  $new_fids = array();
1313
  $old_fids = array();
1314
  $renameable = array();
1315

    
1316
  foreach ($node->webform['components'] as $cid => $component) {
1317
    if ($component['type'] == 'file') {
1318
      $has_file_components = TRUE;
1319
      if (!empty($submission->data[$cid])) {
1320
        foreach ($submission->data[$cid] as $key => $value) {
1321
          if (empty($value)) {
1322
            unset($submission->data[$cid][$key]);
1323
          }
1324
          if (strlen($component['extra']['rename'])) {
1325
            $renameable[$cid][] = $value;
1326
          }
1327
        }
1328
        $new_fids = array_merge($new_fids, $submission->data[$cid]);
1329
      }
1330
    }
1331
  }
1332

    
1333
  if ($has_file_components) {
1334
    // If we're updating a submission, build a list of previous files.
1335
    if (isset($submission->sid)) {
1336
      drupal_static_reset('webform_get_submission');
1337
      $old_submission = webform_get_submission($node->nid, $submission->sid);
1338

    
1339
      foreach ($node->webform['components'] as $cid => $component) {
1340
        if ($component['type'] == 'file') {
1341
          if (!empty($old_submission->data[$cid])) {
1342
            $old_fids = array_merge($old_fids, $old_submission->data[$cid]);
1343
          }
1344
        }
1345
      }
1346
    }
1347

    
1348
    // Only rename files if this is the first time the submission is being saved
1349
    // as finished.
1350
    if ($submission->is_draft || (isset($old_submission) && !$old_submission->is_draft)) {
1351
      $renameable = array();
1352
    }
1353

    
1354
    // Save the list of added or removed files so we can add usage in
1355
    // hook_webform_submission_insert() or _update().
1356
    $submission->file_usage = array(
1357
      // Diff the old against new to determine what files were deleted.
1358
      'deleted_fids' => array_diff($old_fids, $new_fids),
1359
      // Diff the new files against old to determine new uploads.
1360
      'added_fids' => array_diff($new_fids, $old_fids),
1361
      // A list of files which need renaming with tokens.
1362
      'renameable' => $renameable,
1363
    );
1364
  }
1365
}
1366

    
1367
/**
1368
 * Implements hook_webform_submission_insert().
1369
 */
1370
function webform_webform_submission_insert($node, $submission) {
1371
  if (isset($submission->file_usage)) {
1372
    webform_component_include('file');
1373
    webform_file_usage_adjust($submission);
1374
    webform_file_rename($node, $submission);
1375
  }
1376
}
1377

    
1378
/**
1379
 * Implements hook_webform_submission_update().
1380
 */
1381
function webform_webform_submission_update($node, $submission) {
1382
  if (isset($submission->file_usage)) {
1383
    webform_component_include('file');
1384
    webform_file_usage_adjust($submission);
1385
    webform_file_rename($node, $submission);
1386
  }
1387
}
1388

    
1389
/**
1390
 * Implements hook_webform_submission_render_alter().
1391
 */
1392
function webform_webform_submission_render_alter(&$renderable) {
1393
  // If displaying a submission to end-users who are viewing their own
1394
  // submissions (and not through an e-mail), do not show hidden values.
1395
  // This needs to be implemented at the level of the entire submission, since
1396
  // individual components do not get contextual information about where they
1397
  // are being displayed.
1398
  $node = $renderable['#node'];
1399
  $is_admin = webform_results_access($node);
1400
  if (empty($renderable['#email']) && !$is_admin) {
1401
    // Find and hide the display of all hidden components.
1402
    module_load_include('inc', 'webform', 'includes/webform.components');
1403
    foreach ($node->webform['components'] as $cid => $component) {
1404
      if ($component['type'] == 'hidden') {
1405
        $parents = webform_component_parent_keys($node, $component);
1406
        $element = &$renderable;
1407
        foreach ($parents as $pid) {
1408
          $element = &$element[$pid];
1409
        }
1410
        $element['#access'] = FALSE;
1411
      }
1412
    }
1413
  }
1414
}
1415

    
1416
/**
1417
 * Implements hook_file_download().
1418
 *
1419
 * Only allow users with view webform submissions to download files.
1420
 */
1421
function webform_file_download($uri) {
1422
  module_load_include('inc', 'webform', 'includes/webform.submissions');
1423

    
1424
  // Determine whether this file was a webform upload.
1425
  $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();
1426
  if ($row) {
1427
    $file = file_load($row->fid);
1428
  }
1429
  if (!empty($row->sid)) {
1430
    $submissions = webform_get_submissions(array('sid' => $row->sid));
1431
    $submission = reset($submissions);
1432
  }
1433

    
1434
  // Grant or deny file access based on access to the submission.
1435
  if (!empty($submission)) {
1436
    $node = node_load($submission->nid);
1437
    if (webform_submission_access($node, $submission)) {
1438
      return file_get_content_headers($file);
1439
    }
1440
    else {
1441
      return -1;
1442
    }
1443
  }
1444
  // Grant access to files uploaded by a user before the submission is saved.
1445
  elseif (!empty($file) && !empty($_SESSION['webform_files'][$file->fid])) {
1446
    return file_get_content_headers($file);
1447
  }
1448

    
1449
  // Ensure we never completely ignore a webform file request.
1450
  if (strpos(file_uri_target($uri), 'webform/') === 0) {
1451
    // The file is not part of a submission or a submission-in-progress (by
1452
    // the current user), however it may be part of a submission-in-progress
1453
    // (or an abandoned submission) by another user. We assume that all files
1454
    // under our enforced directory prefix are in fact webform files, and so
1455
    // we deny access to the file. Abandoned uploads will be deleted by
1456
    // system_cron() in due course.
1457
    return -1;
1458
  }
1459
}
1460

    
1461
/**
1462
 * Return all content type enabled with webform.
1463
 *
1464
 * @return array
1465
 *   An array of node type names.
1466
 */
1467
function webform_node_types() {
1468
  $types = &drupal_static(__FUNCTION__, NULL);
1469
  if (!isset($types)) {
1470
    $types = array();
1471
    foreach (node_type_get_names() as $type => $name) {
1472
      if (variable_get('webform_node_' . $type, FALSE)) {
1473
        $types[] = $type;
1474
      }
1475
    }
1476
  }
1477
  return $types;
1478
}
1479

    
1480
/**
1481
 * Implements hook_node_type_delete().
1482
 */
1483
function webform_node_type_delete($info) {
1484
  variable_del('webform_node_' . $info->type);
1485
}
1486

    
1487
/**
1488
 * Implements hook_node_insert().
1489
 */
1490
function webform_node_insert($node) {
1491
  if (!variable_get('webform_node_' . $node->type, FALSE)) {
1492
    return;
1493
  }
1494

    
1495
  // If added directly through node_save(), set defaults for the node.
1496
  if (!isset($node->webform)) {
1497
    $node->webform = array();
1498
  }
1499

    
1500
  // Ensure values for all defaults are provided. Useful for importing from
1501
  // older versions into newer ones.
1502
  $node->webform += webform_node_defaults();
1503

    
1504
  // Do not make an entry if this node does not have any Webform settings.
1505
  if ($node->webform == webform_node_defaults() && !in_array($node->type, webform_variable_get('webform_node_types_primary'))) {
1506
    return;
1507
  }
1508

    
1509
  module_load_include('inc', 'webform', 'includes/webform.components');
1510
  module_load_include('inc', 'webform', 'includes/webform.conditionals');
1511
  module_load_include('inc', 'webform', 'includes/webform.emails');
1512

    
1513
  // Prepare the record for writing.
1514
  $node->webform['nid'] = $node->nid;
1515
  $webform_record = $node->webform;
1516
  $webform_record['preview_excluded_components'] = implode(',', $webform_record['preview_excluded_components']);
1517

    
1518
  // Insert the webform.
1519
  $node->webform['record_exists'] = (bool) drupal_write_record('webform', $webform_record);
1520

    
1521
  // Insert the components into the database. Used with clone.module.
1522
  if (isset($node->webform['components']) && !empty($node->webform['components'])) {
1523
    foreach ($node->webform['components'] as $cid => $component) {
1524
      // Required for clone.module.
1525
      $component['nid'] = $node->nid;
1526
      webform_component_insert($component);
1527
    }
1528
  }
1529

    
1530
  // Insert conditionals. Also used with clone.module.
1531
  if (isset($node->webform['conditionals']) && !empty($node->webform['conditionals'])) {
1532
    foreach ($node->webform['conditionals'] as $rgid => $conditional) {
1533
      $conditional['nid'] = $node->nid;
1534
      $conditional['rgid'] = $rgid;
1535
      webform_conditional_insert($conditional);
1536
    }
1537
  }
1538

    
1539
  // Insert emails. Also used with clone.module.
1540
  if (isset($node->webform['emails']) && !empty($node->webform['emails'])) {
1541
    foreach ($node->webform['emails'] as $eid => $email) {
1542
      $email['nid'] = $node->nid;
1543
      webform_email_insert($email);
1544
    }
1545
  }
1546

    
1547
  // Set the per-role submission access control.
1548
  foreach (array_filter($node->webform['roles']) as $rid) {
1549
    db_insert('webform_roles')->fields(array('nid' => $node->nid, 'rid' => $rid))->execute();
1550
  }
1551

    
1552
  // Flush the block cache if creating a block.
1553
  if (function_exists('block_flush_caches') && $node->webform['block']) {
1554
    block_flush_caches();
1555
  }
1556
}
1557

    
1558
/**
1559
 * Implements hook_node_update().
1560
 */
1561
function webform_node_update($node) {
1562
  if (!variable_get('webform_node_' . $node->type, FALSE)) {
1563
    return;
1564
  }
1565

    
1566
  // Check if this node needs a webform record at all. If it matches the
1567
  // defaults, any existing record will be deleted.
1568
  webform_check_record($node);
1569

    
1570
  // If a webform row doesn't even exist, we can assume it needs to be inserted.
1571
  // If the the webform matches the defaults, no row will be inserted.
1572
  if (!$node->webform['record_exists']) {
1573
    webform_node_insert($node);
1574
    return;
1575
  }
1576

    
1577
  // Prepare the record for writing.
1578
  $node->webform['nid'] = $node->nid;
1579
  $webform_record = $node->webform;
1580
  $webform_record['preview_excluded_components'] = implode(',', $webform_record['preview_excluded_components']);
1581

    
1582
  // Update the webform entry.
1583
  drupal_write_record('webform', $webform_record, array('nid'));
1584

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

    
1588
  if ($original->webform['components'] != $node->webform['components']) {
1589
    module_load_include('inc', 'webform', 'includes/webform.components');
1590

    
1591
    $original_cids = array_keys($original->webform['components']);
1592
    $current_cids = array_keys($node->webform['components']);
1593

    
1594
    $all_cids = array_unique(array_merge($original_cids, $current_cids));
1595
    $deleted_cids = array_diff($original_cids, $current_cids);
1596
    $inserted_cids = array_diff($current_cids, $original_cids);
1597

    
1598
    foreach ($all_cids as $cid) {
1599
      $node->webform['components'][$cid]['nid'] = $node->nid;
1600
      if (in_array($cid, $inserted_cids)) {
1601
        webform_component_insert($node->webform['components'][$cid]);
1602
      }
1603
      elseif (in_array($cid, $deleted_cids)) {
1604
        // Delete components only after all updates have been processed.
1605
      }
1606
      elseif ($node->webform['components'][$cid] != $original->webform['components'][$cid]) {
1607
        webform_component_update($node->webform['components'][$cid]);
1608
      }
1609
    }
1610

    
1611
    // Delete components now that any parent changes have been saved. When
1612
    // components are moved and deleted in one operation in FormBuilder, this
1613
    // ensures that only the current children are deleted.
1614
    foreach ($deleted_cids as $cid) {
1615
      webform_component_delete($node, $original->webform['components'][$cid]);
1616
    }
1617
  }
1618

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

    
1623
    // Conditionals don't have unique site-wide IDs or configuration, so our
1624
    // update here is a bit more aggressive than for components and e-mails.
1625
    // Delete any conditionals no longer in the webform or that have changed.
1626
    foreach ($original->webform['conditionals'] as $rgid => $conditional) {
1627
      if (!isset($node->webform['conditionals'][$rgid]) || $conditional != $node->webform['conditionals'][$rgid]) {
1628
        webform_conditional_delete($node, $conditional);
1629
      }
1630
    }
1631
    // Insert any conditionals not in the original or that have changed.
1632
    foreach ($node->webform['conditionals'] as $rgid => $conditional) {
1633
      $conditional['nid'] = $node->nid;
1634
      $conditional['rgid'] = $rgid;
1635
      if (!isset($original->webform['conditionals'][$rgid]) || $original->webform['conditionals'][$rgid] != $conditional) {
1636
        webform_conditional_insert($conditional);
1637
      }
1638
    }
1639
  }
1640

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

    
1645
    $original_eids = array_keys($original->webform['emails']);
1646
    $current_eids = array_keys($node->webform['emails']);
1647

    
1648
    $all_eids = array_unique(array_merge($original_eids, $current_eids));
1649
    $deleted_eids = array_diff($original_eids, $current_eids);
1650
    $inserted_eids = array_diff($current_eids, $original_eids);
1651

    
1652
    foreach ($all_eids as $eid) {
1653
      $node->webform['emails'][$eid]['nid'] = $node->nid;
1654
      if (in_array($eid, $inserted_eids)) {
1655
        webform_email_insert($node->webform['emails'][$eid]);
1656
      }
1657
      elseif (in_array($eid, $deleted_eids)) {
1658
        webform_email_delete($node, $original->webform['emails'][$eid]);
1659
      }
1660
      elseif ($node->webform['emails'][$eid] != $original->webform['emails'][$eid]) {
1661
        webform_email_update($node->webform['emails'][$eid]);
1662
      }
1663
    }
1664
  }
1665

    
1666
  // Just delete and re-insert roles if they've changed.
1667
  if ($original->webform['roles'] != $node->webform['roles']) {
1668
    db_delete('webform_roles')->condition('nid', $node->nid)->execute();
1669
    foreach (array_filter($node->webform['roles']) as $rid) {
1670
      db_insert('webform_roles')->fields(array('nid' => $node->nid, 'rid' => $rid))->execute();
1671
    }
1672
  }
1673

    
1674
  // Flush the block cache if block settings have been changed.
1675
  if (function_exists('block_flush_caches') && $node->webform['block'] != $original->webform['block']) {
1676
    block_flush_caches();
1677
  }
1678
}
1679

    
1680
/**
1681
 * Implements hook_node_delete().
1682
 */
1683
function webform_node_delete($node) {
1684
  if (!variable_get('webform_node_' . $node->type, FALSE)) {
1685
    return;
1686
  }
1687

    
1688
  // Allow components clean up extra data, such as uploaded files.
1689
  module_load_include('inc', 'webform', 'includes/webform.components');
1690
  foreach ($node->webform['components'] as $cid => $component) {
1691
    webform_component_delete($node, $component);
1692
  }
1693

    
1694
  // Remove any trace of webform data from the database.
1695
  db_delete('webform')->condition('nid', $node->nid)->execute();
1696
  db_delete('webform_component')->condition('nid', $node->nid)->execute();
1697
  db_delete('webform_conditional')->condition('nid', $node->nid)->execute();
1698
  db_delete('webform_conditional_rules')->condition('nid', $node->nid)->execute();
1699
  db_delete('webform_conditional_actions')->condition('nid', $node->nid)->execute();
1700
  db_delete('webform_emails')->condition('nid', $node->nid)->execute();
1701
  db_delete('webform_roles')->condition('nid', $node->nid)->execute();
1702
  db_delete('webform_submissions')->condition('nid', $node->nid)->execute();
1703
  db_delete('webform_submitted_data')->condition('nid', $node->nid)->execute();
1704
  db_delete('webform_last_download')->condition('nid', $node->nid)->execute();
1705
}
1706

    
1707
/**
1708
 * Default settings for a newly created webform node.
1709
 */
1710
function webform_node_defaults() {
1711
  $progress_bar_defaults = webform_variable_get('webform_progressbar_style');
1712
  $defaults = array(
1713
    'confirmation' => '',
1714
    'confirmation_format' => NULL,
1715
    'redirect_url' => '<confirmation>',
1716
    'block' => '0',
1717
    'allow_draft' => '0',
1718
    'auto_save' => '0',
1719
    'confidential' => '0',
1720
    'submit_notice' => '1',
1721
    'submit_text' => '',
1722
    'next_serial' => 1,
1723
    'submit_limit' => '-1',
1724
    'submit_interval' => '-1',
1725
    'total_submit_limit' => '-1',
1726
    'total_submit_interval' => '-1',
1727
    'progressbar_page_number' => in_array('progressbar_page_number', $progress_bar_defaults) ? '1' : '0',
1728
    'progressbar_percent' => in_array('progressbar_percent', $progress_bar_defaults) ? '1' : '0',
1729
    'progressbar_bar' => in_array('progressbar_bar', $progress_bar_defaults) ? '1' : '0',
1730
    'progressbar_pagebreak_labels' => in_array('progressbar_pagebreak_labels', $progress_bar_defaults) ? '1' : '0',
1731
    'progressbar_include_confirmation' => in_array('progressbar_include_confirmation', $progress_bar_defaults) ? '1' : '0',
1732
    'progressbar_label_first' => webform_variable_get('webform_progressbar_label_first'),
1733
    'progressbar_label_confirmation' => webform_variable_get('webform_progressbar_label_confirmation'),
1734
    'preview' => 0,
1735
    'preview_next_button_label' => '',
1736
    'preview_prev_button_label' => '',
1737
    'preview_title' => '',
1738
    'preview_message' => '',
1739
    'preview_message_format' => NULL,
1740
    'preview_excluded_components' => array(),
1741
    'status' => '1',
1742
    'record_exists' => FALSE,
1743
    'roles' => array('1', '2'),
1744
    'emails' => array(),
1745
    'components' => array(),
1746
    'conditionals' => array(),
1747
  );
1748
  drupal_alter('webform_node_defaults', $defaults);
1749
  return $defaults;
1750
}
1751

    
1752
/**
1753
 * Implements hook_node_prepare().
1754
 */
1755
function webform_node_prepare($node) {
1756
  if (variable_get('webform_node_' . $node->type, FALSE) && !isset($node->webform)) {
1757
    $node->webform = webform_node_defaults();
1758
  }
1759
}
1760

    
1761
/**
1762
 * Implements hook_node_load().
1763
 */
1764
function webform_node_load($nodes, $types) {
1765
  // Quick check to see if we need to do anything at all for these nodes.
1766
  $webform_types = webform_node_types();
1767
  if (count(array_intersect($types, $webform_types)) == 0) {
1768
    return;
1769
  }
1770

    
1771
  module_load_include('inc', 'webform', 'includes/webform.components');
1772

    
1773
  // Select all webforms that match these node IDs.
1774
  $result = db_select('webform')
1775
    ->fields('webform')
1776
    ->condition('nid', array_keys($nodes), 'IN')
1777
    ->execute()
1778
    ->fetchAllAssoc('nid', PDO::FETCH_ASSOC);
1779

    
1780
  foreach ($result as $nid => $webform) {
1781
    // Load the basic information for each node.
1782
    $nodes[$nid]->webform = $webform;
1783
    $nodes[$nid]->webform['record_exists'] = TRUE;
1784

    
1785
    // Expand the list of excluded preview components.
1786
    $nodes[$nid]->webform['preview_excluded_components'] = array_filter(explode(',', $webform['preview_excluded_components']));
1787
  }
1788

    
1789
  // Load the components, emails, and defaults for all webform-enabled nodes.
1790
  // TODO: Increase efficiency here by pulling in all information all at once
1791
  // instead of individual queries.
1792
  foreach ($nodes as $nid => $node) {
1793
    if (!in_array($node->type, $webform_types)) {
1794
      continue;
1795
    }
1796

    
1797
    // If a webform record doesn't exist, just return the defaults.
1798
    if (!isset($nodes[$nid]->webform)) {
1799
      $nodes[$nid]->webform = webform_node_defaults();
1800
      continue;
1801
    }
1802

    
1803
    $nodes[$nid]->webform['roles'] = db_select('webform_roles')
1804
      ->fields('webform_roles', array('rid'))
1805
      ->condition('nid', $nid)
1806
      ->execute()
1807
      ->fetchCol();
1808
    $nodes[$nid]->webform['emails'] = db_select('webform_emails')
1809
      ->fields('webform_emails')
1810
      ->condition('nid', $nid)
1811
      ->execute()
1812
      ->fetchAllAssoc('eid', PDO::FETCH_ASSOC);
1813

    
1814
    // Unserialize the mappings and excluded component list for e-mails.
1815
    foreach ($nodes[$nid]->webform['emails'] as $eid => $email) {
1816
      $nodes[$nid]->webform['emails'][$eid]['excluded_components'] = array_filter(explode(',', $email['excluded_components']));
1817
      $nodes[$nid]->webform['emails'][$eid]['extra'] = unserialize($email['extra']);
1818
      if (webform_variable_get('webform_format_override')) {
1819
        $nodes[$nid]->webform['emails'][$eid]['html'] = webform_variable_get('webform_default_format');
1820
      }
1821
    }
1822

    
1823
    // Load components for each node.
1824
    $nodes[$nid]->webform['components'] = db_select('webform_component')
1825
      ->fields('webform_component')
1826
      ->condition('nid', $nid)
1827
      ->orderBy('weight')
1828
      ->orderBy('name')
1829
      ->execute()
1830
      ->fetchAllAssoc('cid', PDO::FETCH_ASSOC);
1831

    
1832
    // Do a little cleanup on each component.
1833
    foreach ($nodes[$nid]->webform['components'] as $cid => $component) {
1834
      $nodes[$nid]->webform['components'][$cid]['nid'] = $nid;
1835
      $nodes[$nid]->webform['components'][$cid]['extra'] = unserialize($component['extra']);
1836
      webform_component_defaults($nodes[$nid]->webform['components'][$cid]);
1837
    }
1838

    
1839
    // Organize the components into a fieldset-based order.
1840
    if (!empty($nodes[$nid]->webform['components'])) {
1841
      $component_tree = array();
1842
      $page_count = 1;
1843
      _webform_components_tree_build($nodes[$nid]->webform['components'], $component_tree, 0, $page_count);
1844
      $nodes[$nid]->webform['components'] = _webform_components_tree_flatten($component_tree['children']);
1845
    }
1846

    
1847
    // Load all the conditional information, if any.
1848
    $nodes[$nid]->webform['conditionals'] = db_select('webform_conditional')
1849
      ->fields('webform_conditional')
1850
      ->condition('nid', $nid)
1851
      ->orderBy('weight')
1852
      ->execute()
1853
      ->fetchAllAssoc('rgid', PDO::FETCH_ASSOC);
1854
    if ($nodes[$nid]->webform['conditionals']) {
1855
      $rules = db_select('webform_conditional_rules')
1856
        ->fields('webform_conditional_rules')
1857
        ->condition('nid', $nid)
1858
        ->orderBy('rgid')
1859
        ->orderBy('rid')
1860
        ->execute();
1861
      foreach ($rules as $rule) {
1862
        $nodes[$nid]->webform['conditionals'][$rule->rgid]['rules'][$rule->rid] = (array) $rule;
1863
      }
1864
      $actions = db_select('webform_conditional_actions')
1865
        ->fields('webform_conditional_actions')
1866
        ->condition('nid', $nid)
1867
        ->orderBy('rgid')
1868
        ->orderBy('aid')
1869
        ->execute();
1870
      foreach ($actions as $action) {
1871
        $nodes[$nid]->webform['conditionals'][$action->rgid]['actions'][$action->aid] = (array) $action;
1872
      }
1873
    }
1874
  }
1875
}
1876

    
1877
/**
1878
 * Implements hook_user_role_delete().
1879
 *
1880
 * Removes references to deleted role from existing webforms.
1881
 */
1882
function webform_user_role_delete($role) {
1883
  db_delete('webform_roles')->condition('rid', $role->rid)->execute();
1884
}
1885

    
1886
/**
1887
 * Implements hook_form_alter().
1888
 */
1889
function webform_form_alter(&$form, $form_state, $form_id) {
1890
  $matches = array();
1891
  if (isset($form['#node']->type) && $form_id == $form['#node']->type . '_node_form' && variable_get('webform_node_' . $form['#node']->type, FALSE)) {
1892
    $node = $form['#node'];
1893
    // Preserve all Webform options currently set on the node.
1894
    $form['webform'] = array(
1895
      '#type' => 'value',
1896
      '#value' => $node->webform,
1897
    );
1898

    
1899
    // If a new node, redirect the user to the components form after save.
1900
    if (empty($node->nid) && in_array($node->type, webform_variable_get('webform_node_types_primary'))) {
1901
      $form['actions']['submit']['#submit'][] = 'webform_form_submit';
1902
    }
1903
  }
1904
}
1905

    
1906
/**
1907
 * Implements hook_form_BASE_FORM_ID_alter().
1908
 */
1909
function webform_form_node_type_form_alter(&$form, $form_state) {
1910
  if (isset($form['type'])) {
1911
    $form['webform'] = array(
1912
      '#title' => t('Webform'),
1913
      '#type' => 'fieldset',
1914
      '#collapsible' => TRUE,
1915
      '#collapsed' => TRUE,
1916
      '#group' => 'additional_settings',
1917
      '#weight' => 10,
1918
      '#attached' => array(
1919
        'js' => array(drupal_get_path('module', 'webform') . '/js/node-type-form.js'),
1920
      ),
1921
    );
1922
    $form['webform']['webform_node'] = array(
1923
      '#type' => 'checkbox',
1924
      '#title' => t('Enable webform functionality'),
1925
      '#description' => t('Allows a form to be attached to content. This will add tabs for "Webform" and "Results" on all content of this type.'),
1926
      '#weight' => 0,
1927
      '#default_value' => variable_get('webform_node_' . $form['#node_type']->type, FALSE),
1928
      '#attributes' => array(
1929
        'data-enabled-description' => t('Enabled'),
1930
        'data-disabled-description' => t('Disabled'),
1931
      ),
1932
    );
1933
  }
1934
}
1935

    
1936
/**
1937
 * Submit handler for the webform node form.
1938
 *
1939
 * Redirect the user to the components form on new node inserts. Note that this
1940
 * fires after the hook_submit() function above.
1941
 */
1942
function webform_form_submit($form, &$form_state) {
1943
  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'])));
1944
  $form_state['redirect'] = 'node/' . $form_state['nid'] . '/webform/components';
1945
}
1946

    
1947
/**
1948
 * Implements hook_node_view().
1949
 */
1950
function webform_node_view($node, $view_mode) {
1951
  global $user;
1952

    
1953
  if (!variable_get('webform_node_' . $node->type, FALSE)) {
1954
    return;
1955
  }
1956

    
1957
  // If empty or a new node (during preview) do not display.
1958
  if (empty($node->webform['components']) || empty($node->nid)) {
1959
    return;
1960
  }
1961

    
1962
  // If the webform is not set to display in this view mode, return early.
1963
  // View mode of 'form' is exempted to allow blocks and views to force display.
1964
  $extra_fields = field_extra_fields_get_display('node', $node->type, $view_mode);
1965
  if ($view_mode != 'form' && empty($extra_fields['webform']['visible'])) {
1966
    return;
1967
  }
1968

    
1969
  $submission = FALSE;
1970
  $submission_count = 0;
1971
  $page = node_is_page($node);
1972
  $logging_in = FALSE;
1973
  $total_limit_exceeded = FALSE;
1974
  $user_limit_exceeded = FALSE;
1975
  $closed = FALSE;
1976

    
1977
  // If a teaser, tell the form to load subsequent pages on the node page. A
1978
  // special exception is made for this view mode only.
1979
  if ($view_mode == 'teaser' && !isset($node->webform['action'])) {
1980
    $query = array_diff_key($_GET, array('q' => ''));
1981
    $node->webform['action'] = url('node/' . $node->nid, array('query' => $query));
1982
  }
1983

    
1984
  // When logging in using a form on the same page as a webform node, suppress
1985
  // output messages so that they don't show up after the user has logged in.
1986
  // See http://drupal.org/node/239343.
1987
  if (isset($_POST['op']) && isset($_POST['name']) && isset($_POST['pass'])) {
1988
    $logging_in = TRUE;
1989
  }
1990

    
1991
  if ($node->webform['status'] == 0) {
1992
    $closed = TRUE;
1993
    $enabled = FALSE;
1994
    $allowed_roles = array();
1995
  }
1996
  else {
1997
    // $enabled set by reference.
1998
    $allowed_roles = _webform_allowed_roles($node, $enabled);
1999
  }
2000

    
2001
  // Get a count of previous submissions by this user. Note that the
2002
  // webform_submission_access() function may disable the page cache for
2003
  // anonymous users if they are allowed to edit their own submissions!
2004
  if ($page && webform_submission_access($node, NULL, 'list')) {
2005
    module_load_include('inc', 'webform', 'includes/webform.submissions');
2006
    $submission_count = webform_get_submission_count($node->nid, $user->uid);
2007
  }
2008

    
2009
  // Check if this page is cached or not.
2010
  $cached = drupal_page_is_cacheable();
2011

    
2012
  // Check if the user can add another submission based on the individual
2013
  // submission limit.
2014
  // -1: Submissions are never throttled.
2015
  if ($node->webform['submit_limit'] != -1) {
2016
    module_load_include('inc', 'webform', 'includes/webform.submissions');
2017

    
2018
    // Disable the form if the limit is exceeded and page cache is not active.
2019
    // This prevents one anonymous user from generated a disabled webform page
2020
    // for the cache, which would be shown to other anonymous users who have not
2021
    // exceeded the limit.
2022
    if (($user_limit_exceeded = webform_submission_user_limit_check($node)) && !$cached) {
2023
      $enabled = FALSE;
2024
    }
2025
  }
2026

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

    
2033
    // Disable the form if the limit is exceeded. The cache is irrelevant for
2034
    // the total submission limit; when it is exceeded for one user, it is
2035
    // exceeded for any other user.
2036
    if (($total_limit_exceeded = webform_submission_total_limit_check($node))) {
2037
      $enabled = FALSE;
2038
    }
2039
  }
2040

    
2041
  // Check if this user has a draft for this webform.
2042
  $resume_draft = FALSE;
2043
  if (($node->webform['allow_draft'] || $node->webform['auto_save']) && $user->uid != 0) {
2044
    // Draft found - display form with draft data for further editing.
2045
    if ($draft_sid = _webform_fetch_draft_sid($node->nid, $user->uid)) {
2046
      module_load_include('inc', 'webform', 'includes/webform.submissions');
2047
      $submission = webform_get_submission($node->nid, $draft_sid);
2048
      $enabled = TRUE;
2049
      $resume_draft = TRUE;
2050
    }
2051
  }
2052

    
2053
  // Avoid building the same form twice on the same page request (which can
2054
  // happen if the webform is displayed in a panel or block) because this
2055
  // causes multistep forms to build incorrectly the second time.
2056
  $cached_forms = &drupal_static(__FUNCTION__, array());
2057
  if (isset($cached_forms[$node->nid])) {
2058
    $form = $cached_forms[$node->nid];
2059
  }
2060
  // If this is the first time, generate the form array.
2061
  else {
2062
    $form = drupal_get_form('webform_client_form_' . $node->nid, $node, $submission, $resume_draft);
2063
    $cached_forms[$node->nid] = $form;
2064
  }
2065

    
2066
  // Remove the surrounding <form> tag if this is a preview.
2067
  if (!empty($node->in_preview)) {
2068
    $form['#type'] = 'markup';
2069
  }
2070

    
2071
  // Print out messages for the webform.
2072
  if (empty($node->in_preview) && !isset($node->webform_block) && !$logging_in) {
2073
    theme('webform_view_messages', array(
2074
      'node' => $node,
2075
      'page' => $page,
2076
      'submission_count' => $submission_count,
2077
      'user_limit_exceeded' => $user_limit_exceeded,
2078
      'total_limit_exceeded' => $total_limit_exceeded,
2079
      'allowed_roles' => $allowed_roles,
2080
      'closed' => $closed,
2081
      'cached' => $cached,
2082
        )
2083
    );
2084
  }
2085

    
2086
  // Add the output to the node.
2087
  $node->content['webform'] = array(
2088
    '#theme' => 'webform_view',
2089
    '#node' => $node,
2090
    '#page' => $page,
2091
    '#form' => $form,
2092
    '#enabled' => $enabled,
2093
    '#visible' => $extra_fields['webform']['visible'],
2094
    '#weight' => 10,
2095
  );
2096
}
2097

    
2098
/**
2099
 * Helper. Generates an array of allowed roles.
2100
 *
2101
 * @param stdClass $node
2102
 *   The loaded node object containing a webform.
2103
 * @param bool $user_is_allowed
2104
 *   Reference to boolean to be set to whether the current user is allowed.
2105
 *
2106
 * @return array
2107
 *   Associative array of allowed roles indexed by the role id with a boolean
2108
 *   value indicating if the current user has this role.
2109
 */
2110
function _webform_allowed_roles($node, &$user_is_allowed) {
2111
  global $user;
2112
  if ($node->webform['confidential']) {
2113
    // Confidential webform may only be submitted anonymously, including uid 1.
2114
    $user_is_allowed = user_is_anonymous();
2115
    $allowed_roles = array(DRUPAL_ANONYMOUS_RID => $user_is_allowed);
2116
  }
2117
  elseif (webform_variable_get('webform_submission_access_control')) {
2118
    // Check if the user's role can submit this webform.
2119
    $allowed_roles = array();
2120
    foreach ($node->webform['roles'] as $rid) {
2121
      $allowed_roles[$rid] = isset($user->roles[$rid]);
2122
    }
2123
    $user_is_allowed = $user->uid == 1 || array_search(TRUE, $allowed_roles);
2124
  }
2125
  else {
2126
    // If not using Webform submission access control, allow all roles.
2127
    $user_is_allowed = TRUE;
2128
    $allowed_roles = array_fill_keys(array_keys(user_roles()), TRUE);
2129
  }
2130
  return $allowed_roles;
2131
}
2132

    
2133
/**
2134
 * Output the Webform into the node content.
2135
 *
2136
 * @param stdClass $node
2137
 *   The webform node object.
2138
 * @param stdClass $page
2139
 *   If this webform node is being viewed as the main content of the page.
2140
 * @param array $form
2141
 *   The rendered form.
2142
 * @param $enabled
2143
 *   If the form allowed to be completed by the current user.
2144
 *
2145
 * @return string
2146
 */
2147
function theme_webform_view($variables) {
2148
  // Only show the form if this user is allowed access.
2149
  if ($variables['webform']['#enabled']) {
2150
    return drupal_render($variables['webform']['#form']);
2151
  }
2152
}
2153

    
2154
/**
2155
 * Display a message to a user if they are not allowed to fill out a form.
2156
 *
2157
 * @param stdClass $node
2158
 *   The webform node object.
2159
 * @param $page
2160
 *   If this webform node is being viewed as the main content of the page.
2161
 * @param $submission_count
2162
 *   The number of submissions this user has already submitted. Not calculated
2163
 *   for anonymous users.
2164
 * @param $user_limit_exceeded
2165
 *   Boolean value if the submission limit for this user has been exceeded.
2166
 * @param $total_limit_exceeded
2167
 *   Boolean value if the total submission limit has been exceeded.
2168
 * @param $allowed_roles
2169
 *   A list of user roles that are allowed to submit this webform.
2170
 * @param $closed
2171
 *   Boolean value if submissions are closed.
2172
 */
2173
function theme_webform_view_messages($variables) {
2174
  global $user;
2175

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

    
2185
  $type = 'warning';
2186

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

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

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

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

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

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

    
2283
/**
2284
 * Implements hook_block_view().
2285
 */
2286
function webform_block_view($delta = '') {
2287
  global $user;
2288

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

    
2787
  return $form;
2788
}
2789

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

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

    
2809
  return $form;
2810
}
2811

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

    
2945
/**
2946
 * Validates that the form can still be submitted, saved as draft, or edited.
2947
 *
2948
 * Because forms may be submitted from cache or the webform changed while the
2949
 * submission is in progress, the conditions to allow the form are re-checked
2950
 * upon form submission.
2951
 */
2952
function webform_client_form_prevalidate($form, &$form_state) {
2953
  global $user;
2954

    
2955
  // Refresh the node in case it changed since the form was build and retrieved from cache.
2956
  $node = $form['#node'] = node_load($form['#node']->nid);
2957
  $finished = $form_state['values']['details']['finished'];
2958

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

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

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

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

    
2987
  // Prevent submission by throwing an error.
2988
  if ((!$allowed_role || $total_limit_exceeded || $user_limit_exceeded || $closed)) {
2989
    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));
2990
    form_set_error('', NULL);
2991
  }
2992
}
2993

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

    
3008
  module_load_include('inc', 'webform', 'includes/webform.submissions');
3009
  $node = $form['#node'];
3010

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

    
3257
  // Flatten trees within the submission.
3258
  $form_state['values']['submitted'] = _webform_client_form_submit_flatten($node, $form_state['values']['submitted']);
3259

    
3260
  // Perform post processing by components.
3261
  _webform_client_form_submit_process($node, $form_state['values']['submitted']);
3262

    
3263
  // Assume the form is completed unless the page logic says otherwise.
3264
  $form_state['webform_completed'] = TRUE;
3265

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

    
3273
  // Execute conditionals on submission values.
3274
  $form_state['values']['submitted'] = webform_get_conditional_sorter($node)->executeConditionals($original_values);
3275

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

    
3281
    // Store values from the current page in the form state storage.
3282
    $form_state['storage']['submitted'] = $form_state['values']['submitted'];
3283

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

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

    
3316
    // The form is done if the page number is greater than the page count.
3317
    $form_state['webform_completed'] = $form_state['storage']['page_num'] > $form_state['storage']['page_count'];
3318
  }
3319

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

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

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

    
3343
  if (empty($form_state['save_draft']) && empty($form_state['webform_completed'])) {
3344
    return;
3345
  }
3346

    
3347
  $node = $form['#node'];
3348
  $sid = $form_state['values']['details']['sid'] ? (int) $form_state['values']['details']['sid'] : NULL;
3349

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

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

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

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

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

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

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

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

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

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

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

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

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

    
3495
  $form_state['redirect'] = $redirect;
3496
}
3497

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

    
3522
/**
3523
 * Flattens a submitted values back into a single flat array representation.
3524
 */
3525
function _webform_client_form_submit_flatten($node, $fieldset, $parent = 0) {
3526
  $values = array();
3527

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

    
3545
  return $values;
3546
}
3547

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

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

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

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

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

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

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

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

    
3619
  $vars['progressbar_page_number'] = $vars['node']->webform['progressbar_page_number'];
3620
  $vars['progressbar_percent'] = $vars['node']->webform['progressbar_percent'];
3621
  $vars['progressbar_bar'] = $vars['node']->webform['progressbar_bar'];
3622
  $vars['progressbar_pagebreak_labels'] = $vars['node']->webform['progressbar_pagebreak_labels'];
3623
  $vars['progressbar_include_confirmation'] = $vars['node']->webform['progressbar_include_confirmation'];
3624
  $vars['percent'] = ($vars['page_num'] - 1) / ($vars['page_count'] - 1) * 100;
3625
}
3626

    
3627
/**
3628
 * Prepare to theme the contents of e-mails sent by webform.
3629
 */
3630
function template_preprocess_webform_mail_message(&$vars) {
3631
  global $user;
3632

    
3633
  $vars['user'] = $user;
3634
  $vars['ip_address'] = webform_ip_address($vars['node']);
3635
}
3636

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

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

    
3669
/**
3670
 * Implements template_preprocess_THEME_HOOK().
3671
 */
3672
function template_preprocess_webform_element(&$variables) {
3673
  $element = &$variables['element'];
3674

    
3675
  // Ensure defaults.
3676
  $element += array(
3677
    '#title_display' => 'before',
3678
    '#wrapper_attributes' => array(),
3679
  );
3680
  $element['#wrapper_attributes'] += array(
3681
    'class' => array(),
3682
  );
3683

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

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

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

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

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

    
3722
  // If an internal title is being used, generate no external title.
3723
  if ($element['#title_display'] == 'internal') {
3724
    $element['#title_display'] = 'none';
3725
  }
3726
}
3727

    
3728
/**
3729
 * Replacement for theme_form_element().
3730
 */
3731
function theme_webform_element($variables) {
3732
  $element = $variables['element'];
3733

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

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

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

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

    
3762
    case 'after':
3763
      $output .= ' ' . $description[$above] . $prefix . $element['#children'] . $suffix;
3764
      $output .= ' ' . theme('form_element_label', $variables) . "\n";
3765
      break;
3766

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

    
3774
  $output .= $description[!$above];
3775
  $output .= "</div>\n";
3776

    
3777
  return $output;
3778
}
3779

    
3780
/**
3781
 * Output a form element in plain text format.
3782
 */
3783
function theme_webform_element_text($variables) {
3784
  $element = $variables['element'];
3785
  $value = $variables['element']['#children'];
3786

    
3787
  $output = '';
3788
  $is_group = webform_component_feature($element['#webform_component']['type'], 'group');
3789

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

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

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

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

    
3829
  if ($output) {
3830
    $output .= "\n";
3831
  }
3832

    
3833
  return $output;
3834
}
3835

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

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

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

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

    
3866
  if (!empty($element['#description'])) {
3867
    $output .= ' <div class="description">' . $element['#description'] . "</div>\n";
3868
  }
3869

    
3870
  $output .= "</div>\n";
3871

    
3872
  return $output;
3873
}
3874

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

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

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

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

    
3899
  $attributes = isset($element['#attributes']) ? $element['#attributes'] : array();
3900

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

    
3910
  $attributes['class'][] = 'webform-inline-radio';
3911
  if (!empty($element['#id'])) {
3912
    $attributes['for'] = $element['#id'];
3913
  }
3914

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

    
3919
/**
3920
 * Theme the headers when sending an email from webform.
3921
 *
3922
 * @param stdClass $node
3923
 *   The complete node object for the webform.
3924
 * @param $submission
3925
 *   The webform submission of the user.
3926
 * @param $email
3927
 *   If you desire to make different e-mail headers depending on the recipient,
3928
 *   you can check the $email['email'] property to output different content.
3929
 *   This will be the ID of the component that is a conditional e-mail
3930
 *   recipient. For the normal e-mails, it will have the value of 'default'.
3931
 *
3932
 * @return
3933
 *   An array of headers to be used when sending a webform email. If headers
3934
 *   for "From", "To", or "Subject" are set, they will take precedence over
3935
 *   the values set in the webform configuration.
3936
 */
3937
function theme_webform_mail_headers($variables) {
3938
  $headers = array(
3939
    'X-Mailer' => 'Drupal Webform (PHP/' . phpversion() . ')',
3940
  );
3941
  return $headers;
3942
}
3943

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

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

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

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

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

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

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

    
4075
  $text_tokens = token_scan($text);
4076
  if (empty($text_tokens)) {
4077
    return $text;
4078
  }
4079

    
4080
  $replacements = array();
4081
  foreach ($text_tokens as $type => $tokens) {
4082
    $replacements += array_fill_keys($tokens, '');
4083
  }
4084

    
4085
  $tokens = array_keys($replacements);
4086
  $values = array_values($replacements);
4087

    
4088
  return str_replace($tokens, $values, $text);
4089
}
4090

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

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

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

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

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

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

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

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

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

    
4247
/**
4248
 * Find the label of a given page based on page breaks.
4249
 *
4250
 * @param stdClass $node
4251
 *   The webform node.
4252
 * @param $form_state
4253
 *   The form's state, if available
4254
 *
4255
 * @return array
4256
 *   An array of all page labels, indexed by page number.
4257
 */
4258
function webform_page_labels($node, $form_state = array()) {
4259
  $page_count = 1;
4260
  $page_labels = array();
4261
  $page_labels[0] = t($node->webform['progressbar_label_first']);
4262
  foreach ($node->webform['components'] as $component) {
4263
    if ($component['type'] == 'pagebreak') {
4264
      $page_labels[$page_count] = $component['name'];
4265
      $page_count++;
4266
    }
4267
  }
4268
  if ($node->webform['preview'] || !empty($form_state['webform']['preview'])) {
4269
    $page_labels[] = $node->webform['preview_title'] ? t($node->webform['preview_title']) : t('Preview');
4270
  }
4271
  if ($node->webform['progressbar_include_confirmation']) {
4272
    $page_labels[] = t($node->webform['progressbar_label_confirmation']);
4273
  }
4274
  return $page_labels;
4275
}
4276

    
4277
/**
4278
 * Retrieve a Drupal variable with the appropriate default value.
4279
 */
4280
function webform_variable_get($variable) {
4281
  switch ($variable) {
4282
    case 'webform_blocks':
4283
      $result = variable_get('webform_blocks', array());
4284
      break;
4285

    
4286
    case 'webform_tracking_mode':
4287
      $result = variable_get('webform_tracking_mode', 'cookie');
4288
      break;
4289

    
4290
    case 'webform_allowed_tags':
4291
      $result = variable_get('webform_allowed_tags', array('a', 'em', 'strong', 'code', 'img'));
4292
      break;
4293

    
4294
    case 'webform_email_address_format':
4295
      $result = variable_get('webform_email_address_format', 'long');
4296
      break;
4297

    
4298
    case 'webform_email_address_individual':
4299
      $result = variable_get('webform_email_address_individual', 0);
4300
      break;
4301

    
4302
    case 'webform_default_from_name':
4303
      $result = variable_get('webform_default_from_name', variable_get('site_name', ''));
4304
      break;
4305

    
4306
    case 'webform_default_from_address':
4307
      $result = variable_get('webform_default_from_address', variable_get('site_mail', ini_get('sendmail_from')));
4308
      break;
4309

    
4310
    case 'webform_default_subject':
4311
      $result = variable_get('webform_default_subject', t('Form submission from: [node:title]'));
4312
      break;
4313

    
4314
    case 'webform_email_replyto':
4315
      $result = variable_get('webform_email_replyto', TRUE);
4316
      break;
4317

    
4318
    case 'webform_email_html_capable':
4319
      $result = variable_get('webform_email_html_capable', FALSE);
4320
      break;
4321

    
4322
    case 'webform_default_format':
4323
      $result = variable_get('webform_default_format', 0);
4324
      break;
4325

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

    
4330
    case 'webform_email_select_max':
4331
      $result = variable_get('webform_email_select_max', 50);
4332
      break;
4333

    
4334
    case 'webform_node_types':
4335
      $result = webform_node_types();
4336
      break;
4337

    
4338
    case 'webform_node_types_primary':
4339
      $result = variable_get('webform_node_types_primary', array('webform'));
4340
      break;
4341

    
4342
    case 'webform_date_type':
4343
      $result = variable_get('webform_date_type', 'medium');
4344
      break;
4345

    
4346
    case 'webform_export_format':
4347
      module_load_include('inc', 'webform', 'includes/webform.export');
4348
      $options = webform_export_list();
4349
      $result = variable_get('webform_export_format', 'excel');
4350
      $result = isset($options[$result]) ? $result : key($options);
4351
      break;
4352

    
4353
    case 'webform_csv_delimiter':
4354
      $result = variable_get('webform_csv_delimiter', '\t');
4355
      break;
4356

    
4357
    case 'webform_csv_line_ending':
4358
      $result = variable_get('webform_csv_line_ending', "\n");
4359
      break;
4360

    
4361
    case 'webform_export_wordwrap':
4362
      $result = variable_get('webform_export_wordwrap', 0);
4363
      break;
4364

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

    
4369
    case 'webform_progressbar_style':
4370
      $result = variable_get('webform_progressbar_style', array('progressbar_bar', 'progressbar_pagebreak_labels', 'progressbar_include_confirmation'));
4371
      break;
4372

    
4373
    case 'webform_progressbar_label_first':
4374
      $result = variable_get('webform_progressbar_label_first', t('Start'));
4375
      break;
4376

    
4377
    case 'webform_progressbar_label_confirmation':
4378
      $result = variable_get('webform_progressbar_label_confirmation', t('Complete'));
4379
      break;
4380

    
4381
    case 'webform_table':
4382
      $result = variable_get('webform_table', FALSE);
4383
      break;
4384

    
4385
    case 'webform_submission_access_control':
4386
      $result = variable_get('webform_submission_access_control', 1);
4387
      break;
4388

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

    
4393
    case 'webform_update_batch_size':
4394
      $result = variable_get('webform_update_batch_size', 100);
4395
      break;
4396

    
4397
    case 'webform_disabled_components':
4398
      $result = variable_get('webform_disabled_components', array());
4399
      break;
4400
  }
4401
  return $result;
4402
}
4403

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

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

    
4434
  if (!module_exists('token')) {
4435
    // No token module at all. Display a simple suggestion to enable it.
4436
    $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>';
4437
    unset($help['token_tree']);
4438
  }
4439
  elseif (function_exists('token_page_output_tree')) {
4440
    // Token supports dialogs: display simply as a link.
4441
    $help = $help['token_tree'];
4442
    $help['#dialog'] = TRUE;
4443
  }
4444

    
4445
  return render($help);
4446
}
4447

    
4448
/**
4449
 * Convert a name into an identifier that is safe for machine names, classes,
4450
 * and other ASCII uses.
4451
 */
4452
function _webform_safe_name($name) {
4453
  $new = trim($name);
4454
  $new = _webform_transliterate($new);
4455
  $new = str_replace(array(' ', '-', '/'), array('_', '_', '_'), $new);
4456
  $new = drupal_strtolower($new);
4457
  $new = preg_replace('/[^a-z0-9_]/', '', $new);
4458
  return $new;
4459
}
4460

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

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

    
4513
  if ($name == 'default') {
4514
    $name = webform_variable_get('webform_default_from_name');
4515
  }
4516
  elseif (is_numeric($name) && isset($node->webform['components'][$name])) {
4517
    if (isset($submission->data[$name])) {
4518
      $component = $node->webform['components'][$name];
4519
      $name = $submission->data[$name];
4520

    
4521
      // Convert the FROM name to be the label of select lists.
4522
      if (webform_component_implements($component['type'], 'options')) {
4523
        $options = webform_component_invoke($component['type'], 'options', $component);
4524
        foreach ($name as &$one_name) {
4525
          $one_name = isset($options[$one_name]) ? $options[$one_name] : $one_name;
4526
        }
4527
        // Drop PHP reference.
4528
        unset($one_name);
4529
      }
4530
    }
4531
    else {
4532
      $name = t('Value of !component', array('!component' => $node->webform['components'][$name]['name']));
4533
    }
4534
  }
4535
  elseif (!isset($name)) {
4536
    $name = '';
4537
  }
4538

    
4539
  if ($address == 'default') {
4540
    $address = webform_variable_get('webform_default_from_address');
4541
  }
4542
  elseif (is_numeric($address) && isset($node->webform['components'][$address])) {
4543
    if (isset($submission->data[$address])) {
4544
      $values = $submission->data[$address];
4545
      $address = array();
4546
      foreach ($values as $value) {
4547
        if (isset($mapping) && isset($mapping[$value])) {
4548
          $value = $mapping[$value];
4549
        }
4550
        $address = array_merge($address, explode(',', $value));
4551
      }
4552
    }
4553
    else {
4554
      $address = t('Value of "!component"', array('!component' => $node->webform['components'][$address]['name']));
4555
    }
4556
  }
4557

    
4558
  // Convert single values to an array to simplify processing.
4559
  $address = is_array($address) ? $address : explode(',', $address);
4560
  $name = is_array($name) ? $name : array($name);
4561
  $name_shortage = count($address) - count($name);
4562
  if ($name_shortage > 0) {
4563
    $name += array_fill(count($name), $name_shortage, $name[0]);
4564
  }
4565

    
4566
  foreach ($address as $key => $individual_address) {
4567
    $individual_address = webform_replace_tokens($individual_address, $node, $submission);
4568
    $email_parts = webform_parse_email_address($individual_address);
4569
    if ($format == 'long' && !empty($name[$key]) && !strlen($email_parts['name'])) {
4570
      $individual_name = $name[$key];
4571
      $individual_name = webform_replace_tokens($individual_name, $node, $submission);
4572
      if ($encode) {
4573
        $individual_name = mime_header_encode($individual_name);
4574
      }
4575
      $individual_name = trim($individual_name);
4576
      $individual_address = '"' . $individual_name . '" <' . $individual_address . '>';
4577
    }
4578
    $address[$key] = $individual_address;
4579
  }
4580

    
4581
  return $single ? reset($address) : $address;
4582
}
4583

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

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

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

    
4690
/**
4691
 * Given an email subject, format it with any needed replacements.
4692
 */
4693
function webform_format_email_subject($subject, $node = NULL, $submission = NULL) {
4694
  if ($subject == 'default') {
4695
    $subject = webform_variable_get('webform_default_subject');
4696
  }
4697
  elseif (is_numeric($subject) && isset($node->webform['components'][$subject])) {
4698
    $component = $node->webform['components'][$subject];
4699
    if (isset($submission->data[$subject])) {
4700
      $display_function = '_webform_display_' . $component['type'];
4701
      $value = $submission->data[$subject];
4702

    
4703
      // Convert the value to a clean text representation if possible.
4704
      if (function_exists($display_function)) {
4705
        $display = $display_function($component, $value, 'text');
4706
        $display['#theme_wrappers'] = array();
4707
        $display['#webform_component'] = $component;
4708
        $subject = str_replace("\n", ' ', drupal_render($display));
4709
      }
4710
      else {
4711
        $subject = $value;
4712
      }
4713
    }
4714
    else {
4715
      $subject = t('Value of "!component"', array('!component' => $component['name']));
4716
    }
4717
  }
4718

    
4719
  // Convert arrays to strings (may happen if checkboxes are used as the value).
4720
  if (is_array($subject)) {
4721
    $subject = reset($subject);
4722
  }
4723

    
4724
  return webform_replace_tokens($subject, $node, $submission);
4725
}
4726

    
4727
/**
4728
 * Convert an array of components into a tree.
4729
 */
4730
function _webform_components_tree_build($src, &$tree, $parent, &$page_count) {
4731
  foreach ($src as $cid => $component) {
4732
    if ($component['pid'] == $parent) {
4733
      _webform_components_tree_build($src, $component, $cid, $page_count);
4734
      if ($component['type'] == 'pagebreak') {
4735
        $page_count++;
4736
      }
4737
      $tree['children'][$cid] = $component;
4738
      $tree['children'][$cid]['page_num'] = $page_count;
4739
    }
4740
  }
4741
  return $tree;
4742
}
4743

    
4744
/**
4745
 * Flatten a component tree into a flat list.
4746
 */
4747
function _webform_components_tree_flatten($tree) {
4748
  $components = array();
4749
  foreach ($tree as $cid => $component) {
4750
    if (isset($component['children'])) {
4751
      unset($component['children']);
4752
      $components[$cid] = $component;
4753
      // array_merge() can't be used here because the keys are numeric.
4754
      $children = _webform_components_tree_flatten($tree[$cid]['children']);
4755
      foreach ($children as $ccid => $ccomponent) {
4756
        $components[$ccid] = $ccomponent;
4757
      }
4758
    }
4759
    else {
4760
      $components[$cid] = $component;
4761
    }
4762
  }
4763
  return $components;
4764
}
4765

    
4766
/**
4767
 * Helper for the uasort in webform_tree_sort()
4768
 */
4769
function _webform_components_sort($a, $b) {
4770
  if ($a['weight'] == $b['weight']) {
4771
    return strcasecmp($a['name'], $b['name']);
4772
  }
4773
  return ($a['weight'] < $b['weight']) ? -1 : 1;
4774
}
4775

    
4776
/**
4777
 * Sort each level of a component tree by weight and name.
4778
 */
4779
function _webform_components_tree_sort($tree) {
4780
  if (isset($tree['children']) && is_array($tree['children'])) {
4781
    $children = array();
4782
    uasort($tree['children'], '_webform_components_sort');
4783
    foreach ($tree['children'] as $cid => $component) {
4784
      $children[$cid] = _webform_components_tree_sort($component);
4785
    }
4786
    $tree['children'] = $children;
4787
  }
4788
  return $tree;
4789
}
4790

    
4791
/**
4792
 * Get a list of all available component definitions.
4793
 */
4794
function webform_components($include_disabled = FALSE, $reset = FALSE) {
4795
  static $components, $enabled;
4796

    
4797
  if (!isset($components) || $reset) {
4798
    $components = array();
4799
    $disabled = array_flip(webform_variable_get('webform_disabled_components'));
4800
    foreach (module_implements('webform_component_info') as $module) {
4801
      $module_components = module_invoke($module, 'webform_component_info');
4802
      foreach ($module_components as $type => $info) {
4803
        $module_components[$type]['module'] = $module;
4804
        $module_components[$type]['enabled'] = !array_key_exists($type, $disabled);
4805
      }
4806
      $components += $module_components;
4807
    }
4808
    drupal_alter('webform_component_info', $components);
4809
    uasort($components, function ($a, $b) {
4810
      return strnatcasecmp($a['label'], $b['label']);
4811
    });
4812
    $enabled = array_diff_key($components, $disabled);
4813
  }
4814

    
4815
  return $include_disabled ? $components : $enabled;
4816
}
4817

    
4818
/**
4819
 * Build a list of components suitable for use as select list options.
4820
 */
4821
function webform_component_options($include_disabled = FALSE) {
4822
  $component_info = webform_components($include_disabled);
4823
  $options = array();
4824
  foreach ($component_info as $type => $info) {
4825
    $options[$type] = $info['label'];
4826
  }
4827
  return $options;
4828
}
4829

    
4830
/**
4831
 * Load a component file into memory.
4832
 *
4833
 * @param $component_type
4834
 *   The string machine name of a component.
4835
 */
4836
function webform_component_include($component_type) {
4837
  static $included = array();
4838

    
4839
  // No need to load components that have already been added once.
4840
  if (!isset($included[$component_type])) {
4841
    $components = webform_components(TRUE);
4842
    $included[$component_type] = TRUE;
4843

    
4844
    if (($info = $components[$component_type]) && isset($info['file'])) {
4845
      $pathinfo = pathinfo($info['file']);
4846
      $basename = basename($pathinfo['basename'], '.' . $pathinfo['extension']);
4847
      $path = (!empty($pathinfo['dirname']) ? $pathinfo['dirname'] . '/' : '') . $basename;
4848
      module_load_include($pathinfo['extension'], $info['module'], $path);
4849
    }
4850
  }
4851
}
4852

    
4853
/**
4854
 * Invoke a component callback.
4855
 *
4856
 * @param $type
4857
 *   The component type as a string.
4858
 * @param $callback
4859
 *   The callback to execute.
4860
 * @param ...
4861
 *   Any additional parameters required by the $callback.
4862
 *
4863
 * @return mixed
4864
 *   Return value of the callback on success and FALSE on failure.
4865
 */
4866
function webform_component_invoke($type, $callback) {
4867
  $args = func_get_args();
4868
  $type = array_shift($args);
4869
  $callback = array_shift($args);
4870
  $function = '_webform_' . $callback . '_' . $type;
4871
  webform_component_include($type);
4872
  if (function_exists($function)) {
4873
    return call_user_func_array($function, $args);
4874
  }
4875
}
4876

    
4877
/**
4878
 * Check if a component implements a particular hook.
4879
 *
4880
 * @param $type
4881
 *   The component type as a string.
4882
 * @param $callback
4883
 *   The callback to check.
4884
 *
4885
 * @return bool
4886
 */
4887
function webform_component_implements($type, $callback) {
4888
  $function = '_webform_' . $callback . '_' . $type;
4889
  webform_component_include($type);
4890
  return function_exists($function);
4891
}
4892

    
4893
/**
4894
 * Form API #process function to expand a webform conditional element.
4895
 */
4896
function webform_conditional_expand($element) {
4897
  module_load_include('inc', 'webform', 'includes/webform.conditionals');
4898
  return _webform_conditional_expand($element);
4899
}
4900

    
4901
/**
4902
 * Add class and wrapper class attributes to an element.
4903
 */
4904
function _webform_component_classes(&$element, $component) {
4905
  if (isset($component['extra']['css_classes']) && drupal_strlen($component['extra']['css_classes'])) {
4906
    $element['#attributes']['class'] = isset($element['#attributes']['class']) ? $element['#attributes']['class'] : array();
4907
    $element['#attributes']['class'] = array_merge($element['#attributes']['class'], explode(' ', $component['extra']['css_classes']));
4908
  }
4909
  if (isset($component['extra']['wrapper_classes']) && drupal_strlen($component['extra']['wrapper_classes'])) {
4910
    $element['#wrapper_attributes']['class'] = isset($element['#wrapper_attributes']['class']) ? $element['#wrapper_attributes']['class'] : array();
4911
    $element['#wrapper_attributes']['class'] = array_merge($element['#wrapper_attributes']['class'], explode(' ', $component['extra']['wrapper_classes']));
4912
  }
4913
}
4914

    
4915
/**
4916
 * Disable the Drupal page cache.
4917
 */
4918
function webform_disable_page_cache() {
4919
  drupal_page_is_cacheable(FALSE);
4920
}
4921

    
4922
/**
4923
 * Set the necessary breadcrumb for the page we are on.
4924
 *
4925
 * @param stdClass $node
4926
 *   The loaded webform node.
4927
 * @param bool|object $submission
4928
 *   The submission if the current page is viewing or dealing with a submission,
4929
 *   or TRUE to just include the webform node in the breadcrumbs (used for
4930
 *   the submission completion confirmation page), or NULL for no extra
4931
 *   processing.
4932
 */
4933
function webform_set_breadcrumb($node, $submission = NULL) {
4934
  $node_path = "node/{$node->nid}";
4935

    
4936
  // Set the href of the current menu item to be the node's path. This has two
4937
  // effects. The active trail will be to the node's prefered menu tree
4938
  // location, expanding the menu as appropriate. And the breadcrumbs will be
4939
  // set as if the current page were under the node's preferred location.
4940
  // Note that menu_tree_set_path() could be used to set the path for the menu,
4941
  // but it will not affect the breadcrumbs when the webform is not in the
4942
  // default menu.
4943
  menu_set_item(NULL, array('href' => $node_path) + menu_get_item());
4944

    
4945
  if ($submission) {
4946
    $breadcrumb = menu_get_active_breadcrumb();
4947

    
4948
    // Append the node title (or its menu name), in case it isn't in the path already.
4949
    $active_trail = menu_get_active_trail();
4950
    $last_active = end($active_trail);
4951
    $breadcrumb[] = $last_active['href'] === $node_path && !empty($last_active['in_active_trail'])
4952
                      ? l($last_active['title'], $node_path, $last_active['localized_options'])
4953
                      : l($node->title, $node_path);
4954

    
4955
    // Setting the current menu href will cause the submission title and current
4956
    // tab (if not the default tab) to be added to the active path when the
4957
    // webform is in the default location in the menu (node/NID). The title
4958
    // is desirable, but the tab name (for example Edit or Delete) isn't.
4959
    if (preg_match('/href=".*"/', end($breadcrumb), $matches)) {
4960
      foreach ($breadcrumb as $index => $link) {
4961
        if (stripos($link, $matches[0]) !== FALSE) {
4962
          $breadcrumb = array_slice($breadcrumb, 0, $index + 1);
4963
          break;
4964
        }
4965
      }
4966
    }
4967

    
4968
    // If the user is dealing with a submission, then the breadcrumb should
4969
    // be fudged to allow them to return to a likely list of webforms.
4970
    // Note that this isn't necessarily where they came from, but it's the
4971
    // best guess available.
4972
    if (is_object($submission)) {
4973
      if (webform_results_access($node)) {
4974
        $breadcrumb[] = l(t('Webform results'), $node_path . '/webform-results');
4975
      }
4976
      elseif (user_access('access own webform results')) {
4977
        $breadcrumb[] = l(t('Submissions'), $node_path . '/submissions');
4978
      }
4979
    }
4980

    
4981
    drupal_set_breadcrumb($breadcrumb);
4982
  }
4983
}
4984

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

    
5019
  $return = array();
5020

    
5021
  // Check for a date string.
5022
  if ($type == 'date' || !isset($type)) {
5023
    $return['year'] = $matches[2] !== '' ? (int) $matches[2] : '';
5024
    $return['month'] = $matches[3] !== '' ? (int) $matches[3] : '';
5025
    $return['day'] = $matches[4] !== '' ? (int) $matches[4] : '';
5026
  }
5027

    
5028
  // Check for a time string.
5029
  if ($type == 'time' || !isset($type)) {
5030
    $return['hour'] = $matches[6] !== '' ? (int) $matches[6] : '';
5031
    $return['minute'] = $matches[7] !== '' ? (int) $matches[7] : '';
5032
    $return['second'] = $matches[8] !== '' ? (int) $matches[8] : '';
5033
  }
5034

    
5035
  return $return;
5036
}
5037

    
5038
/**
5039
 * Convert an array of a date or time into an ISO 8601 compatible string.
5040
 *
5041
 * @param $array
5042
 *   The array to convert to a date or time string.
5043
 * @param $type
5044
 *   If wanting a specific string format back specify either "date" or "time".
5045
 *   Otherwise a full ISO 8601 date and time string will be returned.
5046
 *
5047
 * @return string
5048
 *   Date in string format
5049
 */
5050
function webform_date_string($array, $type = NULL) {
5051
  $string = '';
5052

    
5053
  if ($type == 'date' || !isset($type)) {
5054
    $string .= empty($array['year']) ? '0000' : sprintf('%04d', $array['year']);
5055
    $string .= '-';
5056
    $string .= empty($array['month']) ? '00' : sprintf('%02d', $array['month']);
5057
    $string .= '-';
5058
    $string .= empty($array['day']) ? '00' : sprintf('%02d', $array['day']);
5059
  }
5060

    
5061
  if (!isset($type)) {
5062
    $string .= 'T';
5063
  }
5064

    
5065
  if ($type == 'time' || !isset($type)) {
5066
    $string .= empty($array['hour']) ? '00' : sprintf('%02d', $array['hour']);
5067
    $string .= ':';
5068
    $string .= empty($array['minute']) ? '00' : sprintf('%02d', $array['minute']);
5069
    $string .= ':';
5070
    $string .= empty($array['second']) ? '00' : sprintf('%02d', $array['second']);
5071
  }
5072

    
5073
  return $string;
5074
}
5075

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

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

    
5099
    // Date/Time formatting characters
5100
    // WHAT           REQUIRED (at least 1) OPTIONAL (allowed but insufficient)
5101
    // ---------------------------------------------------------------------------
5102
    // Day-of-week                          DlNw
5103
    // Day            dj                    Stz
5104
    // Month          FmMn
5105
    // Year           oYy                   L
5106
    //
5107
    //                NOT ALLOWED
5108
    // --------------------------------------------------------------------------
5109
    // Time           aABgGhHisueIOPTZ
5110
    // Special        /.,-: <space>
5111
    //
5112
    // Strip Time and Special characters from the beginning and end of format.
5113
    $date_format = trim($format, 'aABgGhHisueIOPTZ/.,-: ');
5114

    
5115
    // Ensure that a day, month, and year value are present. Use a default
5116
    // format if all the values are not found. This regular expression uses
5117
    // (?= ), the positive lookahead assertion. It asserts that there are some
5118
    // optional characters (.*) followed by one of the day, month, or year
5119
    // characters. Because it is an assertion, it doesn't consume the
5120
    // characters, so the day, month, and year can be in any order.
5121
    if (!preg_match('/(?=.*[dj])(?=.*[FmMn])(?=.*[oYy])/', $date_format)) {
5122
      $date_format = 'm/d/Y';
5123
    }
5124

    
5125
    // Remove any excluded portions.
5126
    $strip = array(
5127
      'day' => 'DlNwdjStz',
5128
      'month' => 'FmMn',
5129
      'year' => 'oYyL',
5130
    );
5131
    foreach ($exclude as $field) {
5132
      // Strip the format and any trailing /.,-: or space.
5133
      $date_format = preg_replace('#[' . $strip[$field] . ']+[/\.,\-: ]*#', '', $date_format);
5134
      $date_format = trim($date_format, '/.,-: ');
5135
    }
5136

    
5137
    $formats[$id] = $date_format;
5138
  }
5139

    
5140
  return $formats[$id];
5141
}
5142

    
5143
/**
5144
 * Return a date in the desired format taking into consideration user timezones.
5145
 */
5146
function webform_strtodate($format, $string, $timezone_name = NULL, $reference_timestamp = NULL) {
5147
  global $user;
5148

    
5149
  // Adjust the time based on the user or site timezone.
5150
  if (variable_get('configurable_timezones', 1) && $timezone_name == 'user' && $user->uid) {
5151
    $timezone_name = isset($GLOBALS['user']->timezone) ? $GLOBALS['user']->timezone : 'UTC';
5152
  }
5153
  // If the timezone is still empty or not set, use the site timezone.
5154
  if (empty($timezone_name) || $timezone_name == 'user') {
5155
    $timezone_name = variable_get('date_default_timezone', 'UTC');
5156
  }
5157

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

    
5190
/**
5191
 * Get a timestamp in GMT time, ensuring timezone accuracy.
5192
 */
5193
function webform_strtotime($date) {
5194
  $current_tz = date_default_timezone_get();
5195
  date_default_timezone_set('UTC');
5196
  $timestamp = strtotime($date);
5197
  date_default_timezone_set($current_tz);
5198
  return $timestamp;
5199
}
5200

    
5201
/**
5202
 * Wrapper function for i18n_string() if i18nstrings enabled.
5203
 */
5204
function webform_tt($name, $string, $langcode = NULL, $update = FALSE) {
5205
  if (function_exists('i18n_string')) {
5206
    $options = array(
5207
      'langcode' => $langcode,
5208
      'update' => $update,
5209
    );
5210
    return i18n_string($name, $string, $options);
5211
  }
5212
  else {
5213
    return $string;
5214
  }
5215
}
5216

    
5217
/**
5218
 * Returns an IP Address or anonymized IP Address for confidential webforms.
5219
 */
5220
function webform_ip_address($node) {
5221
  return $node->webform['confidential'] ? t('(unknown)') : ip_address();
5222
}
5223

    
5224
/**
5225
 * Implements hook_views_api().
5226
 */
5227
function webform_views_api() {
5228
  return array(
5229
    'api' => 3.0,
5230
    'path' => drupal_get_path('module', 'webform') . '/views',
5231
  );
5232
}
5233

    
5234
/**
5235
 * Implements hook_views_default_views().
5236
 */
5237
function webform_views_default_views() {
5238
  $path = './' . drupal_get_path('module', 'webform') . '/views/default_views/*.inc';
5239
  $views = array();
5240
  foreach (glob($path) as $views_filename) {
5241
    require_once $views_filename;
5242
  }
5243
  return $views;
5244
}
5245

    
5246
/**
5247
 * Implements hook_field_extra_fields().
5248
 */
5249
function webform_field_extra_fields() {
5250
  $extra = array();
5251
  foreach (webform_node_types() as $type) {
5252
    $extra['node'][$type]['display']['webform'] = array(
5253
      'label' => t('Webform'),
5254
      'description' => t('Webform client form.'),
5255
      'weight' => 10,
5256
    );
5257
  }
5258
  return $extra;
5259
}
5260

    
5261
/**
5262
 * Implements hook_mollom_form_list().
5263
 */
5264
function webform_mollom_form_list() {
5265
  $forms = array();
5266
  $webform_types = webform_node_types();
5267
  if (empty($webform_types)) {
5268
    return $forms;
5269
  }
5270

    
5271
  $query = db_select('webform', 'w');
5272
  $query->innerJoin('node', 'n', 'n.nid = w.nid');
5273
  $query->fields('n', array('nid', 'title'));
5274
  $query->condition('n.type', $webform_types, 'IN');
5275
  $result = $query->execute();
5276

    
5277
  foreach ($result as $node) {
5278
    $form_id = 'webform_client_form_' . $node->nid;
5279
    $forms[$form_id] = array(
5280
      'title' => t('@name form', array('@name' => $node->title)),
5281
      'entity' => 'webform',
5282
      'delete form' => 'webform_submission_delete_form',
5283
    );
5284
  }
5285
  return $forms;
5286
}
5287

    
5288
/**
5289
 * Implements hook_mollom_form_info().
5290
 */
5291
function webform_mollom_form_info($form_id) {
5292
  module_load_include('inc', 'webform', 'includes/webform.components');
5293

    
5294
  $nid = drupal_substr($form_id, 20);
5295
  $node = node_load($nid);
5296
  $form_info = array(
5297
    'title' => t('@name form', array('@name' => $node->title)),
5298
    'mode' => MOLLOM_MODE_ANALYSIS,
5299
    'bypass access' => array('edit all webform submissions', 'edit any webform content'),
5300
    'entity' => 'webform',
5301
    'elements' => array(),
5302
    'mapping' => array(
5303
      'post_id' => 'details][sid',
5304
      'author_id' => 'details][uid',
5305
    ),
5306
  );
5307
  // Add components as elements.
5308
  // These components can be enabled for textual analysis (when not using a
5309
  // CAPTCHA-only protection) in Mollom's form configuration.
5310
  foreach ($node->webform['components'] as $cid => $component) {
5311
    if (webform_component_feature($component['type'], 'spam_analysis')) {
5312
      $parents = implode('][', webform_component_parent_keys($node, $component));
5313
      $form_info['elements']['submitted][' . $parents] = check_plain(t($component['name']));
5314
    }
5315
  }
5316
  // Assign field mappings based on webform configuration.
5317
  // Since multiple emails can be configured, we iterate over all and take
5318
  // over the assigned component for the field mapping in any email, unless
5319
  // we already assigned one. We are not interested in administratively
5320
  // configured static strings, only user-submitted values.
5321
  foreach ($node->webform['emails'] as $email) {
5322
    // Subject (post_title).
5323
    if (!isset($form_info['mapping']['post_title'])) {
5324
      $cid = $email['subject'];
5325
      if (is_numeric($cid)) {
5326
        $parents = implode('][', webform_component_parent_keys($node, $node->webform['components'][$cid]));
5327
        $form_info['mapping']['post_title'] = 'submitted][' . $parents;
5328
      }
5329
    }
5330
    // From name (author_name).
5331
    if (!isset($form_info['mapping']['author_name'])) {
5332
      $cid = $email['from_name'];
5333
      if (is_numeric($cid)) {
5334
        $parents = implode('][', webform_component_parent_keys($node, $node->webform['components'][$cid]));
5335
        $form_info['mapping']['author_name'] = 'submitted][' . $parents;
5336
      }
5337
    }
5338
    // From address (author_mail).
5339
    if (!isset($form_info['mapping']['author_mail'])) {
5340
      $cid = $email['from_address'];
5341
      if (is_numeric($cid)) {
5342
        $parents = implode('][', webform_component_parent_keys($node, $node->webform['components'][$cid]));
5343
        $form_info['mapping']['author_mail'] = 'submitted][' . $parents;
5344
      }
5345
    }
5346
  }
5347

    
5348
  return $form_info;
5349
}
5350

    
5351
/**
5352
 * Implements hook_date_views_extra_tables().
5353
 */
5354
function webform_date_views_extra_tables() {
5355
  return array('webform_submissions' => 'webform_submissions');
5356
}
5357

    
5358
/**
5359
 * Returns the next serial number for a given node and increments the serial
5360
 * number.
5361
 *
5362
 * @param int $nid
5363
 *   The nid of the node.
5364
 *
5365
 * @return int
5366
 *   The next value of the serial number.
5367
 */
5368
function _webform_submission_serial_next_value($nid) {
5369
  // Use a transaction with SELECT ... FOR UPDATE to lock the row between
5370
  // the SELECT and the UPDATE, ensuring that multiple Webform submissions
5371
  // at the same time do not have duplicate numbers. FOR UPDATE must be inside
5372
  // a transaction. The return value of db_transaction() must be assigned or the
5373
  // transaction will commit immediately. The transaction will commit when $txn
5374
  // goes out-of-scope.
5375
  $txn = db_transaction();
5376

    
5377
  // Get the next_serial value.
5378
  $next_serial = db_select('webform', 'w')
5379
    // Only add FOR UPDATE when incrementing.
5380
    ->forUpdate()
5381
    ->fields('w', array('next_serial'))
5382
    ->condition('nid', $nid)
5383
    ->execute()
5384
    ->fetchField();
5385

    
5386
  // $next_serial must be greater than any existing serial number.
5387
  $next_serial = max($next_serial, _webform_submission_serial_next_value_used($nid));
5388

    
5389
  // Increment the next_value.
5390
  db_update('webform')
5391
    ->fields(array('next_serial' => $next_serial + 1))
5392
    ->condition('nid', $nid)
5393
    ->execute();
5394

    
5395
  return $next_serial;
5396
}
5397

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

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

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

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

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

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