Projet

Général

Profil

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

root / drupal7 / sites / all / modules / webform / webform.module @ 024de6ea

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
    case 'admin/content/webform':
40
      $output = '<p>' . t('This page lists all of the content on the site that may have a webform attached to it.') . '</p>';
41
      break;
42
    case 'admin/help#webform':
43
      module_load_include('inc', 'webform', 'includes/webform.admin');
44
      $types = webform_admin_type_list();
45
      if (empty($types)) {
46
        $types = t('Webform-enabled piece of content');
47
        $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')));
48
      }
49
      else {
50
        $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')));
51
      }
52
      $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>
53
      <p>Here is how to create one:</p>
54
      <ul>
55
        <li>!webform-types-message</li>
56
        <li>Go to <a href=\"!create-content\">Create content</a> and add a !types piece of content.</li>
57
        <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>
58
        <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>
59
        <li>Finally, visit the <em>Form settings</em> sub-tab under the <em>Webform</em> tab to configure remaining configurations options for your form.
60
          <ul>
61
          <li>Add a confirmation message and/or redirect URL that is to be displayed after successful submission.</li>
62
          <li>Set a submission limit.</li>
63
          <li>Determine which roles may submit the form.</li>
64
          <li>Advanced configuration options such as allowing drafts or show users a message indicating how they can edit their submissions.</li>
65
          </ul>
66
        </li>
67
        <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>
68
      </ul>
69
      <p>Help on adding and configuring the components will be shown after you add your first component.</p>
70
      ", array('!webform-types-message' => $types_message, '!create-content' => url('node/add'), '!types' => $types));
71
      break;
72
    case 'node/%/webform/conditionals':
73
      $output .= '<p>' . t('Conditionals may be used to hide or show certain components (or entire pages!) based on the value of other components.') . '</p>';
74
      break;
75
    case 'node/%/submission/%/resend':
76
      $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>';
77
      break;
78
  }
79

    
80
  return $output;
81
}
82

    
83
/**
84
 * Implements hook_menu().
85
 */
86
function webform_menu() {
87
  $items = array();
88

    
89
  // Submissions listing.
90
  $items['admin/content/webform'] = array(
91
    'title' => 'Webforms',
92
    'page callback' => 'webform_admin_content',
93
    'access callback' => 'user_access',
94
    'access arguments' => array('access all webform results'),
95
    'description' => 'View and edit all the available webforms on your site.',
96
    'file' => 'includes/webform.admin.inc',
97
    'type' => MENU_LOCAL_TASK,
98
  );
99

    
100
  // Admin Settings.
101
  $items['admin/config/content/webform'] = array(
102
    'title' => 'Webform settings',
103
    'page callback' => 'drupal_get_form',
104
    'page arguments' => array('webform_admin_settings'),
105
    'access callback' => 'user_access',
106
    'access arguments' => array('administer site configuration'),
107
    'description' => 'Global configuration of webform functionality.',
108
    'file' => 'includes/webform.admin.inc',
109
    'type' => MENU_NORMAL_ITEM,
110
  );
111

    
112
  // Autocomplete used in Views integration.
113
  $items['webform/autocomplete'] = array(
114
    'title' => 'Webforms',
115
    'page callback' => 'webform_views_autocomplete',
116
    'access arguments' => array('administer views'),
117
    'file' => 'views/webform.views.inc',
118
    'type' => MENU_CALLBACK,
119
  );
120

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

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

    
207
  // Node component forms.
208
  $items['node/%webform_menu/webform/components/%webform_menu_component'] = array(
209
    'load arguments' => array(1, 5),
210
    'page callback' => 'drupal_get_form',
211
    'page arguments' => array('webform_component_edit_form', 1, 4, FALSE),
212
    'access callback' => 'webform_node_update_access',
213
    'access arguments' => array(1),
214
    'file' => 'includes/webform.components.inc',
215
    'type' => MENU_LOCAL_TASK,
216
  );
217
  $items['node/%webform_menu/webform/components/%webform_menu_component/clone'] = array(
218
    'load arguments' => array(1, 5),
219
    'page callback' => 'drupal_get_form',
220
    'page arguments' => array('webform_component_edit_form', 1, 4, TRUE),
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/delete'] = array(
227
    'load arguments' => array(1, 5),
228
    'page callback' => 'drupal_get_form',
229
    'page arguments' => array('webform_component_delete_form', 1, 4),
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

    
236
  // AJAX callback for loading select list options.
237
  $items['webform/ajax/options/%webform_menu'] = array(
238
    'load arguments' => array(3),
239
    'page callback' => 'webform_select_options_ajax',
240
    'access callback' => 'webform_node_update_access',
241
    'access arguments' => array(3),
242
    'file' => 'components/select.inc',
243
    'type' => MENU_CALLBACK,
244
  );
245

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

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

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

    
430
  return $items;
431
}
432

    
433
/**
434
 * Menu loader callback. Load a webform node if the given nid is a webform.
435
 */
436
function webform_menu_load($nid) {
437
  if (!is_numeric($nid)) {
438
    return FALSE;
439
  }
440
  $node = node_load($nid);
441
  if (!isset($node->type) || !variable_get('webform_node_' . $node->type, FALSE)) {
442
    return FALSE;
443
  }
444
  return $node;
445
}
446

    
447
/**
448
 * Menu LOADERNAME_to_arg callback. Determines the arguments used to generate a
449
 * menu link.
450
 *
451
 * This is implemented only to give the webform_localization modules an
452
 * opportunity to link to the orignial webform from the localized one. See
453
 * issue 2097277.
454
 *
455
 * @param string $arg
456
 *   The argument supplied by the caller.
457
 * @param array $map
458
 *   Array of path fragments (e.g. e.g. array('node','123','edit') for
459
 *   'node/123/edit').
460
 * @param integer $index
461
 *   Which element of $map corresponds to $arg.
462
 * @return string
463
 *   The $arg, modified as desired.
464
 */
465
function webform_menu_to_arg($arg, $map, $index) {
466
  return function_exists('webform_localization_webform_menu_to_arg')
467
          ? webform_localization_webform_menu_to_arg($arg, $map, $index)
468
          : $arg;
469
}
470

    
471
/**
472
 * Menu loader callback. Load a webform submission if the given sid is a valid.
473
 */
474
function webform_menu_submission_load($sid, $nid) {
475
  module_load_include('inc', 'webform', 'includes/webform.submissions');
476
  $submission = webform_get_submission($nid, $sid);
477
  return empty($submission) ? FALSE : $submission;
478
}
479

    
480
/**
481
 * Menu loader callback. Load a webform component if the given cid is a valid.
482
 */
483
function webform_menu_component_load($cid, $nid, $type) {
484
  module_load_include('inc', 'webform', 'includes/webform.components');
485
  if ($cid == 'new') {
486
    $components = webform_components();
487
    $component = in_array($type, array_keys($components)) ? array('type' => $type, 'nid' => $nid, 'name' => $_GET['name'], 'required' => $_GET['required'], 'pid' => $_GET['pid'], 'weight' => $_GET['weight']) : FALSE;
488
  }
489
  else {
490
    $node = node_load($nid);
491
    $component = isset($node->webform['components'][$cid]) ? $node->webform['components'][$cid] : FALSE;
492
  }
493
  if ($component) {
494
    webform_component_defaults($component);
495
  }
496
  return $component;
497
}
498

    
499

    
500
/**
501
 * Menu loader callback. Load a webform e-mail if the given eid is a valid.
502
 */
503
function webform_menu_email_load($eid, $nid) {
504
  module_load_include('inc', 'webform', 'includes/webform.emails');
505
  $node = node_load($nid);
506
  $email = webform_email_load($eid, $nid);
507
  if ($eid == 'new') {
508
    if (isset($_GET['option']) && isset($_GET['email'])) {
509
      $type = $_GET['option'];
510
      if ($type == 'custom') {
511
        $email['email'] = $_GET['email'];
512
      }
513
      elseif ($type == 'component' && isset($node->webform['components'][$_GET['email']])) {
514
        $email['email'] = $_GET['email'];
515
      }
516
    }
517
    if (isset($_GET['status'])) {
518
      $email['status'] = $_GET['status'];
519
    }
520
  }
521

    
522
  return $email;
523
}
524

    
525
/**
526
 * Return the access token for a submission.
527
 *
528
 * @param object $submission
529
 *   The submission object.
530
 *
531
 * @return string
532
 *   The access token for the submission.
533
 */
534
function webform_get_submission_access_token($submission) {
535
  return md5($submission->submitted . $submission->sid . drupal_get_private_key());
536
}
537

    
538
/**
539
 * Access function for confirmation pages.
540
 *
541
 * @param $node
542
 *   The webform node object.
543
 *
544
 * @return
545
 *  Boolean whether the user has access to the confirmation page.
546
 */
547
function webform_confirmation_page_access($node) {
548
  global $user;
549

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

    
553
  if ($sid) {
554
    module_load_include('inc', 'webform', 'includes/webform.submissions');
555
    $submission = webform_get_submission($node->nid, $sid);
556
  }
557
  else {
558
    $submission = NULL;
559
  }
560

    
561
  if ($submission) {
562
    // Logged-in users.
563
    if ($user->uid) {
564
      // User's own submission.
565
      if ($submission->uid === $user->uid && node_access('view', $node)) {
566
        return TRUE;
567
      }
568
      // User has results access to this submission.
569
      elseif (webform_submission_access($node, $submission)) {
570
        return TRUE;
571
      }
572
    }
573
    // Anonymous user for their own submission. Hash of submission data must
574
    // match the hash in the query string.
575
    elseif ((int) $user->uid === 0 && (int) $submission->uid === 0) {
576
      $hash_query = !empty($_GET['token']) ? $_GET['token'] : NULL;
577
      $hash = webform_get_submission_access_token($submission);
578
      if ($hash_query === $hash) {
579
        return TRUE;
580
      }
581
    }
582
  }
583
  else {
584
    // No submission exists (such as auto-deleted by another module, such as
585
    // webform_clear), just ensure that the user has access to view the node page.
586
    if (node_access('view', $node)) {
587
      return TRUE;
588
    }
589
  }
590

    
591
  return FALSE;
592
}
593

    
594
/**
595
 * Access function for Webform submissions.
596
 *
597
 * @param $node
598
 *   The webform node object.
599
 * @param $submission
600
 *   The webform submission object.
601
 * @param $op
602
 *   The operation to perform. Must be one of view, edit, delete, list.
603
 * @param $account
604
 *   Optional. A user object or NULL to use the currently logged-in user.
605
 *
606
 * @return
607
 *   Boolean whether the user has access to a webform submission.
608
 */
609
function webform_submission_access($node, $submission, $op = 'view', $account = NULL) {
610
  global $user;
611
  $account = isset($account) ? $account : $user;
612

    
613
  $access_all = user_access('access all webform results', $account);
614
  $access_own_submission = isset($submission) && user_access('access own webform submissions', $account) && (($account->uid && $account->uid == $submission->uid) || isset($_SESSION['webform_submission'][$submission->sid]));
615
  $access_node_submissions = user_access('access own webform results', $account) && $account->uid == $node->uid;
616

    
617
  $token_access = $submission && isset($_GET['token']) && $_GET['token'] == webform_get_submission_access_token($submission);
618

    
619
  // If access is granted via a token, then allow subsequent submission access
620
  // for anonymous users.
621
  if (!$user->uid && $token_access) {
622
    $_SESSION['webform_submission'][$submission->sid] = $node->nid;
623
  }
624

    
625
  $general_access = $access_all || $access_own_submission || $access_node_submissions || $token_access;
626

    
627
  // Disable the page cache for anonymous users in this access callback,
628
  // otherwise the "Access denied" page gets cached.
629
  if (!$account->uid && user_access('access own webform submissions', $account)) {
630
    webform_disable_page_cache();
631
  }
632

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

    
635
  switch ($op) {
636
    case 'view':
637
      return $module_access || $general_access;
638
    case 'edit':
639
      return $module_access || ($general_access && (user_access('edit all webform submissions', $account) || (user_access('edit own webform submissions', $account) && $account->uid == $submission->uid)));
640
    case 'delete':
641
      return $module_access || ($general_access && (user_access('delete all webform submissions', $account) || (user_access('delete own webform submissions', $account) && $account->uid == $submission->uid)));
642
    case 'list':
643
      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);
644
  }
645
}
646

    
647
/**
648
 * Menu access callback. Ensure a user both access and node 'view' permission.
649
 */
650
function webform_results_access($node, $account = NULL) {
651
  global $user;
652
  $account = isset($account) ? $account : $user;
653

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

    
656
  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));
657
}
658

    
659
/**
660
 * Menu access callback. Ensure a user has both results access and permission
661
 * to clear submissions.
662
 */
663
function webform_results_clear_access($node, $account = NULL) {
664
  global $user;
665
  $account = isset($account) ? $account : $user;
666

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

    
669
  return webform_results_access($node, $account) && ($module_access || user_access('delete all webform submissions', $account));
670
}
671

    
672
/**
673
 * Menu access callback. Ensure a sure has access to update a webform node.
674
 *
675
 * Unlike webform_results_access and webform_results_clear_access, access is
676
 * completely overridden by the any implmentation of hook_webform_update_access.
677
 *
678
 * If hook_webform_update_access is implemented by one or more other modules,
679
 * the results must be unanimously TRUE for access to be granted; otherwise it
680
 * is denied if even one implementation returns FALSE, regardless of node_access
681
 * and the 'edit webform components' permission. This allows implementors
682
 * complete flexibility.
683
 *
684
 * hook_webform_update_access should return TRUE if access should absolutely
685
 * be granted, FALSE if it should absolutely be denied, or NULL if node_access
686
 * and 'edit webform components' permission should determine access.
687
 *
688
 * @see hook_webform_update_access().
689
 */
690
function webform_node_update_access($node, $account = NULL) {
691
  global $user;
692
  $account = isset($account) ? $account : $user;
693
  $module_access = module_invoke_all('webform_update_access', $node, $account);
694
  return empty($module_access)
695
            ? node_access('update', $node, $account) && user_access('edit webform components')
696
            : count(array_filter($module_access)) == count($module_access);
697
}
698

    
699
/**
700
 * Implements hook_admin_paths().
701
 */
702
function webform_admin_paths() {
703
  if (variable_get('node_admin_theme')) {
704
    return array(
705
      'node/*/webform' => TRUE,
706
      'node/*/webform/*' => TRUE,
707
      'node/*/webform-results' => TRUE,
708
      'node/*/webform-results/*' => TRUE,
709
      'node/*/submission/*' => TRUE,
710
    );
711
  }
712
}
713

    
714
/**
715
 * Implements hook_perm().
716
 */
717
function webform_permission() {
718
  return array(
719
    'access all webform results' => array(
720
      'title' => t('Access all webform results'),
721
      'description' => t('Grants access to the "Results" tab on all webform content. Generally an administrative permission.'),
722
    ),
723
    'access own webform results' => array(
724
      'title' => t('Access own webform results'),
725
      'description' => t('Grants access to the "Results" tab to the author of webform content they have created.'),
726
    ),
727
    'edit all webform submissions' => array(
728
      'title' => t('Edit all webform submissions'),
729
      'description' => t('Allows editing of any webform submission by any user. Generally an administrative permission.'),
730
    ),
731
    'delete all webform submissions' => array(
732
      'title' => t('Delete all webform submissions'),
733
      'description' => t('Allows deleting of any webform submission by any user. Generally an administrative permission.'),
734
    ),
735
    'access own webform submissions' => array(
736
      'title' => t('Access own webform submissions'),
737
    ),
738
    'edit own webform submissions' => array(
739
      'title' => t('Edit own webform submissions'),
740
    ),
741
    'delete own webform submissions' => array(
742
      'title' => t('Delete own webform submissions'),
743
    ),
744
    'edit webform components' => array(
745
      'title' => t('Content authors: access and edit webform components and settings'),
746
      'description' => t('Grants additional access to the webform components and settings to users who can edit the content. Generally an authenticated user permission.'),
747
    ),
748
  );
749
}
750

    
751
/**
752
 * Implements hook_theme().
753
 */
754
function webform_theme() {
755
  $theme = array(
756
    // webform.module.
757
    'webform_view' => array(
758
      'render element' => 'webform',
759
    ),
760
    'webform_view_messages' => array(
761
      'variables' => array('node' => NULL, 'page' => NULL, 'submission_count' => NULL, 'user_limit_exceeded' => NULL, 'total_limit_exceeded' => NULL, 'allowed_roles' => NULL, 'closed' => NULL, 'cached' => NULL),
762
    ),
763
    'webform_form' => array(
764
      'render element' => 'form',
765
      'template' => 'templates/webform-form',
766
      'pattern' => 'webform_form_[0-9]+',
767
    ),
768
    'webform_confirmation' => array(
769
      'variables' => array('node' => NULL, 'sid' => NULL),
770
      'template' => 'templates/webform-confirmation',
771
      'pattern' => 'webform_confirmation_[0-9]+',
772
    ),
773
    'webform_element' => array(
774
      'render element' => 'element',
775
    ),
776
    'webform_element_text' => array(
777
      'render element' => 'element',
778
    ),
779
    'webform_inline_radio' => array(
780
      'render element' => 'element',
781
    ),
782
    'webform_inline_radio_label' => array(
783
      'render element' => 'element',
784
    ),
785
    'webform_progressbar' => array(
786
      'variables' => array('node' => NULL, 'page_num' => NULL, 'page_count' => NULL, 'page_labels' => array()),
787
      'template' => 'templates/webform-progressbar',
788
    ),
789
    'webform_mail_message' => array(
790
      'variables' => array('node' => NULL, 'submission' => NULL, 'email' => NULL),
791
      'template' => 'templates/webform-mail',
792
      'pattern' => 'webform_mail(_[0-9]+)?',
793
    ),
794
    'webform_mail_headers' => array(
795
      'variables' => array('node' => NULL, 'submission' => NULL, 'email' => NULL),
796
      'pattern' => 'webform_mail_headers_[0-9]+',
797
    ),
798
    'webform_token_help' => array(
799
      'variables' => array('groups' => array('node')),
800
    ),
801
    // webform.admin.inc.
802
    'webform_admin_settings' => array(
803
      'render element' => 'form',
804
      'file' => 'includes/webform.admin.inc',
805
    ),
806
    'webform_admin_content' => array(
807
      'variables' => array('nodes' => NULL),
808
      'file' => 'includes/webform.admin.inc',
809
    ),
810
    // webform.emails.inc.
811
    'webform_emails_form' => array(
812
      'render element' => 'form',
813
      'file' => 'includes/webform.emails.inc',
814
    ),
815
    'webform_email_component_mapping' => array(
816
      'render element' => 'element',
817
      'file' => 'includes/webform.emails.inc',
818
    ),
819
    'webform_email_add_form' => array(
820
      'render element' => 'form',
821
      'file' => 'includes/webform.emails.inc',
822
    ),
823
    'webform_email_edit_form' => array(
824
      'render element' => 'form',
825
      'file' => 'includes/webform.emails.inc',
826
    ),
827
    // webform.components.inc.
828
    'webform_components_page' => array(
829
      'variables' => array('node' => NULL, 'form' => NULL),
830
      'file' => 'includes/webform.components.inc',
831
    ),
832
    'webform_components_form' => array(
833
      'render element' => 'form',
834
      'file' => 'includes/webform.components.inc',
835
    ),
836
    'webform_component_select' => array(
837
      'render element' => 'element',
838
      'file' => 'includes/webform.components.inc',
839
    ),
840
    // webform.conditionals.inc.
841
    'webform_conditional_groups' => array(
842
      'render element' => 'element',
843
      'file' => 'includes/webform.conditionals.inc',
844
    ),
845
    'webform_conditional_group_row' => array(
846
      'render element' => 'element',
847
      'file' => 'includes/webform.conditionals.inc',
848
    ),
849
    'webform_conditional' => array(
850
      'render element' => 'element',
851
      'file' => 'includes/webform.conditionals.inc',
852
    ),
853
    // webform.pages.inc.
854
    'webform_advanced_redirection_form' => array(
855
      'render element' => 'form',
856
      'file' => 'includes/webform.pages.inc',
857
    ),
858
    'webform_advanced_submit_limit_form' => array(
859
      'render element' => 'form',
860
      'file' => 'includes/webform.pages.inc',
861
    ),
862
    'webform_advanced_total_submit_limit_form' => array(
863
      'render element' => 'form',
864
      'file' => 'includes/webform.pages.inc',
865
    ),
866
    // webform.report.inc.
867
    'webform_results_per_page' => array(
868
      'variables' => array('total_count' => NULL, 'pager_count' => NULL),
869
      'file' => 'includes/webform.report.inc',
870
    ),
871
    'webform_results_submissions_header' => array(
872
      'variables' => array('node' => NULL),
873
      'file' => 'includes/webform.report.inc',
874
    ),
875
    'webform_results_submissions' => array(
876
      'render element' => 'element',
877
      'template' => 'templates/webform-results-submissions',
878
      'file' => 'includes/webform.report.inc',
879
    ),
880
    'webform_results_table_header' => array(
881
      'variables' => array('node' => NULL),
882
      'file' => 'includes/webform.report.inc',
883
    ),
884
    'webform_results_table' => array(
885
      'variables' => array('node' => NULL, 'components' => NULL, 'submissions' => NULL, 'total_count' => NULL, 'pager_count' => NULL),
886
      'file' => 'includes/webform.report.inc',
887
    ),
888
    'webform_results_download_range' => array(
889
      'render element' => 'element',
890
      'file' => 'includes/webform.report.inc',
891
    ),
892
    'webform_results_download_select_format' => array(
893
      'render element' => 'element',
894
      'file' => 'includes/webform.report.inc',
895
    ),
896
    'webform_analysis' => array(
897
      'render element' => 'analysis',
898
      'template' => 'templates/webform-analysis',
899
      'file' => 'includes/webform.report.inc',
900
    ),
901
    'webform_analysis_component' => array(
902
      'render element' => 'component_analysis',
903
      'template' => 'templates/webform-analysis-component',
904
      'file' => 'includes/webform.report.inc',
905
    ),
906
    'webform_analysis_component_basic' => array(
907
      'variables' => array('component' => NULL, 'data' => NULL),
908
      'file' => 'includes/webform.report.inc',
909
    ),
910
    // webform.submissions.inc
911
    'webform_submission' => array(
912
      'render element' => 'renderable',
913
      'template' => 'templates/webform-submission',
914
      'pattern' => 'webform_submission_[0-9]+',
915
      'file' => 'includes/webform.submissions.inc',
916
    ),
917
    'webform_submission_page' => array(
918
      'variables' => array('node' => NULL, 'submission' => NULL, 'submission_content' => NULL, 'submission_navigation' => NULL, 'submission_information' => NULL, 'submission_actions' => NULL, 'mode' => NULL),
919
      'template' => 'templates/webform-submission-page',
920
      'file' => 'includes/webform.submissions.inc',
921
    ),
922
    'webform_submission_information' => array(
923
      'variables' => array('node' => NULL, 'submission' => NULL, 'mode' => 'display'),
924
      'template' => 'templates/webform-submission-information',
925
      'file' => 'includes/webform.submissions.inc',
926
    ),
927
    'webform_submission_navigation' => array(
928
      'variables' => array('node' => NULL, 'submission' => NULL, 'mode' => NULL),
929
      'template' => 'templates/webform-submission-navigation',
930
      'file' => 'includes/webform.submissions.inc',
931
    ),
932
    'webform_submission_resend' => array(
933
      'render element' => 'form',
934
      'file' => 'includes/webform.submissions.inc',
935
    ),
936
  );
937

    
938
  // Theme functions in all components.
939
  $components = webform_components(TRUE);
940
  foreach ($components as $type => $component) {
941
    if ($theme_additions = webform_component_invoke($type, 'theme')) {
942
      $theme = array_merge($theme, $theme_additions);
943
    }
944
  }
945
  return $theme;
946
}
947

    
948
/**
949
 * Implements hook_library().
950
 */
951
function webform_library() {
952
  $module_path = drupal_get_path('module', 'webform');
953

    
954
  // Webform administration.
955
  $libraries['admin'] = array(
956
    'title' => 'Webform: Administration',
957
    'website' => 'http://drupal.org/project/webform',
958
    'version' => '1.0',
959
    'js' => array(
960
      $module_path . '/js/webform-admin.js' => array('group' => JS_DEFAULT),
961
    ),
962
    'css' => array(
963
      $module_path . '/css/webform-admin.css' => array('group' => CSS_DEFAULT, 'weight' => 1),
964
    ),
965
  );
966

    
967
  return $libraries;
968
}
969

    
970
/**
971
 * Implements hook_element_info().
972
 */
973
function webform_element_info() {
974
  // A few of our components need to be defined here because Drupal does not
975
  // provide these components natively. Because this hook fires on every page
976
  // load (even on non-webform pages), we don't put this in the component .inc
977
  // files because of the unnecessary loading that it would require.
978
  $elements['webform_time'] = array('#input' => 'TRUE');
979
  $elements['webform_grid'] = array('#input' => 'TRUE');
980

    
981
  $elements['webform_email'] = array(
982
    '#input' => TRUE,
983
    '#theme' => 'webform_email',
984
    '#size' => 60,
985
  );
986
  $elements['webform_number'] = array(
987
    '#input' => TRUE,
988
    '#theme' => 'webform_number',
989
    '#min' => NULL,
990
    '#max' => NULL,
991
    '#step' => NULL,
992
  );
993

    
994
  $elements['webform_conditional'] = array(
995
    '#input' => TRUE,
996
    '#theme' => 'webform_conditional',
997
    '#default_value' => NULL,
998
    '#process' => array('webform_conditional_expand'),
999
  );
1000

    
1001
  return $elements;
1002
}
1003

    
1004
/**
1005
 * Implements hook_webform_component_info().
1006
 */
1007
function webform_webform_component_info() {
1008
  $component_info = array(
1009
    'date' => array(
1010
      'label' => t('Date'),
1011
      'description' => t('Presents month, day, and year fields.'),
1012
      'features' => array(
1013
        'views_range' => TRUE,
1014
        'css_classes' => FALSE,
1015
      ),
1016
      'file' => 'components/date.inc',
1017
      'conditional_type' => 'date',
1018
    ),
1019
    'email' => array(
1020
      'label' => t('E-mail'),
1021
      'description' => t('A special textfield that accepts e-mail addresses.'),
1022
      'file' => 'components/email.inc',
1023
      'features' => array(
1024
        'email_address' => TRUE,
1025
        'spam_analysis' => TRUE,
1026
        'placeholder' => TRUE,
1027
        'conditional_action_set' => TRUE,
1028
      ),
1029
    ),
1030
    'fieldset' => array(
1031
      'label' => t('Fieldset'),
1032
      'description' => t('Fieldsets allow you to organize multiple fields into groups.'),
1033
      'features' => array(
1034
        'csv' => FALSE,
1035
        'default_value' => FALSE,
1036
        'required' => FALSE,
1037
        'conditional' => FALSE,
1038
        'group' => TRUE,
1039
        'title_inline' => FALSE,
1040
        'wrapper_classes' => FALSE,
1041
      ),
1042
      'file' => 'components/fieldset.inc',
1043
    ),
1044
    'grid' => array(
1045
      'label' => t('Grid'),
1046
      'description' => t('Allows creation of grid questions, denoted by radio buttons.'),
1047
      'features' => array(
1048
        'default_value' => FALSE,
1049
        'title_inline' => FALSE,
1050
        'title_internal' => TRUE,
1051
        'css_classes' => FALSE,
1052
        'conditional' => FALSE,
1053
        'group' => TRUE,
1054
      ),
1055
      'file' => 'components/grid.inc',
1056
    ),
1057
    'hidden' => array(
1058
      'label' => t('Hidden'),
1059
      'description' => t('A field which is not visible to the user, but is recorded with the submission.'),
1060
      'file' => 'components/hidden.inc',
1061
      'features' => array(
1062
        'required' => FALSE,
1063
        'description' => FALSE,
1064
        'email_address' => TRUE,
1065
        'email_name' => TRUE,
1066
        'title_display' => FALSE,
1067
        'private' => FALSE,
1068
        'wrapper_classes' => FALSE,
1069
        'css_classes' => FALSE,
1070
        'conditional_action_set' => TRUE,
1071
      ),
1072
    ),
1073
    'markup' => array(
1074
      'label' => t('Markup'),
1075
      'description' => t('Displays text as HTML in the form; does not render a field.'),
1076
      'features' => array(
1077
        'analysis' => FALSE,
1078
        'csv' => FALSE,
1079
        'default_value' => FALSE,
1080
        'description' => FALSE,
1081
        'email' => FALSE,
1082
        'required' => FALSE,
1083
        'conditional' => FALSE,
1084
        'title_display' => FALSE,
1085
        'private' => FALSE,
1086
        'wrapper_classes' => FALSE,
1087
        'css_classes' => FALSE,
1088
        'conditional_action_set' => TRUE,
1089
      ),
1090
      'file' => 'components/markup.inc',
1091
    ),
1092
    'number' => array(
1093
      'label' => t('Number'),
1094
      'description' => t('A numeric input field (either as textfield or select list).'),
1095
      'features' => array(
1096
        'conditional_action_set' => TRUE,
1097
      ),
1098
      'file' => 'components/number.inc',
1099
      'conditional_type' => 'numeric',
1100
    ),
1101
    'pagebreak' => array(
1102
      'label' => t('Page break'),
1103
      'description' => t('Organize forms into multiple pages.'),
1104
      'features' => array(
1105
        'analysis' => FALSE,
1106
        'conditional' => FALSE,
1107
        'csv' => FALSE,
1108
        'default_value' => FALSE,
1109
        'description' => FALSE,
1110
        'private' => FALSE,
1111
        'required' => FALSE,
1112
        'title_display' => FALSE,
1113
        'wrapper_classes' => FALSE,
1114
        'css_classes' => FALSE,
1115
      ),
1116
      'file' => 'components/pagebreak.inc',
1117
    ),
1118
    'select' => array(
1119
      'label' => t('Select options'),
1120
      'description' => t('Allows creation of checkboxes, radio buttons, or select menus.'),
1121
      'file' => 'components/select.inc',
1122
      'features' => array(
1123
        'default_value' => FALSE,
1124
        'email_address' => TRUE,
1125
        'email_name' => TRUE,
1126
        'conditional_action_set' => TRUE,
1127
      ),
1128
      'conditional_type' => 'select',
1129
    ),
1130
    'textarea' => array(
1131
      'label' => t('Textarea'),
1132
      'description' => t('A large text area that allows for multiple lines of input.'),
1133
      'file' => 'components/textarea.inc',
1134
      'features' => array(
1135
        'spam_analysis' => TRUE,
1136
        'placeholder' => TRUE,
1137
        'conditional_action_set' => TRUE,
1138
      ),
1139
    ),
1140
    'textfield' => array(
1141
      'label' => t('Textfield'),
1142
      'description' => t('Basic textfield type.'),
1143
      'file' => 'components/textfield.inc',
1144
      'features' => array(
1145
        'email_name' => TRUE,
1146
        'spam_analysis' => TRUE,
1147
        'placeholder' => TRUE,
1148
        'conditional_action_set' => TRUE,
1149
      ),
1150
    ),
1151
    'time' => array(
1152
      'label' => t('Time'),
1153
      'description' => t('Presents the user with hour and minute fields. Optional am/pm fields.'),
1154
      'features' => array(
1155
        'views_range' => TRUE,
1156
        'css_classes' => FALSE,
1157
      ),
1158
      'file' => 'components/time.inc',
1159
      'conditional_type' => 'time',
1160
    ),
1161
  );
1162

    
1163
  if (module_exists('file')) {
1164
    $component_info['file'] = array(
1165
      'label' => t('File'),
1166
      'description' => t('Allow users to upload files of configurable types.'),
1167
      'features' => array(
1168
        'conditional' => FALSE,
1169
        'default_value' => FALSE,
1170
        'attachment' => TRUE,
1171
      ),
1172
      'file' => 'components/file.inc',
1173
    );
1174
  }
1175

    
1176
  return $component_info;
1177
}
1178

    
1179
/**
1180
 * Implements hook_webform_conditional_operator_info().
1181
 */
1182
function webform_webform_conditional_operator_info() {
1183
  module_load_include('inc', 'webform', 'includes/webform.conditionals');
1184
  return _webform_conditional_operator_info();
1185
}
1186

    
1187
/**
1188
 * Implements hook_forms().
1189
 *
1190
 * All webform_client_form forms share the same form handler
1191
 */
1192
function webform_forms($form_id) {
1193
  $forms = array();
1194
  if (strpos($form_id, 'webform_client_form_') === 0) {
1195
    $forms[$form_id]['callback'] = 'webform_client_form';
1196
  }
1197
  return $forms;
1198
}
1199

    
1200
/**
1201
 * Implements hook_webform_select_options_info().
1202
 */
1203
function webform_webform_select_options_info() {
1204
  module_load_include('inc', 'webform', 'includes/webform.options');
1205
  return _webform_options_info();
1206
}
1207

    
1208
/**
1209
 * Implements hook_webform_webform_submission_actions().
1210
 */
1211
function webform_webform_submission_actions($node, $submission) {
1212
  $actions = array();
1213
  $destination = drupal_get_destination();
1214

    
1215
  if (module_exists('print_pdf') && user_access('access PDF version')) {
1216
    $actions['printpdf'] = array(
1217
      'title' => t('Download PDF'),
1218
      'href' => 'printpdf/' . $node->nid . '/submission/' . $submission->sid,
1219
      'query' => $destination,
1220
    );
1221
  }
1222

    
1223
  if (module_exists('print') && user_access('access print')) {
1224
    $actions['print'] = array(
1225
      'title' => t('Print'),
1226
      'href' => 'print/' . $node->nid . '/submission/' . $submission->sid,
1227
    );
1228
  }
1229

    
1230
  if (webform_results_access($node) && count($node->webform['emails'])) {
1231
    $actions['resend'] = array(
1232
      'title' => t('Resend e-mails'),
1233
      'href' => 'node/' . $node->nid . '/submission/' . $submission->sid . '/resend',
1234
      'query' => drupal_get_destination(),
1235
    );
1236
  }
1237

    
1238
  return $actions;
1239
}
1240

    
1241
/**
1242
 * Implements hook_webform_submission_presave().
1243
 *
1244
 * We implement our own hook here to facilitate the File component, which needs
1245
 * to clean up manage file usage records and delete files from submissions that
1246
 * have been edited if necessary.
1247
 */
1248
function webform_webform_submission_presave($node, &$submission) {
1249
  // Check if there are any file components in this submission and if any of
1250
  // them currently contain files.
1251
  $has_file_components = FALSE;
1252
  $new_fids = array();
1253
  $old_fids = array();
1254
  $renameable = array();
1255

    
1256
  foreach ($node->webform['components'] as $cid => $component) {
1257
    if ($component['type'] == 'file') {
1258
      $has_file_components = TRUE;
1259
      if (!empty($submission->data[$cid])) {
1260
        foreach ($submission->data[$cid] as $key => $value) {
1261
          if (empty($value)) {
1262
            unset($submission->data[$cid][$key]);
1263
          }
1264
          if (strlen($component['extra']['rename'])) {
1265
            $renameable[$cid][] = $value;
1266
          }
1267
        }
1268
        $new_fids = array_merge($new_fids, $submission->data[$cid]);
1269
      }
1270
    }
1271
  }
1272

    
1273
  if ($has_file_components) {
1274
    // If we're updating a submission, build a list of previous files.
1275
    if (isset($submission->sid)) {
1276
      drupal_static_reset('webform_get_submission');
1277
      $old_submission = webform_get_submission($node->nid, $submission->sid);
1278

    
1279
      foreach ($node->webform['components'] as $cid => $component) {
1280
        if ($component['type'] == 'file') {
1281
          if (!empty($old_submission->data[$cid])) {
1282
            $old_fids = array_merge($old_fids, $old_submission->data[$cid]);
1283
          }
1284
        }
1285
      }
1286
    }
1287

    
1288
    // Only rename files if this is the first time the submission is being saved as finished.
1289
    if ($submission->is_draft || (isset($old_submission) && !$old_submission->is_draft)) {
1290
      $renameable = array();
1291
    }
1292

    
1293
    // Save the list of added or removed files so we can add usage in
1294
    // hook_webform_submission_insert() or _update().
1295
    $submission->file_usage = array(
1296
      // Diff the old against new to determine what files were deleted.
1297
      'deleted_fids' => array_diff($old_fids, $new_fids),
1298
      // Diff the new files against old to determine new uploads.
1299
      'added_fids' => array_diff($new_fids, $old_fids),
1300
      // A list of files which need renaming with tokens.
1301
      'renameable' => $renameable,
1302
    );
1303
  }
1304
}
1305

    
1306
/**
1307
 * Implements hook_webform_submission_insert().
1308
 */
1309
function webform_webform_submission_insert($node, $submission) {
1310
  if (isset($submission->file_usage)) {
1311
    webform_component_include('file');
1312
    webform_file_usage_adjust($submission);
1313
    webform_file_rename($node, $submission);
1314
  }
1315
}
1316

    
1317
/**
1318
 * Implements hook_webform_submission_update().
1319
 */
1320
function webform_webform_submission_update($node, $submission) {
1321
  if (isset($submission->file_usage)) {
1322
    webform_component_include('file');
1323
    webform_file_usage_adjust($submission);
1324
    webform_file_rename($node, $submission);
1325
  }
1326
}
1327

    
1328
/**
1329
 * Implements hook_webform_submission_render_alter().
1330
 */
1331
function webform_webform_submission_render_alter(&$renderable) {
1332
  // If displaying a submission to end-users who are viewing their own
1333
  // submissions (and not through an e-mail), do not show hidden values.
1334
  // This needs to be implemented at the level of the entire submission, since
1335
  // individual components do not get contextual information about where they
1336
  // are being displayed.
1337
  $node = $renderable['#node'];
1338
  $is_admin = webform_results_access($node);
1339
  if (empty($renderable['#email']) && !$is_admin) {
1340
    // Find and hide the display of all hidden components.
1341
    module_load_include('inc', 'webform', 'includes/webform.components');
1342
    foreach ($node->webform['components'] as $cid => $component) {
1343
      if ($component['type'] == 'hidden') {
1344
        $parents = webform_component_parent_keys($node, $component);
1345
        $element = &$renderable;
1346
        foreach ($parents as $pid) {
1347
          $element = &$element[$pid];
1348
        }
1349
        $element['#access'] = FALSE;
1350
      }
1351
    }
1352
  }
1353
}
1354

    
1355
/**
1356
 * Implements hook_file_download().
1357
 *
1358
 * Only allow users with view webform submissions to download files.
1359
 */
1360
function webform_file_download($uri) {
1361
  module_load_include('inc', 'webform', 'includes/webform.submissions');
1362

    
1363
  // Determine whether this file was a webform upload.
1364
  $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();
1365
  if ($row) {
1366
    $file = file_load($row->fid);
1367
  }
1368
  if (!empty($row->sid)) {
1369
    $submissions = webform_get_submissions(array('sid' => $row->sid));
1370
    $submission = reset($submissions);
1371
  }
1372

    
1373
  // Grant or deny file access based on access to the submission.
1374
  if (!empty($submission)) {
1375
    $node = node_load($submission->nid);
1376
    if (webform_submission_access($node, $submission)) {
1377
      return file_get_content_headers($file);
1378
    }
1379
    else {
1380
      return -1;
1381
    }
1382
  }
1383
  // Grant access to files uploaded by a user before the submission is saved.
1384
  elseif (!empty($file) && !empty($_SESSION['webform_files'][$file->fid])) {
1385
    return file_get_content_headers($file);
1386
  }
1387

    
1388
  // Ensure we never completely ignore a webform file request.
1389
  if (strpos(file_uri_target($uri), 'webform/') === 0) {
1390
    // The file is not part of a submission or a submission-in-progress (by
1391
    // the current user), however it may be part of a submission-in-progress
1392
    // (or an abandoned submission) by another user. We assume that all files
1393
    // under our enforced directory prefix are in fact webform files, and so
1394
    // we deny access to the file. Abandoned uploads will be deleted by
1395
    // system_cron() in due course.
1396
    return -1;
1397
  }
1398
}
1399

    
1400
/**
1401
 * Return all content type enabled with webform.
1402
 *
1403
 * @return array
1404
 *   An array of node type names.
1405
 */
1406
function webform_node_types() {
1407
  $types = &drupal_static(__FUNCTION__, NULL);
1408
  if (!isset($types)) {
1409
    $types = array();
1410
    foreach (node_type_get_names() as $type => $name) {
1411
      if (variable_get('webform_node_' . $type, FALSE)) {
1412
        $types[] = $type;
1413
      }
1414
    }
1415
  }
1416
  return $types;
1417
}
1418

    
1419
/**
1420
 * Implements hook_node_type_delete().
1421
 */
1422
function webform_node_type_delete($info) {
1423
  variable_del('webform_node_' . $info->type);
1424
}
1425

    
1426
/**
1427
 * Implements hook_node_insert().
1428
 */
1429
function webform_node_insert($node) {
1430
  if (!variable_get('webform_node_' . $node->type, FALSE)) {
1431
    return;
1432
  }
1433

    
1434
  // If added directly through node_save(), set defaults for the node.
1435
  if (!isset($node->webform)) {
1436
    $node->webform = array();
1437
  }
1438

    
1439
  // Ensure values for all defaults are provided. Useful for importing from
1440
  // older versions into newer ones.
1441
  $node->webform += webform_node_defaults();
1442

    
1443
  // Do not make an entry if this node does not have any Webform settings.
1444
  if ($node->webform == webform_node_defaults() && !in_array($node->type, webform_variable_get('webform_node_types_primary'))) {
1445
    return;
1446
  }
1447

    
1448
  module_load_include('inc', 'webform', 'includes/webform.components');
1449
  module_load_include('inc', 'webform', 'includes/webform.conditionals');
1450
  module_load_include('inc', 'webform', 'includes/webform.emails');
1451

    
1452
  // Prepare the record for writing.
1453
  $node->webform['nid'] = $node->nid;
1454
  $webform_record = $node->webform;
1455
  $webform_record['preview_excluded_components'] = implode(',', $webform_record['preview_excluded_components']);
1456

    
1457
  // Insert the webform.
1458
  $node->webform['record_exists'] = (bool) drupal_write_record('webform', $webform_record);
1459

    
1460
  // Insert the components into the database. Used with clone.module.
1461
  if (isset($node->webform['components']) && !empty($node->webform['components'])) {
1462
    foreach ($node->webform['components'] as $cid => $component) {
1463
      $component['nid'] = $node->nid; // Required for clone.module.
1464
      webform_component_insert($component);
1465
    }
1466
  }
1467

    
1468
  // Insert conditionals. Also used with clone.module.
1469
  if (isset($node->webform['conditionals']) && !empty($node->webform['conditionals'])) {
1470
    foreach ($node->webform['conditionals'] as $rgid => $conditional) {
1471
      $conditional['nid'] = $node->nid;
1472
      $conditional['rgid'] = $rgid;
1473
      webform_conditional_insert($conditional);
1474
    }
1475
  }
1476

    
1477
  // Insert emails. Also used with clone.module.
1478
  if (isset($node->webform['emails']) && !empty($node->webform['emails'])) {
1479
    foreach ($node->webform['emails'] as $eid => $email) {
1480
      $email['nid'] = $node->nid;
1481
      webform_email_insert($email);
1482
    }
1483
  }
1484

    
1485
  // Set the per-role submission access control.
1486
  foreach (array_filter($node->webform['roles']) as $rid) {
1487
    db_insert('webform_roles')->fields(array('nid' => $node->nid, 'rid' => $rid))->execute();
1488
  }
1489

    
1490
  // Flush the block cache if creating a block.
1491
  if (function_exists('block_flush_caches') && $node->webform['block']) {
1492
    block_flush_caches();
1493
  }
1494
}
1495

    
1496
/**
1497
 * Implements hook_node_update().
1498
 */
1499
function webform_node_update($node) {
1500
  if (!variable_get('webform_node_' . $node->type, FALSE)) {
1501
    return;
1502
  }
1503

    
1504
  // Check if this node needs a webform record at all. If it matches the
1505
  // defaults, any existing record will be deleted.
1506
  webform_check_record($node);
1507

    
1508
  // If a webform row doesn't even exist, we can assume it needs to be inserted.
1509
  // If the the webform matches the defaults, no row will be inserted.
1510
  if (!$node->webform['record_exists']) {
1511
    webform_node_insert($node);
1512
    return;
1513
  }
1514

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

    
1520
  // Update the webform entry.
1521
  drupal_write_record('webform', $webform_record, array('nid'));
1522

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

    
1526
  if ($original->webform['components'] != $node->webform['components']) {
1527
    module_load_include('inc', 'webform', 'includes/webform.components');
1528

    
1529
    $original_cids = array_keys($original->webform['components']);
1530
    $current_cids = array_keys($node->webform['components']);
1531

    
1532
    $all_cids = array_unique(array_merge($original_cids, $current_cids));
1533
    $deleted_cids = array_diff($original_cids, $current_cids);
1534
    $inserted_cids = array_diff($current_cids, $original_cids);
1535

    
1536
    foreach ($all_cids as $cid) {
1537
      $node->webform['components'][$cid]['nid'] = $node->nid;
1538
      if (in_array($cid, $inserted_cids)) {
1539
        webform_component_insert($node->webform['components'][$cid]);
1540
      }
1541
      elseif (in_array($cid, $deleted_cids)) {
1542
        // Delete components only after all updates have been processed.
1543
      }
1544
      elseif ($node->webform['components'][$cid] != $original->webform['components'][$cid]) {
1545
        webform_component_update($node->webform['components'][$cid]);
1546
      }
1547
    }
1548

    
1549
    // Delete components now that any parent changes have been saved. When
1550
    // components are moved and deleted in one operation in FormBuilder, this
1551
    // ensures that only the current children are deleted.
1552
    foreach ($deleted_cids as $cid) {
1553
      webform_component_delete($node, $original->webform['components'][$cid]);
1554
    }
1555
  }
1556

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

    
1561
    // Conditionals don't have unique site-wide IDs or configuration, so our
1562
    // update here is a bit more aggressive than for components and e-mails.
1563
    // Delete any conditionals no longer in the webform or that have changed.
1564
    foreach ($original->webform['conditionals'] as $rgid => $conditional) {
1565
      if (!isset($node->webform['conditionals'][$rgid]) || $conditional != $node->webform['conditionals'][$rgid]) {
1566
        webform_conditional_delete($node, $conditional);
1567
      }
1568
    }
1569
    // Insert any conditionals not in the original or that have changed.
1570
    foreach ($node->webform['conditionals'] as $rgid => $conditional) {
1571
      $conditional['nid'] = $node->nid;
1572
      $conditional['rgid'] = $rgid;
1573
      if (!isset($original->webform['conditionals'][$rgid]) || $original->webform['conditionals'][$rgid] != $conditional) {
1574
       webform_conditional_insert($conditional);
1575
      }
1576
    }
1577
  }
1578

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

    
1583
    $original_eids = array_keys($original->webform['emails']);
1584
    $current_eids = array_keys($node->webform['emails']);
1585

    
1586
    $all_eids = array_unique(array_merge($original_eids, $current_eids));
1587
    $deleted_eids = array_diff($original_eids, $current_eids);
1588
    $inserted_eids = array_diff($current_eids, $original_eids);
1589

    
1590
    foreach ($all_eids as $eid) {
1591
      $node->webform['emails'][$eid]['nid'] = $node->nid;
1592
      if (in_array($eid, $inserted_eids)) {
1593
        webform_email_insert($node->webform['emails'][$eid]);
1594
      }
1595
      elseif (in_array($eid, $deleted_eids)) {
1596
        webform_email_delete($node, $original->webform['emails'][$eid]);
1597
      }
1598
      elseif ($node->webform['emails'][$eid] != $original->webform['emails'][$eid]) {
1599
        webform_email_update($node->webform['emails'][$eid]);
1600
      }
1601
    }
1602
  }
1603

    
1604
  // Just delete and re-insert roles if they've changed.
1605
  if ($original->webform['roles'] != $node->webform['roles']) {
1606
    db_delete('webform_roles')->condition('nid', $node->nid)->execute();
1607
    foreach (array_filter($node->webform['roles']) as $rid) {
1608
      db_insert('webform_roles')->fields(array('nid' => $node->nid, 'rid' => $rid))->execute();
1609
    }
1610
  }
1611

    
1612
  // Flush the block cache if block settings have been changed.
1613
  if (function_exists('block_flush_caches') && $node->webform['block'] != $original->webform['block']) {
1614
    block_flush_caches();
1615
  }
1616
}
1617

    
1618
/**
1619
 * Implements hook_node_delete().
1620
 */
1621
function webform_node_delete($node) {
1622
  if (!variable_get('webform_node_' . $node->type, FALSE)) {
1623
    return;
1624
  }
1625

    
1626
  // Allow components clean up extra data, such as uploaded files.
1627
  module_load_include('inc', 'webform', 'includes/webform.components');
1628
  foreach ($node->webform['components'] as $cid => $component) {
1629
    webform_component_delete($node, $component);
1630
  }
1631

    
1632
  // Remove any trace of webform data from the database.
1633
  db_delete('webform')->condition('nid', $node->nid)->execute();
1634
  db_delete('webform_component')->condition('nid', $node->nid)->execute();
1635
  db_delete('webform_conditional')->condition('nid', $node->nid)->execute();
1636
  db_delete('webform_conditional_rules')->condition('nid', $node->nid)->execute();
1637
  db_delete('webform_conditional_actions')->condition('nid', $node->nid)->execute();
1638
  db_delete('webform_emails')->condition('nid', $node->nid)->execute();
1639
  db_delete('webform_roles')->condition('nid', $node->nid)->execute();
1640
  db_delete('webform_submissions')->condition('nid', $node->nid)->execute();
1641
  db_delete('webform_submitted_data')->condition('nid', $node->nid)->execute();
1642
  db_delete('webform_last_download')->condition('nid', $node->nid)->execute();
1643
}
1644

    
1645
/**
1646
 * Default settings for a newly created webform node.
1647
 */
1648
function webform_node_defaults() {
1649
  $progress_bar_defaults = webform_variable_get('webform_progressbar_style');
1650
  $defaults = array(
1651
    'confirmation' => '',
1652
    'confirmation_format' => NULL,
1653
    'redirect_url' => '<confirmation>',
1654
    'block' => '0',
1655
    'allow_draft' => '0',
1656
    'auto_save' => '0',
1657
    'confidential' => '0',
1658
    'submit_notice' => '1',
1659
    'submit_text' => '',
1660
    'next_serial' => 1,
1661
    'submit_limit' => '-1',
1662
    'submit_interval' => '-1',
1663
    'total_submit_limit' => '-1',
1664
    'total_submit_interval' => '-1',
1665
    'progressbar_page_number' => in_array('progressbar_page_number', $progress_bar_defaults) ? '1' : '0',
1666
    'progressbar_percent' => in_array('progressbar_percent', $progress_bar_defaults) ? '1' : '0',
1667
    'progressbar_bar' => in_array('progressbar_bar', $progress_bar_defaults) ? '1' : '0',
1668
    'progressbar_pagebreak_labels' => in_array('progressbar_pagebreak_labels', $progress_bar_defaults) ? '1' : '0',
1669
    'progressbar_include_confirmation' => in_array('progressbar_include_confirmation', $progress_bar_defaults) ? '1' : '0',
1670
    'progressbar_label_first' => webform_variable_get('webform_progressbar_label_first'),
1671
    'progressbar_label_confirmation' => webform_variable_get('webform_progressbar_label_confirmation'),
1672
    'preview' => 0,
1673
    'preview_next_button_label' => '',
1674
    'preview_prev_button_label' => '',
1675
    'preview_title' => '',
1676
    'preview_message' => '',
1677
    'preview_message_format' => NULL,
1678
    'preview_excluded_components' => array(),
1679
    'status' => '1',
1680
    'record_exists' => FALSE,
1681
    'roles' => array('1', '2'),
1682
    'emails' => array(),
1683
    'components' => array(),
1684
    'conditionals' => array(),
1685
  );
1686
  drupal_alter('webform_node_defaults', $defaults);
1687
  return $defaults;
1688
}
1689

    
1690
/**
1691
 * Implements hook_node_prepare().
1692
 */
1693
function webform_node_prepare($node) {
1694
  if (variable_get('webform_node_' . $node->type, FALSE) && !isset($node->webform)) {
1695
    $node->webform = webform_node_defaults();
1696
  }
1697
}
1698

    
1699

    
1700
/**
1701
 * Implements hook_node_load().
1702
 */
1703
function webform_node_load($nodes, $types) {
1704
  // Quick check to see if we need to do anything at all for these nodes.
1705
  $webform_types = webform_node_types();
1706
  if (count(array_intersect($types, $webform_types)) == 0) {
1707
    return;
1708
  }
1709

    
1710
  module_load_include('inc', 'webform', 'includes/webform.components');
1711

    
1712
  // Select all webforms that match these node IDs.
1713
  $result = db_select('webform')
1714
    ->fields('webform')
1715
    ->condition('nid', array_keys($nodes), 'IN')
1716
    ->execute()
1717
    ->fetchAllAssoc('nid', PDO::FETCH_ASSOC);
1718

    
1719
  foreach ($result as $nid => $webform) {
1720
    // Load the basic information for each node.
1721
    $nodes[$nid]->webform = $webform;
1722
    $nodes[$nid]->webform['record_exists'] = TRUE;
1723

    
1724
    // Expand the list of excluded preview components.
1725
    $nodes[$nid]->webform['preview_excluded_components'] = array_filter(explode(',', $webform['preview_excluded_components']));
1726
  }
1727

    
1728
  // Load the components, emails, and defaults for all webform-enabled nodes.
1729
  // TODO: Increase efficiency here by pulling in all information all at once
1730
  // instead of individual queries.
1731
  foreach ($nodes as $nid => $node) {
1732
    if (!in_array($node->type, $webform_types)) {
1733
      continue;
1734
    }
1735

    
1736
    // If a webform record doesn't exist, just return the defaults.
1737
    if (!isset($nodes[$nid]->webform)) {
1738
      $nodes[$nid]->webform = webform_node_defaults();
1739
      continue;
1740
    }
1741

    
1742
    $nodes[$nid]->webform['roles'] = db_select('webform_roles')
1743
      ->fields('webform_roles', array('rid'))
1744
      ->condition('nid', $nid)
1745
      ->execute()
1746
      ->fetchCol();
1747
    $nodes[$nid]->webform['emails'] = db_select('webform_emails')
1748
      ->fields('webform_emails')
1749
      ->condition('nid', $nid)
1750
      ->execute()
1751
      ->fetchAllAssoc('eid', PDO::FETCH_ASSOC);
1752

    
1753
    // Unserialize the mappings and excluded component list for e-mails.
1754
    foreach ($nodes[$nid]->webform['emails'] as $eid => $email) {
1755
      $nodes[$nid]->webform['emails'][$eid]['excluded_components'] = array_filter(explode(',', $email['excluded_components']));
1756
      $nodes[$nid]->webform['emails'][$eid]['extra'] = unserialize($email['extra']);
1757
      if (webform_variable_get('webform_format_override')) {
1758
        $nodes[$nid]->webform['emails'][$eid]['html'] = webform_variable_get('webform_default_format');
1759
      }
1760
    }
1761

    
1762
    // Load components for each node.
1763
    $nodes[$nid]->webform['components'] = db_select('webform_component')
1764
      ->fields('webform_component')
1765
      ->condition('nid', $nid)
1766
      ->orderBy('weight')
1767
      ->orderBy('name')
1768
      ->execute()
1769
      ->fetchAllAssoc('cid', PDO::FETCH_ASSOC);
1770

    
1771
    // Do a little cleanup on each component.
1772
    foreach ($nodes[$nid]->webform['components'] as $cid => $component) {
1773
      $nodes[$nid]->webform['components'][$cid]['nid'] = $nid;
1774
      $nodes[$nid]->webform['components'][$cid]['extra'] = unserialize($component['extra']);
1775
      webform_component_defaults($nodes[$nid]->webform['components'][$cid]);
1776
    }
1777

    
1778
    // Organize the components into a fieldset-based order.
1779
    if (!empty($nodes[$nid]->webform['components'])) {
1780
      $component_tree = array();
1781
      $page_count = 1;
1782
      _webform_components_tree_build($nodes[$nid]->webform['components'], $component_tree, 0, $page_count);
1783
      $nodes[$nid]->webform['components'] = _webform_components_tree_flatten($component_tree['children']);
1784
    }
1785

    
1786
    // Load all the conditional information, if any.
1787
    $nodes[$nid]->webform['conditionals'] = db_select('webform_conditional')
1788
      ->fields('webform_conditional')
1789
      ->condition('nid', $nid)
1790
      ->orderBy('weight')
1791
      ->execute()
1792
      ->fetchAllAssoc('rgid', PDO::FETCH_ASSOC);
1793
    if ($nodes[$nid]->webform['conditionals']) {
1794
      $rules = db_select('webform_conditional_rules')
1795
        ->fields('webform_conditional_rules')
1796
        ->condition('nid', $nid)
1797
        ->orderBy('rgid')
1798
        ->orderBy('rid')
1799
        ->execute();
1800
      foreach ($rules as $rule) {
1801
        $nodes[$nid]->webform['conditionals'][$rule->rgid]['rules'][$rule->rid] = (array) $rule;
1802
      }
1803
      $actions = db_select('webform_conditional_actions')
1804
        ->fields('webform_conditional_actions')
1805
        ->condition('nid', $nid)
1806
        ->orderBy('rgid')
1807
        ->orderBy('aid')
1808
        ->execute();
1809
      foreach ($actions as $action) {
1810
        $nodes[$nid]->webform['conditionals'][$action->rgid]['actions'][$action->aid] = (array) $action;
1811
      }
1812
    }
1813
  }
1814

    
1815
}
1816

    
1817
/**
1818
* Implements hook_user_role_delete().
1819
*
1820
* Removes references to deleted role from existing webforms.
1821
*/
1822
function webform_user_role_delete($role) {
1823
  db_delete('webform_roles')->condition('rid', $role->rid)->execute();
1824
}
1825

    
1826
/**
1827
 * Implements hook_form_alter().
1828
 */
1829
function webform_form_alter(&$form, $form_state, $form_id) {
1830
  $matches = array();
1831
  if (isset($form['#node']->type) && $form_id == $form['#node']->type . '_node_form' && variable_get('webform_node_' . $form['#node']->type, FALSE)) {
1832
    $node = $form['#node'];
1833
    // Preserve all Webform options currently set on the node.
1834
    $form['webform'] = array(
1835
      '#type' => 'value',
1836
      '#value' => $node->webform,
1837
    );
1838

    
1839
    // If a new node, redirect the user to the components form after save.
1840
    if (empty($node->nid) && in_array($node->type, webform_variable_get('webform_node_types_primary'))) {
1841
      $form['actions']['submit']['#submit'][] = 'webform_form_submit';
1842
    }
1843
  }
1844
}
1845

    
1846
/**
1847
 * Implements hook_form_BASE_FORM_ID_alter().
1848
 */
1849
function webform_form_node_type_form_alter(&$form, $form_state) {
1850
  if (isset($form['type'])) {
1851
    $form['webform'] = array(
1852
      '#title' => t('Webform'),
1853
      '#type' => 'fieldset',
1854
      '#collapsible' => TRUE,
1855
      '#collapsed' => TRUE,
1856
      '#group' => 'additional_settings',
1857
      '#weight' => 10,
1858
      '#attached' => array(
1859
        'js' => array(drupal_get_path('module', 'webform') . '/js/node-type-form.js'),
1860
      ),
1861
    );
1862
    $form['webform']['webform_node'] = array(
1863
      '#type' => 'checkbox',
1864
      '#title' => t('Enable webform functionality'),
1865
      '#description' => t('Allows a form to be attached to content. This will add tabs for "Webform" and "Results" on all content of this type.'),
1866
      '#weight' => 0,
1867
      '#default_value' => variable_get('webform_node_' . $form['#node_type']->type, FALSE),
1868
      '#attributes' => array(
1869
        'data-enabled-description' => t('Enabled'),
1870
        'data-disabled-description' => t('Disabled'),
1871
      ),
1872
    );
1873
  }
1874
}
1875

    
1876
/**
1877
 * Submit handler for the webform node form.
1878
 *
1879
 * Redirect the user to the components form on new node inserts. Note that this
1880
 * fires after the hook_submit() function above.
1881
 */
1882
function webform_form_submit($form, &$form_state) {
1883
  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'])));
1884
  $form_state['redirect'] = 'node/' . $form_state['nid'] . '/webform/components';
1885
}
1886

    
1887
/**
1888
 * Implements hook_node_view().
1889
 */
1890
function webform_node_view($node, $view_mode) {
1891
  global $user;
1892

    
1893
  if (!variable_get('webform_node_' . $node->type, FALSE)) {
1894
    return;
1895
  }
1896

    
1897
  // If empty or a new node (during preview) do not display.
1898
  if (empty($node->webform['components']) || empty($node->nid)) {
1899
    return;
1900
  }
1901

    
1902
  // If the webform is not set to display in this view mode, return early.
1903
  // View mode of 'form' is exempted to allow blocks and views to force display.
1904
  $extra_fields = field_extra_fields_get_display('node', $node->type, $view_mode);
1905
  if ($view_mode != 'form' && empty($extra_fields['webform']['visible'])) {
1906
    return;
1907
  }
1908

    
1909
  $submission = FALSE;
1910
  $submission_count = 0;
1911
  $page = node_is_page($node);
1912
  $logging_in = FALSE;
1913
  $total_limit_exceeded = FALSE;
1914
  $user_limit_exceeded = FALSE;
1915
  $closed = FALSE;
1916

    
1917
  // If a teaser, tell the form to load subsequent pages on the node page. A
1918
  // special exception is made for this view mode only.
1919
  if ($view_mode == 'teaser' && !isset($node->webform['action'])) {
1920
    $query = array_diff_key($_GET, array('q' => ''));
1921
    $node->webform['action'] = url('node/' . $node->nid, array('query' => $query));
1922
  }
1923

    
1924
  // When logging in using a form on the same page as a webform node, suppress
1925
  // output messages so that they don't show up after the user has logged in.
1926
  // See http://drupal.org/node/239343.
1927
  if (isset($_POST['op']) && isset($_POST['name']) && isset($_POST['pass'])) {
1928
    $logging_in = TRUE;
1929
  }
1930

    
1931
  if ($node->webform['status'] == 0) {
1932
    $closed = TRUE;
1933
    $enabled = FALSE;
1934
    $allowed_roles = array();
1935
  }
1936
  else {
1937
    $allowed_roles = _webform_allowed_roles($node, $enabled); // $enabled set by reference.
1938
  }
1939

    
1940
  // Get a count of previous submissions by this user. Note that the
1941
  // webform_submission_access() function may disable the page cache for
1942
  // anonymous users if they are allowed to edit their own submissions!
1943
  if ($page && webform_submission_access($node, NULL, 'list')) {
1944
    module_load_include('inc', 'webform', 'includes/webform.submissions');
1945
    $submission_count = webform_get_submission_count($node->nid, $user->uid);
1946
  }
1947

    
1948
  // Check if this page is cached or not.
1949
  $cached = drupal_page_is_cacheable();
1950

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

    
1955
    // Disable the form if the limit is exceeded and page cache is not active. This prevent
1956
    // One anonymous user from generated a disabled webform page for the cache, which would
1957
    // be shown to other anonymous users who have not exceeded the limit.
1958
    if (($user_limit_exceeded = webform_submission_user_limit_check($node)) && !$cached) {
1959
      $enabled = FALSE;
1960
    }
1961
  }
1962

    
1963
  // Check if the user can add another submission if there is a limit on total
1964
  // submissions.
1965
  if ($node->webform['total_submit_limit'] != -1) { // -1: Submissions are never throttled.
1966
    module_load_include('inc', 'webform', 'includes/webform.submissions');
1967

    
1968
    // Disable the form if the limit is exceeded. The cache is irrelevant for the total
1969
    // submission limit; when it is exceeded for one user, it is exceeded for any other
1970
    // user.
1971
    if (($total_limit_exceeded = webform_submission_total_limit_check($node))) {
1972
      $enabled = FALSE;
1973
    }
1974
  }
1975

    
1976
  // Check if this user has a draft for this webform.
1977
  $resume_draft = FALSE;
1978
  if (($node->webform['allow_draft'] || $node->webform['auto_save']) && $user->uid != 0) {
1979
    // Draft found - display form with draft data for further editing.
1980
    if ($draft_sid = _webform_fetch_draft_sid($node->nid, $user->uid)) {
1981
      module_load_include('inc', 'webform', 'includes/webform.submissions');
1982
      $submission = webform_get_submission($node->nid, $draft_sid);
1983
      $enabled = TRUE;
1984
      $resume_draft = TRUE;
1985
    }
1986
  }
1987

    
1988
  // Avoid building the same form twice on the same page request (which can
1989
  // happen if the webform is displayed in a panel or block) because this
1990
  // causes multistep forms to build incorrectly the second time.
1991
  $cached_forms = &drupal_static(__FUNCTION__, array());
1992
  if (isset($cached_forms[$node->nid])) {
1993
    $form = $cached_forms[$node->nid];
1994
  }
1995
  // If this is the first time, generate the form array.
1996
  else {
1997
    $form = drupal_get_form('webform_client_form_' . $node->nid, $node, $submission, $resume_draft);
1998
    $cached_forms[$node->nid] = $form;
1999
  }
2000

    
2001
  // Remove the surrounding <form> tag if this is a preview.
2002
  if (!empty($node->in_preview)) {
2003
    $form['#type'] = 'markup';
2004
  }
2005

    
2006
  // Print out messages for the webform.
2007
  if (empty($node->in_preview) && !isset($node->webform_block) && !$logging_in) {
2008
    theme('webform_view_messages', array('node' => $node, 'page' => $page, 'submission_count' => $submission_count, 'user_limit_exceeded' => $user_limit_exceeded, 'total_limit_exceeded' => $total_limit_exceeded, 'allowed_roles' => $allowed_roles, 'closed' => $closed, 'cached' => $cached));
2009
  }
2010

    
2011
  // Add the output to the node.
2012
  $node->content['webform'] = array(
2013
    '#theme' => 'webform_view',
2014
    '#node' => $node,
2015
    '#page' => $page,
2016
    '#form' => $form,
2017
    '#enabled' => $enabled,
2018
    '#visible' => $extra_fields['webform']['visible'],
2019
    '#weight' => 10,
2020
  );
2021
}
2022

    
2023
/**
2024
 * Helper. Generates an array of allowed roles.
2025
 *
2026
 * @param object $node
2027
 *   The loaded node object containing a webform.
2028
 * @param boolean $user_is_allowed
2029
 *   Reference to boolean to be set to whether the current user is allowed.
2030
 * @return array
2031
 *   Associative array of allowed roles indexed by the role id with a boolean
2032
 *   value indicating if the current user has this role.
2033
 */
2034
function _webform_allowed_roles($node, &$user_is_allowed) {
2035
  global $user;
2036
  if ($node->webform['confidential']) {
2037
    // Confidential webform may only be submitted anonymously, including uid 1.
2038
    $user_is_allowed = user_is_anonymous();
2039
    $allowed_roles = array(DRUPAL_ANONYMOUS_RID => $user_is_allowed);
2040
  }
2041
  elseif (webform_variable_get('webform_submission_access_control')) {
2042
    // Check if the user's role can submit this webform.
2043
    $allowed_roles = array();
2044
    foreach ($node->webform['roles'] as $rid) {
2045
      $allowed_roles[$rid] = isset($user->roles[$rid]);
2046
    }
2047
    $user_is_allowed = $user->uid == 1 || array_search(TRUE, $allowed_roles);
2048
  }
2049
  else {
2050
    // If not using Webform submission access control, allow all roles.
2051
    $user_is_allowed = TRUE;
2052
    $allowed_roles = array_fill_keys(array_keys(user_roles()), TRUE);
2053
  }
2054
  return $allowed_roles;
2055
}
2056

    
2057
/**
2058
 * Output the Webform into the node content.
2059
 *
2060
 * @param $node
2061
 *   The webform node object.
2062
 * @param $page
2063
 *   If this webform node is being viewed as the main content of the page.
2064
 * @param $form
2065
 *   The rendered form.
2066
 * @param $enabled
2067
 *   If the form allowed to be completed by the current user.
2068
 */
2069
function theme_webform_view($variables) {
2070
  // Only show the form if this user is allowed access.
2071
  if ($variables['webform']['#enabled']) {
2072
    return drupal_render($variables['webform']['#form']);
2073
  }
2074
}
2075

    
2076
/**
2077
 * Display a message to a user if they are not allowed to fill out a form.
2078
 *
2079
 * @param $node
2080
 *   The webform node object.
2081
 * @param $page
2082
 *   If this webform node is being viewed as the main content of the page.
2083
 * @param $submission_count
2084
 *   The number of submissions this user has already submitted. Not calculated
2085
 *   for anonymous users.
2086
 * @param $user_limit_exceeded
2087
 *   Boolean value if the submission limit for this user has been exceeded.
2088
 * @param $total_limit_exceeded
2089
 *   Boolean value if the total submission limit has been exceeded.
2090
 * @param $allowed_roles
2091
 *   A list of user roles that are allowed to submit this webform.
2092
 * @param $closed
2093
 *   Boolean value if submissions are closed.
2094
 */
2095
function theme_webform_view_messages($variables) {
2096
  global $user;
2097

    
2098
  $node = $variables['node'];
2099
  $page = $variables['page'];
2100
  $submission_count = $variables['submission_count'];
2101
  $user_limit_exceeded = $variables['user_limit_exceeded'];
2102
  $total_limit_exceeded = $variables['total_limit_exceeded'];
2103
  $allowed_roles = $variables['allowed_roles'];
2104
  $closed = $variables['closed'];
2105
  $cached = $variables['cached'];
2106

    
2107
  $type = 'warning';
2108

    
2109
  if ($closed) {
2110
    $message = t('Submissions for this form are closed.');
2111
  }
2112
  elseif ($node->webform['confidential'] && user_is_logged_in()) {
2113
    $message = t('This form is confidential. You must <a href="!url">Log out</a> to submit it.',
2114
                 array('!url' => url('/user/logout', array('query' => array('destination' => request_uri())))));
2115
  }
2116
  // If open and not allowed to submit the form, give an explanation.
2117
  elseif (array_search(TRUE, $allowed_roles) === FALSE && $user->uid != 1) {
2118
    if (empty($allowed_roles)) {
2119
      // No roles are allowed to submit the form.
2120
      $message = t('Submissions for this form are closed.');
2121
    }
2122
    elseif ($user->uid == 0) {
2123
      // The user is anonymous, so (at least) needs to log in to view the form.
2124
      $login = url('user/login', array('query' => drupal_get_destination()));
2125
      $register = url('user/register', array('query' => drupal_get_destination()));
2126
      if (variable_get('user_register', 1) == 0) {
2127
        $message = t('You must <a href="!login">login</a> to view this form.', array('!login' => $login));
2128
      }
2129
      else {
2130
        $message = t('You must <a href="!login">login</a> or <a href="!register">register</a> to view this form.', array('!login' => $login, '!register' => $register));
2131
      }
2132
    }
2133
    else {
2134
      // The user must be some other role to submit.
2135
      $message = t('You do not have permission to view this form.');
2136
      $type = 'error';
2137
    }
2138
  }
2139

    
2140
  // If the user has exceeded the limit of submissions, explain the limit.
2141
  elseif ($user_limit_exceeded && !$cached) {
2142
    if ($node->webform['submit_interval'] == -1 && $node->webform['submit_limit'] > 1) {
2143
      $message = t('You have submitted this form the maximum number of times (@count).', array('@count' => $node->webform['submit_limit']));
2144
    }
2145
    elseif ($node->webform['submit_interval'] == -1 && $node->webform['submit_limit'] == 1) {
2146
      $message = t('You have already submitted this form.');
2147
    }
2148
    else {
2149
      $message = t('You may not submit another entry at this time.');
2150
    }
2151
  }
2152
  elseif ($total_limit_exceeded && !$cached) {
2153
    if ($node->webform['total_submit_interval'] == -1 && $node->webform['total_submit_limit'] > 1) {
2154
      $message = t('This form has received the maximum number of entries.');
2155
    }
2156
    else {
2157
      $message = t('You may not submit another entry at this time.');
2158
    }
2159
  }
2160

    
2161
  // If the user has submitted before, give them a link to their submissions.
2162
  if ($submission_count > 0 && $node->webform['submit_notice'] == 1 && !$cached) {
2163
    if (empty($message)) {
2164
      $message = t('You have already submitted this form.');
2165
      $type = 'status';
2166
    }
2167
    $message .= ' ' . t('<a href="!url">View your previous submissions</a>.', array('!url' => url('node/' . $node->nid . '/submissions')));
2168
  }
2169

    
2170
  if ($page && isset($message)) {
2171
    drupal_set_message($message, $type, FALSE);
2172
  }
2173
}
2174

    
2175
/**
2176
 * Implements hook_mail().
2177
 */
2178
function webform_mail($key, &$message, $params) {
2179
  $message['headers'] = array_merge($message['headers'], $params['headers']);
2180
  $message['subject'] = $params['subject'];
2181
  $message['body'][] = $params['message'];
2182
}
2183

    
2184
/**
2185
 * Implements hook_block_info().
2186
 */
2187
function webform_block_info() {
2188
  $blocks = array();
2189
  $webform_node_types = webform_node_types();
2190
  if (!empty($webform_node_types)) {
2191
    $query = db_select('webform', 'w')->fields('w')->fields('n', array('title'));
2192
    $query->leftJoin('node', 'n', 'w.nid = n.nid');
2193
    $query->condition('w.block', 1);
2194
    $query->condition('n.type', $webform_node_types, 'IN');
2195
    $result = $query->execute();
2196
    foreach ($result as $data) {
2197
      $blocks['client-block-' . $data->nid] = array(
2198
        'info' => t('Webform: !title', array('!title' => $data->title)),
2199
        'cache' => DRUPAL_NO_CACHE,
2200
      );
2201
    }
2202
  }
2203
  return $blocks;
2204
}
2205

    
2206
/**
2207
 * Implements hook_block_view().
2208
 */
2209
function webform_block_view($delta = '') {
2210
  global $user;
2211

    
2212
  // Load the block-specific configuration settings.
2213
  $webform_blocks = webform_variable_get('webform_blocks');
2214
  $settings = isset($webform_blocks[$delta]) ? $webform_blocks[$delta] : array();
2215
  $settings += array(
2216
    'display' => 'form',
2217
    'pages_block' => 1,
2218
    'confirmation_block' => 0,
2219
  );
2220

    
2221
  // Get the node ID from delta.
2222
  $nid = drupal_substr($delta, strrpos($delta, '-') + 1);
2223

    
2224
  // Load node in current language.
2225
  if (module_exists('translation')) {
2226
    global $language;
2227
    if (($translations = translation_node_get_translations($nid)) && (isset($translations[$language->language]))) {
2228
      $nid = $translations[$language->language]->nid;
2229
    }
2230
  }
2231

    
2232
  // The webform node to display in the block.
2233
  $node = node_load($nid);
2234

    
2235
  // Return if user has no access to the webform node.
2236
  if (!node_access('view', $node)) {
2237
    return;
2238
  }
2239

    
2240
  // This is a webform node block.
2241
  $node->webform_block = TRUE;
2242
  $node->webform['confirmation_block'] = $settings['confirmation_block'];
2243

    
2244
  // If not displaying pages in the block, set the #action property on the form.
2245
  if ($settings['pages_block']) {
2246
    $node->webform['action'] = FALSE;
2247
  }
2248
  else {
2249
    $query = array_diff_key($_GET, array('q' => ''));
2250
    $node->webform['action'] = url('node/' . $node->nid, array('query' => $query));
2251
  }
2252

    
2253
  // Generate the content of the block based on display settings.
2254
  $content = array();
2255
  if ($settings['display'] == 'form') {
2256
    webform_node_view($node, 'form');
2257
    if (isset($node->content['webform'])) {
2258
      $content = $node->content['webform'];
2259
      if (!$node->content['webform']['#visible']) {
2260
        // If the webform form is only shown in a block and not as within the
2261
        // node, remove the content from the node.
2262
        unset($node->content['webform']);
2263
      }
2264
    }
2265
  }
2266
  else {
2267
    $content = node_view($node, $settings['display']);
2268
  }
2269

    
2270
  // Check for an in-block confirmation message.
2271
  if (isset($_SESSION['webform_confirmation'][$nid])) {
2272
    if ($_SESSION['webform_confirmation'][$nid]['confirmation_page']) {
2273
      // Replace form with confirmation page.
2274
      $content = array(
2275
        '#theme' => array('webform_confirmation_' . $node->nid, 'webform_confirmation'),
2276
        '#node' => $node,
2277
        '#sid' => $_SESSION['webform_confirmation'][$nid]['sid'],
2278
      );
2279
    } 
2280
    elseif (strlen(trim(strip_tags($node->webform['confirmation'])))) {
2281
      // Display confirmation link drupal status messages, but in the block.
2282
      $message = webform_replace_tokens($node->webform['confirmation'],
2283
                                        $node,
2284
                                        webform_get_submission($nid, $_SESSION['webform_confirmation'][$nid]['confirmation_page']),
2285
                                        NULL,
2286
                                        $node->webform['confirmation_format']);
2287
      $content = array(
2288
        'confirmation_message' => array(
2289
          '#markup' => "<div class=\"messages status webform-confirmation\">\n" .
2290
                       '<h2 class="element-invisible">' . t('Status message') . "</h2>\n" .
2291
                       $message .
2292
                       "</div>\n",
2293
          '#weight' => -1,
2294
        ),
2295
        'webform_view' => $content,
2296
      );
2297

    
2298
    }
2299
    unset($_SESSION['webform_confirmation'][$nid]);
2300
    if (empty($_SESSION['webform_confirmation'])) {
2301
      unset($_SESSION['webform_confirmation']);
2302
    }
2303
  }
2304

    
2305
  // Add contextual links for the webform node if they aren't already there.
2306
  if (!isset($content['#contextual_links']['node'])) {
2307
    $content['#contextual_links']['node'] = array('node', array($node->nid));
2308
  }
2309

    
2310
  // Create the block, using the node title for the block title.
2311
  // Note that we render the content immediately here rather than passing back
2312
  // a renderable so that if the block is empty it is hidden.
2313
  $block = array(
2314
    'subject' => check_plain($node->title),
2315
    'content' => drupal_render($content),
2316
  );
2317
  return $block;
2318
}
2319

    
2320
/**
2321
 * Implements hook_block_configure().
2322
 */
2323
function webform_block_configure($delta = '') {
2324
  $nid = str_replace('client-block-', '', $delta);
2325
  $node = node_load($nid);
2326

    
2327
  // Load the block-specific configuration settings.
2328
  $webform_blocks = webform_variable_get('webform_blocks');
2329
  $settings = isset($webform_blocks[$delta]) ? $webform_blocks[$delta] : array();
2330
  $settings += array(
2331
    'display' => 'form',
2332
    'pages_block' => 1,
2333
    'confirmation_block' => 0,
2334
  );
2335

    
2336
  // Build a list of view modes for this node.
2337
  $entity_info = entity_get_info('node');
2338
  $view_modes = array(
2339
    'form' => t('Form only'),
2340
  );
2341
  foreach ($entity_info['view modes'] as $view_mode_key => $view_mode_info) {
2342
    $view_modes[$view_mode_key] = $view_mode_info['label'];
2343
  }
2344

    
2345
  $form = array();
2346
  $form['display'] = array(
2347
    '#type' => 'select',
2348
    '#title' => t('View mode'),
2349
    '#default_value' => $settings['display'],
2350
    '#options' => $view_modes,
2351
    '#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'))),
2352
  );
2353

    
2354
  $form['pages_block'] = array(
2355
    '#type' => 'radios',
2356
    '#title' => t('Multi-page handling'),
2357
    '#options' => array(
2358
      1 => t('Display all pages inside block'),
2359
      0 => t('Redirect to the node page after the first page'),
2360
    ),
2361
    '#default_value' => $settings['pages_block'],
2362
    '#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.'),
2363
  );
2364

    
2365
  $form['confirmation_block'] = array(
2366
    '#type' => 'radios',
2367
    '#title' => t('Confirmation message'),
2368
    '#options' => array(
2369
      0 => t('Display as configured in the webform'),
2370
      1 => t('Display the confirmation page in the block on the same page (no redirect)'),
2371
    ),
2372
    '#default_value' => $settings['confirmation_block'],
2373
    '#description' => t('This setting overrides the webform\'s configuration and redirection location settings when the webform is submitted via this block.'),
2374
  );
2375
  return $form;
2376
}
2377

    
2378
/**
2379
 * Implements hook_block_save().
2380
 */
2381
function webform_block_save($delta = '', $edit = array()) {
2382
  // Load the previously defined block-specific configuration settings.
2383
  $settings = webform_variable_get('webform_blocks');
2384
  // Build the settings array.
2385
  $new_settings[$delta] = array(
2386
    'display' => $edit['display'],
2387
    'pages_block' => $edit['pages_block'],
2388
    'confirmation_block' => $edit['confirmation_block'],
2389
  );
2390
  // We store settings for multiple blocks in just one variable
2391
  // so we merge the existing settings with the new ones before save.
2392
  variable_set('webform_blocks', array_merge($settings, $new_settings));
2393
}
2394

    
2395
/**
2396
 * Client form generation function. If this is displaying an existing
2397
 * submission, pass in the $submission variable with the contents of the
2398
 * submission to be displayed.
2399
 *
2400
 * @param $form
2401
 *   The current form array (always empty).
2402
 * @param $form_state
2403
 *   The current form values of a submission, used in multipage webforms.
2404
 * @param $node
2405
 *   The current webform node.
2406
 * @param $submission
2407
 *   An object containing information about the form submission if we're
2408
 *   displaying a result.
2409
 * @param $resume_draft
2410
 *   Optional. Set to TRUE when resuming a draft and skipping past previously-
2411
 *   validated pages is desired.
2412
 * @param $filter
2413
 *   Whether or not to filter the contents of descriptions and values when
2414
 *   building the form. Values need to be unfiltered to be editable by
2415
 *   Form Builder.
2416
 */
2417
function webform_client_form($form, &$form_state, $node, $submission = FALSE, $resume_draft = FALSE, $filter = TRUE) {
2418
  global $user;
2419

    
2420
  // Attach necessary JavaScript and CSS.
2421
  $form['#attached'] = array(
2422
    'css' => array(drupal_get_path('module', 'webform') . '/css/webform.css'),
2423
    'js' => array(drupal_get_path('module', 'webform') . '/js/webform.js'),
2424
  );
2425
  form_load_include($form_state, 'inc', 'webform', 'includes/webform.components');
2426
  form_load_include($form_state, 'inc', 'webform', 'includes/webform.submissions');
2427

    
2428
  // For ajax requests, $form_state['values']['details'] is missing. Restore
2429
  // from storage, if available, for multi-page forms.
2430
  if (empty($form_state['values']['details']) && !empty($form_state['storage']['details'])) {
2431
    $form_state['values']['details'] = $form_state['storage']['details'];
2432
  }
2433

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

    
2440
  $finished = isset($submission->is_draft) ? (!$submission->is_draft) : 0;
2441
  $submit_button_text = $finished
2442
                          ? t('Save')
2443
                          : (empty($node->webform['submit_text']) ? t('Submit') : t($node->webform['submit_text']));
2444

    
2445
  // Bind arguments to $form to make them available in theming and form_alter.
2446
  $form['#node'] = $node;
2447
  $form['#submission'] = $submission;
2448
  $form['#is_draft'] = $submission && $submission->is_draft;
2449
  $form['#filter'] = $filter;
2450

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

    
2454
  // Add a CSS class for all client forms.
2455
  $form['#attributes']['class'][] = 'webform-client-form';
2456
  $form['#attributes']['class'][] = 'webform-client-form-' . $node->nid;
2457

    
2458
  // Sometimes when displaying a webform as a teaser or block, a custom action
2459
  // property is set to direct the user to the node page.
2460
  if (!empty($node->webform['action'])) {
2461
    $form['#action'] = $node->webform['action'];
2462
  }
2463

    
2464
  $form['#submit'] = array('webform_client_form_pages', 'webform_client_form_submit');
2465
  $form['#validate'] = array('webform_client_form_validate');
2466
  // Add includes for used component types and pre/post validation handlers
2467
  $form['#process'] = array('webform_client_form_process');
2468

    
2469
  if (is_array($node->webform['components']) && !empty($node->webform['components'])) {
2470
    // Prepare a new form array.
2471
    $form['submitted'] = array(
2472
      '#tree' => TRUE
2473
    );
2474
    $form['details'] = array(
2475
      '#tree' => TRUE,
2476
    );
2477

    
2478
    // Put the components into a tree structure.
2479
    if (!isset($form_state['storage']['component_tree'])) {
2480
      $form_state['webform']['component_tree'] = array();
2481
      $form_state['webform']['page_count'] = 1;
2482
      $form_state['webform']['page_num'] = 1;
2483
      _webform_components_tree_build($node->webform['components'], $form_state['webform']['component_tree'], 0, $form_state['webform']['page_count']);
2484

    
2485
      // If preview is enabled, increase the page count by one.
2486
      if ($node->webform['preview']) {
2487
        $form_state['webform']['page_count']++;
2488
      }
2489
      $form_state['webform']['preview'] = $node->webform['preview'];
2490

    
2491
      // If this is the first time this draft has been restore and presented to
2492
      // the user, let them know that they are looking at a draft, rather than
2493
      // a new form. This applies to the node view page, but not to a submission
2494
      // edit page (where they presummably know what submission they are
2495
      // editing).
2496
      if ($resume_draft && empty($form_state['input'])) {
2497
        drupal_set_message(t('A partially-completed form was found. Please complete the remaining portions.'));
2498
      }
2499
    }
2500
    else {
2501
      $form_state['webform']['component_tree'] = $form_state['storage']['component_tree'];
2502
      $form_state['webform']['page_count'] = $form_state['storage']['page_count'];
2503
      $form_state['webform']['page_num'] = $form_state['storage']['page_num'];
2504
      $form_state['webform']['preview'] = $form_state['storage']['preview'];
2505
    }
2506

    
2507
    // Set the input values based on whether we're editing an existing
2508
    // submission or not.
2509
    $input_values = isset($submission->data) ? $submission->data : array();
2510

    
2511
    // Form state storage override any default submission information. Convert
2512
    // the value structure to always be an array, matching $submission->data.
2513
    if (isset($form_state['storage']['submitted'])) {
2514
      foreach ($form_state['storage']['submitted'] as $cid => $data) {
2515
        $input_values[$cid] = is_array($data) ? $data : array($data);
2516
      }
2517
    }
2518

    
2519
    // Form state values override any default submission information. Convert
2520
    // the value structure to always be an array, matching $submission->data.
2521
    if (isset($form_state['values']['submitted'])) {
2522
      foreach ($form_state['values']['submitted'] as $cid => $data) {
2523
        $input_values[$cid] = is_array($data) ? $data : array($data);
2524
      }
2525
    }
2526

    
2527
    // Generate conditional topological order & report any errors.
2528
    $sorter = webform_get_conditional_sorter($node);
2529
    $sorter->reportErrors();
2530

    
2531
    // Excecute the condtionals on the current input values
2532
    $input_values = $sorter->executeConditionals($input_values);
2533

    
2534
    // Allow values from other pages to be sent to browser for conditionals.
2535
    $form['#conditional_values'] = $input_values;
2536

    
2537
    // Allow components access to most up-to-date values.
2538
    $form_state['#conditional_values'] = $input_values;
2539

    
2540
    // For resuming a previous draft, find the next page after the last
2541
    // validated page.
2542
    if (!isset($form_state['storage']['page_num']) && $submission && $submission->is_draft && $submission->highest_valid_page) {
2543
      // Find the
2544
      //    1) previous/next non-empty page, or
2545
      //    2) the preview page, or
2546
      //    3) the preview page, forcing its display if the form would unexpectedly submit, or
2547
      //    4) page 1 even if empty, if no other previous page would be shown
2548
      $form_state['webform']['page_num'] = $submission->highest_valid_page;
2549
      do {
2550
        $form_state['webform']['page_num']++;
2551
      } while (!webform_get_conditional_sorter($node)->pageVisibility($form_state['webform']['page_num']));
2552
      if (!$form_state['webform']['preview'] && $form_state['webform']['page_num'] == $form_state['webform']['page_count'] + (int)!$form_state['webform']['preview']) {
2553
        // Force a preview to avert an unintended submission via Next.
2554
        $form_state['webform']['preview'] = TRUE;
2555
        $form_state['webform']['page_count']++;
2556
      }
2557
      // The form hasn't been submitted (ever) and the preview code will
2558
      // expect $form_state['values']['submitted'] to be set from a previous
2559
      // submission, so provide these values here.
2560
      $form_state['values']['submitted'] = $input_values;
2561
      $form_state['storage']['submitted'] = $input_values;
2562
    }
2563

    
2564
    // Shorten up our variable names.
2565
    $component_tree = $form_state['webform']['component_tree'];
2566
    $page_count = $form_state['webform']['page_count'];
2567
    $page_num = $form_state['webform']['page_num'];
2568
    $preview = $form_state['webform']['preview'];
2569

    
2570
    if ($page_count > 1) {
2571
      $page_labels = webform_page_labels($node, $form_state);
2572
      $form['progressbar'] = array(
2573
        '#theme' => 'webform_progressbar',
2574
        '#node' => $node,
2575
        '#page_num' => $page_num,
2576
        '#page_count' => count($page_labels),
2577
        '#page_labels' => $page_labels,
2578
        '#weight' => -100
2579
      );
2580
    }
2581

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

    
2587
    // Recursively add components to the form. The unfiltered version of the
2588
    // form (typically used in Form Builder), includes all components.
2589
    foreach ($component_tree['children'] as $cid => $component) {
2590
      if ($component['type'] == 'pagebreak') {
2591
        $next_page_labels[$component['page_num'] - 1] = !empty($component['extra']['next_page_label']) ? t($component['extra']['next_page_label']) : t('Next Page >');
2592
        $prev_page_labels[$component['page_num']] = !empty($component['extra']['prev_page_label']) ? t($component['extra']['prev_page_label']) : t('< Previous Page');
2593
      }
2594
      if (!$filter || $sorter->componentVisibility($cid, $page_num)) {
2595
        $component_value = isset($input_values[$cid]) ? $input_values[$cid] : NULL;
2596
        _webform_client_form_add_component($node, $component, $component_value, $form['submitted'], $form, $input_values, 'form', $page_num, $filter);
2597
      }
2598
    }
2599
    if ($preview) {
2600
      $next_page_labels[$page_count - 1] = $node->webform['preview_next_button_label'] ? t($node->webform['preview_next_button_label']) : t('Preview');
2601
      $prev_page_labels[$page_count] = $node->webform['preview_prev_button_label'] ? t($node->webform['preview_prev_button_label']) : t('< Previous');
2602
    }
2603

    
2604
    // Add the preview if needed.
2605
    if ($preview && $page_num === $page_count) {
2606
      $preview_submission = webform_submission_create($node, $user, $form_state, TRUE, $submission);
2607
      $preview_message = $node->webform['preview_message'];
2608
      if (strlen(trim(strip_tags($preview_message))) === 0) {
2609
        $preview_message = t('Please review your submission. Your submission is not complete until you press the "!button" button!', array('!button' => $submit_button_text));
2610
      }
2611
      $form['preview_message'] = array(
2612
        '#type' => 'markup',
2613
        '#markup' => webform_replace_tokens($preview_message, $node, $preview_submission, NULL, $node->webform['preview_message_format']),
2614
      );
2615

    
2616
      $form['preview'] = webform_submission_render($node, $preview_submission, NULL, 'html', $node->webform['preview_excluded_components']);
2617
      $form['#attributes']['class'][] = 'preview';
2618
    }
2619

    
2620
    // These form details help managing data upon submission.
2621
    $form['details']['nid'] = array(
2622
      '#type' => 'value',
2623
      '#value' => $node->nid,
2624
    );
2625
    $form['details']['sid'] = array(
2626
      '#type' => 'hidden',
2627
      '#value' => isset($submission->sid) ? $submission->sid : NULL,
2628
    );
2629
    $form['details']['uid'] = array(
2630
      '#type' => 'value',
2631
      '#value' => isset($submission->uid) ? $submission->uid : $user->uid,
2632
    );
2633
    $form['details']['page_num'] = array(
2634
      '#type'  => 'hidden',
2635
      '#value' => $page_num,
2636
    );
2637
    $form['details']['page_count'] = array(
2638
      '#type'  => 'hidden',
2639
      '#value' => $page_count,
2640
    );
2641
    $form['details']['finished'] = array(
2642
      '#type' => 'hidden',
2643
      '#value' => $finished,
2644
    );
2645

    
2646
    // Add process functions to remove the IDs forced upon buttons and wrappers.
2647
    $actions_pre_render = array_merge(element_info_property('actions', '#pre_render', array()), array('webform_pre_render_remove_id'));
2648
    $buttons_pre_render = array_merge(element_info_property('submit', '#pre_render', array()), array('webform_pre_render_remove_id'));
2649

    
2650
    // Add buttons for pages, drafts, and submissions.
2651
    $form['actions'] = array(
2652
      '#type' => 'actions',
2653
      '#weight' => 1000,
2654
      '#pre_render' => $actions_pre_render,
2655
    );
2656

    
2657
    // Add the draft button.
2658
    if ($node->webform['allow_draft'] && (empty($submission) || $submission->is_draft) && $user->uid != 0) {
2659
      $form['actions']['draft'] = array(
2660
        '#type' => 'submit',
2661
        '#value' => t('Save Draft'),
2662
        '#weight' => -2,
2663
        '#validate' => array('webform_client_form_prevalidate'),    // Prevalidation only; no element validation for Save Draft
2664
        '#attributes' => array(
2665
          'formnovalidate' => 'formnovalidate',
2666
          'class' => array('webform-draft'),
2667
        ),
2668
        '#pre_render' => $buttons_pre_render,
2669
      );
2670
    }
2671

    
2672
    // Add the submit button(s).
2673
    if ($page_num > 1) {
2674
      $form['actions']['previous'] = array(
2675
        '#type' => 'submit',
2676
        '#value' => $prev_page_labels[$page_num],
2677
        '#weight' => 5,
2678
        '#validate' => array(),
2679
        '#attributes' => array(
2680
          'formnovalidate' => 'formnovalidate',
2681
          'class' => array('webform-previous'),
2682
        ),
2683
        '#pre_render' => $buttons_pre_render,
2684
      );
2685
    }
2686
    if ($page_num == $page_count) {
2687
      $form['actions']['submit'] = array(
2688
        '#type' => 'submit',
2689
        '#value' => $submit_button_text,
2690
        '#weight' => 10,
2691
        '#attributes' => array(
2692
          'class' => array('webform-submit', 'button-primary'),
2693
        ),
2694
        '#pre_render' => $buttons_pre_render,
2695
      );
2696
    }
2697
    elseif ($page_num < $page_count) {
2698
      $form['actions']['next'] = array(
2699
        '#type' => 'submit',
2700
        '#value' => $next_page_labels[$page_num],
2701
        '#weight' => 10,
2702
        '#attributes' => array(
2703
          'class' => array('webform-next', 'button-primary'),
2704
        ),
2705
        '#pre_render' => $buttons_pre_render,
2706
      );
2707
    }
2708
  }
2709

    
2710
  return $form;
2711
}
2712

    
2713
/**
2714
 * Process function for webform_client_form().
2715
 *
2716
 * Include all the enabled components for this form to ensure availability.
2717
 * Also adds the pre- and post-validators to ensure that hook_form_alters don't
2718
 * add their validation functions in the wrong order.
2719
 */
2720
function webform_client_form_process($form, $form_state) {
2721
  $components = webform_components();
2722
  foreach ($components as $component_type => $component) {
2723
    webform_component_include($component_type);
2724
  }
2725

    
2726
  // Add the post validation to end of validators. Do this first on the off
2727
  // chance that an _alter function has unset form['#validate'].
2728
  $form['#validate'][] = 'webform_client_form_postvalidate';
2729
  // Add the pre-validator to the front of the list to run first
2730
  array_unshift($form['#validate'], 'webform_client_form_prevalidate');
2731

    
2732
  return $form;
2733
}
2734

    
2735

    
2736
/**
2737
 * Add a component to a renderable array. Called recursively for fieldsets.
2738
 *
2739
 * This function assists in the building of the client form, as well as the
2740
 * display of results, and the text of e-mails.
2741
 *
2742
 * @param $node
2743
 *   The current webform node.
2744
 * @param $component
2745
 *   The component to be added to the form.
2746
 * @param $component_value
2747
 *   The components current value if known.
2748
 * @param $parent_fieldset
2749
 *   The fieldset to which this element will be added.
2750
 * @param $form
2751
 *   The entire form array.
2752
 * @param $input_values
2753
 *   All the values for this form, keyed by the component IDs. This may be
2754
 *   pulled from $form_state['values']['submitted'] or $submission->data.
2755
 *   These values are used to check if the component should be displayed
2756
 *   conditionally.
2757
 * @param $format
2758
 *   The format the form should be displayed as. May be one of the following:
2759
 *   - form: Show as an editable form.
2760
 *   - html: Show as HTML results.
2761
 *   - text: Show as plain text.
2762
 * @param $page_num
2763
 *   The page number. Defaults to 0.
2764
 * @param $filter
2765
 *   Whether the form element properties should be filtered. Only set to FALSE
2766
 *   if needing the raw properties for editing.
2767
 *
2768
 * @see webform_client_form()
2769
 * @see webform_submission_render()
2770
 */
2771
function _webform_client_form_add_component($node, $component, $component_value, &$parent_fieldset, &$form, $input_values, $format = 'form', $page_num = 0, $filter = TRUE) {
2772
  $cid = $component['cid'];
2773
  $component_access = empty($component['extra']['private']) || webform_results_access($node);
2774

    
2775
  // Load with submission information if necessary.
2776
  if ($format != 'form') {
2777
    // This component is display only.
2778
    $data = empty($input_values[$cid]) ? NULL : $input_values[$cid];
2779
    if ($display_element = webform_component_invoke($component['type'], 'display', $component, $data, $format, $form['#submission'])) {
2780
      // Set access based on the private property.
2781
      $display_element += array('#access' => TRUE);
2782
      $display_element['#access'] = $display_element['#access'] && $component_access;
2783

    
2784
      // Ensure the component is added as a property.
2785
      $display_element['#webform_component'] = $component;
2786

    
2787
      // Add custom CSS classes to the field and wrapper.
2788
      _webform_component_classes($display_element, $component);
2789

    
2790
      // Allow modules to modify a "display only" webform component.
2791
      drupal_alter('webform_component_display', $display_element, $component);
2792

    
2793
      // The form_builder() function usually adds #parents and #id for us, but
2794
      // because these are not marked for #input, we need to add them manually.
2795
      if (!isset($display_element['#parents'])) {
2796
        $parents = isset($parent_fieldset['#parents']) ? $parent_fieldset['#parents'] : array('submitted');
2797
        $parents[] = $component['form_key'];
2798
        $display_element['#parents'] = $parents;
2799
      }
2800
      if (!isset($display_element['#id'])) {
2801
        $display_element['#id'] = drupal_clean_css_identifier('edit-' . implode('-', $display_element['#parents']));
2802
      }
2803

    
2804
      // Add the element into the proper parent in the display.
2805
      $parent_fieldset[$component['form_key']] = $display_element;
2806
    }
2807
  }
2808
  // Show the component only on its form page, or if building an unfiltered
2809
  // version of the form (such as for Form Builder).
2810
  elseif ($component['page_num'] == $page_num || $filter == FALSE) {
2811
    // Add this user-defined field to the form (with all the values that are always available).
2812
    if ($element = webform_component_invoke($component['type'], 'render', $component, $component_value, $filter, $form['#submission'])) {
2813
      // Set access based on the private property.
2814
      $element += array('#access' => TRUE);
2815
      $element['#access'] = $element['#access'] && $component_access;
2816

    
2817
      // Ensure the component is added as a property.
2818
      $element['#webform_component'] = $component;
2819

    
2820
      // The 'private' option is in most components, but it's not a real
2821
      // property. Add it for Form Builder compatibility.
2822
      if (webform_component_feature($component['type'], 'private')) {
2823
        $element['#webform_private'] = $component['extra']['private'];
2824
      }
2825

    
2826
      // The 'placeholder' option is in some components, but it's not a real
2827
      // property. Add it for Form Builder compatibility.
2828
      if (webform_component_feature($component['type'], 'placeholder')) {
2829
        $element['#webform_placeholder'] = $component['extra']['placeholder'];
2830
      }
2831

    
2832
      // Add custom CSS classes to the field and wrapper.
2833
      _webform_component_classes($element, $component);
2834

    
2835
      // Allow modules to modify a webform component that is going to be render in a form.
2836
      drupal_alter('webform_component_render', $element, $component);
2837

    
2838
      // Add the element into the proper parent in the form.
2839
      $parent_fieldset[$component['form_key']] = $element;
2840
    }
2841
    else {
2842
      drupal_set_message(t('The webform component @type is not able to be displayed', array('@type' => $component['type'])));
2843
    }
2844
  }
2845

    
2846
  // Disable validation initially on all elements. We manually validate
2847
  // all webform elements in webform_client_form_validate().
2848
  if (isset($parent_fieldset[$component['form_key']])) {
2849
    $parent_fieldset[$component['form_key']]['#validated'] = TRUE;
2850
    $parent_fieldset[$component['form_key']]['#webform_validated'] = FALSE;
2851
  }
2852

    
2853
  if (isset($component['children']) && is_array($component['children'])) {
2854
    $sorter = webform_get_conditional_sorter($node);
2855
    foreach ($component['children'] as $scid => $subcomponent) {
2856
      $subcomponent_value = isset($input_values[$scid]) ? $input_values[$scid] : NULL;
2857
      // Include if always shown, or for forms, also if currently hidden but might be shown due to conditionals.
2858
      $visibility = $sorter->componentVisibility($scid, $subcomponent['page_num']);
2859
      if ($visibility == WebformConditionals::componentShown || ($format == 'form' && $visibility) || !$filter) {
2860
        _webform_client_form_add_component($node, $subcomponent, $subcomponent_value, $parent_fieldset[$component['form_key']], $form, $input_values, $format, $page_num, $filter);
2861
      }
2862
    }
2863
  }
2864
}
2865

    
2866
/**
2867
 * Validates that the form can still be submitted, saved as draft, or edited.
2868
 *
2869
 * Because forms may be submitted from cache or the webform changed while the
2870
 * submission is in progress, the conditions to allow the form are re-checked
2871
 * upon form submission.
2872
 */
2873
function webform_client_form_prevalidate($form, &$form_state) {
2874
  global $user;
2875

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

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

    
2885
  // Check that the submissions have not exceeded the total submission limit.
2886
  $total_limit_exceeded = FALSE;
2887
  if ($node->webform['total_submit_limit'] != -1 && !$finished) {
2888
    $total_limit_exceeded = webform_submission_total_limit_check($node);
2889
  }
2890

    
2891
  // Check that the user has not exceeded the submission limit.
2892
  // This usually will only apply to anonymous users when the page cache is
2893
  // enabled, because they may submit the form even if they do not have access.
2894
  $user_limit_exceeded = FALSE;
2895
  if ($node->webform['submit_limit'] != -1 && !$finished) {
2896
    $user_limit_exceeded = webform_submission_user_limit_check($node);
2897
  }
2898

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

    
2908
  // Prevent submission by throwing an error.
2909
  if ((!$allowed_role || $total_limit_exceeded || $user_limit_exceeded || $closed)) {
2910
    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));
2911
    form_set_error('', NULL);
2912
  }
2913
}
2914

    
2915
/**
2916
 * Form API #validate handler for the webform_client_form() form.
2917
 */
2918
function webform_client_form_validate($form, &$form_state) {
2919
  if (($errors = form_get_errors()) && key_exists('', $errors)) {
2920
    // Prevalidation failed. The form cannot be submitted. Do not attemp futher validation.
2921
    return;
2922
  }
2923
  if ($form_state['webform']['preview'] && $form_state['webform']['page_count'] === $form_state['webform']['page_num']) {
2924
    // Form has already passed validation and is on the preview page.
2925
    return;
2926
  }
2927

    
2928
  module_load_include('inc', 'webform', 'includes/webform.submissions');
2929
  $node = $form['#node'];
2930

    
2931
  // Assemble an array of all past and new input values that will determine if
2932
  // certain elements need validation at all.
2933
  if (!empty($node->webform['conditionals'])) {
2934
    $input_values = isset($form_state['storage']['submitted']) ? $form_state['storage']['submitted'] : array();
2935
    $new_values = isset($form_state['values']['submitted']) ? _webform_client_form_submit_flatten($form['#node'], $form_state['values']['submitted']) : array();
2936
    foreach ($new_values as $cid => $values) {
2937
      $input_values[$cid] = $values;
2938
    }
2939
    // Ensure that all conditionally-hidden values are removed.
2940
    $input_values = webform_get_conditional_sorter($node)->executeConditionals($input_values, $form_state['webform']['page_num']);
2941
  }
2942
  else {
2943
    $input_values = NULL;
2944
  }
2945

    
2946

    
2947
  // Run all #element_validate and #required checks. These are skipped initially
2948
  // by setting #validated = TRUE on all components when they are added.
2949
  _webform_client_form_validate($form, $form_state, 'webform_client_form', $input_values);
2950
}
2951

    
2952
/**
2953
 * Recursive validation function to trigger normal Drupal validation.
2954
 *
2955
 * This function imitates _form_validate in Drupal's form.inc, only it sets
2956
 * a different property to ensure that validation has occurred.
2957
 */
2958
function _webform_client_form_validate(&$elements, &$form_state, $form_id = NULL, $input_values = NULL) {
2959
  if (isset($input_values) && isset($elements['#webform_component'])) {
2960
    $sorter = webform_get_conditional_sorter($form_state['complete form']['#node']);
2961
    $cid = $elements['#webform_component']['cid'];
2962
    $page_num = $form_state['values']['details']['page_num'];
2963
    // Webform-specific enhancements:
2964
    // 1) Only validate the field if it was used in this submission.
2965
    //    This both skips validation on the field and sets the value of the
2966
    //    field to NULL, preventing any dangerous input. Short-circuit
2967
    //    validation for a hidden component (hidden by rules dependent upon
2968
    //    component on previous pages), or a component this is dependent upon
2969
    //    values on the current page, but is hidden based upon their current
2970
    //    values.
2971
    // 2) Only valididate if the field has not been set by conditionals.
2972
    //    The user will be unable to fix the validation without surmising the
2973
    //    logic and changing the trigger for the conditional. Also, it isn't
2974
    //    possible to set $element['#value'] without component-specific
2975
    //    knowledge of how the data is stored because $input_values is already
2976
    //    webform-normalized to contain values in arrays.
2977
    if ($sorter->componentVisibility($cid, $page_num) != WebformConditionals::componentShown) {
2978
      form_set_value($elements, NULL, $form_state);
2979
      return;
2980
    }
2981
    if ($sorter->componentSet($cid, $page_num)) {
2982
      $component = $elements['#webform_component'];
2983
      $value = $input_values[$cid];
2984
      $value = is_array($value) ? $value[0] : $value;
2985
      // webform_component_invoke cannot be called with reference arguments. Call directly.
2986
      // webform_component_invoke($component['type'], 'action_set', $component, $elements, $form_state, $value);
2987
      $function = '_webform_action_set_' . $component['type'];
2988
      $function($component, $elements, $form_state, $value);
2989
    }
2990

    
2991
    // Check for changes in required status made by conditionals.
2992
    $required = $sorter->componentRequired($cid, $page_num);
2993
    if (isset($required)) {
2994
      $elements['#required'] = $required;
2995

    
2996
      // Some components, e.g. grids, have nested sub-elements. Extend required
2997
      // to any sub-components.
2998
      foreach (element_children($elements) as $key) {
2999
        if (isset($elements[$key]) && $elements[$key] && !isset($elements[$key]['#webform_component'])) {
3000
          // Child is *not* a component.
3001
          $elements[$key]['#required'] = $required;
3002
        }
3003
      }
3004
    }
3005
  }
3006

    
3007
  // Recurse through all children.
3008
  foreach (element_children($elements) as $key) {
3009
    if (isset($elements[$key]) && $elements[$key]) {
3010
      _webform_client_form_validate($elements[$key], $form_state, NULL, $input_values);
3011
    }
3012
  }
3013
  // Validate the current input.
3014
  if (isset($elements['#webform_validated']) && !$elements['#webform_validated']) {
3015
    if (isset($elements['#needs_validation'])) {
3016
      // Make sure a value is passed when the field is required.
3017
      // A simple call to empty() will not cut it here as some fields, like
3018
      // checkboxes, can return a valid value of '0'. Instead, check the
3019
      // length if it's a string, and the item count if it's an array. For
3020
      // radios, FALSE means that no value was submitted, so check that too.
3021
      if ($elements['#required'] && (!count($elements['#value']) || (is_string($elements['#value']) && strlen(trim($elements['#value'])) == 0) || $elements['#value'] === FALSE)) {
3022
        form_error($elements, t('!name field is required.', array('!name' => $elements['#title'])));
3023
      }
3024

    
3025
      // Verify that the value is not longer than #maxlength.
3026
      if (isset($elements['#maxlength']) && drupal_strlen($elements['#value']) > $elements['#maxlength']) {
3027
        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']))));
3028
      }
3029

    
3030
      if (isset($elements['#options']) && isset($elements['#value'])) {
3031
        if ($elements['#type'] == 'select') {
3032
          $options = form_options_flatten($elements['#options']);
3033
        }
3034
        else {
3035
          $options = $elements['#options'];
3036
        }
3037
        if (is_array($elements['#value'])) {
3038
          $value = $elements['#type'] == 'checkboxes' ? array_keys(array_filter($elements['#value'])) : $elements['#value'];
3039
          foreach ($value as $v) {
3040
            if (!isset($options[$v])) {
3041
              form_error($elements, t('An illegal choice has been detected. Please contact the site administrator.'));
3042
              watchdog('form', 'Illegal choice %choice in !name element.', array('%choice' => $v, '!name' => empty($elements['#title']) ? $elements['#parents'][0] : $elements['#title']), WATCHDOG_ERROR);
3043
            }
3044
          }
3045
        }
3046
        elseif ($elements['#value'] !== '' && !isset($options[$elements['#value']])) {
3047
          form_error($elements, t('An illegal choice has been detected. Please contact the site administrator.'));
3048
          watchdog('form', 'Illegal choice %choice in %name element.', array('%choice' => $elements['#value'], '%name' => empty($elements['#title']) ? $elements['#parents'][0] : $elements['#title']), WATCHDOG_ERROR);
3049
        }
3050
      }
3051
    }
3052

    
3053
    // Call user-defined form level validators.
3054
    if (isset($form_id)) {
3055
      form_execute_handlers('validate', $elements, $form_state);
3056
    }
3057
    // Call any element-specific validators. These must act on the element
3058
    // #value data.
3059
    elseif (isset($elements['#element_validate'])) {
3060
      foreach ($elements['#element_validate'] as $function) {
3061
        if (is_callable($function))  {
3062
          $function($elements, $form_state, $form_state['complete form']);
3063
        }
3064
      }
3065
    }
3066
    $elements['#webform_validated'] = TRUE;
3067
  }
3068
}
3069

    
3070
/**
3071
 * Saves submissions that fail validation as drafts.
3072
 *
3073
 * When a user attempts to submit an unfinished form and auto-save is allowed,
3074
 * automatically save the form as a draft to allow the user to complete the
3075
 * form later. This prevents the common failure of a user trying to submit a
3076
 * form and not noticing validation errors. The user then leaves the page
3077
 * without realizing that the form hasn't been submitted.
3078
 *
3079
 * THEORY OF OPERATION:
3080
 * The Drupal 7 Form API lacks an easy way to rebuild the form in the event of
3081
 * validation errors. The opertions is thus:
3082
 *
3083
 * 1) The form is first displayed. If it is an existing draft,
3084
 *    webform_client_form will generated a form to edit the draft submission.
3085
 *    Otherwise it creates a form for a new, empty submission. As usual.
3086
 * 2) The submit button is pressed. The form is retrieved from cache or is
3087
 *    recreated by webform_client_form. The values from the $_POST are merged in
3088
 *    and the validation routines are called. As usual.
3089
 * 3) The postvalidation routine, below, detects that validation errors should
3090
 *    be autosaved and calls the submit handlers on a copy of the form and
3091
 *    form_state. This creates the submission, or saves to the existing
3092
 *    submission. The original form and form_state are not modified (yet).
3093
 * 4) If a new submission was created, the form and form_state are updated with
3094
 *    the newly-created sid of the submission, which is returned to the
3095
 *    browser in the hidden field [details][sid]. The form is set to not be
3096
 *    cached, and any existing cached copy is cleared to force step 5. The form
3097
 *    is presented with validation errors as usual.
3098
 * 5) When the form is submitted again, the form must be rebuilt because it is
3099
 *    not in the cache. The existing draft detection in _webform_fetch_draft_sid
3100
 *    detects that a webform draft is being submitted, and uses its sid in
3101
 *    preference to any other stored draft sid in the database. In the event
3102
 *    that multiple drafts are being implemented by another module, this ensures
3103
 *    that the correct draft is edited.
3104
 * 6) Repeat from step 2 until the form is abandoned (leaving the draft) or
3105
 *    successfully submitted.
3106
 */
3107
function webform_client_form_postvalidate(&$form, &$form_state) {
3108
  $errors = form_get_errors();
3109
  $nid = $form_state['values']['details']['nid'];
3110
  $node = node_load($nid);
3111
  if (user_is_logged_in() &&
3112
      $errors && !key_exists('', $errors) &&
3113
      $node->webform['auto_save'] &&
3114
      !$form_state['values']['details']['finished'] &&
3115
      !empty($form_state['values']['op'])) {
3116
    // Validation errors are present, prevalidation succeeded (e.g. submission
3117
    // limits are ok), auto-save is enabled, this form isn't finished (i.e. is
3118
    // or soon will be a draft) and a button was pushed (not ajax).
3119

    
3120
    // Process submission on a copy of the form and form_state to prevent the
3121
    // submission handlers from making unintended changes. Use a button that
3122
    // isn't Save Draft, Next Page, Submit, etc to avoid triggering any
3123
    // unwanted side effects.
3124
    $submit_form = $form;
3125
    $submit_form_state = $form_state;
3126
    $submit_form_state['values']['op'] = '__AUTOSAVE__';
3127
    form_execute_handlers('submit', $submit_form, $submit_form_state);
3128
    $sid = $submit_form_state['values']['details']['sid'];
3129
    if ($sid != $form_state['values']['details']['sid']) {
3130
      // A new submission was created. Update the form and form_state as if it
3131
      // has been submitted with the new sid. This causes the Form API to
3132
      // render the form with new sid.
3133
      $form_state['values']['details']['sid'] = $sid;
3134
      $form_state['input']['details']['sid'] = $sid;
3135
      $form['details']['sid']['#value'] = $sid;
3136

    
3137
      // Prevent the form from being cached, forcing it to be rebuilt from the
3138
      // form definition function, which will honor the new sid.
3139
      $form_state['no_cache'] = TRUE;
3140
      if (!empty($form_state['values']['form_build_id'])) {
3141
        cache_clear_all('form_' . $form_state['values']['form_build_id'], 'cache_form');
3142
        cache_clear_all('form_state_' . $form_state['values']['form_build_id'], 'cache_form');
3143
      }
3144
    }
3145
  }
3146
}
3147

    
3148
/**
3149
 * Handle the processing of pages and conditional logic.
3150
 */
3151
function webform_client_form_pages($form, &$form_state) {
3152
  $node = node_load($form_state['values']['details']['nid']);
3153

    
3154
  // Multistep forms may not have any components on the first page.
3155
  if (!isset($form_state['values']['submitted'])) {
3156
    $form_state['values']['submitted'] = array();
3157
  }
3158

    
3159
  // Move special settings to storage.
3160
  if (isset($form_state['webform']['component_tree'])) {
3161
    $form_state['storage']['component_tree'] = $form_state['webform']['component_tree'];
3162
    $form_state['storage']['page_count'] = $form_state['webform']['page_count'];
3163
    $form_state['storage']['page_num'] = $form_state['webform']['page_num'];
3164
    $form_state['storage']['preview'] = $form_state['webform']['preview'];
3165
  }
3166

    
3167
  // Flatten trees within the submission.
3168
  $form_state['values']['submitted'] = _webform_client_form_submit_flatten($node, $form_state['values']['submitted']);
3169

    
3170
  // Perform post processing by components.
3171
  _webform_client_form_submit_process($node, $form_state['values']['submitted']);
3172

    
3173
  // Assume the form is completed unless the page logic says otherwise.
3174
  $form_state['webform_completed'] = TRUE;
3175

    
3176
  // Merge any stored submission data for multistep forms.
3177
  $original_values = is_array($form_state['values']['submitted']) ? $form_state['values']['submitted'] : array();
3178
  if (isset($form_state['storage']['submitted'])) {
3179
    // Array + operator keeps all elements of left operand and discards any duplicate elements in right operand.
3180
    $original_values += $form_state['storage']['submitted'];
3181
  }
3182

    
3183
  // Execute conditionals on submission values.
3184
  $form_state['values']['submitted'] = webform_get_conditional_sorter($node)->executeConditionals($original_values);
3185

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

    
3191
    // Store values from the current page in the form state storage.
3192
    $form_state['storage']['submitted'] = $form_state['values']['submitted'];
3193

    
3194
    // Set the page number.
3195
    if (!isset($form_state['storage']['page_num'])) {
3196
      $form_state['storage']['page_num'] = 1;
3197
    }
3198
    if (end($form_state['clicked_button']['#parents']) == 'next') {
3199
      $forward = 1;
3200
    }
3201
    elseif (end($form_state['clicked_button']['#parents']) == 'previous') {
3202
      $forward = -1;
3203
    }
3204
    $current_page = $form_state['storage']['page_num'];
3205

    
3206
    if (isset($forward)) {
3207
      // Find the
3208
      //    1) previous/next non-empty page, or
3209
      //    2) the preview page, or
3210
      //    3) the preview page, forcing its display if the form would unexpectedly submit, or
3211
      //    4) page 1 even if empty, if no other previous page would be shown
3212
      $preview_page_num = $form_state['storage']['page_count'] + (int)!$form_state['webform']['preview'];
3213
      $page_num = $current_page;
3214
      do {
3215
        $page_num += $forward;
3216
      } while (!webform_get_conditional_sorter($node)->pageVisibility($page_num));
3217
      if (!$form_state['webform']['preview'] && $page_num == $preview_page_num) {
3218
        // Force a preview to avert an unintended submission via Next.
3219
        $form_state['webform']['preview'] = TRUE;
3220
        $form_state['storage']['preview'] = TRUE;
3221
        $form_state['storage']['page_count']++;
3222
      }
3223
      $form_state['storage']['page_num'] = $page_num;
3224
    }
3225

    
3226
    // The form is done if the page number is greater than the page count.
3227
    $form_state['webform_completed'] = $form_state['storage']['page_num'] > $form_state['storage']['page_count'];
3228
  }
3229

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

    
3234
  // Determine what we need to do on the next page.
3235
  if (!empty($form_state['save_draft']) || !$form_state['webform_completed']) {
3236
    // Rebuild the form and display the current (on drafts) or next page.
3237
    $form_state['rebuild'] = TRUE;
3238
  }
3239
  else {
3240
    // Remove the form state storage now that we're done with the pages.
3241
    $form_state['rebuild'] = FALSE;
3242
    unset($form_state['storage']);
3243
  }
3244
}
3245

    
3246
/**
3247
 * Submit handler for saving the form values and sending e-mails.
3248
 */
3249
function webform_client_form_submit($form, &$form_state) {
3250
  module_load_include('inc', 'webform', 'includes/webform.submissions');
3251
  module_load_include('inc', 'webform', 'includes/webform.components');
3252
  global $user;
3253

    
3254
  if (empty($form_state['save_draft']) && empty($form_state['webform_completed'])) {
3255
    return;
3256
  }
3257

    
3258
  $node = $form['#node'];
3259
  $sid = $form_state['values']['details']['sid'] ? (int) $form_state['values']['details']['sid'] : NULL;
3260

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

    
3264
  // To maintain time and user information, load the existing submission.
3265
  // If a draft is deleted while a user is working on completing it, $sid will
3266
  // exist, but webform_get_submission() will not find the draft. So, make a new
3267
  // submission.
3268
  if ($sid && $submission = webform_get_submission($node->webform['nid'], $sid)) {
3269
    // Store original data on object for use in update hook.
3270
    $submission->original = clone $submission;
3271

    
3272
    // Merge with new submission data. The + operator maintains numeric keys.
3273
    // This maintains existing data with just-submitted data when a user resumes
3274
    // a submission previously saved as a draft.
3275
    // Remove any existing data on this and previous pages. If components are hidden, they may
3276
    // be in the $submission->data but absent entirely from $new_data;
3277
    $page_map = webform_get_conditional_sorter($node)->getPageMap();
3278
    for ($page_nr = 1; $page_nr <= $form_state['webform']['page_num']; $page_nr++) {
3279
      $submission->data = array_diff_key($submission->data, $page_map[$page_nr]);
3280
    }
3281
    $submission->data = webform_submission_data($node, $form_state['values']['submitted']) + $submission->data;
3282
  }
3283
  else {
3284
    // Create a new submission object.
3285
    $submission = webform_submission_create($node, $user, $form_state);
3286
    // Since this is a new submission, a new sid is needed.
3287
    $sid = NULL;
3288
  }
3289

    
3290
  // Save draft state, and for drafts, save the current page (if clicking next)
3291
  // or the previous page (if not) as the last valid page.
3292
  $submission->is_draft = $is_draft;
3293
  $submission->highest_valid_page = 0;
3294
  if ($is_draft) {
3295
     $submission->highest_valid_page = end($form_state['clicked_button']['#parents']) == 'next' && $form_state['values']['op'] != '__AUTOSAVE__'
3296
                                          ? $form_state['webform']['page_num']
3297
                                          : $form_state['webform']['page_num'] - 1;
3298
  }
3299

    
3300
  // If there is no data to be saved (such as on a multipage form with no fields
3301
  // on the first page), process no further. Submissions with no data cannot
3302
  // be loaded from the database as efficiently, so we don't save them at all.
3303
  if (empty($submission->data)) {
3304
    return;
3305
  }
3306

    
3307
  // Save the submission to the database.
3308
  if (!$sid) {
3309
    // No sid was found thus insert it in the dataabase.
3310
    $form_state['values']['details']['sid'] = $sid = webform_submission_insert($node, $submission);
3311
    $form_state['values']['details']['is_new'] = TRUE;
3312

    
3313
    // Save the new details in storage. When ajax calls for file upload/remove,
3314
    // $form_state['values']['details'] is missing. This allows the proper
3315
    // submission to be retrieved in webform_client_form. See #2562703.
3316
    $form_state['storage']['details'] = $form_state['values']['details'];
3317

    
3318
    // Set a cookie including the server's submission time. The cookie expires
3319
    // in the length of the interval plus a day to compensate for timezones.
3320
    $tracking_mode = webform_variable_get('webform_tracking_mode');
3321
    if ($tracking_mode === 'cookie' || $tracking_mode === 'strict') {
3322
      $cookie_name = 'webform-' . $node->nid;
3323
      $time = REQUEST_TIME;
3324
      $params = session_get_cookie_params();
3325
      setcookie($cookie_name . '[' . $time . ']', $time, $time + $node->webform['submit_interval'] + 86400, $params['path'], $params['domain'], $params['secure'], $params['httponly']);
3326
    }
3327

    
3328
    // Save session information about this submission for anonymous users,
3329
    // allowing them to access or edit their submissions.
3330
    if (!$user->uid && user_access('access own webform submissions')) {
3331
      $_SESSION['webform_submission'][$sid] = $node->nid;
3332
    }
3333
  }
3334
  else {
3335
    // Sid was found thus update the existing sid in the database.
3336
    webform_submission_update($node, $submission);
3337
    $form_state['values']['details']['is_new'] = FALSE;
3338
  }
3339

    
3340
  // Check if this form is sending an email.
3341
  if (!$is_draft && !$form_state['values']['details']['finished']) {
3342
    drupal_static_reset('webform_get_submission');
3343
    $submission = webform_get_submission($node->webform['nid'], $sid);
3344
    webform_submission_send_mail($node, $submission);
3345
  }
3346

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

    
3350
  // Check confirmation and redirect_url fields.
3351
  $redirect = NULL;
3352
  $redirect_url = trim($node->webform['redirect_url']);
3353
  if (isset($form['actions']['draft']['#value']) && $form_state['values']['op'] == $form['actions']['draft']['#value']) {
3354
    $message = t('Submission saved. You may return to this form later and it will restore the current values.');
3355
  }
3356
  elseif ($is_draft) {
3357
    // No redirect needed. No confirmation message needed.
3358
    $message = FALSE;
3359
  }
3360
  elseif (!empty($form_state['values']['details']['finished'])) {
3361
    $message = t('Submission updated.');
3362
    $redirect = "node/{$node->nid}/submission/$sid";
3363
  }
3364
  elseif (!empty($node->webform['confirmation_block'])) {
3365
    $message = FALSE;
3366
    // Webform was submitted in a block and the confirmation message is to be
3367
    // displayed in the block.
3368
    $_SESSION['webform_confirmation'][$node->nid] = array(
3369
      'sid' => $sid,
3370
      'confirmation_page' => $redirect_url == '<confirmation>',
3371
    );
3372
    drupal_page_is_cacheable(FALSE);
3373
  }
3374
  elseif ($redirect_url == '<none>') {
3375
    // No redirect needed. Show a confirmatin message if there is one.
3376
  }
3377
  elseif ($redirect_url == '<confirmation>') {
3378
    // No confirmation message needed because it will be shown on the
3379
    // confirmation page.
3380
    $message = FALSE;
3381
    $query = array('sid' => $sid);
3382
    if ((int) $user->uid === 0) {
3383
      $query['token'] = webform_get_submission_access_token($submission);
3384
    }
3385
    $redirect = array('node/' . $node->nid . '/done', array('query' => $query));
3386
  }
3387
  else {
3388
    // Clean up the redirect URL, filter it for tokens and detect external
3389
    // domains. If the redirect is to an external URL, then don't show the
3390
    // confirmation message.
3391
    $redirect = webform_replace_url_tokens($redirect_url, $node, $submission);
3392
    if ($redirect[1]['#webform_external']) {
3393
      $message = FALSE;
3394
    }
3395
  }
3396

    
3397
  // Show a message if manually set.
3398
  if (is_string($message)) {
3399
    drupal_set_message($message);
3400
  }
3401
  // If redirecting and we have a confirmation message, show it as a message.
3402
  elseif ($message) {
3403
    drupal_set_message(webform_replace_tokens($node->webform['confirmation'], $node, $submission, NULL, $node->webform['confirmation_format']));
3404
  }
3405

    
3406
  $form_state['redirect'] = $redirect;
3407
}
3408

    
3409
/**
3410
 * Post processes the submission tree with any updates from components.
3411
 *
3412
 * @param $node
3413
 *   The full webform node.
3414
 * @param $form_values
3415
 *   The form values for the form.
3416
 * @param $types
3417
 *   Optional. Specific types to perform processing.
3418
 * @param $parent
3419
 *   Internal use. The current parent CID whose children are being processed.
3420
 */
3421
function _webform_client_form_submit_process($node, &$form_values) {
3422
  foreach ($form_values as $cid => $value) {
3423
    if (isset($node->webform['components'][$cid])) {
3424
      // Call the component process submission function.
3425
      $component = $node->webform['components'][$cid];
3426
      if ((!isset($types) || in_array($component['type'], $types)) && webform_component_implements($component['type'], 'submit')) {
3427
        $form_values[$cid] = webform_component_invoke($component['type'], 'submit', $component, $form_values[$cid]);
3428
      }
3429
    }
3430
  }
3431
}
3432

    
3433
/**
3434
 * Flattens a submitted values back into a single flat array representation.
3435
 */
3436
function _webform_client_form_submit_flatten($node, $fieldset, $parent = 0) {
3437
  $values = array();
3438

    
3439
  if (is_array($fieldset)) {
3440
    foreach ($fieldset as $form_key => $value) {
3441
      if ($cid = webform_get_cid($node, $form_key, $parent)) {
3442
        if (is_array($value) && webform_component_feature($node->webform['components'][$cid]['type'], 'group')) {
3443
          $values += _webform_client_form_submit_flatten($node, $value, $cid);
3444
        }
3445
        else {
3446
          $values[$cid] = $value;
3447
        }
3448
      }
3449
      else {
3450
        // This $form_key must belong to the parent. For example, a grid.
3451
        $values[$parent][$form_key] = $value;
3452
      }
3453
    }
3454
  }
3455

    
3456
  return $values;
3457
}
3458

    
3459
/**
3460
 * Prints the confirmation message after a successful submission.
3461
 */
3462
function _webform_confirmation($node) {
3463
  drupal_set_title($node->title);
3464
  webform_set_breadcrumb($node, TRUE);
3465
  $sid = isset($_GET['sid']) ? $_GET['sid'] : NULL;
3466
  return theme(array('webform_confirmation_' . $node->nid, 'webform_confirmation'), array('node' => $node, 'sid' => $sid));
3467
}
3468

    
3469
/**
3470
 * Prepare for theming of the webform form.
3471
 */
3472
function template_preprocess_webform_form(&$vars) {
3473
  if (isset($vars['form']['details']['nid']['#value'])) {
3474
    $vars['nid'] = $vars['form']['details']['nid']['#value'];
3475
  }
3476
  elseif (isset($vars['form']['submission']['#value'])) {
3477
    $vars['nid'] = $vars['form']['submission']['#value']->nid;
3478
  }
3479

    
3480
  if (!empty($vars['form']['#node']->webform['conditionals']) && empty($vars['form']['preview'])) {
3481
    module_load_include('inc', 'webform', 'includes/webform.conditionals');
3482
    $submission_data = isset($vars['form']['#conditional_values']) ? $vars['form']['#conditional_values'] : array();
3483
    $settings = webform_conditional_prepare_javascript($vars['form']['#node'],
3484
                                                       $submission_data,
3485
                                                       $vars['form']['details']['page_num']['#value']);
3486
    drupal_add_js(array('webform' => array('conditionals' => array('webform-client-form-' . $vars['nid'] => $settings))), 'setting');
3487
  }
3488

    
3489
}
3490

    
3491
/**
3492
 * Prepare for theming of the webform submission confirmation.
3493
 */
3494
function template_preprocess_webform_confirmation(&$vars) {
3495
  $node = $vars['node'];
3496
  // Strip out empty tags added by WYSIWYG editors if needed.
3497
  $confirmation = $node->webform['confirmation'];
3498
  $confirmation = strlen(trim(strip_tags($confirmation))) ? $confirmation : '';
3499

    
3500
  // Replace tokens.
3501
  module_load_include('inc', 'webform', 'includes/webform.submissions');
3502
  $submission = webform_get_submission($node->nid, $vars['sid']);
3503
  $vars['confirmation_message'] = webform_replace_tokens($confirmation, $node, $submission, NULL, $node->webform['confirmation_format']);
3504

    
3505
  // URL back to form (or same page for in-block confirmations).
3506
  $vars['url'] = empty($node->webform_block)
3507
                    ? url('node/' . $node->nid)
3508
                    : url(current_path(), array('query' => drupal_get_query_parameters()));
3509

    
3510
  // Progress bar.
3511
  $vars['progressbar'] = '';
3512
  $page_labels = webform_page_labels($node);
3513
  $page_count = count($page_labels);
3514
  if ($node->webform['progressbar_include_confirmation'] && $page_count > 2) {
3515
    $vars['progressbar'] = theme('webform_progressbar', array(
3516
      'node' => $node,
3517
      'page_num' => $page_count,
3518
      'page_count' => $page_count,
3519
      'page_labels' => $page_labels,
3520
    ));
3521
  }
3522
}
3523

    
3524
/**
3525
 * Prepare for theming of the webform progressbar.
3526
 */
3527
function template_preprocess_webform_progressbar(&$vars) {
3528
  // Add CSS used by the progress bar.
3529
  drupal_add_css(drupal_get_path('module', 'webform') . '/css/webform.css');
3530

    
3531
  $vars['progressbar_page_number'] = $vars['node']->webform['progressbar_page_number'];
3532
  $vars['progressbar_percent'] = $vars['node']->webform['progressbar_percent'];
3533
  $vars['progressbar_bar'] = $vars['node']->webform['progressbar_bar'];
3534
  $vars['progressbar_pagebreak_labels'] = $vars['node']->webform['progressbar_pagebreak_labels'];
3535
  $vars['progressbar_include_confirmation'] = $vars['node']->webform['progressbar_include_confirmation'];
3536
  $vars['percent'] = ($vars['page_num'] - 1) / ($vars['page_count'] - 1) * 100;
3537
}
3538

    
3539
/**
3540
 * Prepare to theme the contents of e-mails sent by webform.
3541
 */
3542
function template_preprocess_webform_mail_message(&$vars) {
3543
  global $user;
3544

    
3545
  $vars['user'] = $user;
3546
  $vars['ip_address'] = webform_ip_address($vars['node']);
3547
}
3548

    
3549
/**
3550
 * A Form API #pre_render function. Sets display based on #title_display.
3551
 *
3552
 * This function is used regularly in D6 for all elements, but specifically for
3553
 * fieldsets in D7, which don't support #title_display natively.
3554
 */
3555
function webform_element_title_display($element) {
3556
  if (isset($element['#title_display']) && strcmp($element['#title_display'], 'none') === 0) {
3557
    $element['#title'] = NULL;
3558
  }
3559
  return $element;
3560
}
3561

    
3562
/**
3563
 * A Form API #pre_render function that removes the ID from an element.
3564
 *
3565
 * Drupal forcibly adds IDs to all form elements, including those that do not
3566
 * need them for any reason, such as the actions wrapper or submit buttons. We
3567
 * use this process function wherever we wish to remove an ID from an element.
3568
 * Because #states and #ajax require IDs, they are only removed if the states
3569
 * and ajax arrays are empty.
3570
 */
3571
function webform_pre_render_remove_id($element) {
3572
  if (empty($element['#states']) && empty($element['#ajax'])) {
3573
    $element['#id'] = NULL;
3574
    // Removing array parents is required to prevent theme_container from adding
3575
    // an empty ID attribute.
3576
    $element['#array_parents'] = NULL;
3577
  }
3578
  return $element;
3579
}
3580

    
3581
/**
3582
 * Implements template_preprocess_THEME_HOOK().
3583
 */
3584
function template_preprocess_webform_element(&$variables) {
3585
  $element = &$variables['element'];
3586

    
3587
  // Ensure defaults.
3588
  $element += array(
3589
    '#title_display' => 'before',
3590
    '#wrapper_attributes' => array(),
3591
  );
3592
  $element['#wrapper_attributes'] += array(
3593
    'class' => array(),
3594
  );
3595

    
3596
  // All elements using this for display only are given the "display" type.
3597
  if (isset($element['#format']) && $element['#format'] == 'html') {
3598
    $type = 'display';
3599
  }
3600
  else {
3601
    $type = ($element['#webform_component']['type'] == 'select' && isset($element['#type'])) ? $element['#type'] : $element['#webform_component']['type'];
3602
  }
3603

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

    
3608
  // Build up a list of classes to apply on the element wrapper.
3609
  $wrapper_classes = array(
3610
    'form-item',
3611
    'webform-component',
3612
    'webform-component-' . str_replace('_', '-', $type),
3613
    'webform-component--' . $parents,
3614
  );
3615
  if (isset($element['#title_display']) && strcmp($element['#title_display'], 'inline') === 0) {
3616
    $wrapper_classes[] = 'webform-container-inline';
3617
  }
3618
  $element['#wrapper_attributes']['class'] = array_merge($element['#wrapper_attributes']['class'], $wrapper_classes);
3619

    
3620
  // If #title_display is none, set it to invisible instead - none only used if
3621
  // we have no title at all to use.
3622
  if ($element['#title_display'] == 'none') {
3623
    $element['#title_display'] = 'invisible';
3624
    if (empty($element['#attributes']['title']) && !empty($element['#title'])) {
3625
      $element['#attributes']['title'] = $element['#title'];
3626
    }
3627
  }
3628

    
3629
  // If #title is not set, we don't display any label or required marker.
3630
  if (!isset($element['#title'])) {
3631
    $element['#title_display'] = 'none';
3632
  }
3633

    
3634
  // If an internal title is being used, generate no external title.
3635
  if ($element['#title_display'] == 'internal') {
3636
    $element['#title_display'] = 'none';
3637
  }
3638
}
3639

    
3640
/**
3641
 * Replacement for theme_form_element().
3642
 */
3643
function theme_webform_element($variables) {
3644
  $element = $variables['element'];
3645

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

    
3650
  // Generate description for above or below the field.
3651
  $above = !empty($element['#webform_component']['extra']['description_above']);
3652
  $description = array(
3653
    FALSE => '',
3654
    TRUE => !empty($element['#description']) ? ' <div class="description">' . $element['#description'] . "</div>\n" : '',
3655
  );
3656

    
3657
  switch ($element['#title_display']) {
3658
    case 'inline':
3659
      $output .= $description[$above];
3660
      $description[$above] = '';
3661
      // FALL THRU.
3662
    case 'before':
3663
    case 'invisible':
3664
      $output .= ' ' . theme('form_element_label', $variables);
3665
      $output .= ' ' . $description[$above] . $prefix . $element['#children'] . $suffix . "\n";
3666
      break;
3667

    
3668
    case 'after':
3669
      $output .= ' ' . $description[$above] . $prefix . $element['#children'] . $suffix;
3670
      $output .= ' ' . theme('form_element_label', $variables) . "\n";
3671
      break;
3672

    
3673
    case 'none':
3674
    case 'attribute':
3675
      // Output no label and no required marker, only the children.
3676
      $output .= ' ' . $description[$above] . $prefix . $element['#children'] . $suffix . "\n";
3677
      break;
3678
  }
3679

    
3680
  $output .= $description[!$above];
3681
  $output .= "</div>\n";
3682

    
3683
  return $output;
3684
}
3685

    
3686
/**
3687
 * Output a form element in plain text format.
3688
 */
3689
function theme_webform_element_text($variables) {
3690
  $element = $variables['element'];
3691
  $value = $variables['element']['#children'];
3692

    
3693
  $output = '';
3694
  $is_group = webform_component_feature($element['#webform_component']['type'], 'group');
3695

    
3696
  // Output the element title.
3697
  if (isset($element['#title'])) {
3698
    if ($is_group) {
3699
      $output .= '==' . $element['#title'] . '==';
3700
    }
3701
    elseif (!in_array(drupal_substr($element['#title'], -1), array('?', ':', '!', '%', ';', '@'))) {
3702
      $output .= $element['#title'] . ':';
3703
    }
3704
    else {
3705
      $output .= $element['#title'];
3706
    }
3707
  }
3708

    
3709
  // Wrap long values at 65 characters, allowing for a few fieldset indents.
3710
  // It's common courtesy to wrap at 75 characters in e-mails.
3711
  if ($is_group && drupal_strlen($value) > 65) {
3712
    $value = wordwrap($value, 65, "\n");
3713
    $lines = explode("\n", $value);
3714
    foreach ($lines as $key => $line) {
3715
      $lines[$key] = '  ' . $line;
3716
    }
3717
    $value = implode("\n", $lines);
3718
  }
3719

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

    
3723
  // Indent fieldsets.
3724
  if ($is_group) {
3725
    $lines = explode("\n", $output);
3726
    foreach ($lines as $number => $line) {
3727
      if (strlen($line)) {
3728
        $lines[$number] = '  ' . $line;
3729
      }
3730
    }
3731
    $output = implode("\n", $lines);
3732
    $output .= "\n";
3733
  }
3734

    
3735
  if ($output) {
3736
    $output .= "\n";
3737
  }
3738

    
3739
  return $output;
3740
}
3741

    
3742
/**
3743
 * Theme a radio button and another element together.
3744
 *
3745
 * This is used in the e-mail configuration to show a radio button and a text
3746
 * field or select list on the same line.
3747
 */
3748
function theme_webform_inline_radio($variables) {
3749
  $element = $variables['element'];
3750

    
3751
  // Add element's #type and #name as class to aid with JS/CSS selectors.
3752
  $class = array('form-item');
3753
  if (!empty($element['#type'])) {
3754
    $class[] = 'form-type-' . strtr($element['#type'], '_', '-');
3755
  }
3756
  if (!empty($element['#name'])) {
3757
    $class[] = 'form-item-' . strtr($element['#name'], array(' ' => '-', '_' => '-', '[' => '-', ']' => ''));
3758
  }
3759

    
3760
  // Add container-inline to all elements.
3761
  $class[] = 'webform-container-inline';
3762
  if (isset($element['#inline_element']) && isset($variables['element']['#title'])) {
3763
    $variables['element']['#title'] .= ': ';
3764
  }
3765

    
3766
  $output = '<div class="' . implode(' ', $class) . '">' . "\n";
3767
  $output .= ' ' . $element['#children'];
3768
  if (!empty($element['#title'])) {
3769
    $output .= ' ' . theme('webform_inline_radio_label', $variables) . "\n";
3770
  }
3771

    
3772
  if (!empty($element['#description'])) {
3773
    $output .= ' <div class="description">' . $element['#description'] . "</div>\n";
3774
  }
3775

    
3776
  $output .= "</div>\n";
3777

    
3778
  return $output;
3779
}
3780

    
3781
/**
3782
 * Replacement for theme_form_element_label()
3783
 *
3784
 * This varies from theme_element_label in that it allows inline fields such
3785
 * as select and input tags within the label itself.
3786
 */
3787
function theme_webform_inline_radio_label($variables) {
3788
  $element = $variables['element'];
3789
  // This is also used in the installer, pre-database setup.
3790
  $t = get_t();
3791

    
3792
  // If title and required marker are both empty, output no label.
3793
  if ((!isset($element['#title']) || $element['#title'] === '') && empty($element['#required'])) {
3794
    return '';
3795
  }
3796

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

    
3800
  // theme_element_label() does a filter_xss() here, we skip it because we know
3801
  // every use where this theme function is used and we need to allow input and
3802
  // select elements.
3803
  $title = $element['#title'];
3804

    
3805
  $attributes = isset($element['#attributes']) ? $element['#attributes'] : array();
3806

    
3807
  // Style the label as class option to display inline with the element.
3808
  if ($element['#title_display'] == 'after') {
3809
    $attributes['class'][] = 'option';
3810
  }
3811
  // Show label only to screen readers to avoid disruption in visual flows.
3812
  elseif ($element['#title_display'] == 'invisible') {
3813
    $attributes['class'][] = 'element-invisible';
3814
  }
3815

    
3816
   $attributes['class'][] = 'webform-inline-radio';
3817
   if (!empty($element['#id'])) {
3818
     $attributes['for'] = $element['#id'];
3819
   }
3820

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

    
3825
/**
3826
 * Theme the headers when sending an email from webform.
3827
 *
3828
 * @param $node
3829
 *   The complete node object for the webform.
3830
 * @param $submission
3831
 *   The webform submission of the user.
3832
 * @param $email
3833
 *   If you desire to make different e-mail headers depending on the recipient,
3834
 *   you can check the $email['email'] property to output different content.
3835
 *   This will be the ID of the component that is a conditional e-mail
3836
 *   recipient. For the normal e-mails, it will have the value of 'default'.
3837
 * @return
3838
 *   An array of headers to be used when sending a webform email. If headers
3839
 *   for "From", "To", or "Subject" are set, they will take precedence over
3840
 *   the values set in the webform configuration.
3841
 */
3842
function theme_webform_mail_headers($variables) {
3843
  $headers = array(
3844
    'X-Mailer' => 'Drupal Webform (PHP/' . phpversion() . ')',
3845
  );
3846
  return $headers;
3847
}
3848

    
3849
/**
3850
 * Check if current user has a draft of this webform, and return the sid.
3851
 */
3852
function _webform_fetch_draft_sid($nid, $uid) {
3853
  // Detect whether a webform draft is being edited. If so, that is the one that
3854
  // should be returned.
3855
  if (isset($_POST['form_id']) && stripos($_POST['form_id'], 'webform_client_form_') === 0 &&
3856
      !empty($_POST['details']['sid']) && empty($_POST['details']['finished'])) {
3857
    // A draft is already being edited
3858
    $sid = $_POST['details']['sid'];
3859
  }
3860
  else {
3861
    $sid = db_select('webform_submissions')
3862
      ->fields('webform_submissions', array('sid'))
3863
      ->condition('nid', $nid)
3864
      ->condition('uid', $uid)
3865
      ->condition('is_draft', 1)
3866
      ->orderBy('submitted', 'DESC')
3867
      ->execute()
3868
      ->fetchField();
3869

    
3870
    if ($sid) {
3871
      $context = array(
3872
        'nid' => $nid,
3873
        'uid' => $uid,
3874
      );
3875
      drupal_alter('webform_draft', $sid, $context);
3876
    }
3877
  }
3878
  return $sid;
3879
}
3880

    
3881
/**
3882
 * Returns a new or cached WebformConditionals object for the specified node.
3883
 *
3884
 * @param object $node
3885
 *   The loaded webform node.
3886
 * @returns object
3887
 *   Object of type WebformConditionals, possibly with the conditionals already
3888
 *   analyzed for dependencies.
3889
 */
3890
function webform_get_conditional_sorter($node) {
3891
  return WebformConditionals::factory($node);
3892
}
3893

    
3894
/**
3895
 * This function is deprecated! Use webform_replace_tokens() instead.
3896
 *
3897
 * @deprecated
3898
 */
3899
function _webform_filter_values($string, $node = NULL, $submission = NULL, $email = NULL, $strict = TRUE) {
3900
  $output = webform_replace_tokens($string, $node, $submission, $email, $strict);
3901
  return $strict ? webform_filter_xss($output) : $output;
3902
}
3903

    
3904
/*
3905
 * Replace tokens with Webform contexts populated.
3906
 *
3907
 * @param $string
3908
 *   The string to have its tokens replaced.
3909
 * @param $node
3910
 *   If replacing node-level tokens, the node for which tokens will be created.
3911
 * @param $submission
3912
 *   If replacing submission-level tokens, the submission for which tokens will
3913
 *   be created.
3914
 * @param $email
3915
 *   If replacing tokens within the context of an e-mail, the Webform e-mail
3916
 *   settings array.
3917
 * @param $sanitize
3918
 *   Boolean or format name value indicating if the results will be displayed as
3919
 *   HTML output. If FALSE, the contents returned will be unsanitized. This will
3920
 *   also result in all Webform submission tokens being returned as plain-text,
3921
 *   without HTML markup, in preparation for e-mailing or other text-only
3922
 *   purposes (default values, etc.). If TRUE, the tokens only are sanitized by
3923
 *   token_replace. Otherwise $sanitize is the machine name of an import filter
3924
 *   to be used with check_markup().
3925
 */
3926
function webform_replace_tokens($string, $node = NULL, $submission = NULL, $email = NULL, $sanitize = FALSE) {
3927
  // Don't do any filtering if the string is empty.
3928
  if (!strlen(trim($string)) || !webform_variable_get('webform_token_access')) {
3929
    return $string;
3930
  }
3931

    
3932
  $token_data = array();
3933
  if ($node) {
3934
    $token_data['node'] = $node;
3935
  }
3936
  if ($submission) {
3937
    $token_data['webform-submission'] = $submission;
3938
  }
3939
  if ($email) {
3940
    $token_data['webform-email'] = $email;
3941
  }
3942
  $clear = is_bool($sanitize);
3943
  $string = token_replace($string, $token_data, array('clear' => $clear, 'sanitize' => $sanitize === TRUE));
3944
  if (!$clear) {
3945
    $string = webform_replace_tokens_clear(check_markup($string, $sanitize));
3946
  }
3947
  return $string;
3948
}
3949

    
3950
/**
3951
 * Removes tokens from string.
3952
 *
3953
 * Call this function in cases where you need to remove unreplaced tokens but
3954
 * can't call webform_replace_tokens() with the option $clear = TRUE.
3955
 *
3956
 * In some cases the function token_replace() in webform_replace_tokens() needs
3957
 * to be called with the option 'clear' => FALSE, to not remove input filters.
3958
 * For security reasons webform_replace_tokens() is called before
3959
 * check_markup(), where input filters get replaced. Tokens won't be replaced if
3960
 * there is no value provided. These tokens i.e. [current-page:query:*] needs to
3961
 * be removed to not show up in the output.
3962
 *
3963
 * Note: This function was previously named webform_clear_tokens, which
3964
 * conflicted with the webform_clear module, being called as hook_tokens.
3965
 *
3966
 * @param string $text
3967
 *   The text to have its tokens removed.
3968
 * @see token_replace()
3969
 */
3970
function webform_replace_tokens_clear($text) {
3971
  if (empty($text) || !webform_variable_get('webform_token_access')) {
3972
    return $text;
3973
  }
3974

    
3975
  $text_tokens = token_scan($text);
3976
  if (empty($text_tokens)) {
3977
    return $text;
3978
  }
3979

    
3980
  $replacements = array();
3981
  foreach ($text_tokens as $type => $tokens) {
3982
    $replacements += array_fill_keys($tokens, '');
3983
  }
3984

    
3985
  $tokens = array_keys($replacements);
3986
  $values = array_values($replacements);
3987

    
3988
  return str_replace($tokens, $values, $text);
3989
}
3990

    
3991
/**
3992
 * Replace tokens within a URL, encoding the parts within the query string.
3993
 *
3994
 * @param string $redirect_url
3995
 *   The redirect URL, with everything other than tokens already URL encoded.
3996
 * @param $node
3997
 *   If replacing node-level tokens, the node for which tokens will be created.
3998
 * @param $submission
3999
 *   If replacing submission-level tokens, the submission for which tokens will
4000
 *   be created.
4001
 * @return array
4002
 *   An array of path and url() options, suitable for a redirect or drupal_goto.
4003
 */
4004
function webform_replace_url_tokens($redirect_url, $node = NULL, $submission = NULL) {
4005
  // Parse the url into its components.
4006
  $parsed_redirect_url = drupal_parse_url($redirect_url);
4007
  // Replace tokens in each component.
4008
  $parsed_redirect_url['path'] = webform_replace_tokens($parsed_redirect_url['path'], $node, $submission);
4009
  if (!empty($parsed_redirect_url['query'])) {
4010
    foreach ($parsed_redirect_url['query'] as $key => $value) {
4011
      $parsed_redirect_url['query'][$key] = trim(webform_replace_tokens($value, $node, $submission));
4012
    }
4013
  }
4014
  $parsed_redirect_url['fragment'] = webform_replace_tokens($parsed_redirect_url['fragment'], $node, $submission);
4015
  // Determine whether the path is internal or external. Paths which contain the site's
4016
  // base url are still considered internal. #webform_external is private to webform.
4017
  $parsed_redirect_url['#webform_external'] = url_is_external($parsed_redirect_url['path']);
4018
  foreach (array(NULL, TRUE, FALSE) as $https) {
4019
    if (stripos($parsed_redirect_url['path'], url('', array('absolute' => TRUE, 'https' => $https))) === 0) {
4020
      $parsed_redirect_url['#webform_external'] = FALSE;
4021
    }
4022
  }
4023
  // Return an array suitable for a form redirect or drupal_goto.
4024
  return array($parsed_redirect_url['path'], $parsed_redirect_url);
4025
}
4026

    
4027
/**
4028
 * Replace tokens in descriptions and sanitize according to Webform settings.
4029
 */
4030
function webform_filter_descriptions($string, $node = NULL, $submission = NULL) {
4031
  return strlen($string) == 0 ? '' : webform_filter_xss(webform_replace_tokens($string, $node, $submission));
4032
}
4033

    
4034
/**
4035
 * Deprecated! Use webform_filter_descriptions() instead.
4036
 *
4037
 * @deprecated
4038
 */
4039
function _webform_filter_descriptions($string, $node = NULL, $submission = NULL) {
4040
  return webform_filter_descriptions($string, $node, $submission);
4041
}
4042

    
4043
/**
4044
 * Filter labels for display by running through XSS checks.
4045
 */
4046
function webform_filter_xss($string) {
4047
  static $allowed_tags;
4048
  $allowed_tags = isset($allowed_tags) ? $allowed_tags : webform_variable_get('webform_allowed_tags');
4049
  return filter_xss($string, $allowed_tags);
4050
}
4051

    
4052
/**
4053
 * Deprecated! Use webform_filter_xss() instead!
4054
 *
4055
 * @deprecated
4056
 */
4057
function _webform_filter_xss($string) {
4058
  return webform_filter_xss($string);
4059
}
4060

    
4061
/**
4062
 * Utility function to ensure that a webform record exists in the database.
4063
 *
4064
 * @param $node
4065
 *   The node object to check if a database entry exists.
4066
 * @return
4067
 *   This function should always return TRUE if no errors were encountered,
4068
 *   ensuring that a webform table row has been created. Will return FALSE if
4069
 *   a record does not exist and a new one could not be created.
4070
 */
4071
function webform_ensure_record(&$node) {
4072
  if (!$node->webform['record_exists']) {
4073
    // Even though webform_node_insert() would set this property to TRUE,
4074
    // we set record_exists to trigger a difference from the defaults.
4075
    $node->webform['record_exists'] = TRUE;
4076
    webform_node_insert($node);
4077
  }
4078
  return $node->webform['record_exists'];
4079
}
4080

    
4081
/**
4082
 * Utility function to check if a webform record is necessary in the database.
4083
 *
4084
 * If the node is no longer using any webform settings, this function will
4085
 * delete the settings from the webform table. Note that this function will NOT
4086
 * delete rows from the webform table if the node-type is exclusively used for
4087
 * webforms (per the "webform_node_types_primary" variable).
4088
 *
4089
 * @param $node
4090
 *   The node object to check if a database entry is still required.
4091
 * @return
4092
 *   Returns TRUE if the webform still has a record in the database. Returns
4093
 *   FALSE if the webform does not have a record or if the previously existing
4094
 *   record was just deleted.
4095
 */
4096
function webform_check_record(&$node) {
4097
  $webform = $node->webform;
4098
  $webform['record_exists'] = FALSE;
4099
  unset($webform['nid']);
4100

    
4101
  // Don't include empty values in the comparison, this makes it so modules that
4102
  // extend Webform with empty defaults won't affect cleanup of rows.
4103
  $webform = array_filter($webform);
4104
  $defaults = array_filter(webform_node_defaults());
4105
  if ($webform == $defaults && !in_array($node->type, webform_variable_get('webform_node_types_primary'))) {
4106
    webform_node_delete($node);
4107
    $node->webform = webform_node_defaults();
4108
  }
4109
  return $node->webform['record_exists'];
4110
}
4111

    
4112
/**
4113
 * Given a form_key and a list of form_key parents, determine the cid.
4114
 *
4115
 * @param $node
4116
 *   A fully loaded node object.
4117
 * @param $form_key
4118
 *   The form key for which we're finding a cid.
4119
 * @param $parent
4120
 *   The cid of the parent component.
4121
 */
4122
function webform_get_cid(&$node, $form_key, $pid) {
4123
  foreach ($node->webform['components'] as $cid => $component) {
4124
    if ($component['form_key'] == $form_key && $component['pid'] == $pid) {
4125
      return $cid;
4126
    }
4127
  }
4128
}
4129

    
4130
/**
4131
 * Find the label of a given page based on page breaks.
4132
 *
4133
 * @param $node
4134
 *   The webform node.
4135
 * @param $form_state
4136
 *   The form's state, if available
4137
 * @return array
4138
 *   An array of all page labels, indexed by page number.
4139
 */
4140
function webform_page_labels($node, $form_state = array()) {
4141
  $page_count = 1;
4142
  $page_labels = array();
4143
  $page_labels[0] = t($node->webform['progressbar_label_first']);
4144
  foreach ($node->webform['components'] as $component) {
4145
    if ($component['type'] == 'pagebreak') {
4146
      $page_labels[$page_count] = $component['name'];
4147
      $page_count++;
4148
    }
4149
  }
4150
  if ($node->webform['preview'] || !empty($form_state['webform']['preview'])) {
4151
    $page_labels[] = $node->webform['preview_title'] ? t($node->webform['preview_title']) : t('Preview');
4152
  }
4153
  if ($node->webform['progressbar_include_confirmation']) {
4154
    $page_labels[] = t($node->webform['progressbar_label_confirmation']);
4155
  }
4156
  return $page_labels;
4157
}
4158

    
4159
/**
4160
 * Retrieve a Drupal variable with the appropriate default value.
4161
 */
4162
function webform_variable_get($variable) {
4163
  switch ($variable) {
4164
    case 'webform_blocks':
4165
      $result = variable_get('webform_blocks', array());
4166
      break;
4167
    case 'webform_tracking_mode':
4168
      $result = variable_get('webform_tracking_mode', 'cookie');
4169
      break;
4170
    case 'webform_allowed_tags':
4171
      $result = variable_get('webform_allowed_tags', array('a', 'em', 'strong', 'code', 'img'));
4172
      break;
4173
    case 'webform_email_address_format':
4174
      $result = variable_get('webform_email_address_format', 'long');
4175
      break;
4176
    case 'webform_email_address_individual':
4177
      $result = variable_get('webform_email_address_individual', 0);
4178
      break;
4179
    case 'webform_default_from_name':
4180
      $result = variable_get('webform_default_from_name', variable_get('site_name', ''));
4181
      break;
4182
    case 'webform_default_from_address':
4183
      $result = variable_get('webform_default_from_address', variable_get('site_mail', ini_get('sendmail_from')));
4184
      break;
4185
    case 'webform_default_subject':
4186
      $result = variable_get('webform_default_subject', t('Form submission from: [node:title]'));
4187
      break;
4188
    case 'webform_email_replyto':
4189
      $result = variable_get('webform_email_replyto', TRUE);
4190
      break;
4191
    case 'webform_email_html_capable':
4192
      $result = variable_get('webform_email_html_capable', FALSE);
4193
      break;
4194
    case 'webform_default_format':
4195
      $result = variable_get('webform_default_format', 0);
4196
      break;
4197
    case 'webform_format_override':
4198
      $result = variable_get('webform_format_override', 0);
4199
      break;
4200
    case 'webform_email_select_max':
4201
      $result = variable_get('webform_email_select_max', 50);
4202
      break;
4203
    case 'webform_node_types':
4204
      $result = webform_node_types();
4205
      break;
4206
    case 'webform_node_types_primary':
4207
      $result = variable_get('webform_node_types_primary', array('webform'));
4208
      break;
4209
    case 'webform_date_type':
4210
      $result = variable_get('webform_date_type', 'medium');
4211
      break;
4212
    case 'webform_export_format':
4213
      module_load_include('inc', 'webform', 'includes/webform.export');
4214
      $options = webform_export_list();
4215
      $result = variable_get('webform_export_format', 'excel');
4216
      $result = isset($options[$result]) ? $result : key($options);
4217
      break;
4218
    case 'webform_csv_delimiter':
4219
      $result = variable_get('webform_csv_delimiter', '\t');
4220
      break;
4221
    case 'webform_csv_line_ending':
4222
      $result = variable_get('webform_csv_line_ending', "\n");
4223
      break;
4224
    case 'webform_export_wordwrap':
4225
      $result = variable_get('webform_export_wordwrap', 0);
4226
      break;
4227
    case 'webform_excel_legacy_exporter':
4228
      $result = variable_get('webform_excel_legacy_exporter', 0);
4229
      break;
4230
    case 'webform_progressbar_style':
4231
      $result = variable_get('webform_progressbar_style', array('progressbar_bar', 'progressbar_pagebreak_labels', 'progressbar_include_confirmation'));
4232
      break;
4233
    case 'webform_progressbar_label_first':
4234
      $result = variable_get('webform_progressbar_label_first', t('Start'));
4235
      break;
4236
    case 'webform_progressbar_label_confirmation':
4237
      $result = variable_get('webform_progressbar_label_confirmation', t('Complete'));
4238
      break;
4239
    case 'webform_table':
4240
      $result = variable_get('webform_table', FALSE);
4241
      break;
4242
    case 'webform_submission_access_control':
4243
      $result = variable_get('webform_submission_access_control', 1);
4244
      break;
4245
    case 'webform_token_access':
4246
      $result = variable_get('webform_token_access', 1);
4247
      break;
4248
    case 'webform_update_batch_size':
4249
      $result = variable_get('webform_update_batch_size', 100);
4250
      break;
4251
    case 'webform_disabled_components':
4252
      $result = variable_get('webform_disabled_components', array());
4253
      break;
4254
  }
4255
  return $result;
4256
}
4257

    
4258
/**
4259
 * Output the contents of token help used throughout Webform.
4260
 *
4261
 * In earlier versions of Token, a fieldset is used to show all the tokens.
4262
 * Later versions now use a modal dialog that is accessed through a link. If
4263
 * Token module is not available, a message should be displayed.
4264
 */
4265
function theme_webform_token_help($variables) {
4266
  if (!webform_variable_get('webform_token_access')) {
4267
    return '';
4268
  }
4269
  $groups = $variables['groups'];
4270
  module_load_include('inc', 'token', 'token.pages');
4271

    
4272
  // Assume dialogs are not supported, show as a fieldset.
4273
  $help = array(
4274
    '#title' => t('Token values'),
4275
    '#type' => 'fieldset',
4276
    '#collapsible' => TRUE,
4277
    '#collapsed' => TRUE,
4278
    '#attributes' => array('class' => array('collapsible', 'collapsed')),
4279
    'help' => array(
4280
      '#markup' => '<p>' . t('This field supports dynamic token values. Common values might be [current-user:mail] or [node:title].') . '</p>',
4281
    ),
4282
    'token_tree' => array(
4283
      '#theme' => 'token_tree',
4284
      '#token_types' => $groups,
4285
    ),
4286
  );
4287

    
4288
  if (!module_exists('token')) {
4289
    // No token module at all. Display a simple suggestion to enable it.
4290
    $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>';
4291
    unset($help['token_tree']);
4292
  }
4293
  elseif (function_exists('token_page_output_tree')) {
4294
    // Token supports dialogs: display simply as a link.
4295
    $help = $help['token_tree'];
4296
    $help['#dialog'] = TRUE;
4297
  }
4298

    
4299
  return render($help);
4300
}
4301

    
4302
/**
4303
 * Convert a name into an identifier that is safe for machine names, classes,
4304
 * and other ASCII uses.
4305
 */
4306
function _webform_safe_name($name) {
4307
  $new = trim($name);
4308
  $new = _webform_transliterate($new);
4309
  $new = str_replace(array(' ', '-', '/'), array('_', '_', '_'), $new);
4310
  $new = drupal_strtolower($new);
4311
  $new = preg_replace('/[^a-z0-9_]/', '', $new);
4312
  return $new;
4313
}
4314

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

    
4327
/**
4328
 * Given an email address and a name, format an e-mail address.
4329
 *
4330
 * The address can be the cid of a component with multiple values. When $single
4331
 * is true, a single address is return (the first of any multiples). When not
4332
 * true, an array of addresses is returned.
4333
 *
4334
 * Note that multiple names could be used with multiple addresses, but this
4335
 * capability is not currently possible with the webform UI. Separate names
4336
 * are only used with the From address, which is always single.
4337
 *
4338
 * @param $address
4339
 *   The e-mail address.
4340
 * @param $name
4341
 *   The name to be used in the formatted address. If the address contains a
4342
 *   name in 'Some Name <somename@example.com>' format, $name is ignored.
4343
 * @param $node
4344
 *   The webform node if replacements will be done.
4345
 * @param $submission
4346
 *   The webform submission values if replacements will be done.
4347
 * @param $encode
4348
 *   Encode the text for use in an e-mail.
4349
 * @param $single
4350
 *   Force a single value to be returned, even if a component expands to
4351
 *   multiple addresses. This is useful to ensure a single e-mail will be
4352
 *   returned for the "From" address.
4353
 * @param $format
4354
 *   The e-mail format, defaults to the site-wide setting. May be "short",
4355
 *   "long", or NULL for the system default.
4356
 * @param $mapping
4357
 *   A mapping array to be applied to the address values.
4358
 * @return string|array
4359
 *   The formatted e-mail address -- or addresses (if not $single)
4360
 */
4361
function webform_format_email_address($address, $name, $node = NULL, $submission = NULL, $encode = TRUE, $single = TRUE, $format = NULL, $mapping = NULL) {
4362
  if (!isset($format)) {
4363
    $format = webform_variable_get('webform_email_address_format');
4364
  }
4365

    
4366
  if ($name == 'default') {
4367
    $name = webform_variable_get('webform_default_from_name');
4368
  }
4369
  elseif (is_numeric($name) && isset($node->webform['components'][$name])) {
4370
    if (isset($submission->data[$name])) {
4371
      $component = $node->webform['components'][$name];
4372
      $name = $submission->data[$name];
4373

    
4374
      // Convert the FROM name to be the label of select lists.
4375
      if (webform_component_implements($component['type'], 'options')) {
4376
        $options = webform_component_invoke($component['type'], 'options', $component);
4377
        foreach ($name as &$one_name) {
4378
          $one_name = isset($options[$one_name]) ? $options[$one_name] : $one_name;
4379
        }
4380
        unset($one_name); // Drop PHP reference.
4381
      }
4382
    }
4383
    else {
4384
      $name = t('Value of !component', array('!component' => $node->webform['components'][$name]['name']));
4385
    }
4386
  }
4387
  elseif (!isset($name)) {
4388
    $name = '';
4389
  }
4390

    
4391
  if ($address == 'default') {
4392
    $address = webform_variable_get('webform_default_from_address');
4393
  }
4394
  elseif (is_numeric($address) && isset($node->webform['components'][$address])) {
4395
    if (isset($submission->data[$address])) {
4396
      $values = $submission->data[$address];
4397
      $address = array();
4398
      foreach ($values as $value) {
4399
        if (isset($mapping) && isset($mapping[$value])) {
4400
          $value = $mapping[$value];
4401
        }
4402
        $address = array_merge($address, explode(',', $value));
4403
      }
4404
    }
4405
    else {
4406
      $address = t('Value of "!component"', array('!component' => $node->webform['components'][$address]['name']));
4407
    }
4408
  }
4409

    
4410
  // Convert single values to an array to simplify processing.
4411
  $address = is_array($address) ? $address : explode(',', $address);
4412
  $name = is_array($name) ? $name : array($name);
4413
  $name_shortage = count($address) - count($name);
4414
  if ($name_shortage > 0) {
4415
    $name += array_fill(count($name), $name_shortage, $name[0]);
4416
  }
4417

    
4418
  foreach ($address as $key => $individual_address) {
4419
    $individual_address = webform_replace_tokens($individual_address, $node, $submission);
4420
    $email_parts = webform_parse_email_address($individual_address);
4421
    if ($format == 'long' && !empty($name[$key]) && !strlen($email_parts['name'])) {
4422
      $individual_name = $name[$key];
4423
      $individual_name = webform_replace_tokens($individual_name, $node, $submission);
4424
      if ($encode) {
4425
        $individual_name = mime_header_encode($individual_name);
4426
      }
4427
      $individual_name = trim($individual_name);
4428
      $individual_address = '"' . $individual_name . '" <' . $individual_address . '>';
4429
    }
4430
    $address[$key] = $individual_address;
4431
  }
4432

    
4433
  return $single ? reset($address) : $address;
4434

    
4435
}
4436

    
4437
/**
4438
 * Validates an email form element.
4439
 *
4440
 * @param string $emails
4441
 *   An email or list of comma-seperated email addresses. Passed by reference.
4442
 *   Empty emails will be eliminated, and mutiple addresses will be seperated
4443
 *   with a comma and space.
4444
 * @param string $form_name
4445
 *   The name of the form element to receive an error, in form_set_error format.
4446
 * @param boolean $allow_empty
4447
 *   TRUE if optional. FALSE if required.
4448
 * @param boolean $allow_multiple
4449
 *   TRUE if a list of emails is allowed. FALSE if only one.
4450
 * @param boolean $allow_tokens
4451
 *   TRUE if any token should be assumed to contain a valid e-mail address.
4452
 * @param string $format
4453
 *   'short', 'long', or NULL (for default) format. Long format has a name and
4454
 *   the address in angle brackets.
4455
 * @return integer|boolean
4456
 *   The number of valid addresses found, or FALSE for an invalid email found.
4457
 */
4458
function webform_email_validate(&$emails, $form_name, $allow_empty, $allow_multiple, $allow_tokens, $format = NULL) {
4459
  $nr_valid = webform_valid_email_address($emails, $allow_tokens, $format);
4460
  if ($nr_valid === FALSE) {
4461
    form_set_error($form_name, t('The entered e-mail address "@email" does not appear valid.', array('@email' => $emails)));
4462
  }
4463
  elseif ($nr_valid === 0 && !$allow_empty) {
4464
    form_set_error($form_name, t('When adding a new custom e-mail, the e-mail field is required.'));
4465
  }
4466
  elseif ($nr_valid > 1 && !$allow_multiple) {
4467
    form_set_error($form_name, t('Only one e-mail address is allowed.'));
4468
  }
4469
  return $nr_valid;
4470
}
4471

    
4472
/**
4473
 * Validates email address(es) with optional name(s).
4474
 *
4475
 * @param string $emails
4476
 *   An email address, a list of comma-separated email addresses. If all the
4477
 *   addresses are valid, the list of trimmed, non-empty emails is returned by
4478
 *   reference.
4479
 * @param boolean $allow_tokens
4480
 *   TRUE if any token should be assumed to contain a valid e-mail address.
4481
 * @param string $format
4482
 *   'short', 'long', or NULL (for default) format. Long format has a name and
4483
 *   the address in angle brackets.
4484
 * @return boolean|integer
4485
 *   Returns FALSE if an invalid e-mail address was found, 0 if no email
4486
 *   address(es) were found, or the number of valid e-mail addresses found.
4487
 */
4488
function webform_valid_email_address(&$emails, $allow_tokens = FALSE, $format = NULL) {
4489
  $email_array = array_filter(array_map('trim', explode(',', $emails)));
4490
  $count = 0;
4491
  foreach ($email_array as $email) {
4492
    if ($allow_tokens && webform_variable_get('webform_token_access')) {
4493
      foreach (token_scan($email) as $tokens) {
4494
        foreach ($tokens as $token) {
4495
          $email = str_replace($token, 'admin@example.com', $email);
4496
        }
4497
      }
4498
    }
4499
    $matches = webform_parse_email_address($email, $format);
4500
    if (!valid_email_address($matches['address'])) {
4501
      return FALSE;
4502
    }
4503
    $count++;
4504
  }
4505
  $emails = implode(', ', $email_array);
4506
  return $count;
4507
}
4508

    
4509
/**
4510
 * Parses an e-mail address into name and address.
4511
 *
4512
 * @param string $email
4513
 *   The email address to be parsed, with an optional name.
4514
 * @param string $format
4515
 *   'short', 'long', or NULL (for default) format. Long format has a name and
4516
 *   the address in angle brackets.
4517
 * @return array
4518
 *   Associative array indexed by 'name' and 'address'.
4519
 */
4520
function webform_parse_email_address($email, $format = NULL) {
4521
  if (!$format) {
4522
    $format = webform_variable_get('webform_email_address_format');
4523
  }
4524
  if ($format == 'long') {
4525
    // Match e-mails of the form 'My Name <email@domain.com>' as follows:
4526
    // ^          = beginning of string
4527
    // "?         = optional quote
4528
    // ([^<]*?)   = match optional characters that aren't a < (non-greedy)
4529
    // "?         = optional quote
4530
    // SPACE*     = optional spaces
4531
    // (?:<(.*)>) = < matching stuff > (without the angle brakets)
4532
    // $          = end of string
4533
    preg_match('/^"?([^<]*?)"? *(?:<(.*)>)?$/', $email, $matches);
4534
    if (isset($matches[2]) && strlen($matches[2])) {
4535
      return array(
4536
        'name' => $matches[1],
4537
        'address' => $matches[2],
4538
      );
4539
    }
4540
  }
4541
  return array(
4542
    'name' => '',
4543
    'address' => $email,
4544
  );
4545
}
4546

    
4547
/**
4548
 * Given an email subject, format it with any needed replacements.
4549
 */
4550
function webform_format_email_subject($subject, $node = NULL, $submission = NULL) {
4551
  if ($subject == 'default') {
4552
    $subject = webform_variable_get('webform_default_subject');
4553
  }
4554
  elseif (is_numeric($subject) && isset($node->webform['components'][$subject])) {
4555
    $component = $node->webform['components'][$subject];
4556
    if (isset($submission->data[$subject])) {
4557
      $display_function = '_webform_display_' . $component['type'];
4558
      $value = $submission->data[$subject];
4559

    
4560
      // Convert the value to a clean text representation if possible.
4561
      if (function_exists($display_function)) {
4562
        $display = $display_function($component, $value, 'text');
4563
        $display['#theme_wrappers'] = array();
4564
        $display['#webform_component'] = $component;
4565
        $subject = str_replace("\n", ' ', drupal_render($display));
4566
      }
4567
      else {
4568
        $subject = $value;
4569
      }
4570
    }
4571
    else {
4572
      $subject = t('Value of "!component"', array('!component' => $component['name']));
4573
    }
4574
  }
4575

    
4576
  // Convert arrays to strings (may happen if checkboxes are used as the value).
4577
  if (is_array($subject)) {
4578
    $subject = reset($subject);
4579
  }
4580

    
4581
  return webform_replace_tokens($subject, $node, $submission);
4582
}
4583

    
4584
/**
4585
 * Convert an array of components into a tree
4586
 */
4587
function _webform_components_tree_build($src, &$tree, $parent, &$page_count) {
4588
  foreach ($src as $cid => $component) {
4589
    if ($component['pid'] == $parent) {
4590
      _webform_components_tree_build($src, $component, $cid, $page_count);
4591
      if ($component['type'] == 'pagebreak') {
4592
        $page_count++;
4593
      }
4594
      $tree['children'][$cid] = $component;
4595
      $tree['children'][$cid]['page_num'] = $page_count;
4596
    }
4597
  }
4598
  return $tree;
4599
}
4600

    
4601
/**
4602
 * Flatten a component tree into a flat list.
4603
 */
4604
function _webform_components_tree_flatten($tree) {
4605
  $components = array();
4606
  foreach ($tree as $cid => $component) {
4607
    if (isset($component['children'])) {
4608
      unset($component['children']);
4609
      $components[$cid] = $component;
4610
      // array_merge() can't be used here because the keys are numeric.
4611
      $children = _webform_components_tree_flatten($tree[$cid]['children']);
4612
      foreach ($children as $ccid => $ccomponent) {
4613
        $components[$ccid] = $ccomponent;
4614
      }
4615
    }
4616
    else {
4617
      $components[$cid] = $component;
4618
    }
4619
  }
4620
  return $components;
4621
}
4622

    
4623
/**
4624
 * Helper for the uasort in webform_tree_sort()
4625
 */
4626
function _webform_components_sort($a, $b) {
4627
  if ($a['weight'] == $b['weight']) {
4628
    return strcasecmp($a['name'], $b['name']);
4629
  }
4630
  return ($a['weight'] < $b['weight']) ? -1 : 1;
4631
}
4632

    
4633
/**
4634
 * Sort each level of a component tree by weight and name
4635
 */
4636
function _webform_components_tree_sort($tree) {
4637
  if (isset($tree['children']) && is_array($tree['children'])) {
4638
    $children = array();
4639
    uasort($tree['children'], '_webform_components_sort');
4640
    foreach ($tree['children'] as $cid => $component) {
4641
      $children[$cid] = _webform_components_tree_sort($component);
4642
    }
4643
    $tree['children'] = $children;
4644
  }
4645
  return $tree;
4646
}
4647

    
4648
/**
4649
 * Get a list of all available component definitions.
4650
 */
4651
function webform_components($include_disabled = FALSE, $reset = FALSE) {
4652
  static $components, $enabled;
4653

    
4654
  if (!isset($components) || $reset) {
4655
    $components = array();
4656
    $disabled = array_flip(webform_variable_get('webform_disabled_components'));
4657
    foreach (module_implements('webform_component_info') as $module) {
4658
      $module_components = module_invoke($module, 'webform_component_info');
4659
      foreach ($module_components as $type => $info) {
4660
        $module_components[$type]['module'] = $module;
4661
        $module_components[$type]['enabled'] = !array_key_exists($type, $disabled);
4662
      }
4663
      $components += $module_components;
4664
    }
4665
    drupal_alter('webform_component_info', $components);
4666
    uasort($components, function($a, $b) {
4667
      return strnatcasecmp($a['label'], $b['label']);
4668
    });
4669
    $enabled = array_diff_key($components, $disabled);
4670
  }
4671

    
4672
  return $include_disabled ? $components : $enabled;
4673
}
4674

    
4675
/**
4676
 * Build a list of components suitable for use as select list options.
4677
 */
4678
function webform_component_options($include_disabled = FALSE) {
4679
  $component_info = webform_components($include_disabled);
4680
  $options = array();
4681
  foreach ($component_info as $type => $info) {
4682
    $options[$type] = $info['label'];
4683
  }
4684
  return $options;
4685
}
4686

    
4687
/**
4688
 * Load a component file into memory.
4689
 *
4690
 * @param $component_type
4691
 *   The string machine name of a component.
4692
 */
4693
function webform_component_include($component_type) {
4694
  static $included = array();
4695

    
4696
  // No need to load components that have already been added once.
4697
  if (!isset($included[$component_type])) {
4698
    $components = webform_components(TRUE);
4699
    $included[$component_type] = TRUE;
4700

    
4701
    if (($info = $components[$component_type]) && isset($info['file'])) {
4702
      $pathinfo = pathinfo($info['file']);
4703
      $basename = basename($pathinfo['basename'], '.' . $pathinfo['extension']);
4704
      $path = (!empty($pathinfo['dirname']) ? $pathinfo['dirname'] . '/' : '') . $basename;
4705
      module_load_include($pathinfo['extension'], $info['module'], $path);
4706
    }
4707
  }
4708
}
4709

    
4710
/**
4711
 * Invoke a component callback.
4712
 *
4713
 * @param $type
4714
 *   The component type as a string.
4715
 * @param $callback
4716
 *   The callback to execute.
4717
 * @param ...
4718
 *   Any additional parameters required by the $callback.
4719
 */
4720
function webform_component_invoke($type, $callback) {
4721
  $args = func_get_args();
4722
  $type = array_shift($args);
4723
  $callback = array_shift($args);
4724
  $function = '_webform_' . $callback . '_' . $type;
4725
  webform_component_include($type);
4726
  if (function_exists($function)) {
4727
    return call_user_func_array($function, $args);
4728
  }
4729
}
4730

    
4731
/**
4732
 * Check if a component implements a particular hook.
4733
 *
4734
 * @param $type
4735
 *   The component type as a string.
4736
 * @param $callback
4737
 *   The callback to check.
4738
 */
4739
function webform_component_implements($type, $callback) {
4740
  $function = '_webform_' . $callback . '_' . $type;
4741
  webform_component_include($type);
4742
  return function_exists($function);
4743
}
4744

    
4745
/**
4746
 * Form API #process function to expand a webform conditional element.
4747
 */
4748
function webform_conditional_expand($element) {
4749
  module_load_include('inc', 'webform', 'includes/webform.conditionals');
4750
  return _webform_conditional_expand($element);
4751
}
4752

    
4753
/**
4754
 * Add class and wrapper class attributes to an element.
4755
 */
4756
function _webform_component_classes(&$element, $component) {
4757
  if (isset($component['extra']['css_classes']) && drupal_strlen($component['extra']['css_classes'])) {
4758
    $element['#attributes']['class'] = isset($element['#attributes']['class']) ? $element['#attributes']['class'] : array();
4759
    $element['#attributes']['class'] = array_merge($element['#attributes']['class'], explode(' ' , $component['extra']['css_classes']));
4760
  }
4761
  if (isset($component['extra']['wrapper_classes']) && drupal_strlen($component['extra']['wrapper_classes'])) {
4762
    $element['#wrapper_attributes']['class'] = isset($element['#wrapper_attributes']['class']) ? $element['#wrapper_attributes']['class'] : array();
4763
    $element['#wrapper_attributes']['class'] = array_merge($element['#wrapper_attributes']['class'], explode(' ' , $component['extra']['wrapper_classes']));
4764
  }
4765
}
4766

    
4767
/**
4768
 * Disable the Drupal page cache.
4769
 */
4770
function webform_disable_page_cache() {
4771
  drupal_page_is_cacheable(FALSE);
4772
}
4773

    
4774
/**
4775
 * Set the necessary breadcrumb for the page we are on.
4776
 *
4777
 * @param object $node
4778
 *   The loaded webform node.
4779
 * @param boolean|object $submission
4780
 *   The submission if the current page is viewing or dealing with a submission,
4781
 *   or TRUE to just include the webform node in the breadcrumbs (used for
4782
 *   the submission completion confirmation page), or NULL for no extra
4783
 *   processing
4784
 */
4785
function webform_set_breadcrumb($node, $submission = NULL) {
4786
  $node_path = "node/{$node->nid}";
4787

    
4788
  // Set the href of the current menu item to be the node's path. This has two
4789
  // effects. The active trail will be to the node's prefered menu tree
4790
  // location, expanding the menu as appropriate. And the breadcrumbs will be
4791
  // set as if the current page were under the node's preferred location.
4792
  // Note that menu_tree_set_path() could be used to set the path for the menu,
4793
  // but it will not affect the breadcrumbs when the webform is not in the
4794
  // default menu.
4795
  menu_set_item(NULL, array('href' => $node_path) + menu_get_item());
4796

    
4797
  if ($submission) {
4798
    $breadcrumb = menu_get_active_breadcrumb();
4799

    
4800
    // Append the node title (or its menu name), in case it isn't in the path already.
4801
    $active_trail = menu_get_active_trail();
4802
    $last_active = end($active_trail);
4803
    $breadcrumb[] = $last_active['href'] === $node_path && !empty($last_active['in_active_trail'])
4804
                      ? l($last_active['title'], $node_path, $last_active['localized_options'])
4805
                      : l($node->title, $node_path);
4806

    
4807
    // Setting the current menu href will cause the submission title and current
4808
    // tab (if not the default tab) to be added to the active path when the
4809
    // webform is in the default location in the menu (node/NID). The title
4810
    // is desirable, but the tab name (e.g. Edit or Delete) isn't.
4811
    if (preg_match('/href=".*"/', end($breadcrumb), $matches)) {
4812
      foreach ($breadcrumb as $index => $link) {
4813
        if (stripos($link, $matches[0]) !== FALSE) {
4814
          $breadcrumb = array_slice($breadcrumb, 0, $index + 1);
4815
          break;
4816
        }
4817
      }
4818
  }
4819

    
4820
    // If the user is dealing with a submission, then the breadcrumb should
4821
    // be fudged to allow them to return to a likely list of webforms.
4822
    // Note that this isn't necessarily where they came from, but it's the
4823
    // best guess available.
4824
    if (is_object($submission)) {
4825
      if (webform_results_access($node)) {
4826
        $breadcrumb[] = l(t('Webform results'), $node_path . '/webform-results');
4827
      }
4828
      elseif (user_access('access own webform results')) {
4829
        $breadcrumb[] = l(t('Submissions'), $node_path . '/submissions');
4830
      }
4831
    }
4832

    
4833
    drupal_set_breadcrumb($breadcrumb);
4834
  }
4835
}
4836

    
4837
/**
4838
 * Convert an ISO 8601 date or time into an array.
4839
 *
4840
 * This converts full format dates or times. Either a date or time may be
4841
 * provided, in which case only those portions will be returned. Dashes and
4842
 * colons must be used, never implied.
4843
 *
4844
 * Formats:
4845
 * Dates: YYYY-MM-DD
4846
 * Times: HH:MM:SS
4847
 * Datetimes: YYYY-MM-DDTHH:MM:SS
4848
 *
4849
 * @param $string
4850
 *   An ISO 8601 date, time, or datetime.
4851
 * @param $type
4852
 *   If wanting only specific fields back, specify either "date" or "time".
4853
 *   Leaving empty will return an array with both date and time keys, even if
4854
 *   some are empty. Returns an array with the following keys:
4855
 *   - year
4856
 *   - month
4857
 *   - day
4858
 *   - hour (in 24hr notation)
4859
 *   - minute
4860
 *   - second
4861
 */
4862
function webform_date_array($string, $type = NULL) {
4863
  $pattern = '/((\d{4}?)-(\d{2}?)-(\d{2}?))?(T?(\d{2}?):(\d{2}?):(\d{2}?))?/';
4864
  $matches = array();
4865
  preg_match($pattern, $string, $matches);
4866
  $matches += array_fill(0, 9, '');
4867

    
4868
  $return = array();
4869

    
4870
  // Check for a date string.
4871
  if ($type == 'date' || !isset($type)) {
4872
    $return['year'] = $matches[2] !== '' ? (int) $matches[2] : '';
4873
    $return['month'] = $matches[3] !== '' ? (int) $matches[3] : '';
4874
    $return['day'] = $matches[4] !== '' ? (int) $matches[4] : '';
4875
  }
4876

    
4877
  // Check for a time string.
4878
  if ($type == 'time' || !isset($type)) {
4879
    $return['hour'] = $matches[6] !== '' ? (int) $matches[6] : '';
4880
    $return['minute'] = $matches[7] !== '' ? (int) $matches[7] : '';
4881
    $return['second'] = $matches[8] !== '' ? (int) $matches[8] : '';
4882
  }
4883

    
4884
  return $return;
4885
}
4886

    
4887
/**
4888
 * Convert an array of a date or time into an ISO 8601 compatible string.
4889
 *
4890
 * @param $array
4891
 *   The array to convert to a date or time string.
4892
 * @param $type
4893
 *   If wanting a specific string format back specify either "date" or "time".
4894
 *   Otherwise a full ISO 8601 date and time string will be returned.
4895
 */
4896
function webform_date_string($array, $type = NULL) {
4897
  $string = '';
4898

    
4899
  if ($type == 'date' || !isset($type)) {
4900
    $string .= empty($array['year']) ? '0000' : sprintf('%04d', $array['year']);
4901
    $string .= '-';
4902
    $string .= empty($array['month']) ? '00' : sprintf('%02d', $array['month']);
4903
    $string .= '-';
4904
    $string .= empty($array['day']) ? '00' : sprintf('%02d', $array['day']);
4905
  }
4906

    
4907
  if (!isset($type)) {
4908
    $string .= 'T';
4909
  }
4910

    
4911
  if ($type == 'time' || !isset($type)) {
4912
    $string .= empty($array['hour']) ? '00' :  sprintf('%02d', $array['hour']);
4913
    $string .= ':';
4914
    $string .= empty($array['minute']) ? '00' :  sprintf('%02d', $array['minute']);
4915
    $string .= ':';
4916
    $string .= empty($array['second']) ? '00' :  sprintf('%02d', $array['second']);
4917
  }
4918

    
4919
  return $string;
4920
}
4921

    
4922
/**
4923
 * Get a date format according to the site settings.
4924
 *
4925
 * @param $type
4926
 *   A choice of 'short', 'medium', 'long' , or other user-defined date formats.
4927
 *   Use NULL for the webform-specific date format choosen in the webform
4928
 *   settings.
4929
 * @param array $exclude
4930
 *   An array containing 'day', 'month', and/or 'year' if they should be
4931
 *   removed from the format.
4932
 * @return string
4933
 *   A date/time format string.
4934
 */
4935
function webform_date_format($type = NULL, $exclude = array()) {
4936
  static $formats = array();
4937
  $id = $type . ':' . implode('', $exclude);
4938
  if (!isset($formats[$id])) {
4939
    $type_name = $type ? $type : webform_variable_get('webform_date_type');
4940

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

    
4944
    // Date/Time formatting characters
4945
    // WHAT           REQUIRED (at least 1) OPTIONAL (allowed but insufficient)
4946
    // ---------------------------------------------------------------------------
4947
    // Day-of-week                          DlNw
4948
    // Day            dj                    Stz
4949
    // Month          FmMn
4950
    // Year           oYy                   L
4951
    //
4952
    //                NOT ALLOWED
4953
    // --------------------------------------------------------------------------
4954
    // Time           aABgGhHisueIOPTZ
4955
    // Special        /.,-: <space>
4956

    
4957
    // Strip Time and Special characters from the beginning and end of format.
4958
    $date_format = trim($format, 'aABgGhHisueIOPTZ/.,-: ');
4959

    
4960
    // Ensure that a day, month, and year value are present. Use a default
4961
    // format if all the values are not found. This regular expression uses
4962
    // (?= ), the positive lookahead assertion. It asserts that there are some
4963
    // optional characters (.*) followed by one of the day, month, or year
4964
    // characters. Because it is an assertion, it doesn't consume the
4965
    // characters, so the day, month, and year can be in any order.
4966
    if (!preg_match('/(?=.*[dj])(?=.*[FmMn])(?=.*[oYy])/', $date_format)) {
4967
      $date_format = 'm/d/Y';
4968
    }
4969

    
4970
    // Remove any excluded portions.
4971
    $strip = array(
4972
      'day' => 'DlNwdjStz',
4973
      'month' => 'FmMn',
4974
      'year' => 'oYyL',
4975
    );
4976
    foreach ($exclude as $field) {
4977
      // Strip the format and any trailing /.,-: or space.
4978
      $date_format = preg_replace('#[' . $strip[$field] . ']+[/\.,\-: ]*#', '', $date_format);
4979
      $date_format = trim($date_format, '/.,-: ');
4980
    }
4981

    
4982
    $formats[$id] = $date_format;
4983
  }
4984

    
4985
  return $formats[$id];
4986
}
4987

    
4988
/**
4989
 * Return a date in the desired format taking into consideration user timezones.
4990
 */
4991
function webform_strtodate($format, $string, $timezone_name = NULL, $reference_timestamp = NULL) {
4992
  global $user;
4993

    
4994
  // Adjust the time based on the user or site timezone.
4995
  if (variable_get('configurable_timezones', 1) && $timezone_name == 'user' && $user->uid) {
4996
    $timezone_name = isset($GLOBALS['user']->timezone) ? $GLOBALS['user']->timezone : 'UTC';
4997
  }
4998
  // If the timezone is still empty or not set, use the site timezone.
4999
  if (empty($timezone_name) || $timezone_name == 'user') {
5000
    $timezone_name = variable_get('date_default_timezone', 'UTC');
5001
  }
5002

    
5003
  if (!empty($timezone_name) && class_exists('DateTimeZone')) {
5004
    // Suppress errors if encountered during string conversion. Exceptions are
5005
    // only supported for DateTime in PHP 5.3 and higher.
5006
    try {
5007
      @$timezone = new DateTimeZone($timezone_name);
5008
      if (isset($reference_timestamp)) {
5009
        // A reference for relative dates has been provided.
5010
        // 1) Convert the reference timestamp (in UTC) to a DateTime.
5011
        // 2) Set to time zone to the user or system timezone, recreating
5012
        //    the reference time in the appropriate time zone.
5013
        // 3) Set the time to midnight because when a non-referenced relative
5014
        //    date is created without a time, it is created at midnight (0:00).
5015
        // 4) Adjust to the specified relative (or absolute) time.
5016

    
5017
        @$datetime = new DateTime('@' . $reference_timestamp);
5018
        @$datetime->setTimezone($timezone)
5019
                  ->setTime(0, 0, 0)
5020
                  ->modify($string);
5021
      }
5022
      else {
5023
        @$datetime = new DateTime($string, $timezone);
5024
      }
5025
      return @$datetime->format($format);
5026
    }
5027
    catch (Exception $e) {
5028
      return '';
5029
    }
5030
  }
5031
  else {
5032
    return date($format, isset($reference_timestamp) ? strtotime($string, $reference_timestamp) : strtotime($string));
5033
  }
5034
}
5035

    
5036
/**
5037
 * Get a timestamp in GMT time, ensuring timezone accuracy.
5038
 */
5039
function webform_strtotime($date) {
5040
  $current_tz = date_default_timezone_get();
5041
  date_default_timezone_set('UTC');
5042
  $timestamp = strtotime($date);
5043
  date_default_timezone_set($current_tz);
5044
  return $timestamp;
5045
}
5046

    
5047
/**
5048
 * Wrapper function for i18n_string() if i18nstrings enabled.
5049
 */
5050
function webform_tt($name, $string, $langcode = NULL, $update = FALSE) {
5051
  if (function_exists('i18n_string')) {
5052
    $options = array(
5053
      'langcode' => $langcode,
5054
      'update' => $update,
5055
    );
5056
    return i18n_string($name, $string, $options);
5057
  }
5058
  else {
5059
    return $string;
5060
  }
5061
}
5062

    
5063
/**
5064
 * Returns an IP Address or anonymized IP Address for confidential webforms.
5065
 */
5066
function webform_ip_address($node) {
5067
  return $node->webform['confidential'] ? t('(unknown)') : ip_address();
5068
}
5069

    
5070
/**
5071
 * Implements hook_views_api().
5072
 */
5073
function webform_views_api() {
5074
  return array(
5075
    'api' => 3.0,
5076
    'path' => drupal_get_path('module', 'webform') . '/views',
5077
  );
5078
}
5079

    
5080
/**
5081
 * Implements hook_views_default_views().
5082
 */
5083
function webform_views_default_views() {
5084
  $path = './' . drupal_get_path('module', 'webform') . '/views/default_views/*.inc';
5085
  $views = array();
5086
  foreach (glob($path) as $views_filename) {
5087
    require_once($views_filename);
5088
  }
5089
  return $views;
5090
}
5091

    
5092
/**
5093
 * Implements hook_field_extra_fields().
5094
 */
5095
function webform_field_extra_fields() {
5096
  $extra = array();
5097
  foreach (webform_node_types() as $type) {
5098
    $extra['node'][$type]['display']['webform'] = array(
5099
      'label' => t('Webform'),
5100
      'description' => t('Webform client form.'),
5101
      'weight' => 10,
5102
    );
5103
  }
5104
  return $extra;
5105
}
5106

    
5107
/**
5108
 * Implements hook_mollom_form_list().
5109
 */
5110
function webform_mollom_form_list() {
5111
  $forms = array();
5112
  $webform_types = webform_node_types();
5113
  if (empty($webform_types)) {
5114
    return $forms;
5115
  }
5116

    
5117
  $query = db_select('webform', 'w');
5118
  $query->innerJoin('node', 'n', 'n.nid = w.nid');
5119
  $query->fields('n', array('nid', 'title'));
5120
  $query->condition('n.type', $webform_types, 'IN');
5121
  $result = $query->execute();
5122

    
5123
  foreach ($result as $node) {
5124
    $form_id = 'webform_client_form_' . $node->nid;
5125
    $forms[$form_id] = array(
5126
      'title' => t('@name form', array('@name' => $node->title)),
5127
      'entity' => 'webform',
5128
      'delete form' => 'webform_submission_delete_form',
5129
    );
5130
  }
5131
  return $forms;
5132
}
5133

    
5134
/**
5135
 * Implements hook_mollom_form_info().
5136
 */
5137
function webform_mollom_form_info($form_id) {
5138
  module_load_include('inc', 'webform', 'includes/webform.components');
5139

    
5140
  $nid = drupal_substr($form_id, 20);
5141
  $node = node_load($nid);
5142
  $form_info = array(
5143
    'title' => t('@name form', array('@name' => $node->title)),
5144
    'mode' => MOLLOM_MODE_ANALYSIS,
5145
    'bypass access' => array('edit all webform submissions', 'edit any webform content'),
5146
    'entity' => 'webform',
5147
    'elements' => array(),
5148
    'mapping' => array(
5149
      'post_id' => 'details][sid',
5150
      'author_id' => 'details][uid',
5151
    ),
5152
  );
5153
  // Add components as elements.
5154
  // These components can be enabled for textual analysis (when not using a
5155
  // CAPTCHA-only protection) in Mollom's form configuration.
5156
  foreach ($node->webform['components'] as $cid => $component) {
5157
    if (webform_component_feature($component['type'], 'spam_analysis')) {
5158
      $parents = implode('][', webform_component_parent_keys($node, $component));
5159
      $form_info['elements']['submitted][' . $parents] = check_plain(t($component['name']));
5160
    }
5161
  }
5162
  // Assign field mappings based on webform configuration.
5163
  // Since multiple emails can be configured, we iterate over all and take
5164
  // over the assigned component for the field mapping in any email, unless
5165
  // we already assigned one. We are not interested in administratively
5166
  // configured static strings, only user-submitted values.
5167
  foreach ($node->webform['emails'] as $email) {
5168
    // Subject (post_title).
5169
    if (!isset($form_info['mapping']['post_title'])) {
5170
      $cid = $email['subject'];
5171
      if (is_numeric($cid)) {
5172
        $parents = implode('][', webform_component_parent_keys($node, $node->webform['components'][$cid]));
5173
        $form_info['mapping']['post_title'] = 'submitted][' . $parents;
5174
      }
5175
    }
5176
    // From name (author_name).
5177
    if (!isset($form_info['mapping']['author_name'])) {
5178
      $cid = $email['from_name'];
5179
      if (is_numeric($cid)) {
5180
        $parents = implode('][', webform_component_parent_keys($node, $node->webform['components'][$cid]));
5181
        $form_info['mapping']['author_name'] = 'submitted][' . $parents;
5182
      }
5183
    }
5184
    // From address (author_mail).
5185
    if (!isset($form_info['mapping']['author_mail'])) {
5186
      $cid = $email['from_address'];
5187
      if (is_numeric($cid)) {
5188
        $parents = implode('][', webform_component_parent_keys($node, $node->webform['components'][$cid]));
5189
        $form_info['mapping']['author_mail'] = 'submitted][' . $parents;
5190
      }
5191
    }
5192
  }
5193

    
5194
  return $form_info;
5195
}
5196

    
5197
/**
5198
 * Implements hook_date_views_extra_tables().
5199
 */
5200
function webform_date_views_extra_tables() {
5201
  return array('webform_submissions' => 'webform_submissions');
5202
}
5203

    
5204
/**
5205
 * Returns the next serial number for a given node and increments the serial
5206
 * number.
5207
 *
5208
 * @param int $nid
5209
 *   The nid of the node.
5210
 *
5211
 * @return int
5212
 *   The next value of the serial number.
5213
 */
5214
function _webform_submission_serial_next_value($nid) {
5215
  // Use a transaction with SELECT ... FOR UPDATE to lock the row between
5216
  // the SELECT and the UPDATE, ensuring that multiple Webform submissions
5217
  // at the same time do not have duplicate numbers. FOR UPDATE must be inside
5218
  // a transaction. The return value of db_transaction() must be assigned or the
5219
  // transaction will commit immediately. The transaction will commit when $txn
5220
  // goes out-of-scope.
5221
  $txn = db_transaction();
5222

    
5223
  // Get the next_serial value.
5224
  $next_serial = db_select('webform', 'w')
5225
    // Only add FOR UPDATE when incrementing.
5226
    ->forUpdate()
5227
    ->fields('w', array('next_serial'))
5228
    ->condition('nid', $nid)
5229
    ->execute()
5230
    ->fetchField();
5231

    
5232
  // $next_serial must be greater than any existing serial number.
5233
  $next_serial = max($next_serial, _webform_submission_serial_next_value_used($nid));
5234

    
5235
  // Increment the next_value.
5236
  db_update('webform')
5237
    ->fields(array('next_serial' => $next_serial + 1))
5238
    ->condition('nid', $nid)
5239
    ->execute();
5240

    
5241
  return $next_serial;
5242
}
5243

    
5244
/**
5245
 * Returns the next submission serial number to be used, based on the
5246
 * submissions in the database.
5247
 *
5248
 * @param int $nid
5249
 *   The Node ID of the Webform.
5250
 *
5251
 * $return int
5252
 *   The largest serial number used by a submission plus 1 for the specified
5253
 *   node or 1 when there are no submissions.
5254
 */
5255
function _webform_submission_serial_next_value_used($nid) {
5256
  $max_serial = db_select('webform_submissions');
5257
  $max_serial->addExpression('MAX(serial)');
5258
  $max_serial = $max_serial
5259
    ->condition('nid', $nid)
5260
    ->execute()
5261
    ->fetchField();
5262
  // $max_serial will be a numeric string or NULL.
5263
  return $max_serial + 1;
5264
}
5265

    
5266
/**
5267
 * Alter the node before saving a clone.
5268
 *
5269
 * @param $node
5270
 *   Reference to the fully loaded node object being saved (the clone) that
5271
 *   can be altered as needed.
5272
 * @param array $context
5273
 *   An array of context describing the clone operation. The keys are:
5274
 *   - 'method' : Can be either 'prepopulate' or 'save-edit'.
5275
 *   - 'original_node' : The original fully loaded node object being cloned.
5276
 *
5277
 * @see clone_node_save()
5278
 * @see drupal_alter()
5279
 */
5280
function webform_clone_node_alter(&$node, $context) {
5281
  if (isset($node->webform)) {
5282
    $defaults = webform_node_defaults();
5283
    $node->webform['next_serial'] = $defaults['next_serial'];
5284
  }
5285
}
5286

    
5287
/**
5288
 * Check if the last form submission exceeded the servers max_input_vars
5289
 * limit and optionally preflight the current form to be returned in this
5290
 * request.
5291
 *
5292
 * @param array $form
5293
 *   Reference to the form, which will be changed if $parent_key is set.
5294
 * @param array $form_state
5295
 *   Form's state or NULL for no form state check.
5296
 * @param string $detect_key
5297
 *   A key that will always be present in the posted data when an actual form
5298
 *   submission has been made.
5299
 * @param string $parent_key
5300
 *   Omit to not preflight the form, or the array key for the parent of where
5301
 *   the preflight warning should be inserted into the form.
5302
 */
5303
function webform_input_vars_check(&$form, $form_state, $detect_key, $parent_key = NULL) {
5304
  if (isset($parent_key)) {
5305
    $form['#pre_render'] = array('webform_pre_render_input_vars');
5306
    $form['#input_var_waring_parent'] = $parent_key;
5307
  }
5308
  if (!empty($form_state['input']) && key_exists($detect_key, $form_state['input']) && !key_exists('form_id', $form_state['input'])) {
5309
    // A form was submitted with POST, but the form_id was missing. The most likely cause of this
5310
    // is that the POST was truncated because PHP exceeded its max_input_vars limit.
5311
    $subs = array(
5312
      '@count' => webform_count_terminals($_POST),
5313
      '@limit' => (int)ini_get('max_input_vars'),
5314
    );
5315
    drupal_set_message(user_access('administer site configuration')
5316
                          ? 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)
5317
                          : t('This form could not be submitted because it exceeds the server configuration. Contact the administrator.'),
5318
                      'error');
5319
    watchdog('webform',
5320
             'POST truncated to @count input vars. PHP max_input_vars is @limit. Increase max_input_vars.',
5321
             $subs,
5322
             WATCHDOG_ERROR);
5323
  }
5324
}
5325

    
5326
/**
5327
 * Checks the number of input form elements on this page to ensure that the
5328
 * PHP max_input_vars limit is not exceeded.
5329
 *
5330
 * Install this function as a #pre_render function.
5331
 */
5332
function webform_pre_render_input_vars($element) {
5333
  // Determine the limit on input vars for this server configuration.
5334
  $limit = ini_get('max_input_vars');
5335
  if ($limit) {
5336
    // Estimate the number of input vars needed to see if the PHP limit has been exceeded.
5337
    $count = 1 + webform_count_input_vars($element); // Additional input_vars: op
5338
    if ($count > $limit * 0.95) {
5339
      $subs = array(
5340
        '@count' => $count,
5341
        '@limit' => $limit,
5342
      );
5343
      $warning = array(
5344
        '#markup' => '<div class="messages warning">' .
5345
                     (user_access('administer site configuration')
5346
                        ? t('This form contains @count input elements. PHP max_input_vars is @limit and should be increased.', $subs)
5347
                        : t('This form may be too long to work properly. Contact the administrator.'))
5348
                     . '</div>',
5349
        '#weight' => -1,
5350
      );
5351
      if ($element['#input_var_waring_parent']) {
5352
        $element[$element['#input_var_waring_parent']]['input_vars_warning'] = $warning;
5353
      }
5354
      else {
5355
        $element['input_vars_warning'] = $warning;
5356
      }
5357
      watchdog('webform',
5358
               'Page contains @count input elements but PHP max_input_vars is only @limit. Increase max_input_vars.',
5359
               $subs,
5360
               WATCHDOG_ERROR);
5361
    }
5362
  }
5363
  return $element;
5364
}
5365

    
5366
/**
5367
 * Counts the number of input form elements.
5368
 *
5369
 * Note that this is somewhat imprecise. The number of input vars returned in
5370
 * $_POST can vary with the form element. For example, a multiple-select
5371
 * listbox returns one input var for each selection actually made.
5372
 *
5373
 * The primary use for this count is for the conditionals page, where only
5374
 * select, textfield, hidden, and token elements are used. If a more accurate
5375
 * count for webform_client_form is needed, a mechanism to predict the number
5376
 * of input elements for each component type and each component instance would
5377
 * be needed.
5378
 *
5379
 * @param array $element
5380
 *   The form whose elements should be counted.
5381
 * @return integer
5382
 *   The number of elements in the form that will result in $_POST entries.
5383
 */
5384
function webform_count_input_vars($element) {
5385
  static $input_types = array(
5386
    'checkbox' => 1,
5387
    'date' => 1,
5388
    'file' => 1,
5389
    'managed_file' => 1,
5390
    'password' => 1,
5391
    'password_confirm' => 1,
5392
    'radios' => 1,
5393
    'select' => 1,
5394
    'textfield' => 1,
5395
    'textarea' => 1,
5396
    'token' => 1,
5397
    'weight' => 1,
5398
    'hidden' => 1,
5399
    'value' => 1,
5400
    'webform_email' => 1,
5401
    'webform_number' => 1,
5402
  );
5403
  $children = array_intersect_key($element, array_flip(element_children($element)));
5404
  return $children
5405
          ? array_reduce($children, function($carry, $item) {return $carry + webform_count_input_vars($item);}, 0)
5406
          : (isset($element['#type']) && isset($input_types[$element['#type']]) ? $input_types[$element['#type']] : 0);
5407
}
5408

    
5409
/**
5410
 * Counts terminals in an array. Useful for counting how many input_vars were
5411
 * returned in $_POST.
5412
 *
5413
 * @param $a
5414
 *   Array or array element to be counted
5415
 * @return integer
5416
 *   Number of non-array elements within $a.
5417
 */
5418
function webform_count_terminals($a) {
5419
  return is_array($a)
5420
            ? array_reduce($a, function($carry, $item) {return $carry + webform_count_terminals($item);}, 0)
5421
            : 1;
5422
}