Projet

Général

Profil

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

root / drupal7 / sites / all / modules / webform / webform.module @ 27370441

1 85ad3d82 Assos Assos
<?php
2
3
/**
4 a45e4bc1 Assos Assos
 * @file
5 85ad3d82 Assos Assos
 * 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 a45e4bc1 Assos Assos
/**
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 85ad3d82 Assos Assos
/**
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 a45e4bc1 Assos Assos
        $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 85ad3d82 Assos Assos
      }
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 a45e4bc1 Assos Assos
    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 85ad3d82 Assos Assos
    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 a45e4bc1 Assos Assos
  // 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 85ad3d82 Assos Assos
  // 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 a45e4bc1 Assos Assos
    'access callback' => 'webform_confirmation_page_access',
127
    'access arguments' => array(1),
128 85ad3d82 Assos Assos
    '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 a45e4bc1 Assos Assos
    'access callback' => 'webform_node_update_access',
135
    'access arguments' => array(1),
136 85ad3d82 Assos Assos
    '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 a45e4bc1 Assos Assos
    'access callback' => 'webform_node_update_access',
146
    'access arguments' => array(1),
147 85ad3d82 Assos Assos
    'file' => 'includes/webform.components.inc',
148
    'weight' => 0,
149
    'type' => MENU_DEFAULT_LOCAL_TASK,
150
  );
151 a45e4bc1 Assos Assos
  $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 85ad3d82 Assos Assos
  $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 a45e4bc1 Assos Assos
    'access callback' => 'webform_node_update_access',
166
    'access arguments' => array(1),
167 85ad3d82 Assos Assos
    'file' => 'includes/webform.pages.inc',
168 a45e4bc1 Assos Assos
    'weight' => 5,
169 85ad3d82 Assos Assos
    '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 a45e4bc1 Assos Assos
    'access callback' => 'webform_node_update_access',
178
    'access arguments' => array(1),
179 85ad3d82 Assos Assos
    'file' => 'includes/webform.emails.inc',
180 a45e4bc1 Assos Assos
    'weight' => 4,
181 85ad3d82 Assos Assos
    '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 a45e4bc1 Assos Assos
    'access callback' => 'webform_node_update_access',
187
    'access arguments' => array(1),
188 85ad3d82 Assos Assos
    'file' => 'includes/webform.emails.inc',
189 a45e4bc1 Assos Assos
    '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 85ad3d82 Assos Assos
  );
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 a45e4bc1 Assos Assos
    'access callback' => 'webform_node_update_access',
203
    'access arguments' => array(1),
204
    'type' => MENU_LOCAL_TASK,
205 85ad3d82 Assos Assos
  );
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 a45e4bc1 Assos Assos
    'access callback' => 'webform_node_update_access',
213
    'access arguments' => array(1),
214 85ad3d82 Assos Assos
    '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 a45e4bc1 Assos Assos
    'access callback' => 'webform_node_update_access',
222
    'access arguments' => array(1),
223 85ad3d82 Assos Assos
    '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 a45e4bc1 Assos Assos
    'access callback' => 'webform_node_update_access',
231
    'access arguments' => array(1),
232 85ad3d82 Assos Assos
    '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 a45e4bc1 Assos Assos
    'access callback' => 'webform_node_update_access',
241
    'access arguments' => array(3),
242 85ad3d82 Assos Assos
    '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 a45e4bc1 Assos Assos
    '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 85ad3d82 Assos Assos
  );
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 a45e4bc1 Assos Assos
  $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 85ad3d82 Assos Assos
  $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 a45e4bc1 Assos Assos
  // 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 85ad3d82 Assos Assos
  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 a45e4bc1 Assos Assos
  if (!isset($node->type) || !variable_get('webform_node_' . $node->type, FALSE)) {
442 85ad3d82 Assos Assos
    return FALSE;
443
  }
444
  return $node;
445
}
446
447 a45e4bc1 Assos Assos
/**
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 85ad3d82 Assos Assos
/**
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 a45e4bc1 Assos Assos
    $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 85ad3d82 Assos Assos
  }
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 a45e4bc1 Assos Assos
    if (isset($_GET['status'])) {
518
      $email['status'] = $_GET['status'];
519
    }
520 85ad3d82 Assos Assos
  }
521
522
  return $email;
523
}
524
525 a45e4bc1 Assos Assos
/**
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 85ad3d82 Assos Assos
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 a45e4bc1 Assos Assos
  $token_access = $submission && isset($_GET['token']) && $_GET['token'] == webform_get_submission_access_token($submission);
618
619 01d522a6 Assos Assos
  // 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 a45e4bc1 Assos Assos
  $general_access = $access_all || $access_own_submission || $access_node_submissions || $token_access;
626 85ad3d82 Assos Assos
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 a45e4bc1 Assos Assos
/**
660
 * Menu access callback. Ensure a user has both results access and permission
661
 * to clear submissions.
662
 */
663 85ad3d82 Assos Assos
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 a45e4bc1 Assos Assos
/**
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 85ad3d82 Assos Assos
/**
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 a45e4bc1 Assos Assos
    '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 85ad3d82 Assos Assos
  );
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 a45e4bc1 Assos Assos
      '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 85ad3d82 Assos Assos
    ),
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 a45e4bc1 Assos Assos
    '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 85ad3d82 Assos Assos
    '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 a45e4bc1 Assos Assos
      'variables' => array('groups' => array('node')),
800 85ad3d82 Assos Assos
    ),
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 a45e4bc1 Assos Assos
    'webform_email_component_mapping' => array(
816
      'render element' => 'element',
817
      'file' => 'includes/webform.emails.inc',
818
    ),
819 85ad3d82 Assos Assos
    '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 a45e4bc1 Assos Assos
    // 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 85ad3d82 Assos Assos
    // 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, 'node' => 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 a45e4bc1 Assos Assos
    '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 85ad3d82 Assos Assos
      '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 a45e4bc1 Assos Assos
  $elements['webform_conditional'] = array(
995
    '#input' => TRUE,
996
    '#theme' => 'webform_conditional',
997
    '#default_value' => NULL,
998
    '#process' => array('webform_conditional_expand'),
999
  );
1000
1001 85ad3d82 Assos Assos
  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 a45e4bc1 Assos Assos
        'views_range' => TRUE,
1014
        'css_classes' => FALSE,
1015 85ad3d82 Assos Assos
      ),
1016
      'file' => 'components/date.inc',
1017 a45e4bc1 Assos Assos
      'conditional_type' => 'date',
1018 85ad3d82 Assos Assos
    ),
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 a45e4bc1 Assos Assos
        'placeholder' => TRUE,
1027
        'conditional_action_set' => TRUE,
1028 85ad3d82 Assos Assos
      ),
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 a45e4bc1 Assos Assos
        'wrapper_classes' => FALSE,
1041 85ad3d82 Assos Assos
      ),
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 ba09eb79 Assos Assos
        'title_internal' => TRUE,
1051 a45e4bc1 Assos Assos
        'css_classes' => FALSE,
1052 ba09eb79 Assos Assos
        'conditional' => FALSE,
1053
        'group' => TRUE,
1054 85ad3d82 Assos Assos
      ),
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 a45e4bc1 Assos Assos
        'wrapper_classes' => FALSE,
1069
        'css_classes' => FALSE,
1070
        'conditional_action_set' => TRUE,
1071 85ad3d82 Assos Assos
      ),
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 a45e4bc1 Assos Assos
        'analysis' => FALSE,
1078 85ad3d82 Assos Assos
        '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 a45e4bc1 Assos Assos
        'wrapper_classes' => FALSE,
1087
        'css_classes' => FALSE,
1088
        'conditional_action_set' => TRUE,
1089 85ad3d82 Assos Assos
      ),
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 ba09eb79 Assos Assos
        'conditional_action_set' => TRUE,
1097 85ad3d82 Assos Assos
      ),
1098
      'file' => 'components/number.inc',
1099 a45e4bc1 Assos Assos
      'conditional_type' => 'numeric',
1100 85ad3d82 Assos Assos
    ),
1101
    'pagebreak' => array(
1102
      'label' => t('Page break'),
1103
      'description' => t('Organize forms into multiple pages.'),
1104
      'features' => array(
1105 a45e4bc1 Assos Assos
        'analysis' => FALSE,
1106
        'conditional' => FALSE,
1107 85ad3d82 Assos Assos
        'csv' => FALSE,
1108
        'default_value' => FALSE,
1109
        'description' => FALSE,
1110
        'private' => FALSE,
1111
        'required' => FALSE,
1112
        'title_display' => FALSE,
1113 a45e4bc1 Assos Assos
        'wrapper_classes' => FALSE,
1114
        'css_classes' => FALSE,
1115 85ad3d82 Assos Assos
      ),
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 a45e4bc1 Assos Assos
        'conditional_action_set' => TRUE,
1127 85ad3d82 Assos Assos
      ),
1128 a45e4bc1 Assos Assos
      'conditional_type' => 'select',
1129 85ad3d82 Assos Assos
    ),
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 a45e4bc1 Assos Assos
        'placeholder' => TRUE,
1137
        'conditional_action_set' => TRUE,
1138 85ad3d82 Assos Assos
      ),
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 a45e4bc1 Assos Assos
        'placeholder' => TRUE,
1148
        'conditional_action_set' => TRUE,
1149 85ad3d82 Assos Assos
      ),
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 a45e4bc1 Assos Assos
        'views_range' => TRUE,
1156
        'css_classes' => FALSE,
1157 85ad3d82 Assos Assos
      ),
1158
      'file' => 'components/time.inc',
1159 a45e4bc1 Assos Assos
      'conditional_type' => 'time',
1160 85ad3d82 Assos Assos
    ),
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 a45e4bc1 Assos Assos
/**
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 85ad3d82 Assos Assos
/**
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 a45e4bc1 Assos Assos
 * Implements hook_webform_submission_presave().
1243 85ad3d82 Assos Assos
 *
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 a45e4bc1 Assos Assos
  $renameable = array();
1255 85ad3d82 Assos Assos
1256
  foreach ($node->webform['components'] as $cid => $component) {
1257
    if ($component['type'] == 'file') {
1258
      $has_file_components = TRUE;
1259 a45e4bc1 Assos Assos
      if (!empty($submission->data[$cid])) {
1260
        foreach ($submission->data[$cid] as $key => $value) {
1261 3753f249 Assos Assos
          if (empty($value)) {
1262 a45e4bc1 Assos Assos
            unset($submission->data[$cid][$key]);
1263
          }
1264
          if (strlen($component['extra']['rename'])) {
1265
            $renameable[$cid][] = $value;
1266 3753f249 Assos Assos
          }
1267
        }
1268 a45e4bc1 Assos Assos
        $new_fids = array_merge($new_fids, $submission->data[$cid]);
1269 85ad3d82 Assos Assos
      }
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 a45e4bc1 Assos Assos
      drupal_static_reset('webform_get_submission');
1277
      $old_submission = webform_get_submission($node->nid, $submission->sid);
1278 85ad3d82 Assos Assos
1279
      foreach ($node->webform['components'] as $cid => $component) {
1280
        if ($component['type'] == 'file') {
1281 a45e4bc1 Assos Assos
          if (!empty($old_submission->data[$cid])) {
1282
            $old_fids = array_merge($old_fids, $old_submission->data[$cid]);
1283 85ad3d82 Assos Assos
          }
1284
        }
1285
      }
1286
    }
1287
1288 a45e4bc1 Assos Assos
    // 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 85ad3d82 Assos Assos
    // 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 a45e4bc1 Assos Assos
      'added_fids' => array_diff($new_fids, $old_fids),
1300
      // A list of files which need renaming with tokens.
1301
      'renameable' => $renameable,
1302 85ad3d82 Assos Assos
    );
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 a45e4bc1 Assos Assos
    webform_file_rename($node, $submission);
1314 85ad3d82 Assos Assos
  }
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 a45e4bc1 Assos Assos
    webform_file_rename($node, $submission);
1325 85ad3d82 Assos Assos
  }
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 a45e4bc1 Assos Assos
    module_load_include('inc', 'webform', 'includes/webform.components');
1342 85ad3d82 Assos Assos
    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 a45e4bc1 Assos Assos
  // Grant or deny file access based on access to the submission.
1374 85ad3d82 Assos Assos
  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 a45e4bc1 Assos Assos
    else {
1380
      return -1;
1381
    }
1382 85ad3d82 Assos Assos
  }
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 a45e4bc1 Assos Assos
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 85ad3d82 Assos Assos
}
1399
1400
/**
1401 a45e4bc1 Assos Assos
 * Return all content type enabled with webform.
1402 85ad3d82 Assos Assos
 *
1403 a45e4bc1 Assos Assos
 * @return array
1404
 *   An array of node type names.
1405 85ad3d82 Assos Assos
 */
1406 a45e4bc1 Assos Assos
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 85ad3d82 Assos Assos
    }
1415
  }
1416 a45e4bc1 Assos Assos
  return $types;
1417 85ad3d82 Assos Assos
}
1418
1419
/**
1420
 * Implements hook_node_type_delete().
1421
 */
1422
function webform_node_type_delete($info) {
1423 a45e4bc1 Assos Assos
  variable_del('webform_node_' . $info->type);
1424 85ad3d82 Assos Assos
}
1425
1426
/**
1427
 * Implements hook_node_insert().
1428
 */
1429
function webform_node_insert($node) {
1430 a45e4bc1 Assos Assos
  if (!variable_get('webform_node_' . $node->type, FALSE)) {
1431 85ad3d82 Assos Assos
    return;
1432
  }
1433
1434
  // If added directly through node_save(), set defaults for the node.
1435
  if (!isset($node->webform)) {
1436 a45e4bc1 Assos Assos
    $node->webform = array();
1437 85ad3d82 Assos Assos
  }
1438
1439 a45e4bc1 Assos Assos
  // 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 85ad3d82 Assos Assos
  // 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 a45e4bc1 Assos Assos
  module_load_include('inc', 'webform', 'includes/webform.conditionals');
1450 85ad3d82 Assos Assos
  module_load_include('inc', 'webform', 'includes/webform.emails');
1451
1452 a45e4bc1 Assos Assos
  // Prepare the record for writing.
1453 85ad3d82 Assos Assos
  $node->webform['nid'] = $node->nid;
1454 a45e4bc1 Assos Assos
  $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 85ad3d82 Assos Assos
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 a45e4bc1 Assos Assos
  // 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 85ad3d82 Assos Assos
  // 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 3753f249 Assos Assos
1490
  // Flush the block cache if creating a block.
1491 a45e4bc1 Assos Assos
  if (function_exists('block_flush_caches') && $node->webform['block']) {
1492 3753f249 Assos Assos
    block_flush_caches();
1493
  }
1494 85ad3d82 Assos Assos
}
1495
1496
/**
1497
 * Implements hook_node_update().
1498
 */
1499
function webform_node_update($node) {
1500 a45e4bc1 Assos Assos
  if (!variable_get('webform_node_' . $node->type, FALSE)) {
1501 85ad3d82 Assos Assos
    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 a45e4bc1 Assos Assos
  // Prepare the record for writing.
1516 85ad3d82 Assos Assos
  $node->webform['nid'] = $node->nid;
1517 a45e4bc1 Assos Assos
  $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 85ad3d82 Assos Assos
1523
  // Compare the webform components and don't do anything if it's not needed.
1524 a45e4bc1 Assos Assos
  $original = $node->original;
1525 85ad3d82 Assos Assos
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 a45e4bc1 Assos Assos
      $node->webform['components'][$cid]['nid'] = $node->nid;
1538 85ad3d82 Assos Assos
      if (in_array($cid, $inserted_cids)) {
1539
        webform_component_insert($node->webform['components'][$cid]);
1540
      }
1541
      elseif (in_array($cid, $deleted_cids)) {
1542
        webform_component_delete($node, $original->webform['components'][$cid]);
1543
      }
1544
      elseif ($node->webform['components'][$cid] != $original->webform['components'][$cid]) {
1545
        webform_component_update($node->webform['components'][$cid]);
1546
      }
1547
    }
1548
  }
1549
1550 a45e4bc1 Assos Assos
  // Compare the webform conditionals and don't do anything if it's not needed.
1551
  if ($original->webform['conditionals'] != $node->webform['conditionals']) {
1552
    module_load_include('inc', 'webform', 'includes/webform.conditionals');
1553
1554
    // Conditionals don't have unique site-wide IDs or configuration, so our
1555
    // update here is a bit more aggressive than for components and e-mails.
1556
    foreach ($original->webform['conditionals'] as $rgid => $conditional) {
1557
      if (!isset($node->webform['conditionals'][$rgid]) || $original->webform['conditionals'][$rgid] != $node->webform['conditionals'][$rgid]) {
1558
        webform_conditional_delete($node, $conditional);
1559
      }
1560
    }
1561
    foreach ($node->webform['conditionals'] as $rgid => $conditional) {
1562
      $conditional['nid'] = $node->nid;
1563
      $conditional['rgid'] = $rgid;
1564
      webform_conditional_insert($conditional);
1565
    }
1566
  }
1567
1568 85ad3d82 Assos Assos
  // Compare the webform e-mails and don't do anything if it's not needed.
1569
  if ($original->webform['emails'] != $node->webform['emails']) {
1570
    module_load_include('inc', 'webform', 'includes/webform.emails');
1571
1572
    $original_eids = array_keys($original->webform['emails']);
1573
    $current_eids = array_keys($node->webform['emails']);
1574
1575
    $all_eids = array_unique(array_merge($original_eids, $current_eids));
1576
    $deleted_eids = array_diff($original_eids, $current_eids);
1577
    $inserted_eids = array_diff($current_eids, $original_eids);
1578
1579
    foreach ($all_eids as $eid) {
1580 a45e4bc1 Assos Assos
      $node->webform['emails'][$eid]['nid'] = $node->nid;
1581 85ad3d82 Assos Assos
      if (in_array($eid, $inserted_eids)) {
1582
        webform_email_insert($node->webform['emails'][$eid]);
1583
      }
1584
      elseif (in_array($eid, $deleted_eids)) {
1585
        webform_email_delete($node, $original->webform['emails'][$eid]);
1586
      }
1587
      elseif ($node->webform['emails'][$eid] != $original->webform['emails'][$eid]) {
1588
        webform_email_update($node->webform['emails'][$eid]);
1589
      }
1590
    }
1591
  }
1592
1593
  // Just delete and re-insert roles if they've changed.
1594
  if ($original->webform['roles'] != $node->webform['roles']) {
1595
    db_delete('webform_roles')->condition('nid', $node->nid)->execute();
1596
    foreach (array_filter($node->webform['roles']) as $rid) {
1597
      db_insert('webform_roles')->fields(array('nid' => $node->nid, 'rid' => $rid))->execute();
1598
    }
1599
  }
1600 3753f249 Assos Assos
1601
  // Flush the block cache if block settings have been changed.
1602 a45e4bc1 Assos Assos
  if (function_exists('block_flush_caches') && $node->webform['block'] != $original->webform['block']) {
1603 3753f249 Assos Assos
    block_flush_caches();
1604
  }
1605 85ad3d82 Assos Assos
}
1606
1607
/**
1608 c22e192e Assos Assos
 * Implements hook_node_delete().
1609 85ad3d82 Assos Assos
 */
1610
function webform_node_delete($node) {
1611 a45e4bc1 Assos Assos
  if (!variable_get('webform_node_' . $node->type, FALSE)) {
1612 85ad3d82 Assos Assos
    return;
1613
  }
1614
1615
  // Allow components clean up extra data, such as uploaded files.
1616
  module_load_include('inc', 'webform', 'includes/webform.components');
1617
  foreach ($node->webform['components'] as $cid => $component) {
1618
    webform_component_delete($node, $component);
1619
  }
1620
1621
  // Remove any trace of webform data from the database.
1622
  db_delete('webform')->condition('nid', $node->nid)->execute();
1623
  db_delete('webform_component')->condition('nid', $node->nid)->execute();
1624 a45e4bc1 Assos Assos
  db_delete('webform_conditional')->condition('nid', $node->nid)->execute();
1625
  db_delete('webform_conditional_rules')->condition('nid', $node->nid)->execute();
1626
  db_delete('webform_conditional_actions')->condition('nid', $node->nid)->execute();
1627 85ad3d82 Assos Assos
  db_delete('webform_emails')->condition('nid', $node->nid)->execute();
1628
  db_delete('webform_roles')->condition('nid', $node->nid)->execute();
1629
  db_delete('webform_submissions')->condition('nid', $node->nid)->execute();
1630
  db_delete('webform_submitted_data')->condition('nid', $node->nid)->execute();
1631
  db_delete('webform_last_download')->condition('nid', $node->nid)->execute();
1632
}
1633
1634
/**
1635
 * Default settings for a newly created webform node.
1636
 */
1637
function webform_node_defaults() {
1638 a45e4bc1 Assos Assos
  $progress_bar_defaults = webform_variable_get('webform_progressbar_style');
1639 85ad3d82 Assos Assos
  $defaults = array(
1640
    'confirmation' => '',
1641
    'confirmation_format' => NULL,
1642
    'redirect_url' => '<confirmation>',
1643
    'block' => '0',
1644
    'allow_draft' => '0',
1645
    'auto_save' => '0',
1646 a45e4bc1 Assos Assos
    'confidential' => '0',
1647 85ad3d82 Assos Assos
    'submit_notice' => '1',
1648
    'submit_text' => '',
1649 a45e4bc1 Assos Assos
    'next_serial' => 1,
1650 85ad3d82 Assos Assos
    'submit_limit' => '-1',
1651
    'submit_interval' => '-1',
1652
    'total_submit_limit' => '-1',
1653
    'total_submit_interval' => '-1',
1654 a45e4bc1 Assos Assos
    'progressbar_page_number' => in_array('progressbar_page_number', $progress_bar_defaults) ? '1' : '0',
1655
    'progressbar_percent' => in_array('progressbar_percent', $progress_bar_defaults) ? '1' : '0',
1656
    'progressbar_bar' => in_array('progressbar_bar', $progress_bar_defaults) ? '1' : '0',
1657
    'progressbar_pagebreak_labels' => in_array('progressbar_pagebreak_labels', $progress_bar_defaults) ? '1' : '0',
1658
    'progressbar_include_confirmation' => in_array('progressbar_include_confirmation', $progress_bar_defaults) ? '1' : '0',
1659
    'progressbar_label_first' => webform_variable_get('webform_progressbar_label_first'),
1660
    'progressbar_label_confirmation' => webform_variable_get('webform_progressbar_label_confirmation'),
1661
    'preview' => 0,
1662
    'preview_next_button_label' => '',
1663
    'preview_prev_button_label' => '',
1664
    'preview_title' => '',
1665
    'preview_message' => '',
1666
    'preview_message_format' => NULL,
1667
    'preview_excluded_components' => array(),
1668 85ad3d82 Assos Assos
    'status' => '1',
1669
    'record_exists' => FALSE,
1670
    'roles' => array('1', '2'),
1671
    'emails' => array(),
1672
    'components' => array(),
1673 a45e4bc1 Assos Assos
    'conditionals' => array(),
1674 85ad3d82 Assos Assos
  );
1675
  drupal_alter('webform_node_defaults', $defaults);
1676
  return $defaults;
1677
}
1678
1679
/**
1680
 * Implements hook_node_prepare().
1681
 */
1682
function webform_node_prepare($node) {
1683 a45e4bc1 Assos Assos
  if (variable_get('webform_node_' . $node->type, FALSE) && !isset($node->webform)) {
1684 85ad3d82 Assos Assos
    $node->webform = webform_node_defaults();
1685
  }
1686
}
1687
1688
1689
/**
1690
 * Implements hook_node_load().
1691
 */
1692
function webform_node_load($nodes, $types) {
1693
  // Quick check to see if we need to do anything at all for these nodes.
1694 a45e4bc1 Assos Assos
  $webform_types = webform_node_types();
1695 85ad3d82 Assos Assos
  if (count(array_intersect($types, $webform_types)) == 0) {
1696
    return;
1697
  }
1698
1699
  module_load_include('inc', 'webform', 'includes/webform.components');
1700
1701
  // Select all webforms that match these node IDs.
1702
  $result = db_select('webform')
1703
    ->fields('webform')
1704
    ->condition('nid', array_keys($nodes), 'IN')
1705
    ->execute()
1706
    ->fetchAllAssoc('nid', PDO::FETCH_ASSOC);
1707
1708
  foreach ($result as $nid => $webform) {
1709
    // Load the basic information for each node.
1710
    $nodes[$nid]->webform = $webform;
1711
    $nodes[$nid]->webform['record_exists'] = TRUE;
1712 a45e4bc1 Assos Assos
1713
    // Expand the list of excluded preview components.
1714
    $nodes[$nid]->webform['preview_excluded_components'] = array_filter(explode(',', $webform['preview_excluded_components']));
1715 85ad3d82 Assos Assos
  }
1716
1717
  // Load the components, emails, and defaults for all webform-enabled nodes.
1718
  // TODO: Increase efficiency here by pulling in all information all at once
1719
  // instead of individual queries.
1720
  foreach ($nodes as $nid => $node) {
1721
    if (!in_array($node->type, $webform_types)) {
1722
      continue;
1723
    }
1724
1725
    // If a webform record doesn't exist, just return the defaults.
1726
    if (!isset($nodes[$nid]->webform)) {
1727
      $nodes[$nid]->webform = webform_node_defaults();
1728
      continue;
1729
    }
1730
1731
    $nodes[$nid]->webform['roles'] = db_select('webform_roles')
1732
      ->fields('webform_roles', array('rid'))
1733
      ->condition('nid', $nid)
1734
      ->execute()
1735
      ->fetchCol();
1736
    $nodes[$nid]->webform['emails'] = db_select('webform_emails')
1737
      ->fields('webform_emails')
1738
      ->condition('nid', $nid)
1739
      ->execute()
1740
      ->fetchAllAssoc('eid', PDO::FETCH_ASSOC);
1741
1742 a45e4bc1 Assos Assos
    // Unserialize the mappings and excluded component list for e-mails.
1743 85ad3d82 Assos Assos
    foreach ($nodes[$nid]->webform['emails'] as $eid => $email) {
1744
      $nodes[$nid]->webform['emails'][$eid]['excluded_components'] = array_filter(explode(',', $email['excluded_components']));
1745 a45e4bc1 Assos Assos
      $nodes[$nid]->webform['emails'][$eid]['extra'] = unserialize($email['extra']);
1746
      if (webform_variable_get('webform_format_override')) {
1747
        $nodes[$nid]->webform['emails'][$eid]['html'] = webform_variable_get('webform_default_format');
1748 85ad3d82 Assos Assos
      }
1749
    }
1750
1751
    // Load components for each node.
1752
    $nodes[$nid]->webform['components'] = db_select('webform_component')
1753
      ->fields('webform_component')
1754
      ->condition('nid', $nid)
1755
      ->orderBy('weight')
1756
      ->orderBy('name')
1757
      ->execute()
1758
      ->fetchAllAssoc('cid', PDO::FETCH_ASSOC);
1759
1760
    // Do a little cleanup on each component.
1761
    foreach ($nodes[$nid]->webform['components'] as $cid => $component) {
1762
      $nodes[$nid]->webform['components'][$cid]['nid'] = $nid;
1763
      $nodes[$nid]->webform['components'][$cid]['extra'] = unserialize($component['extra']);
1764
      webform_component_defaults($nodes[$nid]->webform['components'][$cid]);
1765
    }
1766
1767
    // Organize the components into a fieldset-based order.
1768
    if (!empty($nodes[$nid]->webform['components'])) {
1769
      $component_tree = array();
1770
      $page_count = 1;
1771
      _webform_components_tree_build($nodes[$nid]->webform['components'], $component_tree, 0, $page_count);
1772
      $nodes[$nid]->webform['components'] = _webform_components_tree_flatten($component_tree['children']);
1773
    }
1774 a45e4bc1 Assos Assos
1775
    // Load all the conditional information, if any.
1776
    $nodes[$nid]->webform['conditionals'] = db_select('webform_conditional')
1777
      ->fields('webform_conditional')
1778
      ->condition('nid', $nid)
1779
      ->orderBy('weight')
1780
      ->execute()
1781
      ->fetchAllAssoc('rgid', PDO::FETCH_ASSOC);
1782
    if ($nodes[$nid]->webform['conditionals']) {
1783
      $rules = db_select('webform_conditional_rules')
1784
        ->fields('webform_conditional_rules')
1785
        ->condition('nid', $nid)
1786
        ->orderBy('rgid')
1787
        ->orderBy('rid')
1788
        ->execute();
1789
      foreach ($rules as $rule) {
1790
        $nodes[$nid]->webform['conditionals'][$rule->rgid]['rules'][$rule->rid] = (array) $rule;
1791
      }
1792
      $actions = db_select('webform_conditional_actions')
1793
        ->fields('webform_conditional_actions')
1794
        ->condition('nid', $nid)
1795
        ->orderBy('rgid')
1796
        ->orderBy('aid')
1797
        ->execute();
1798
      foreach ($actions as $action) {
1799
        $nodes[$nid]->webform['conditionals'][$action->rgid]['actions'][$action->aid] = (array) $action;
1800
      }
1801
    }
1802 85ad3d82 Assos Assos
  }
1803 a45e4bc1 Assos Assos
1804
}
1805
1806
/**
1807
* Implements hook_user_role_delete().
1808
*
1809
* Removes references to deleted role from existing webforms.
1810
*/
1811
function webform_user_role_delete($role) {
1812
  db_delete('webform_roles')->condition('rid', $role->rid)->execute();
1813 85ad3d82 Assos Assos
}
1814
1815
/**
1816
 * Implements hook_form_alter().
1817
 */
1818
function webform_form_alter(&$form, $form_state, $form_id) {
1819
  $matches = array();
1820 a45e4bc1 Assos Assos
  if (isset($form['#node']->type) && $form_id == $form['#node']->type . '_node_form' && variable_get('webform_node_' . $form['#node']->type, FALSE)) {
1821 85ad3d82 Assos Assos
    $node = $form['#node'];
1822
    // Preserve all Webform options currently set on the node.
1823
    $form['webform'] = array(
1824
      '#type' => 'value',
1825
      '#value' => $node->webform,
1826
    );
1827
1828
    // If a new node, redirect the user to the components form after save.
1829
    if (empty($node->nid) && in_array($node->type, webform_variable_get('webform_node_types_primary'))) {
1830
      $form['actions']['submit']['#submit'][] = 'webform_form_submit';
1831
    }
1832
  }
1833
}
1834
1835 a45e4bc1 Assos Assos
/**
1836
 * Implements hook_form_BASE_FORM_ID_alter().
1837
 */
1838
function webform_form_node_type_form_alter(&$form, $form_state) {
1839
  if (isset($form['type'])) {
1840
    $form['webform'] = array(
1841
      '#title' => t('Webform'),
1842
      '#type' => 'fieldset',
1843
      '#collapsible' => TRUE,
1844
      '#collapsed' => TRUE,
1845
      '#group' => 'additional_settings',
1846
      '#weight' => 10,
1847
      '#attached' => array(
1848
        'js' => array(drupal_get_path('module', 'webform') . '/js/node-type-form.js'),
1849
      ),
1850
    );
1851
    $form['webform']['webform_node'] = array(
1852
      '#type' => 'checkbox',
1853
      '#title' => t('Enable webform functionality'),
1854
      '#description' => t('Allows a form to be attached to content. This will add tabs for "Webform" and "Results" on all content of this type.'),
1855
      '#weight' => 0,
1856
      '#default_value' => variable_get('webform_node_' . $form['#node_type']->type, FALSE),
1857
      '#attributes' => array(
1858
        'data-enabled-description' => t('Enabled'),
1859
        'data-disabled-description' => t('Disabled'),
1860
      ),
1861
    );
1862
  }
1863
}
1864
1865 85ad3d82 Assos Assos
/**
1866
 * Submit handler for the webform node form.
1867
 *
1868
 * Redirect the user to the components form on new node inserts. Note that this
1869
 * fires after the hook_submit() function above.
1870
 */
1871
function webform_form_submit($form, &$form_state) {
1872
  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'])));
1873
  $form_state['redirect'] = 'node/' . $form_state['nid'] . '/webform/components';
1874
}
1875
1876
/**
1877
 * Implements hook_node_view().
1878
 */
1879
function webform_node_view($node, $view_mode) {
1880
  global $user;
1881
1882 a45e4bc1 Assos Assos
  if (!variable_get('webform_node_' . $node->type, FALSE)) {
1883 85ad3d82 Assos Assos
    return;
1884
  }
1885
1886 a45e4bc1 Assos Assos
  // If empty or a new node (during preview) do not display.
1887
  if (empty($node->webform['components']) || empty($node->nid)) {
1888 85ad3d82 Assos Assos
    return;
1889
  }
1890
1891 c22e192e Assos Assos
  // If the webform is not set to display in this view mode, return early.
1892 e3063c4a Assos Assos
  // View mode of 'form' is exempted to allow blocks and views to force display.
1893 c22e192e Assos Assos
  $extra_fields = field_extra_fields_get_display('node', $node->type, $view_mode);
1894 e3063c4a Assos Assos
  if ($view_mode != 'form' && empty($extra_fields['webform']['visible'])) {
1895 c22e192e Assos Assos
    return;
1896
  }
1897
1898 a45e4bc1 Assos Assos
  $submission = FALSE;
1899 85ad3d82 Assos Assos
  $submission_count = 0;
1900 a45e4bc1 Assos Assos
  $page = node_is_page($node);
1901 85ad3d82 Assos Assos
  $logging_in = FALSE;
1902
  $total_limit_exceeded = FALSE;
1903
  $user_limit_exceeded = FALSE;
1904
  $closed = FALSE;
1905
1906 a45e4bc1 Assos Assos
  // If a teaser, tell the form to load subsequent pages on the node page. A
1907
  // special exception is made for this view mode only.
1908
  if ($view_mode == 'teaser' && !isset($node->webform['action'])) {
1909 85ad3d82 Assos Assos
    $query = array_diff_key($_GET, array('q' => ''));
1910
    $node->webform['action'] = url('node/' . $node->nid, array('query' => $query));
1911
  }
1912
1913
  // When logging in using a form on the same page as a webform node, suppress
1914
  // output messages so that they don't show up after the user has logged in.
1915
  // See http://drupal.org/node/239343.
1916
  if (isset($_POST['op']) && isset($_POST['name']) && isset($_POST['pass'])) {
1917
    $logging_in = TRUE;
1918
  }
1919
1920
  if ($node->webform['status'] == 0) {
1921
    $closed = TRUE;
1922
    $enabled = FALSE;
1923 a45e4bc1 Assos Assos
    $allowed_roles = array();
1924 85ad3d82 Assos Assos
  }
1925
  else {
1926 a45e4bc1 Assos Assos
    $allowed_roles = _webform_allowed_roles($node, $enabled); // $enabled set by reference.
1927 85ad3d82 Assos Assos
  }
1928
1929
  // Get a count of previous submissions by this user. Note that the
1930
  // webform_submission_access() function may disable the page cache for
1931
  // anonymous users if they are allowed to edit their own submissions!
1932
  if ($page && webform_submission_access($node, NULL, 'list')) {
1933
    module_load_include('inc', 'webform', 'includes/webform.submissions');
1934
    $submission_count = webform_get_submission_count($node->nid, $user->uid);
1935
  }
1936
1937
  // Check if this page is cached or not.
1938 a45e4bc1 Assos Assos
  $cached = drupal_page_is_cacheable();
1939 85ad3d82 Assos Assos
1940 a45e4bc1 Assos Assos
  // Check if the user can add another submission based on the individual submission limit.
1941 85ad3d82 Assos Assos
  if ($node->webform['submit_limit'] != -1) { // -1: Submissions are never throttled.
1942
    module_load_include('inc', 'webform', 'includes/webform.submissions');
1943
1944 a45e4bc1 Assos Assos
    // Disable the form if the limit is exceeded and page cache is not active. This prevent
1945
    // One anonymous user from generated a disabled webform page for the cache, which would
1946
    // be shown to other anonymous users who have not exceeded the limit.
1947
    if (($user_limit_exceeded = webform_submission_user_limit_check($node)) && !$cached) {
1948 85ad3d82 Assos Assos
      $enabled = FALSE;
1949
    }
1950
  }
1951
1952
  // Check if the user can add another submission if there is a limit on total
1953
  // submissions.
1954
  if ($node->webform['total_submit_limit'] != -1) { // -1: Submissions are never throttled.
1955
    module_load_include('inc', 'webform', 'includes/webform.submissions');
1956
1957 a45e4bc1 Assos Assos
    // Disable the form if the limit is exceeded. The cache is irrelevant for the total
1958
    // submission limit; when it is exceeded for one user, it is exceeded for any other
1959
    // user.
1960
    if (($total_limit_exceeded = webform_submission_total_limit_check($node))) {
1961 85ad3d82 Assos Assos
      $enabled = FALSE;
1962
    }
1963
  }
1964
1965
  // Check if this user has a draft for this webform.
1966 a45e4bc1 Assos Assos
  $resume_draft = FALSE;
1967 85ad3d82 Assos Assos
  if (($node->webform['allow_draft'] || $node->webform['auto_save']) && $user->uid != 0) {
1968
    // Draft found - display form with draft data for further editing.
1969
    if ($draft_sid = _webform_fetch_draft_sid($node->nid, $user->uid)) {
1970
      module_load_include('inc', 'webform', 'includes/webform.submissions');
1971
      $submission = webform_get_submission($node->nid, $draft_sid);
1972
      $enabled = TRUE;
1973 a45e4bc1 Assos Assos
      $resume_draft = TRUE;
1974 85ad3d82 Assos Assos
    }
1975
  }
1976
1977 c22e192e Assos Assos
  // Avoid building the same form twice on the same page request (which can
1978
  // happen if the webform is displayed in a panel or block) because this
1979
  // causes multistep forms to build incorrectly the second time.
1980
  $cached_forms = &drupal_static(__FUNCTION__, array());
1981
  if (isset($cached_forms[$node->nid])) {
1982
    $form = $cached_forms[$node->nid];
1983
  }
1984
  // If this is the first time, generate the form array.
1985
  else {
1986 a45e4bc1 Assos Assos
    $form = drupal_get_form('webform_client_form_' . $node->nid, $node, $submission, $resume_draft);
1987 c22e192e Assos Assos
    $cached_forms[$node->nid] = $form;
1988
  }
1989 85ad3d82 Assos Assos
1990
  // Remove the surrounding <form> tag if this is a preview.
1991
  if (!empty($node->in_preview)) {
1992
    $form['#type'] = 'markup';
1993
  }
1994
1995
  // Print out messages for the webform.
1996
  if (empty($node->in_preview) && !isset($node->webform_block) && !$logging_in) {
1997 a45e4bc1 Assos Assos
    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));
1998 85ad3d82 Assos Assos
  }
1999
2000
  // Add the output to the node.
2001
  $node->content['webform'] = array(
2002
    '#theme' => 'webform_view',
2003
    '#node' => $node,
2004
    '#page' => $page,
2005
    '#form' => $form,
2006
    '#enabled' => $enabled,
2007 a45e4bc1 Assos Assos
    '#visible' => $extra_fields['webform']['visible'],
2008 85ad3d82 Assos Assos
    '#weight' => 10,
2009
  );
2010
}
2011
2012 a45e4bc1 Assos Assos
/**
2013
 * Helper. Generates an array of allowed roles.
2014
 *
2015
 * @param object $node
2016
 *   The loaded node object containing a webform.
2017
 * @param boolean $user_is_allowed
2018
 *   Reference to boolean to be set to whether the current user is allowed.
2019
 * @return array
2020
 *   Associative array of allowed roles indexed by the role id with a boolean
2021
 *   value indicating if the current user has this role.
2022
 */
2023
function _webform_allowed_roles($node, &$user_is_allowed) {
2024
  global $user;
2025
  if ($node->webform['confidential']) {
2026
    // Confidential webform may only be submitted anonymously, including uid 1.
2027
    $user_is_allowed = user_is_anonymous();
2028
    $allowed_roles = array(DRUPAL_ANONYMOUS_RID => $user_is_allowed);
2029
  }
2030
  elseif (webform_variable_get('webform_submission_access_control')) {
2031
    // Check if the user's role can submit this webform.
2032
    $allowed_roles = array();
2033
    foreach ($node->webform['roles'] as $rid) {
2034
      $allowed_roles[$rid] = isset($user->roles[$rid]);
2035
    }
2036
    $user_is_allowed = $user->uid == 1 || array_search(TRUE, $allowed_roles);
2037
  }
2038
  else {
2039
    // If not using Webform submission access control, allow all roles.
2040
    $user_is_allowed = TRUE;
2041
    $allowed_roles = array_fill_keys(array_keys(user_roles()), TRUE);
2042
  }
2043
  return $allowed_roles;
2044
}
2045
2046 85ad3d82 Assos Assos
/**
2047
 * Output the Webform into the node content.
2048
 *
2049
 * @param $node
2050
 *   The webform node object.
2051
 * @param $page
2052
 *   If this webform node is being viewed as the main content of the page.
2053
 * @param $form
2054
 *   The rendered form.
2055
 * @param $enabled
2056
 *   If the form allowed to be completed by the current user.
2057
 */
2058
function theme_webform_view($variables) {
2059
  // Only show the form if this user is allowed access.
2060
  if ($variables['webform']['#enabled']) {
2061
    return drupal_render($variables['webform']['#form']);
2062
  }
2063
}
2064
2065
/**
2066
 * Display a message to a user if they are not allowed to fill out a form.
2067
 *
2068
 * @param $node
2069
 *   The webform node object.
2070
 * @param $page
2071
 *   If this webform node is being viewed as the main content of the page.
2072
 * @param $submission_count
2073
 *   The number of submissions this user has already submitted. Not calculated
2074
 *   for anonymous users.
2075
 * @param $user_limit_exceeded
2076
 *   Boolean value if the submission limit for this user has been exceeded.
2077
 * @param $total_limit_exceeded
2078
 *   Boolean value if the total submission limit has been exceeded.
2079
 * @param $allowed_roles
2080
 *   A list of user roles that are allowed to submit this webform.
2081
 * @param $closed
2082
 *   Boolean value if submissions are closed.
2083
 */
2084
function theme_webform_view_messages($variables) {
2085
  global $user;
2086
2087
  $node = $variables['node'];
2088
  $page = $variables['page'];
2089
  $submission_count = $variables['submission_count'];
2090
  $user_limit_exceeded = $variables['user_limit_exceeded'];
2091
  $total_limit_exceeded = $variables['total_limit_exceeded'];
2092
  $allowed_roles = $variables['allowed_roles'];
2093
  $closed = $variables['closed'];
2094
  $cached = $variables['cached'];
2095
2096 a45e4bc1 Assos Assos
  $type = 'warning';
2097 85ad3d82 Assos Assos
2098
  if ($closed) {
2099
    $message = t('Submissions for this form are closed.');
2100
  }
2101 a45e4bc1 Assos Assos
  elseif ($node->webform['confidential'] && user_is_logged_in()) {
2102
    $message = t('This form is confidential. You must <a href="!url">Log out</a> to submit it.',
2103
                 array('!url' => url('/user/logout', array('query' => array('destination' => request_uri())))));
2104
  }
2105 85ad3d82 Assos Assos
  // If open and not allowed to submit the form, give an explanation.
2106
  elseif (array_search(TRUE, $allowed_roles) === FALSE && $user->uid != 1) {
2107
    if (empty($allowed_roles)) {
2108
      // No roles are allowed to submit the form.
2109
      $message = t('Submissions for this form are closed.');
2110
    }
2111 a45e4bc1 Assos Assos
    elseif ($user->uid == 0) {
2112
      // The user is anonymous, so (at least) needs to log in to view the form.
2113 85ad3d82 Assos Assos
      $login = url('user/login', array('query' => drupal_get_destination()));
2114
      $register = url('user/register', array('query' => drupal_get_destination()));
2115
      if (variable_get('user_register', 1) == 0) {
2116
        $message = t('You must <a href="!login">login</a> to view this form.', array('!login' => $login));
2117
      }
2118
      else {
2119
        $message = t('You must <a href="!login">login</a> or <a href="!register">register</a> to view this form.', array('!login' => $login, '!register' => $register));
2120
      }
2121
    }
2122
    else {
2123
      // The user must be some other role to submit.
2124
      $message = t('You do not have permission to view this form.');
2125 3753f249 Assos Assos
      $type = 'error';
2126 85ad3d82 Assos Assos
    }
2127
  }
2128
2129
  // If the user has exceeded the limit of submissions, explain the limit.
2130
  elseif ($user_limit_exceeded && !$cached) {
2131
    if ($node->webform['submit_interval'] == -1 && $node->webform['submit_limit'] > 1) {
2132
      $message = t('You have submitted this form the maximum number of times (@count).', array('@count' => $node->webform['submit_limit']));
2133
    }
2134
    elseif ($node->webform['submit_interval'] == -1 && $node->webform['submit_limit'] == 1) {
2135
      $message = t('You have already submitted this form.');
2136
    }
2137
    else {
2138
      $message = t('You may not submit another entry at this time.');
2139
    }
2140
  }
2141
  elseif ($total_limit_exceeded && !$cached) {
2142
    if ($node->webform['total_submit_interval'] == -1 && $node->webform['total_submit_limit'] > 1) {
2143
      $message = t('This form has received the maximum number of entries.');
2144
    }
2145
    else {
2146
      $message = t('You may not submit another entry at this time.');
2147
    }
2148
  }
2149
2150
  // If the user has submitted before, give them a link to their submissions.
2151
  if ($submission_count > 0 && $node->webform['submit_notice'] == 1 && !$cached) {
2152
    if (empty($message)) {
2153 a45e4bc1 Assos Assos
      $message = t('You have already submitted this form.');
2154
      $type = 'status';
2155 85ad3d82 Assos Assos
    }
2156 a45e4bc1 Assos Assos
    $message .= ' ' . t('<a href="!url">View your previous submissions</a>.', array('!url' => url('node/' . $node->nid . '/submissions')));
2157 85ad3d82 Assos Assos
  }
2158
2159
  if ($page && isset($message)) {
2160
    drupal_set_message($message, $type, FALSE);
2161
  }
2162
}
2163
2164
/**
2165
 * Implements hook_mail().
2166
 */
2167
function webform_mail($key, &$message, $params) {
2168
  $message['headers'] = array_merge($message['headers'], $params['headers']);
2169
  $message['subject'] = $params['subject'];
2170
  $message['body'][] = $params['message'];
2171
}
2172
2173
/**
2174
 * Implements hook_block_info().
2175
 */
2176
function webform_block_info() {
2177
  $blocks = array();
2178 a45e4bc1 Assos Assos
  $webform_node_types = webform_node_types();
2179 85ad3d82 Assos Assos
  if (!empty($webform_node_types)) {
2180
    $query = db_select('webform', 'w')->fields('w')->fields('n', array('title'));
2181
    $query->leftJoin('node', 'n', 'w.nid = n.nid');
2182
    $query->condition('w.block', 1);
2183
    $query->condition('n.type', $webform_node_types, 'IN');
2184
    $result = $query->execute();
2185
    foreach ($result as $data) {
2186
      $blocks['client-block-' . $data->nid] = array(
2187
        'info' => t('Webform: !title', array('!title' => $data->title)),
2188
        'cache' => DRUPAL_NO_CACHE,
2189
      );
2190
    }
2191
  }
2192
  return $blocks;
2193
}
2194
2195
/**
2196
 * Implements hook_block_view().
2197
 */
2198
function webform_block_view($delta = '') {
2199
  global $user;
2200
2201
  // Load the block-specific configuration settings.
2202 a45e4bc1 Assos Assos
  $webform_blocks = webform_variable_get('webform_blocks');
2203 85ad3d82 Assos Assos
  $settings = isset($webform_blocks[$delta]) ? $webform_blocks[$delta] : array();
2204
  $settings += array(
2205
    'display' => 'form',
2206 a45e4bc1 Assos Assos
    'pages_block' => 1,
2207
    'confirmation_block' => 0,
2208 85ad3d82 Assos Assos
  );
2209
2210
  // Get the node ID from delta.
2211
  $nid = drupal_substr($delta, strrpos($delta, '-') + 1);
2212
2213
  // Load node in current language.
2214
  if (module_exists('translation')) {
2215
    global $language;
2216
    if (($translations = translation_node_get_translations($nid)) && (isset($translations[$language->language]))) {
2217
      $nid = $translations[$language->language]->nid;
2218
    }
2219
  }
2220
2221
  // The webform node to display in the block.
2222
  $node = node_load($nid);
2223
2224
  // Return if user has no access to the webform node.
2225
  if (!node_access('view', $node)) {
2226
    return;
2227
  }
2228
2229
  // This is a webform node block.
2230
  $node->webform_block = TRUE;
2231 a45e4bc1 Assos Assos
  $node->webform['confirmation_block'] = $settings['confirmation_block'];
2232 85ad3d82 Assos Assos
2233
  // If not displaying pages in the block, set the #action property on the form.
2234
  if ($settings['pages_block']) {
2235
    $node->webform['action'] = FALSE;
2236
  }
2237
  else {
2238
    $query = array_diff_key($_GET, array('q' => ''));
2239
    $node->webform['action'] = url('node/' . $node->nid, array('query' => $query));
2240
  }
2241
2242
  // Generate the content of the block based on display settings.
2243 a45e4bc1 Assos Assos
  $content = array();
2244 85ad3d82 Assos Assos
  if ($settings['display'] == 'form') {
2245 e3063c4a Assos Assos
    webform_node_view($node, 'form');
2246 a45e4bc1 Assos Assos
    if (isset($node->content['webform'])) {
2247
      $content = $node->content['webform'];
2248
      if (!$node->content['webform']['#visible']) {
2249
        // If the webform form is only shown in a block and not as within the
2250
        // node, remove the content from the node.
2251
        unset($node->content['webform']);
2252
      }
2253
    }
2254 85ad3d82 Assos Assos
  }
2255
  else {
2256 a45e4bc1 Assos Assos
    $content = node_view($node, $settings['display']);
2257
  }
2258
2259
  // Check for an in-block confirmation message.
2260
  if (isset($_SESSION['webform_confirmation'][$nid])) {
2261
    if ($_SESSION['webform_confirmation'][$nid]['confirmation_page']) {
2262
      // Replace form with confirmation page.
2263
      $content = array(
2264
        '#theme' => array('webform_confirmation_' . $node->nid, 'webform_confirmation'),
2265
        '#node' => $node,
2266
        '#sid' => $_SESSION['webform_confirmation'][$nid]['sid'],
2267
      );
2268 01d522a6 Assos Assos
    } 
2269
    elseif (strlen(trim(strip_tags($node->webform['confirmation'])))) {
2270 a45e4bc1 Assos Assos
      // Display confirmation link drupal status messages, but in the block.
2271
      $message = webform_replace_tokens($node->webform['confirmation'],
2272
                                        $node,
2273
                                        webform_get_submission($nid, $_SESSION['webform_confirmation'][$nid]['confirmation_page']),
2274
                                        NULL,
2275
                                        $node->webform['confirmation_format']);
2276
      $content = array(
2277
        'confirmation_message' => array(
2278
          '#markup' => "<div class=\"messages status webform-confirmation\">\n" .
2279
                       '<h2 class="element-invisible">' . t('Status message') . "</h2>\n" .
2280
                       $message .
2281
                       "</div>\n",
2282
          '#weight' => -1,
2283
        ),
2284
        'webform_view' => $content,
2285
      );
2286
2287
    }
2288
    unset($_SESSION['webform_confirmation'][$nid]);
2289
    if (empty($_SESSION['webform_confirmation'])) {
2290
      unset($_SESSION['webform_confirmation']);
2291
    }
2292 85ad3d82 Assos Assos
  }
2293
2294
  // Add contextual links for the webform node if they aren't already there.
2295
  if (!isset($content['#contextual_links']['node'])) {
2296
    $content['#contextual_links']['node'] = array('node', array($node->nid));
2297
  }
2298
2299 4cfd8be6 Assos Assos
  // Create the block, using the node title for the block title.
2300 85ad3d82 Assos Assos
  // Note that we render the content immediately here rather than passing back
2301
  // a renderable so that if the block is empty it is hidden.
2302
  $block = array(
2303 4cfd8be6 Assos Assos
    'subject' => check_plain($node->title),
2304 85ad3d82 Assos Assos
    'content' => drupal_render($content),
2305
  );
2306
  return $block;
2307
}
2308
2309
/**
2310
 * Implements hook_block_configure().
2311
 */
2312
function webform_block_configure($delta = '') {
2313 a45e4bc1 Assos Assos
  $nid = str_replace('client-block-', '', $delta);
2314
  $node = node_load($nid);
2315
2316 85ad3d82 Assos Assos
  // Load the block-specific configuration settings.
2317 a45e4bc1 Assos Assos
  $webform_blocks = webform_variable_get('webform_blocks');
2318 85ad3d82 Assos Assos
  $settings = isset($webform_blocks[$delta]) ? $webform_blocks[$delta] : array();
2319
  $settings += array(
2320
    'display' => 'form',
2321 a45e4bc1 Assos Assos
    'pages_block' => 1,
2322
    'confirmation_block' => 0,
2323
  );
2324
2325
  // Build a list of view modes for this node.
2326
  $entity_info = entity_get_info('node');
2327
  $view_modes = array(
2328
    'form' => t('Form only'),
2329 85ad3d82 Assos Assos
  );
2330 a45e4bc1 Assos Assos
  foreach ($entity_info['view modes'] as $view_mode_key => $view_mode_info) {
2331
    $view_modes[$view_mode_key] = $view_mode_info['label'];
2332
  }
2333 85ad3d82 Assos Assos
2334
  $form = array();
2335
  $form['display'] = array(
2336 a45e4bc1 Assos Assos
    '#type' => 'select',
2337
    '#title' => t('View mode'),
2338 85ad3d82 Assos Assos
    '#default_value' => $settings['display'],
2339 a45e4bc1 Assos Assos
    '#options' => $view_modes,
2340
    '#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'))),
2341 85ad3d82 Assos Assos
  );
2342
2343
  $form['pages_block'] = array(
2344 a45e4bc1 Assos Assos
    '#type' => 'radios',
2345
    '#title' => t('Multi-page handling'),
2346
    '#options' => array(
2347
      1 => t('Display all pages inside block'),
2348
      0 => t('Redirect to the node page after the first page'),
2349
    ),
2350 85ad3d82 Assos Assos
    '#default_value' => $settings['pages_block'],
2351 a45e4bc1 Assos Assos
    '#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.'),
2352 85ad3d82 Assos Assos
  );
2353
2354 a45e4bc1 Assos Assos
  $form['confirmation_block'] = array(
2355
    '#type' => 'radios',
2356
    '#title' => t('Confirmation message'),
2357
    '#options' => array(
2358
      0 => t('Display as configured in the webform'),
2359
      1 => t('Display the confirmation page in the block on the same page (no redirect)'),
2360
    ),
2361
    '#default_value' => $settings['confirmation_block'],
2362
    '#description' => t('This setting overrides the webform\'s configuration and redirection location settings when the webform is submitted via this block.'),
2363
  );
2364 85ad3d82 Assos Assos
  return $form;
2365
}
2366
2367
/**
2368
 * Implements hook_block_save().
2369
 */
2370
function webform_block_save($delta = '', $edit = array()) {
2371
  // Load the previously defined block-specific configuration settings.
2372 a45e4bc1 Assos Assos
  $settings = webform_variable_get('webform_blocks');
2373 85ad3d82 Assos Assos
  // Build the settings array.
2374
  $new_settings[$delta] = array(
2375
    'display' => $edit['display'],
2376
    'pages_block' => $edit['pages_block'],
2377 a45e4bc1 Assos Assos
    'confirmation_block' => $edit['confirmation_block'],
2378 85ad3d82 Assos Assos
  );
2379
  // We store settings for multiple blocks in just one variable
2380
  // so we merge the existing settings with the new ones before save.
2381
  variable_set('webform_blocks', array_merge($settings, $new_settings));
2382
}
2383
2384
/**
2385
 * Client form generation function. If this is displaying an existing
2386
 * submission, pass in the $submission variable with the contents of the
2387
 * submission to be displayed.
2388
 *
2389
 * @param $form
2390
 *   The current form array (always empty).
2391
 * @param $form_state
2392
 *   The current form values of a submission, used in multipage webforms.
2393
 * @param $node
2394
 *   The current webform node.
2395
 * @param $submission
2396
 *   An object containing information about the form submission if we're
2397
 *   displaying a result.
2398 a45e4bc1 Assos Assos
 * @param $resume_draft
2399
 *   Optional. Set to TRUE when resuming a draft and skipping past previously-
2400
 *   validated pages is desired.
2401 85ad3d82 Assos Assos
 * @param $filter
2402
 *   Whether or not to filter the contents of descriptions and values when
2403
 *   building the form. Values need to be unfiltered to be editable by
2404
 *   Form Builder.
2405
 */
2406 a45e4bc1 Assos Assos
function webform_client_form($form, &$form_state, $node, $submission = FALSE, $resume_draft = FALSE, $filter = TRUE) {
2407 85ad3d82 Assos Assos
  global $user;
2408
2409
  // Attach necessary JavaScript and CSS.
2410
  $form['#attached'] = array(
2411
    'css' => array(drupal_get_path('module', 'webform') . '/css/webform.css'),
2412
    'js' => array(drupal_get_path('module', 'webform') . '/js/webform.js'),
2413
  );
2414
  form_load_include($form_state, 'inc', 'webform', 'includes/webform.components');
2415
  form_load_include($form_state, 'inc', 'webform', 'includes/webform.submissions');
2416
2417 01d522a6 Assos Assos
  // For ajax requests, $form_state['values']['details'] is missing. Restore
2418
  // from storage, if available, for multi-page forms.
2419
  if (empty($form_state['values']['details']) && !empty($form_state['storage']['details'])) {
2420
    $form_state['values']['details'] = $form_state['storage']['details'];
2421
  }
2422
2423 85ad3d82 Assos Assos
  // If in a multi-step form, a submission ID may be specified in form state.
2424
  // Load this submission. This allows anonymous users to use auto-save.
2425
  if (empty($submission) && !empty($form_state['values']['details']['sid'])) {
2426
    $submission = webform_get_submission($node->nid, $form_state['values']['details']['sid']);
2427
  }
2428
2429 a45e4bc1 Assos Assos
  $finished = isset($submission->is_draft) ? (!$submission->is_draft) : 0;
2430
  $submit_button_text = $finished
2431
                          ? t('Save')
2432
                          : (empty($node->webform['submit_text']) ? t('Submit') : t($node->webform['submit_text']));
2433
2434 85ad3d82 Assos Assos
  // Bind arguments to $form to make them available in theming and form_alter.
2435
  $form['#node'] = $node;
2436
  $form['#submission'] = $submission;
2437 a45e4bc1 Assos Assos
  $form['#is_draft'] = $submission && $submission->is_draft;
2438 85ad3d82 Assos Assos
  $form['#filter'] = $filter;
2439
2440
  // Add a theme function for this form.
2441
  $form['#theme'] = array('webform_form_' . $node->nid, 'webform_form');
2442
2443 a45e4bc1 Assos Assos
  // Add a CSS class for all client forms.
2444
  $form['#attributes']['class'][] = 'webform-client-form';
2445
  $form['#attributes']['class'][] = 'webform-client-form-' . $node->nid;
2446 85ad3d82 Assos Assos
2447
  // Set the encoding type (necessary for file uploads).
2448
  $form['#attributes']['enctype'] = 'multipart/form-data';
2449
2450
  // Sometimes when displaying a webform as a teaser or block, a custom action
2451
  // property is set to direct the user to the node page.
2452
  if (!empty($node->webform['action'])) {
2453
    $form['#action'] = $node->webform['action'];
2454
  }
2455
2456
  $form['#submit'] = array('webform_client_form_pages', 'webform_client_form_submit');
2457
  $form['#validate'] = array('webform_client_form_validate');
2458 a45e4bc1 Assos Assos
  // Add includes for used component types and pre/post validation handlers
2459
  $form['#process'] = array('webform_client_form_process');
2460 85ad3d82 Assos Assos
2461
  if (is_array($node->webform['components']) && !empty($node->webform['components'])) {
2462
    // Prepare a new form array.
2463
    $form['submitted'] = array(
2464
      '#tree' => TRUE
2465
    );
2466
    $form['details'] = array(
2467
      '#tree' => TRUE,
2468
    );
2469
2470
    // Put the components into a tree structure.
2471
    if (!isset($form_state['storage']['component_tree'])) {
2472
      $form_state['webform']['component_tree'] = array();
2473
      $form_state['webform']['page_count'] = 1;
2474
      $form_state['webform']['page_num'] = 1;
2475
      _webform_components_tree_build($node->webform['components'], $form_state['webform']['component_tree'], 0, $form_state['webform']['page_count']);
2476 a45e4bc1 Assos Assos
2477
      // If preview is enabled, increase the page count by one.
2478
      if ($node->webform['preview']) {
2479
        $form_state['webform']['page_count']++;
2480
      }
2481
      $form_state['webform']['preview'] = $node->webform['preview'];
2482
2483
      // If this is the first time this draft has been restore and presented to
2484
      // the user, let them know that they are looking at a draft, rather than
2485
      // a new form. This applies to the node view page, but not to a submission
2486
      // edit page (where they presummably know what submission they are
2487
      // editing).
2488
      if ($resume_draft && empty($form_state['input'])) {
2489
        drupal_set_message(t('A partially-completed form was found. Please complete the remaining portions.'));
2490
      }
2491 85ad3d82 Assos Assos
    }
2492
    else {
2493
      $form_state['webform']['component_tree'] = $form_state['storage']['component_tree'];
2494
      $form_state['webform']['page_count'] = $form_state['storage']['page_count'];
2495
      $form_state['webform']['page_num'] = $form_state['storage']['page_num'];
2496 a45e4bc1 Assos Assos
      $form_state['webform']['preview'] = $form_state['storage']['preview'];
2497
    }
2498
2499
    // Set the input values based on whether we're editing an existing
2500
    // submission or not.
2501
    $input_values = isset($submission->data) ? $submission->data : array();
2502
2503
    // Form state storage override any default submission information. Convert
2504
    // the value structure to always be an array, matching $submission->data.
2505
    if (isset($form_state['storage']['submitted'])) {
2506
      foreach ($form_state['storage']['submitted'] as $cid => $data) {
2507
        $input_values[$cid] = is_array($data) ? $data : array($data);
2508
      }
2509
    }
2510
2511
    // Form state values override any default submission information. Convert
2512
    // the value structure to always be an array, matching $submission->data.
2513
    if (isset($form_state['values']['submitted'])) {
2514
      foreach ($form_state['values']['submitted'] as $cid => $data) {
2515
        $input_values[$cid] = is_array($data) ? $data : array($data);
2516
      }
2517
    }
2518
2519
    // Generate conditional topological order & report any errors.
2520
    $sorter = webform_get_conditional_sorter($node);
2521
    $sorter->reportErrors();
2522
2523
    // Excecute the condtionals on the current input values
2524
    $input_values = $sorter->executeConditionals($input_values);
2525
2526
    // Allow values from other pages to be sent to browser for conditionals.
2527
    $form['#conditional_values'] = $input_values;
2528
2529 01d522a6 Assos Assos
    // Allow components access to most up-to-date values.
2530
    $form_state['#conditional_values'] = $input_values;
2531
2532 a45e4bc1 Assos Assos
    // For resuming a previous draft, find the next page after the last
2533
    // validated page.
2534
    if (!isset($form_state['storage']['page_num']) && $submission && $submission->is_draft && $submission->highest_valid_page) {
2535
      // Find the
2536
      //    1) previous/next non-empty page, or
2537
      //    2) the preview page, or
2538
      //    3) the preview page, forcing its display if the form would unexpectedly submit, or
2539
      //    4) page 1 even if empty, if no other previous page would be shown
2540
      $form_state['webform']['page_num'] = $submission->highest_valid_page;
2541
      do {
2542
        $form_state['webform']['page_num']++;
2543
      } while (!webform_get_conditional_sorter($node)->pageVisibility($form_state['webform']['page_num']));
2544
      if (!$form_state['webform']['preview'] && $form_state['webform']['page_num'] == $form_state['webform']['page_count'] + (int)!$form_state['webform']['preview']) {
2545
        // Force a preview to avert an unintended submission via Next.
2546
        $form_state['webform']['preview'] = TRUE;
2547
        $form_state['webform']['page_count']++;
2548
      }
2549 01d522a6 Assos Assos
      // The form hasn't been submitted (ever) and the preview code will
2550
      // expect $form_state['values']['submitted'] to be set from a previous
2551
      // submission, so provide these values here.
2552
      $form_state['values']['submitted'] = $input_values;
2553 a45e4bc1 Assos Assos
      $form_state['storage']['submitted'] = $input_values;
2554 85ad3d82 Assos Assos
    }
2555
2556
    // Shorten up our variable names.
2557
    $component_tree = $form_state['webform']['component_tree'];
2558
    $page_count = $form_state['webform']['page_count'];
2559
    $page_num = $form_state['webform']['page_num'];
2560 a45e4bc1 Assos Assos
    $preview = $form_state['webform']['preview'];
2561 85ad3d82 Assos Assos
2562
    if ($page_count > 1) {
2563 a45e4bc1 Assos Assos
      $page_labels = webform_page_labels($node, $form_state);
2564
      $form['progressbar'] = array(
2565
        '#theme' => 'webform_progressbar',
2566
        '#node' => $node,
2567
        '#page_num' => $page_num,
2568
        '#page_count' => count($page_labels),
2569
        '#page_labels' => $page_labels,
2570
        '#weight' => -100
2571
      );
2572 85ad3d82 Assos Assos
    }
2573
2574 a45e4bc1 Assos Assos
    // Check whether a previous submission was truncated. The length of the client form is not
2575
    // estimated before submission because a) the determination may not be accurate for some
2576
    // webform components and b) the error will be apparent upon submission.
2577
    webform_input_vars_check($form, $form_state, 'submitted');
2578
2579 85ad3d82 Assos Assos
    // Recursively add components to the form. The unfiltered version of the
2580
    // form (typically used in Form Builder), includes all components.
2581
    foreach ($component_tree['children'] as $cid => $component) {
2582 a45e4bc1 Assos Assos
      if ($component['type'] == 'pagebreak') {
2583
        $next_page_labels[$component['page_num'] - 1] = !empty($component['extra']['next_page_label']) ? t($component['extra']['next_page_label']) : t('Next Page >');
2584
        $prev_page_labels[$component['page_num']] = !empty($component['extra']['prev_page_label']) ? t($component['extra']['prev_page_label']) : t('< Previous Page');
2585 85ad3d82 Assos Assos
      }
2586 a45e4bc1 Assos Assos
      if (!$filter || $sorter->componentVisibility($cid, $page_num)) {
2587
        $component_value = isset($input_values[$cid]) ? $input_values[$cid] : NULL;
2588
        _webform_client_form_add_component($node, $component, $component_value, $form['submitted'], $form, $input_values, 'form', $page_num, $filter);
2589
      }
2590
    }
2591
    if ($preview) {
2592
      $next_page_labels[$page_count - 1] = $node->webform['preview_next_button_label'] ? t($node->webform['preview_next_button_label']) : t('Preview');
2593
      $prev_page_labels[$page_count] = $node->webform['preview_prev_button_label'] ? t($node->webform['preview_prev_button_label']) : t('< Previous');
2594
    }
2595
2596
    // Add the preview if needed.
2597
    if ($preview && $page_num === $page_count) {
2598
      $preview_submission = webform_submission_create($node, $user, $form_state, TRUE, $submission);
2599
      $preview_message = $node->webform['preview_message'];
2600
      if (strlen(trim(strip_tags($preview_message))) === 0) {
2601
        $preview_message = t('Please review your submission. Your submission is not complete until you press the "!button" button!', array('!button' => $submit_button_text));
2602
      }
2603
      $form['preview_message'] = array(
2604
        '#type' => 'markup',
2605
        '#markup' => webform_replace_tokens($preview_message, $node, $preview_submission, NULL, $node->webform['preview_message_format']),
2606
      );
2607
2608
      $form['preview'] = webform_submission_render($node, $preview_submission, NULL, 'html', $node->webform['preview_excluded_components']);
2609
      $form['#attributes']['class'][] = 'preview';
2610 85ad3d82 Assos Assos
    }
2611
2612
    // These form details help managing data upon submission.
2613
    $form['details']['nid'] = array(
2614
      '#type' => 'value',
2615
      '#value' => $node->nid,
2616
    );
2617
    $form['details']['sid'] = array(
2618
      '#type' => 'hidden',
2619 3753f249 Assos Assos
      '#value' => isset($submission->sid) ? $submission->sid : NULL,
2620 85ad3d82 Assos Assos
    );
2621
    $form['details']['uid'] = array(
2622
      '#type' => 'value',
2623
      '#value' => isset($submission->uid) ? $submission->uid : $user->uid,
2624
    );
2625
    $form['details']['page_num'] = array(
2626
      '#type'  => 'hidden',
2627
      '#value' => $page_num,
2628
    );
2629
    $form['details']['page_count'] = array(
2630
      '#type'  => 'hidden',
2631
      '#value' => $page_count,
2632
    );
2633
    $form['details']['finished'] = array(
2634
      '#type' => 'hidden',
2635 a45e4bc1 Assos Assos
      '#value' => $finished,
2636 85ad3d82 Assos Assos
    );
2637
2638 a45e4bc1 Assos Assos
    // Add process functions to remove the IDs forced upon buttons and wrappers.
2639
    $actions_pre_render = array_merge(element_info_property('actions', '#pre_render', array()), array('webform_pre_render_remove_id'));
2640
    $buttons_pre_render = array_merge(element_info_property('submit', '#pre_render', array()), array('webform_pre_render_remove_id'));
2641
2642 85ad3d82 Assos Assos
    // Add buttons for pages, drafts, and submissions.
2643
    $form['actions'] = array(
2644
      '#type' => 'actions',
2645
      '#weight' => 1000,
2646 a45e4bc1 Assos Assos
      '#pre_render' => $actions_pre_render,
2647 85ad3d82 Assos Assos
    );
2648
2649
    // Add the draft button.
2650
    if ($node->webform['allow_draft'] && (empty($submission) || $submission->is_draft) && $user->uid != 0) {
2651
      $form['actions']['draft'] = array(
2652
        '#type' => 'submit',
2653
        '#value' => t('Save Draft'),
2654
        '#weight' => -2,
2655 a45e4bc1 Assos Assos
        '#validate' => array('webform_client_form_prevalidate'),    // Prevalidation only; no element validation for Save Draft
2656
        '#attributes' => array(
2657
          'formnovalidate' => 'formnovalidate',
2658
          'class' => array('webform-draft'),
2659
        ),
2660
        '#pre_render' => $buttons_pre_render,
2661 85ad3d82 Assos Assos
      );
2662
    }
2663
2664 a45e4bc1 Assos Assos
    // Add the submit button(s).
2665
    if ($page_num > 1) {
2666
      $form['actions']['previous'] = array(
2667
        '#type' => 'submit',
2668
        '#value' => $prev_page_labels[$page_num],
2669
        '#weight' => 5,
2670
        '#validate' => array(),
2671
        '#attributes' => array(
2672
          'formnovalidate' => 'formnovalidate',
2673
          'class' => array('webform-previous'),
2674
        ),
2675
        '#pre_render' => $buttons_pre_render,
2676
      );
2677 85ad3d82 Assos Assos
    }
2678 a45e4bc1 Assos Assos
    if ($page_num == $page_count) {
2679 85ad3d82 Assos Assos
      $form['actions']['submit'] = array(
2680
        '#type' => 'submit',
2681 a45e4bc1 Assos Assos
        '#value' => $submit_button_text,
2682
        '#weight' => 10,
2683
        '#attributes' => array(
2684
          'class' => array('webform-submit', 'button-primary'),
2685
        ),
2686
        '#pre_render' => $buttons_pre_render,
2687
      );
2688
    }
2689
    elseif ($page_num < $page_count) {
2690
      $form['actions']['next'] = array(
2691
        '#type' => 'submit',
2692
        '#value' => $next_page_labels[$page_num],
2693 85ad3d82 Assos Assos
        '#weight' => 10,
2694 a45e4bc1 Assos Assos
        '#attributes' => array(
2695
          'class' => array('webform-next', 'button-primary'),
2696
        ),
2697
        '#pre_render' => $buttons_pre_render,
2698 85ad3d82 Assos Assos
      );
2699
    }
2700
  }
2701
2702
  return $form;
2703
}
2704
2705
/**
2706
 * Process function for webform_client_form().
2707
 *
2708
 * Include all the enabled components for this form to ensure availability.
2709 a45e4bc1 Assos Assos
 * Also adds the pre- and post-validators to ensure that hook_form_alters don't
2710
 * add their validation functions in the wrong order.
2711 85ad3d82 Assos Assos
 */
2712 a45e4bc1 Assos Assos
function webform_client_form_process($form, $form_state) {
2713 85ad3d82 Assos Assos
  $components = webform_components();
2714
  foreach ($components as $component_type => $component) {
2715
    webform_component_include($component_type);
2716
  }
2717
2718 a45e4bc1 Assos Assos
  // Add the post validation to end of validators. Do this first on the off
2719
  // chance that an _alter function has unset form['#validate'].
2720
  $form['#validate'][] = 'webform_client_form_postvalidate';
2721
  // Add the pre-validator to the front of the list to run first
2722
  array_unshift($form['#validate'], 'webform_client_form_prevalidate');
2723 85ad3d82 Assos Assos
2724 a45e4bc1 Assos Assos
  return $form;
2725 85ad3d82 Assos Assos
}
2726
2727 a45e4bc1 Assos Assos
2728 85ad3d82 Assos Assos
/**
2729
 * Add a component to a renderable array. Called recursively for fieldsets.
2730
 *
2731
 * This function assists in the building of the client form, as well as the
2732
 * display of results, and the text of e-mails.
2733
 *
2734
 * @param $component
2735
 *   The component to be added to the form.
2736
 * @param $component_value
2737
 *   The components current value if known.
2738
 * @param $parent_fieldset
2739
 *   The fieldset to which this element will be added.
2740
 * @param $form
2741
 *   The entire form array.
2742 a45e4bc1 Assos Assos
 * @param $input_values
2743
 *   All the values for this form, keyed by the component IDs. This may be
2744
 *   pulled from $form_state['values']['submitted'] or $submission->data.
2745
 *   These values are used to check if the component should be displayed
2746
 *   conditionally.
2747 85ad3d82 Assos Assos
 * @param $format
2748
 *   The format the form should be displayed as. May be one of the following:
2749
 *   - form: Show as an editable form.
2750
 *   - html: Show as HTML results.
2751
 *   - text: Show as plain text.
2752
 * @param $filter
2753
 *   Whether the form element properties should be filtered. Only set to FALSE
2754
 *   if needing the raw properties for editing.
2755
 *
2756
 * @see webform_client_form()
2757
 * @see webform_submission_render()
2758
 */
2759 a45e4bc1 Assos Assos
function _webform_client_form_add_component($node, $component, $component_value, &$parent_fieldset, &$form, $input_values, $format = 'form', $page_num = 0, $filter = TRUE) {
2760 85ad3d82 Assos Assos
  $cid = $component['cid'];
2761 ca0757b9 Assos Assos
  $component_access = empty($component['extra']['private']) || webform_results_access($node);
2762 85ad3d82 Assos Assos
2763
  // Load with submission information if necessary.
2764
  if ($format != 'form') {
2765
    // This component is display only.
2766 a45e4bc1 Assos Assos
    $data = empty($input_values[$cid]) ? NULL : $input_values[$cid];
2767
    if ($display_element = webform_component_invoke($component['type'], 'display', $component, $data, $format, $form['#submission'])) {
2768 ca0757b9 Assos Assos
      // Set access based on the private property.
2769 a45e4bc1 Assos Assos
      $display_element += array('#access' => TRUE);
2770
      $display_element['#access'] = $display_element['#access'] && $component_access;
2771 ca0757b9 Assos Assos
2772 85ad3d82 Assos Assos
      // Ensure the component is added as a property.
2773
      $display_element['#webform_component'] = $component;
2774
2775 a45e4bc1 Assos Assos
      // Add custom CSS classes to the field and wrapper.
2776
      _webform_component_classes($display_element, $component);
2777
2778 85ad3d82 Assos Assos
      // Allow modules to modify a "display only" webform component.
2779
      drupal_alter('webform_component_display', $display_element, $component);
2780
2781
      // The form_builder() function usually adds #parents and #id for us, but
2782
      // because these are not marked for #input, we need to add them manually.
2783
      if (!isset($display_element['#parents'])) {
2784
        $parents = isset($parent_fieldset['#parents']) ? $parent_fieldset['#parents'] : array('submitted');
2785
        $parents[] = $component['form_key'];
2786
        $display_element['#parents'] = $parents;
2787
      }
2788
      if (!isset($display_element['#id'])) {
2789
        $display_element['#id'] = drupal_clean_css_identifier('edit-' . implode('-', $display_element['#parents']));
2790
      }
2791
2792
      // Add the element into the proper parent in the display.
2793
      $parent_fieldset[$component['form_key']] = $display_element;
2794
    }
2795
  }
2796
  // Show the component only on its form page, or if building an unfiltered
2797
  // version of the form (such as for Form Builder).
2798
  elseif ($component['page_num'] == $page_num || $filter == FALSE) {
2799
    // Add this user-defined field to the form (with all the values that are always available).
2800 a45e4bc1 Assos Assos
    if ($element = webform_component_invoke($component['type'], 'render', $component, $component_value, $filter, $form['#submission'])) {
2801 ca0757b9 Assos Assos
      // Set access based on the private property.
2802 a45e4bc1 Assos Assos
      $element += array('#access' => TRUE);
2803
      $element['#access'] = $element['#access'] && $component_access;
2804 ca0757b9 Assos Assos
2805 85ad3d82 Assos Assos
      // Ensure the component is added as a property.
2806
      $element['#webform_component'] = $component;
2807
2808
      // The 'private' option is in most components, but it's not a real
2809
      // property. Add it for Form Builder compatibility.
2810
      if (webform_component_feature($component['type'], 'private')) {
2811
        $element['#webform_private'] = $component['extra']['private'];
2812
      }
2813
2814 a45e4bc1 Assos Assos
      // The 'placeholder' option is in some components, but it's not a real
2815
      // property. Add it for Form Builder compatibility.
2816
      if (webform_component_feature($component['type'], 'placeholder')) {
2817
        $element['#webform_placeholder'] = $component['extra']['placeholder'];
2818
      }
2819
2820
      // Add custom CSS classes to the field and wrapper.
2821
      _webform_component_classes($element, $component);
2822
2823 85ad3d82 Assos Assos
      // Allow modules to modify a webform component that is going to be render in a form.
2824
      drupal_alter('webform_component_render', $element, $component);
2825
2826
      // Add the element into the proper parent in the form.
2827
      $parent_fieldset[$component['form_key']] = $element;
2828
    }
2829
    else {
2830
      drupal_set_message(t('The webform component @type is not able to be displayed', array('@type' => $component['type'])));
2831
    }
2832
  }
2833
2834
  // Disable validation initially on all elements. We manually validate
2835
  // all webform elements in webform_client_form_validate().
2836
  if (isset($parent_fieldset[$component['form_key']])) {
2837
    $parent_fieldset[$component['form_key']]['#validated'] = TRUE;
2838
    $parent_fieldset[$component['form_key']]['#webform_validated'] = FALSE;
2839
  }
2840
2841
  if (isset($component['children']) && is_array($component['children'])) {
2842 a45e4bc1 Assos Assos
    $sorter = webform_get_conditional_sorter($node);
2843 85ad3d82 Assos Assos
    foreach ($component['children'] as $scid => $subcomponent) {
2844 a45e4bc1 Assos Assos
      $subcomponent_value = isset($input_values[$scid]) ? $input_values[$scid] : NULL;
2845
      // Include if always shown, or for forms, also if currently hidden but might be shown due to conditionals.
2846
      $visibility = $sorter->componentVisibility($scid, $subcomponent['page_num']);
2847 ba09eb79 Assos Assos
      if ($visibility == WebformConditionals::componentShown || ($format == 'form' && $visibility) || !$filter) {
2848 a45e4bc1 Assos Assos
        _webform_client_form_add_component($node, $subcomponent, $subcomponent_value, $parent_fieldset[$component['form_key']], $form, $input_values, $format, $page_num, $filter);
2849 85ad3d82 Assos Assos
      }
2850
    }
2851
  }
2852
}
2853
2854 a45e4bc1 Assos Assos
/**
2855
 * Validates that the form can still be submitted, saved as draft, or edited.
2856
 *
2857
 * Because forms may be submitted from cache or the webform changed while the
2858
 * submission is in progress, the conditions to allow the form are re-checked
2859
 * upon form submission.
2860
 */
2861
function webform_client_form_prevalidate($form, &$form_state) {
2862
  global $user;
2863
2864
  // Refresh the node in case it changed since the form was build and retrieved from cache.
2865
  $node = $form['#node'] = node_load($form['#node']->nid);
2866 85ad3d82 Assos Assos
  $finished = $form_state['values']['details']['finished'];
2867
2868 a45e4bc1 Assos Assos
  // Check if the user is allowed to submit based on role. This check is
2869
  // repeated here to ensure the user is still logged in at the time of
2870
  // submission, otherwise a stale form in another window may be allowed.
2871
  $allowed_roles = _webform_allowed_roles($node, $allowed_role); // $allowed_role set by reference.
2872
2873 85ad3d82 Assos Assos
  // Check that the submissions have not exceeded the total submission limit.
2874 a45e4bc1 Assos Assos
  $total_limit_exceeded = FALSE;
2875
  if ($node->webform['total_submit_limit'] != -1 && !$finished) {
2876
    $total_limit_exceeded = webform_submission_total_limit_check($node);
2877 85ad3d82 Assos Assos
  }
2878
2879
  // Check that the user has not exceeded the submission limit.
2880
  // This usually will only apply to anonymous users when the page cache is
2881
  // enabled, because they may submit the form even if they do not have access.
2882 a45e4bc1 Assos Assos
  $user_limit_exceeded = FALSE;
2883
  if ($node->webform['submit_limit'] != -1 && !$finished) {
2884
    $user_limit_exceeded = webform_submission_user_limit_check($node);
2885
  }
2886 85ad3d82 Assos Assos
2887 a45e4bc1 Assos Assos
  // Check that the form is still open at time of submission.
2888
  // See https://www.drupal.org/node/2317273
2889
  // Consider the webform closed when it's status is closed AND either there
2890
  // is no submission yet (hence isn't being edited) or the user isn't an admin.
2891
  // Another way to consider this is that the form is open when its status is
2892
  // open OR there is a submission and the user is an admin.
2893
  $closed = empty($node->webform['status']) &&
2894
            (empty($form['#submission']) || !user_access('edit all webform submissions'));
2895
2896
  // Prevent submission by throwing an error.
2897
  if ((!$allowed_role || $total_limit_exceeded || $user_limit_exceeded || $closed)) {
2898
    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));
2899
    form_set_error('', NULL);
2900
  }
2901
}
2902
2903
/**
2904
 * Form API #validate handler for the webform_client_form() form.
2905
 */
2906
function webform_client_form_validate($form, &$form_state) {
2907
  if (($errors = form_get_errors()) && key_exists('', $errors)) {
2908
    // Prevalidation failed. The form cannot be submitted. Do not attemp futher validation.
2909
    return;
2910
  }
2911
  if ($form_state['webform']['preview'] && $form_state['webform']['page_count'] === $form_state['webform']['page_num']) {
2912
    // Form has already passed validation and is on the preview page.
2913
    return;
2914
  }
2915
2916
  module_load_include('inc', 'webform', 'includes/webform.submissions');
2917
  $node = $form['#node'];
2918
2919
  // Assemble an array of all past and new input values that will determine if
2920
  // certain elements need validation at all.
2921
  if (!empty($node->webform['conditionals'])) {
2922
    $input_values = isset($form_state['storage']['submitted']) ? $form_state['storage']['submitted'] : array();
2923
    $new_values = isset($form_state['values']['submitted']) ? _webform_client_form_submit_flatten($form['#node'], $form_state['values']['submitted']) : array();
2924
    foreach ($new_values as $cid => $values) {
2925
      $input_values[$cid] = $values;
2926 85ad3d82 Assos Assos
    }
2927 a45e4bc1 Assos Assos
    // Ensure that all conditionally-hidden values are removed.
2928
    $input_values = webform_get_conditional_sorter($node)->executeConditionals($input_values, $form_state['webform']['page_num']);
2929
  }
2930
  else {
2931
    $input_values = NULL;
2932 85ad3d82 Assos Assos
  }
2933
2934 a45e4bc1 Assos Assos
2935 85ad3d82 Assos Assos
  // Run all #element_validate and #required checks. These are skipped initially
2936
  // by setting #validated = TRUE on all components when they are added.
2937 a45e4bc1 Assos Assos
  _webform_client_form_validate($form, $form_state, 'webform_client_form', $input_values);
2938 85ad3d82 Assos Assos
}
2939
2940
/**
2941
 * Recursive validation function to trigger normal Drupal validation.
2942
 *
2943
 * This function imitates _form_validate in Drupal's form.inc, only it sets
2944
 * a different property to ensure that validation has occurred.
2945
 */
2946 a45e4bc1 Assos Assos
function _webform_client_form_validate(&$elements, &$form_state, $form_id = NULL, $input_values = NULL) {
2947
  if (isset($input_values) && isset($elements['#webform_component'])) {
2948
    $sorter = webform_get_conditional_sorter($form_state['complete form']['#node']);
2949
    $cid = $elements['#webform_component']['cid'];
2950
    $page_num = $form_state['values']['details']['page_num'];
2951
    // Webform-specific enhancements:
2952
    // 1) Only validate the field if it was used in this submission.
2953
    //    This both skips validation on the field and sets the value of the
2954
    //    field to NULL, preventing any dangerous input. Short-circuit
2955
    //    validation for a hidden component (hidden by rules dependent upon
2956
    //    component on previous pages), or a component this is dependent upon
2957
    //    values on the current page, but is hidden based upon their current
2958
    //    values.
2959
    // 2) Only valididate if the field has not been set by conditionals.
2960
    //    The user will be unable to fix the validation without surmising the
2961
    //    logic and changing the trigger for the conditional. Also, it isn't
2962
    //    possible to set $element['#value'] without component-specific
2963
    //    knowledge of how the data is stored because $input_values is already
2964
    //    webform-normalized to contain values in arrays.
2965
    if ($sorter->componentVisibility($cid, $page_num) != WebformConditionals::componentShown) {
2966
      form_set_value($elements, NULL, $form_state);
2967
      return;
2968
    }
2969
    if ($sorter->componentSet($cid, $page_num)) {
2970
      $component = $elements['#webform_component'];
2971
      $value = $input_values[$cid];
2972
      $value = is_array($value) ? $value[0] : $value;
2973
      // webform_component_invoke cannot be called with reference arguments. Call directly.
2974
      // webform_component_invoke($component['type'], 'action_set', $component, $elements, $form_state, $value);
2975
      $function = '_webform_action_set_' . $component['type'];
2976
      $function($component, $elements, $form_state, $value);
2977
    }
2978
2979
    // Check for changes in required status made by conditionals.
2980
    $required = $sorter->componentRequired($cid, $page_num);
2981
    if (isset($required)) {
2982
      $elements['#required'] = $required;
2983
2984
      // Some components, e.g. grids, have nested sub-elements. Extend required
2985
      // to any sub-components.
2986
      foreach (element_children($elements) as $key) {
2987
        if (isset($elements[$key]) && $elements[$key] && !isset($elements[$key]['#webform_component'])) {
2988
          // Child is *not* a component.
2989
          $elements[$key]['#required'] = $required;
2990
        }
2991
      }
2992
    }
2993 85ad3d82 Assos Assos
  }
2994
2995
  // Recurse through all children.
2996
  foreach (element_children($elements) as $key) {
2997
    if (isset($elements[$key]) && $elements[$key]) {
2998 a45e4bc1 Assos Assos
      _webform_client_form_validate($elements[$key], $form_state, NULL, $input_values);
2999 85ad3d82 Assos Assos
    }
3000
  }
3001
  // Validate the current input.
3002 a45e4bc1 Assos Assos
  if (isset($elements['#webform_validated']) && !$elements['#webform_validated']) {
3003 85ad3d82 Assos Assos
    if (isset($elements['#needs_validation'])) {
3004
      // Make sure a value is passed when the field is required.
3005
      // A simple call to empty() will not cut it here as some fields, like
3006
      // checkboxes, can return a valid value of '0'. Instead, check the
3007
      // length if it's a string, and the item count if it's an array. For
3008
      // radios, FALSE means that no value was submitted, so check that too.
3009
      if ($elements['#required'] && (!count($elements['#value']) || (is_string($elements['#value']) && strlen(trim($elements['#value'])) == 0) || $elements['#value'] === FALSE)) {
3010
        form_error($elements, t('!name field is required.', array('!name' => $elements['#title'])));
3011
      }
3012
3013
      // Verify that the value is not longer than #maxlength.
3014
      if (isset($elements['#maxlength']) && drupal_strlen($elements['#value']) > $elements['#maxlength']) {
3015
        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']))));
3016
      }
3017
3018
      if (isset($elements['#options']) && isset($elements['#value'])) {
3019
        if ($elements['#type'] == 'select') {
3020
          $options = form_options_flatten($elements['#options']);
3021
        }
3022
        else {
3023
          $options = $elements['#options'];
3024
        }
3025
        if (is_array($elements['#value'])) {
3026
          $value = $elements['#type'] == 'checkboxes' ? array_keys(array_filter($elements['#value'])) : $elements['#value'];
3027
          foreach ($value as $v) {
3028
            if (!isset($options[$v])) {
3029
              form_error($elements, t('An illegal choice has been detected. Please contact the site administrator.'));
3030
              watchdog('form', 'Illegal choice %choice in !name element.', array('%choice' => $v, '!name' => empty($elements['#title']) ? $elements['#parents'][0] : $elements['#title']), WATCHDOG_ERROR);
3031
            }
3032
          }
3033
        }
3034
        elseif ($elements['#value'] !== '' && !isset($options[$elements['#value']])) {
3035
          form_error($elements, t('An illegal choice has been detected. Please contact the site administrator.'));
3036
          watchdog('form', 'Illegal choice %choice in %name element.', array('%choice' => $elements['#value'], '%name' => empty($elements['#title']) ? $elements['#parents'][0] : $elements['#title']), WATCHDOG_ERROR);
3037
        }
3038
      }
3039
    }
3040
3041 a45e4bc1 Assos Assos
    // Call user-defined form level validators.
3042
    if (isset($form_id)) {
3043
      form_execute_handlers('validate', $elements, $form_state);
3044
    }
3045 85ad3d82 Assos Assos
    // Call any element-specific validators. These must act on the element
3046
    // #value data.
3047 a45e4bc1 Assos Assos
    elseif (isset($elements['#element_validate'])) {
3048 85ad3d82 Assos Assos
      foreach ($elements['#element_validate'] as $function) {
3049
        if (function_exists($function))  {
3050 a45e4bc1 Assos Assos
          $function($elements, $form_state, $form_state['complete form']);
3051 85ad3d82 Assos Assos
        }
3052
      }
3053
    }
3054
    $elements['#webform_validated'] = TRUE;
3055
  }
3056
}
3057
3058 a45e4bc1 Assos Assos
/**
3059
 * Saves submissions that fail validation as drafts.
3060
 *
3061
 * When a user attempts to submit an unfinished form and auto-save is allowed,
3062
 * automatically save the form as a draft to allow the user to complete the
3063
 * form later. This prevents the common failure of a user trying to submit a
3064
 * form and not noticing validation errors. The user then leaves the page
3065
 * without realizing that the form hasn't been submitted.
3066
 *
3067
 * THEORY OF OPERATION:
3068
 * The Drupal 7 Form API lacks an easy way to rebuild the form in the event of
3069
 * validation errors. The opertions is thus:
3070
 *
3071
 * 1) The form is first displayed. If it is an existing draft,
3072
 *    webform_client_form will generated a form to edit the draft submission.
3073
 *    Otherwise it creates a form for a new, empty submission. As usual.
3074
 * 2) The submit button is pressed. The form is retrieved from cache or is
3075
 *    recreated by webform_client_form. The values from the $_POST are merged in
3076
 *    and the validation routines are called. As usual.
3077
 * 3) The postvalidation routine, below, detects that validation errors should
3078
 *    be autosaved and calls the submit handlers on a copy of the form and
3079
 *    form_state. This creates the submission, or saves to the existing
3080
 *    submission. The original form and form_state are not modified (yet).
3081
 * 4) If a new submission was created, the form and form_state are updated with
3082
 *    the newly-created sid of the submission, which is returned to the
3083
 *    browser in the hidden field [details][sid]. The form is set to not be
3084
 *    cached, and any existing cached copy is cleared to force step 5. The form
3085
 *    is presented with validation errors as usual.
3086
 * 5) When the form is submitted again, the form must be rebuilt because it is
3087
 *    not in the cache. The existing draft detection in _webform_fetch_draft_sid
3088
 *    detects that a webform draft is being submitted, and uses its sid in
3089
 *    preference to any other stored draft sid in the database. In the event
3090
 *    that multiple drafts are being implemented by another module, this ensures
3091
 *    that the correct draft is edited.
3092
 * 6) Repeat from step 2 until the form is abandoned (leaving the draft) or
3093
 *    successfully submitted.
3094
 */
3095
function webform_client_form_postvalidate(&$form, &$form_state) {
3096
  $errors = form_get_errors();
3097
  $nid = $form_state['values']['details']['nid'];
3098
  $node = node_load($nid);
3099
  if (user_is_logged_in() &&
3100
      $errors && !key_exists('', $errors) &&
3101
      $node->webform['auto_save'] &&
3102
      !$form_state['values']['details']['finished'] &&
3103
      !empty($form_state['values']['op'])) {
3104
    // Validation errors are present, prevalidation succeeded (e.g. submission
3105
    // limits are ok), auto-save is enabled, this form isn't finished (i.e. is
3106
    // or soon will be a draft) and a button was pushed (not ajax).
3107
3108
    // Process submission on a copy of the form and form_state to prevent the
3109
    // submission handlers from making unintended changes. Use a button that
3110
    // isn't Save Draft, Next Page, Submit, etc to avoid triggering any
3111
    // unwanted side effects.
3112
    $submit_form = $form;
3113
    $submit_form_state = $form_state;
3114
    $submit_form_state['values']['op'] = '__AUTOSAVE__';
3115
    form_execute_handlers('submit', $submit_form, $submit_form_state);
3116
    $sid = $submit_form_state['values']['details']['sid'];
3117
    if ($sid != $form_state['values']['details']['sid']) {
3118
      // A new submission was created. Update the form and form_state as if it
3119
      // has been submitted with the new sid. This causes the Form API to
3120
      // render the form with new sid.
3121
      $form_state['values']['details']['sid'] = $sid;
3122
      $form_state['input']['details']['sid'] = $sid;
3123
      $form['details']['sid']['#value'] = $sid;
3124
3125
      // Prevent the form from being cached, forcing it to be rebuilt from the
3126
      // form definition function, which will honor the new sid.
3127
      $form_state['no_cache'] = TRUE;
3128
      if (!empty($form_state['values']['form_build_id'])) {
3129
        cache_clear_all('form_' . $form_state['values']['form_build_id'], 'cache_form');
3130
        cache_clear_all('form_state_' . $form_state['values']['form_build_id'], 'cache_form');
3131
      }
3132
    }
3133
  }
3134
}
3135
3136 85ad3d82 Assos Assos
/**
3137
 * Handle the processing of pages and conditional logic.
3138
 */
3139
function webform_client_form_pages($form, &$form_state) {
3140
  $node = node_load($form_state['values']['details']['nid']);
3141
3142
  // Multistep forms may not have any components on the first page.
3143
  if (!isset($form_state['values']['submitted'])) {
3144
    $form_state['values']['submitted'] = array();
3145
  }
3146
3147
  // Move special settings to storage.
3148
  if (isset($form_state['webform']['component_tree'])) {
3149
    $form_state['storage']['component_tree'] = $form_state['webform']['component_tree'];
3150
    $form_state['storage']['page_count'] = $form_state['webform']['page_count'];
3151
    $form_state['storage']['page_num'] = $form_state['webform']['page_num'];
3152 a45e4bc1 Assos Assos
    $form_state['storage']['preview'] = $form_state['webform']['preview'];
3153 85ad3d82 Assos Assos
  }
3154
3155
  // Flatten trees within the submission.
3156
  $form_state['values']['submitted'] = _webform_client_form_submit_flatten($node, $form_state['values']['submitted']);
3157
3158 a45e4bc1 Assos Assos
  // Perform post processing by components.
3159
  _webform_client_form_submit_process($node, $form_state['values']['submitted']);
3160
3161 85ad3d82 Assos Assos
  // Assume the form is completed unless the page logic says otherwise.
3162
  $form_state['webform_completed'] = TRUE;
3163
3164 a45e4bc1 Assos Assos
  // Merge any stored submission data for multistep forms.
3165
  $original_values = is_array($form_state['values']['submitted']) ? $form_state['values']['submitted'] : array();
3166
  if (isset($form_state['storage']['submitted'])) {
3167
    // Array + operator keeps all elements of left operand and discards any duplicate elements in right operand.
3168
    $original_values += $form_state['storage']['submitted'];
3169
  }
3170
3171
  // Execute conditionals on submission values.
3172
  $form_state['values']['submitted'] = webform_get_conditional_sorter($node)->executeConditionals($original_values);
3173
3174 85ad3d82 Assos Assos
  // Check for a multi-page form that is not yet complete.
3175
  $submit_op = !empty($form['actions']['submit']['#value']) ? $form['actions']['submit']['#value'] : t('Submit');
3176
  $draft_op = !empty($form['actions']['draft']['#value']) ? $form['actions']['draft']['#value'] : t('Save Draft');
3177 a45e4bc1 Assos Assos
  if (!in_array($form_state['values']['op'], array($submit_op, $draft_op, '__AUTOSAVE__'))) {
3178 85ad3d82 Assos Assos
3179 a45e4bc1 Assos Assos
    // Store values from the current page in the form state storage.
3180
    $form_state['storage']['submitted'] = $form_state['values']['submitted'];
3181 85ad3d82 Assos Assos
3182
    // Set the page number.
3183
    if (!isset($form_state['storage']['page_num'])) {
3184
      $form_state['storage']['page_num'] = 1;
3185
    }
3186
    if (end($form_state['clicked_button']['#parents']) == 'next') {
3187 a45e4bc1 Assos Assos
      $forward = 1;
3188 85ad3d82 Assos Assos
    }
3189 a45e4bc1 Assos Assos
    elseif (end($form_state['clicked_button']['#parents']) == 'previous') {
3190
      $forward = -1;
3191
    }
3192
    $current_page = $form_state['storage']['page_num'];
3193
3194
    if (isset($forward)) {
3195
      // Find the
3196
      //    1) previous/next non-empty page, or
3197
      //    2) the preview page, or
3198
      //    3) the preview page, forcing its display if the form would unexpectedly submit, or
3199
      //    4) page 1 even if empty, if no other previous page would be shown
3200
      $preview_page_num = $form_state['storage']['page_count'] + (int)!$form_state['webform']['preview'];
3201
      $page_num = $current_page;
3202
      do {
3203
        $page_num += $forward;
3204
      } while (!webform_get_conditional_sorter($node)->pageVisibility($page_num));
3205
      if (!$form_state['webform']['preview'] && $page_num == $preview_page_num) {
3206
        // Force a preview to avert an unintended submission via Next.
3207
        $form_state['webform']['preview'] = TRUE;
3208
        $form_state['storage']['preview'] = TRUE;
3209
        $form_state['storage']['page_count']++;
3210 85ad3d82 Assos Assos
      }
3211 a45e4bc1 Assos Assos
      $form_state['storage']['page_num'] = $page_num;
3212 85ad3d82 Assos Assos
    }
3213
3214
    // The form is done if the page number is greater than the page count.
3215
    $form_state['webform_completed'] = $form_state['storage']['page_num'] > $form_state['storage']['page_count'];
3216
  }
3217
3218
  // Inform the submit handlers that a draft will be saved.
3219 a45e4bc1 Assos Assos
  $form_state['save_draft'] = in_array($form_state['values']['op'], array($draft_op, '__AUTOSAVE__')) ||
3220
                              ($node->webform['auto_save'] && !$form_state['values']['details']['finished'] && !$form_state['webform_completed'] && user_is_logged_in());
3221 85ad3d82 Assos Assos
3222
  // Determine what we need to do on the next page.
3223
  if (!empty($form_state['save_draft']) || !$form_state['webform_completed']) {
3224
    // Rebuild the form and display the current (on drafts) or next page.
3225
    $form_state['rebuild'] = TRUE;
3226
  }
3227
  else {
3228
    // Remove the form state storage now that we're done with the pages.
3229
    $form_state['rebuild'] = FALSE;
3230
    unset($form_state['storage']);
3231
  }
3232
}
3233
3234
/**
3235
 * Submit handler for saving the form values and sending e-mails.
3236
 */
3237
function webform_client_form_submit($form, &$form_state) {
3238
  module_load_include('inc', 'webform', 'includes/webform.submissions');
3239
  module_load_include('inc', 'webform', 'includes/webform.components');
3240
  global $user;
3241
3242
  if (empty($form_state['save_draft']) && empty($form_state['webform_completed'])) {
3243
    return;
3244
  }
3245
3246
  $node = $form['#node'];
3247
  $sid = $form_state['values']['details']['sid'] ? (int) $form_state['values']['details']['sid'] : NULL;
3248
3249
  // Check if user is submitting as a draft.
3250
  $is_draft = (int) !empty($form_state['save_draft']);
3251
3252 a45e4bc1 Assos Assos
  // To maintain time and user information, load the existing submission.
3253
  // If a draft is deleted while a user is working on completing it, $sid will
3254
  // exist, but webform_get_submission() will not find the draft. So, make a new
3255
  // submission.
3256
  if ($sid && $submission = webform_get_submission($node->webform['nid'], $sid)) {
3257 01d522a6 Assos Assos
    // Store original data on object for use in update hook.
3258
    $submission->original = clone $submission;
3259
3260 85ad3d82 Assos Assos
    // Merge with new submission data. The + operator maintains numeric keys.
3261
    // This maintains existing data with just-submitted data when a user resumes
3262
    // a submission previously saved as a draft.
3263 a45e4bc1 Assos Assos
    // Remove any existing data on this and previous pages. If components are hidden, they may
3264
    // be in the $submission->data but absent entirely from $new_data;
3265
    $page_map = webform_get_conditional_sorter($node)->getPageMap();
3266
    for ($page_nr = 1; $page_nr <= $form_state['webform']['page_num']; $page_nr++) {
3267
      $submission->data = array_diff_key($submission->data, $page_map[$page_nr]);
3268
    }
3269
    $submission->data = webform_submission_data($node, $form_state['values']['submitted']) + $submission->data;
3270
  }
3271
  else {
3272
    // Create a new submission object.
3273
    $submission = webform_submission_create($node, $user, $form_state);
3274
    // Since this is a new submission, a new sid is needed.
3275
    $sid = NULL;
3276
  }
3277
3278
  // Save draft state, and for drafts, save the current page (if clicking next)
3279
  // or the previous page (if not) as the last valid page.
3280
  $submission->is_draft = $is_draft;
3281
  $submission->highest_valid_page = 0;
3282
  if ($is_draft) {
3283
     $submission->highest_valid_page = end($form_state['clicked_button']['#parents']) == 'next' && $form_state['values']['op'] != '__AUTOSAVE__'
3284
                                          ? $form_state['webform']['page_num']
3285
                                          : $form_state['webform']['page_num'] - 1;
3286 85ad3d82 Assos Assos
  }
3287
3288
  // If there is no data to be saved (such as on a multipage form with no fields
3289
  // on the first page), process no further. Submissions with no data cannot
3290
  // be loaded from the database as efficiently, so we don't save them at all.
3291
  if (empty($submission->data)) {
3292
    return;
3293
  }
3294
3295
  // Save the submission to the database.
3296
  if (!$sid) {
3297
    // No sid was found thus insert it in the dataabase.
3298
    $form_state['values']['details']['sid'] = $sid = webform_submission_insert($node, $submission);
3299
    $form_state['values']['details']['is_new'] = TRUE;
3300
3301 01d522a6 Assos Assos
    // Save the new details in storage. When ajax calls for file upload/remove,
3302
    // $form_state['values']['details'] is missing. This allows the proper
3303
    // submission to be retrieved in webform_client_form. See #2562703.
3304
    $form_state['storage']['details'] = $form_state['values']['details'];
3305
3306 a45e4bc1 Assos Assos
    // Set a cookie including the server's submission time. The cookie expires
3307
    // in the length of the interval plus a day to compensate for timezones.
3308
    $tracking_mode = webform_variable_get('webform_tracking_mode');
3309
    if ($tracking_mode === 'cookie' || $tracking_mode === 'strict') {
3310 85ad3d82 Assos Assos
      $cookie_name = 'webform-' . $node->nid;
3311
      $time = REQUEST_TIME;
3312
      $params = session_get_cookie_params();
3313
      setcookie($cookie_name . '[' . $time . ']', $time, $time + $node->webform['submit_interval'] + 86400, $params['path'], $params['domain'], $params['secure'], $params['httponly']);
3314
    }
3315
3316
    // Save session information about this submission for anonymous users,
3317
    // allowing them to access or edit their submissions.
3318
    if (!$user->uid && user_access('access own webform submissions')) {
3319 01d522a6 Assos Assos
      $_SESSION['webform_submission'][$sid] = $node->nid;
3320 85ad3d82 Assos Assos
    }
3321
  }
3322
  else {
3323
    // Sid was found thus update the existing sid in the database.
3324
    webform_submission_update($node, $submission);
3325
    $form_state['values']['details']['is_new'] = FALSE;
3326
  }
3327
3328
  // Check if this form is sending an email.
3329
  if (!$is_draft && !$form_state['values']['details']['finished']) {
3330 a45e4bc1 Assos Assos
    drupal_static_reset('webform_get_submission');
3331
    $submission = webform_get_submission($node->webform['nid'], $sid);
3332 85ad3d82 Assos Assos
    webform_submission_send_mail($node, $submission);
3333
  }
3334
3335
  // Strip out empty tags added by WYSIWYG editors if needed.
3336 a45e4bc1 Assos Assos
  $message = strlen(trim(strip_tags($node->webform['confirmation']))) > 0;
3337 85ad3d82 Assos Assos
3338
  // Check confirmation and redirect_url fields.
3339
  $redirect = NULL;
3340 a45e4bc1 Assos Assos
  $redirect_url = trim($node->webform['redirect_url']);
3341 85ad3d82 Assos Assos
  if (isset($form['actions']['draft']['#value']) && $form_state['values']['op'] == $form['actions']['draft']['#value']) {
3342
    $message = t('Submission saved. You may return to this form later and it will restore the current values.');
3343
  }
3344
  elseif ($is_draft) {
3345 a45e4bc1 Assos Assos
    // No redirect needed. No confirmation message needed.
3346
    $message = FALSE;
3347 85ad3d82 Assos Assos
  }
3348
  elseif (!empty($form_state['values']['details']['finished'])) {
3349
    $message = t('Submission updated.');
3350 a45e4bc1 Assos Assos
    $redirect = "node/{$node->nid}/submission/$sid";
3351
  }
3352
  elseif (!empty($node->webform['confirmation_block'])) {
3353
    $message = FALSE;
3354
    // Webform was submitted in a block and the confirmation message is to be
3355
    // displayed in the block.
3356
    $_SESSION['webform_confirmation'][$node->nid] = array(
3357
      'sid' => $sid,
3358
      'confirmation_page' => $redirect_url == '<confirmation>',
3359
    );
3360
    drupal_page_is_cacheable(FALSE);
3361 85ad3d82 Assos Assos
  }
3362
  elseif ($redirect_url == '<none>') {
3363 a45e4bc1 Assos Assos
    // No redirect needed. Show a confirmatin message if there is one.
3364 85ad3d82 Assos Assos
  }
3365
  elseif ($redirect_url == '<confirmation>') {
3366 a45e4bc1 Assos Assos
    // No confirmation message needed because it will be shown on the
3367
    // confirmation page.
3368
    $message = FALSE;
3369
    $query = array('sid' => $sid);
3370
    if ((int) $user->uid === 0) {
3371
      $query['token'] = webform_get_submission_access_token($submission);
3372
    }
3373
    $redirect = array('node/' . $node->nid . '/done', array('query' => $query));
3374 85ad3d82 Assos Assos
  }
3375 a45e4bc1 Assos Assos
  else {
3376
    // Clean up the redirect URL, filter it for tokens and detect external
3377
    // domains. If the redirect is to an external URL, then don't show the
3378
    // confirmation message.
3379
    $redirect = webform_replace_url_tokens($redirect_url, $node, $submission);
3380
    if ($redirect[1]['#webform_external']) {
3381
      $message = FALSE;
3382
    }
3383 85ad3d82 Assos Assos
  }
3384
3385
  // Show a message if manually set.
3386 a45e4bc1 Assos Assos
  if (is_string($message)) {
3387 85ad3d82 Assos Assos
    drupal_set_message($message);
3388
  }
3389
  // If redirecting and we have a confirmation message, show it as a message.
3390 a45e4bc1 Assos Assos
  elseif ($message) {
3391
    drupal_set_message(webform_replace_tokens($node->webform['confirmation'], $node, $submission, NULL, $node->webform['confirmation_format']));
3392 85ad3d82 Assos Assos
  }
3393
3394
  $form_state['redirect'] = $redirect;
3395
}
3396
3397
/**
3398
 * Post processes the submission tree with any updates from components.
3399
 *
3400
 * @param $node
3401
 *   The full webform node.
3402
 * @param $form_values
3403
 *   The form values for the form.
3404
 * @param $types
3405
 *   Optional. Specific types to perform processing.
3406
 * @param $parent
3407
 *   Internal use. The current parent CID whose children are being processed.
3408
 */
3409 a45e4bc1 Assos Assos
function _webform_client_form_submit_process($node, &$form_values) {
3410
  foreach ($form_values as $cid => $value) {
3411
    if (isset($node->webform['components'][$cid])) {
3412
      // Call the component process submission function.
3413
      $component = $node->webform['components'][$cid];
3414
      if ((!isset($types) || in_array($component['type'], $types)) && webform_component_implements($component['type'], 'submit')) {
3415
        $form_values[$cid] = webform_component_invoke($component['type'], 'submit', $component, $form_values[$cid]);
3416 85ad3d82 Assos Assos
      }
3417
    }
3418
  }
3419
}
3420
3421
/**
3422 a45e4bc1 Assos Assos
 * Flattens a submitted values back into a single flat array representation.
3423 85ad3d82 Assos Assos
 */
3424
function _webform_client_form_submit_flatten($node, $fieldset, $parent = 0) {
3425
  $values = array();
3426
3427
  if (is_array($fieldset)) {
3428
    foreach ($fieldset as $form_key => $value) {
3429 a45e4bc1 Assos Assos
      if ($cid = webform_get_cid($node, $form_key, $parent)) {
3430
        if (is_array($value) && webform_component_feature($node->webform['components'][$cid]['type'], 'group')) {
3431
          $values += _webform_client_form_submit_flatten($node, $value, $cid);
3432
        }
3433
        else {
3434
          $values[$cid] = $value;
3435
        }
3436 85ad3d82 Assos Assos
      }
3437 ba09eb79 Assos Assos
      else {
3438
        // This $form_key must belong to the parent. For example, a grid.
3439
        $values[$parent][$form_key] = $value;
3440
      }
3441 85ad3d82 Assos Assos
    }
3442
  }
3443
3444
  return $values;
3445
}
3446
3447
/**
3448
 * Prints the confirmation message after a successful submission.
3449
 */
3450
function _webform_confirmation($node) {
3451
  drupal_set_title($node->title);
3452 c22e192e Assos Assos
  webform_set_breadcrumb($node, TRUE);
3453 85ad3d82 Assos Assos
  $sid = isset($_GET['sid']) ? $_GET['sid'] : NULL;
3454
  return theme(array('webform_confirmation_' . $node->nid, 'webform_confirmation'), array('node' => $node, 'sid' => $sid));
3455
}
3456
3457
/**
3458
 * Prepare for theming of the webform form.
3459
 */
3460
function template_preprocess_webform_form(&$vars) {
3461
  if (isset($vars['form']['details']['nid']['#value'])) {
3462
    $vars['nid'] = $vars['form']['details']['nid']['#value'];
3463
  }
3464
  elseif (isset($vars['form']['submission']['#value'])) {
3465
    $vars['nid'] = $vars['form']['submission']['#value']->nid;
3466
  }
3467 a45e4bc1 Assos Assos
3468
  if (!empty($vars['form']['#node']->webform['conditionals']) && empty($vars['form']['preview'])) {
3469
    module_load_include('inc', 'webform', 'includes/webform.conditionals');
3470
    $submission_data = isset($vars['form']['#conditional_values']) ? $vars['form']['#conditional_values'] : array();
3471
    $settings = webform_conditional_prepare_javascript($vars['form']['#node'],
3472
                                                       $submission_data,
3473
                                                       $vars['form']['details']['page_num']['#value']);
3474
    drupal_add_js(array('webform' => array('conditionals' => array('webform-client-form-' . $vars['nid'] => $settings))), 'setting');
3475
  }
3476
3477 85ad3d82 Assos Assos
}
3478
3479
/**
3480
 * Prepare for theming of the webform submission confirmation.
3481
 */
3482
function template_preprocess_webform_confirmation(&$vars) {
3483 a45e4bc1 Assos Assos
  $node = $vars['node'];
3484 85ad3d82 Assos Assos
  // Strip out empty tags added by WYSIWYG editors if needed.
3485 a45e4bc1 Assos Assos
  $confirmation = $node->webform['confirmation'];
3486
  $confirmation = strlen(trim(strip_tags($confirmation))) ? $confirmation : '';
3487
3488
  // Replace tokens.
3489
  module_load_include('inc', 'webform', 'includes/webform.submissions');
3490
  $submission = webform_get_submission($node->nid, $vars['sid']);
3491
  $vars['confirmation_message'] = webform_replace_tokens($confirmation, $node, $submission, NULL, $node->webform['confirmation_format']);
3492
3493
  // URL back to form (or same page for in-block confirmations).
3494
  $vars['url'] = empty($node->webform_block)
3495
                    ? url('node/' . $node->nid)
3496
                    : url(current_path(), array('query' => drupal_get_query_parameters()));
3497
3498
  // Progress bar.
3499
  $vars['progressbar'] = '';
3500
  $page_labels = webform_page_labels($node);
3501
  $page_count = count($page_labels);
3502
  if ($node->webform['progressbar_include_confirmation'] && $page_count > 2) {
3503
    $vars['progressbar'] = theme('webform_progressbar', array(
3504
      'node' => $node,
3505
      'page_num' => $page_count,
3506
      'page_count' => $page_count,
3507
      'page_labels' => $page_labels,
3508
    ));
3509
  }
3510
}
3511
3512
/**
3513
 * Prepare for theming of the webform progressbar.
3514
 */
3515
function template_preprocess_webform_progressbar(&$vars) {
3516
  // Add CSS used by the progress bar.
3517
  drupal_add_css(drupal_get_path('module', 'webform') . '/css/webform.css');
3518
3519
  $vars['progressbar_page_number'] = $vars['node']->webform['progressbar_page_number'];
3520
  $vars['progressbar_percent'] = $vars['node']->webform['progressbar_percent'];
3521
  $vars['progressbar_bar'] = $vars['node']->webform['progressbar_bar'];
3522
  $vars['progressbar_pagebreak_labels'] = $vars['node']->webform['progressbar_pagebreak_labels'];
3523
  $vars['progressbar_include_confirmation'] = $vars['node']->webform['progressbar_include_confirmation'];
3524
  $vars['percent'] = ($vars['page_num'] - 1) / ($vars['page_count'] - 1) * 100;
3525 85ad3d82 Assos Assos
}
3526
3527
/**
3528
 * Prepare to theme the contents of e-mails sent by webform.
3529
 */
3530
function template_preprocess_webform_mail_message(&$vars) {
3531
  global $user;
3532
3533
  $vars['user'] = $user;
3534 a45e4bc1 Assos Assos
  $vars['ip_address'] = webform_ip_address($vars['node']);
3535 85ad3d82 Assos Assos
}
3536
3537
/**
3538
 * A Form API #pre_render function. Sets display based on #title_display.
3539
 *
3540
 * This function is used regularly in D6 for all elements, but specifically for
3541
 * fieldsets in D7, which don't support #title_display natively.
3542
 */
3543
function webform_element_title_display($element) {
3544
  if (isset($element['#title_display']) && strcmp($element['#title_display'], 'none') === 0) {
3545
    $element['#title'] = NULL;
3546
  }
3547
  return $element;
3548
}
3549
3550
/**
3551 a45e4bc1 Assos Assos
 * A Form API #pre_render function that removes the ID from an element.
3552
 *
3553
 * Drupal forcibly adds IDs to all form elements, including those that do not
3554
 * need them for any reason, such as the actions wrapper or submit buttons. We
3555
 * use this process function wherever we wish to remove an ID from an element.
3556
 * Because #states and #ajax require IDs, they are only removed if the states
3557
 * and ajax arrays are empty.
3558 85ad3d82 Assos Assos
 */
3559 a45e4bc1 Assos Assos
function webform_pre_render_remove_id($element) {
3560
  if (empty($element['#states']) && empty($element['#ajax'])) {
3561
    $element['#id'] = NULL;
3562
    // Removing array parents is required to prevent theme_container from adding
3563
    // an empty ID attribute.
3564
    $element['#array_parents'] = NULL;
3565
  }
3566
  return $element;
3567
}
3568
3569
/**
3570
 * Implements template_preprocess_THEME_HOOK().
3571
 */
3572
function template_preprocess_webform_element(&$variables) {
3573
  $element = &$variables['element'];
3574
3575 85ad3d82 Assos Assos
  // Ensure defaults.
3576 a45e4bc1 Assos Assos
  $element += array(
3577 85ad3d82 Assos Assos
    '#title_display' => 'before',
3578 a45e4bc1 Assos Assos
    '#wrapper_attributes' => array(),
3579
  );
3580
  $element['#wrapper_attributes'] += array(
3581
    'class' => array(),
3582 85ad3d82 Assos Assos
  );
3583
3584
  // All elements using this for display only are given the "display" type.
3585
  if (isset($element['#format']) && $element['#format'] == 'html') {
3586
    $type = 'display';
3587
  }
3588
  else {
3589 a45e4bc1 Assos Assos
    $type = ($element['#webform_component']['type'] == 'select' && isset($element['#type'])) ? $element['#type'] : $element['#webform_component']['type'];
3590 85ad3d82 Assos Assos
  }
3591
3592
  // Convert the parents array into a string, excluding the "submitted" wrapper.
3593
  $nested_level = $element['#parents'][0] == 'submitted' ? 1 : 0;
3594
  $parents = str_replace('_', '-', implode('--', array_slice($element['#parents'], $nested_level)));
3595
3596 a45e4bc1 Assos Assos
  // Build up a list of classes to apply on the element wrapper.
3597 85ad3d82 Assos Assos
  $wrapper_classes = array(
3598 a45e4bc1 Assos Assos
    'form-item',
3599
    'webform-component',
3600
    'webform-component-' . str_replace('_', '-', $type),
3601
    'webform-component--' . $parents,
3602 85ad3d82 Assos Assos
  );
3603
  if (isset($element['#title_display']) && strcmp($element['#title_display'], 'inline') === 0) {
3604
    $wrapper_classes[] = 'webform-container-inline';
3605
  }
3606 a45e4bc1 Assos Assos
  $element['#wrapper_attributes']['class'] = array_merge($element['#wrapper_attributes']['class'], $wrapper_classes);
3607 85ad3d82 Assos Assos
3608 ca0757b9 Assos Assos
  // If #title_display is none, set it to invisible instead - none only used if
3609
  // we have no title at all to use.
3610
  if ($element['#title_display'] == 'none') {
3611
    $element['#title_display'] = 'invisible';
3612 a45e4bc1 Assos Assos
    if (empty($element['#attributes']['title']) && !empty($element['#title'])) {
3613
      $element['#attributes']['title'] = $element['#title'];
3614
    }
3615 ca0757b9 Assos Assos
  }
3616 a45e4bc1 Assos Assos
3617 85ad3d82 Assos Assos
  // If #title is not set, we don't display any label or required marker.
3618
  if (!isset($element['#title'])) {
3619
    $element['#title_display'] = 'none';
3620
  }
3621 ba09eb79 Assos Assos
3622
  // If an internal title is being used, generate no external title.
3623
  if ($element['#title_display'] == 'internal') {
3624
    $element['#title_display'] = 'none';
3625
  }
3626 a45e4bc1 Assos Assos
}
3627
3628
/**
3629
 * Replacement for theme_form_element().
3630
 */
3631
function theme_webform_element($variables) {
3632
  $element = $variables['element'];
3633
3634
  $output = '<div ' . drupal_attributes($element['#wrapper_attributes']) . '>' . "\n";
3635
  $prefix = isset($element['#field_prefix']) ? '<span class="field-prefix">' . webform_filter_xss($element['#field_prefix']) . '</span> ' : '';
3636
  $suffix = isset($element['#field_suffix']) ? ' <span class="field-suffix">' . webform_filter_xss($element['#field_suffix']) . '</span>' : '';
3637 85ad3d82 Assos Assos
3638 01d522a6 Assos Assos
  // Generate description for above or below the field.
3639
  $above = !empty($element['#webform_component']['extra']['description_above']);
3640
  $description = array(
3641
    FALSE => '',
3642
    TRUE => !empty($element['#description']) ? ' <div class="description">' . $element['#description'] . "</div>\n" : '',
3643
  );
3644
3645 85ad3d82 Assos Assos
  switch ($element['#title_display']) {
3646
    case 'inline':
3647 01d522a6 Assos Assos
      $output .= $description[$above];
3648
      $description[$above] = '';
3649
      // FALL THRU.
3650 85ad3d82 Assos Assos
    case 'before':
3651
    case 'invisible':
3652
      $output .= ' ' . theme('form_element_label', $variables);
3653 01d522a6 Assos Assos
      $output .= ' ' . $prefix . $description[$above] . $element['#children'] . $suffix . "\n";
3654 85ad3d82 Assos Assos
      break;
3655
3656
    case 'after':
3657 01d522a6 Assos Assos
      $output .= ' ' . $prefix . $description[$above] . $element['#children'] . $suffix;
3658 85ad3d82 Assos Assos
      $output .= ' ' . theme('form_element_label', $variables) . "\n";
3659
      break;
3660
3661
    case 'none':
3662
    case 'attribute':
3663
      // Output no label and no required marker, only the children.
3664 01d522a6 Assos Assos
      $output .= ' ' . $prefix . $description[$above] . $element['#children'] . $suffix . "\n";
3665 85ad3d82 Assos Assos
      break;
3666
  }
3667
3668 01d522a6 Assos Assos
  $output .= $description[!$above];
3669 85ad3d82 Assos Assos
  $output .= "</div>\n";
3670
3671
  return $output;
3672
}
3673
3674
/**
3675
 * Output a form element in plain text format.
3676
 */
3677
function theme_webform_element_text($variables) {
3678
  $element = $variables['element'];
3679
  $value = $variables['element']['#children'];
3680
3681
  $output = '';
3682
  $is_group = webform_component_feature($element['#webform_component']['type'], 'group');
3683
3684
  // Output the element title.
3685
  if (isset($element['#title'])) {
3686
    if ($is_group) {
3687 a45e4bc1 Assos Assos
      $output .= '==' . $element['#title'] . '==';
3688 85ad3d82 Assos Assos
    }
3689
    elseif (!in_array(drupal_substr($element['#title'], -1), array('?', ':', '!', '%', ';', '@'))) {
3690
      $output .= $element['#title'] . ':';
3691
    }
3692
    else {
3693
      $output .= $element['#title'];
3694
    }
3695
  }
3696
3697
  // Wrap long values at 65 characters, allowing for a few fieldset indents.
3698
  // It's common courtesy to wrap at 75 characters in e-mails.
3699
  if ($is_group && drupal_strlen($value) > 65) {
3700
    $value = wordwrap($value, 65, "\n");
3701
    $lines = explode("\n", $value);
3702
    foreach ($lines as $key => $line) {
3703
      $lines[$key] = '  ' . $line;
3704
    }
3705
    $value = implode("\n", $lines);
3706
  }
3707
3708 3753f249 Assos Assos
  // Add the value to the output. Add a newline before the response if needed.
3709
  $output .= (strpos($value, "\n") === FALSE ? ' ' : "\n") . $value;
3710 85ad3d82 Assos Assos
3711
  // Indent fieldsets.
3712
  if ($is_group) {
3713
    $lines = explode("\n", $output);
3714
    foreach ($lines as $number => $line) {
3715
      if (strlen($line)) {
3716
        $lines[$number] = '  ' . $line;
3717
      }
3718
    }
3719
    $output = implode("\n", $lines);
3720
    $output .= "\n";
3721
  }
3722
3723
  if ($output) {
3724
    $output .= "\n";
3725
  }
3726
3727
  return $output;
3728
}
3729
3730
/**
3731
 * Theme a radio button and another element together.
3732
 *
3733
 * This is used in the e-mail configuration to show a radio button and a text
3734
 * field or select list on the same line.
3735
 */
3736
function theme_webform_inline_radio($variables) {
3737
  $element = $variables['element'];
3738
3739
  // Add element's #type and #name as class to aid with JS/CSS selectors.
3740
  $class = array('form-item');
3741
  if (!empty($element['#type'])) {
3742
    $class[] = 'form-type-' . strtr($element['#type'], '_', '-');
3743
  }
3744
  if (!empty($element['#name'])) {
3745
    $class[] = 'form-item-' . strtr($element['#name'], array(' ' => '-', '_' => '-', '[' => '-', ']' => ''));
3746
  }
3747
3748
  // Add container-inline to all elements.
3749
  $class[] = 'webform-container-inline';
3750
  if (isset($element['#inline_element']) && isset($variables['element']['#title'])) {
3751
    $variables['element']['#title'] .= ': ';
3752
  }
3753
3754
  $output = '<div class="' . implode(' ', $class) . '">' . "\n";
3755
  $output .= ' ' . $element['#children'];
3756
  if (!empty($element['#title'])) {
3757 a45e4bc1 Assos Assos
    $output .= ' ' . theme('webform_inline_radio_label', $variables) . "\n";
3758 85ad3d82 Assos Assos
  }
3759
3760
  if (!empty($element['#description'])) {
3761
    $output .= ' <div class="description">' . $element['#description'] . "</div>\n";
3762
  }
3763
3764
  $output .= "</div>\n";
3765
3766
  return $output;
3767
}
3768
3769 a45e4bc1 Assos Assos
/**
3770
 * Replacement for theme_form_element_label()
3771
 *
3772
 * This varies from theme_element_label in that it allows inline fields such
3773
 * as select and input tags within the label itself.
3774
 */
3775
function theme_webform_inline_radio_label($variables) {
3776
  $element = $variables['element'];
3777
  // This is also used in the installer, pre-database setup.
3778
  $t = get_t();
3779
3780
  // If title and required marker are both empty, output no label.
3781
  if ((!isset($element['#title']) || $element['#title'] === '') && empty($element['#required'])) {
3782
    return '';
3783
  }
3784
3785
  // If the element is required, a required marker is appended to the label.
3786
  $required = !empty($element['#required']) ? theme('form_required_marker', array('element' => $element)) : '';
3787
3788
  // theme_element_label() does a filter_xss() here, we skip it because we know
3789
  // every use where this theme function is used and we need to allow input and
3790
  // select elements.
3791
  $title = $element['#title'];
3792
3793
  $attributes = isset($element['#attributes']) ? $element['#attributes'] : array();
3794
3795
  // Style the label as class option to display inline with the element.
3796
  if ($element['#title_display'] == 'after') {
3797
    $attributes['class'][] = 'option';
3798
  }
3799
  // Show label only to screen readers to avoid disruption in visual flows.
3800
  elseif ($element['#title_display'] == 'invisible') {
3801
    $attributes['class'][] = 'element-invisible';
3802
  }
3803
3804
   $attributes['class'][] = 'webform-inline-radio';
3805
   if (!empty($element['#id'])) {
3806
     $attributes['for'] = $element['#id'];
3807
   }
3808
3809
  // The leading whitespace helps visually separate fields from inline labels.
3810
  return ' <label' . drupal_attributes($attributes) . '>' . $t('!title !required', array('!title' => $title, '!required' => $required)) . "</label>\n";
3811
}
3812
3813 85ad3d82 Assos Assos
/**
3814
 * Theme the headers when sending an email from webform.
3815
 *
3816
 * @param $node
3817
 *   The complete node object for the webform.
3818
 * @param $submission
3819
 *   The webform submission of the user.
3820
 * @param $email
3821
 *   If you desire to make different e-mail headers depending on the recipient,
3822
 *   you can check the $email['email'] property to output different content.
3823
 *   This will be the ID of the component that is a conditional e-mail
3824
 *   recipient. For the normal e-mails, it will have the value of 'default'.
3825
 * @return
3826
 *   An array of headers to be used when sending a webform email. If headers
3827
 *   for "From", "To", or "Subject" are set, they will take precedence over
3828
 *   the values set in the webform configuration.
3829
 */
3830
function theme_webform_mail_headers($variables) {
3831
  $headers = array(
3832
    'X-Mailer' => 'Drupal Webform (PHP/' . phpversion() . ')',
3833
  );
3834
  return $headers;
3835
}
3836
3837
/**
3838
 * Check if current user has a draft of this webform, and return the sid.
3839
 */
3840
function _webform_fetch_draft_sid($nid, $uid) {
3841 a45e4bc1 Assos Assos
  // Detect whether a webform draft is being edited. If so, that is the one that
3842
  // should be returned.
3843
  if (isset($_POST['form_id']) && stripos($_POST['form_id'], 'webform_client_form_') === 0 &&
3844
      !empty($_POST['details']['sid']) && empty($_POST['details']['finished'])) {
3845
    // A draft is already being edited
3846
    $sid = $_POST['details']['sid'];
3847
  }
3848
  else {
3849
    $sid = db_select('webform_submissions')
3850
      ->fields('webform_submissions', array('sid'))
3851
      ->condition('nid', $nid)
3852
      ->condition('uid', $uid)
3853
      ->condition('is_draft', 1)
3854
      ->orderBy('submitted', 'DESC')
3855
      ->execute()
3856
      ->fetchField();
3857
3858
    if ($sid) {
3859
      $context = array(
3860
        'nid' => $nid,
3861
        'uid' => $uid,
3862
      );
3863
      drupal_alter('webform_draft', $sid, $context);
3864
    }
3865
  }
3866
  return $sid;
3867 85ad3d82 Assos Assos
}
3868
3869
/**
3870 a45e4bc1 Assos Assos
 * Returns a new or cached WebformConditionals object for the specified node.
3871
 *
3872
 * @param object $node
3873
 *   The loaded webform node.
3874
 * @returns object
3875
 *   Object of type WebformConditionals, possibly with the conditionals already
3876
 *   analyzed for dependencies.
3877
 */
3878
function webform_get_conditional_sorter($node) {
3879
  return WebformConditionals::factory($node);
3880
}
3881
3882
/**
3883
 * This function is deprecated! Use webform_replace_tokens() instead.
3884
 *
3885
 * @deprecated
3886
 */
3887
function _webform_filter_values($string, $node = NULL, $submission = NULL, $email = NULL, $strict = TRUE) {
3888
  $output = webform_replace_tokens($string, $node, $submission, $email, $strict);
3889
  return $strict ? webform_filter_xss($output) : $output;
3890
}
3891
3892
/*
3893
 * Replace tokens with Webform contexts populated.
3894 85ad3d82 Assos Assos
 *
3895
 * @param $string
3896
 *   The string to have its tokens replaced.
3897
 * @param $node
3898
 *   If replacing node-level tokens, the node for which tokens will be created.
3899
 * @param $submission
3900
 *   If replacing submission-level tokens, the submission for which tokens will
3901
 *   be created.
3902
 * @param $email
3903
 *   If replacing tokens within the context of an e-mail, the Webform e-mail
3904
 *   settings array.
3905 a45e4bc1 Assos Assos
 * @param $sanitize
3906
 *   Boolean or format name value indicating if the results will be displayed as
3907
 *   HTML output. If FALSE, the contents returned will be unsanitized. This will
3908
 *   also result in all Webform submission tokens being returned as plain-text,
3909
 *   without HTML markup, in preparation for e-mailing or other text-only
3910
 *   purposes (default values, etc.). If TRUE, the tokens only are sanitized by
3911
 *   token_replace. Otherwise $sanitize is the machine name of an import filter
3912
 *   to be used with check_markup().
3913
 */
3914
function webform_replace_tokens($string, $node = NULL, $submission = NULL, $email = NULL, $sanitize = FALSE) {
3915 85ad3d82 Assos Assos
  // Don't do any filtering if the string is empty.
3916 ba09eb79 Assos Assos
  if (!strlen(trim($string)) || !webform_variable_get('webform_token_access')) {
3917 85ad3d82 Assos Assos
    return $string;
3918
  }
3919
3920 a45e4bc1 Assos Assos
  $token_data = array();
3921
  if ($node) {
3922
    $token_data['node'] = $node;
3923 85ad3d82 Assos Assos
  }
3924 a45e4bc1 Assos Assos
  if ($submission) {
3925
    $token_data['webform-submission'] = $submission;
3926 85ad3d82 Assos Assos
  }
3927 a45e4bc1 Assos Assos
  if ($email) {
3928
    $token_data['webform-email'] = $email;
3929
  }
3930
  $clear = is_bool($sanitize);
3931
  $string = token_replace($string, $token_data, array('clear' => $clear, 'sanitize' => $sanitize === TRUE));
3932
  if (!$clear) {
3933
    $string = webform_replace_tokens_clear(check_markup($string, $sanitize));
3934
  }
3935
  return $string;
3936
}
3937 85ad3d82 Assos Assos
3938 a45e4bc1 Assos Assos
/**
3939
 * Removes tokens from string.
3940
 *
3941
 * Call this function in cases where you need to remove unreplaced tokens but
3942
 * can't call webform_replace_tokens() with the option $clear = TRUE.
3943
 *
3944
 * In some cases the function token_replace() in webform_replace_tokens() needs
3945
 * to be called with the option 'clear' => FALSE, to not remove input filters.
3946
 * For security reasons webform_replace_tokens() is called before
3947
 * check_markup(), where input filters get replaced. Tokens won't be replaced if
3948
 * there is no value provided. These tokens i.e. [current-page:query:*] needs to
3949
 * be removed to not show up in the output.
3950
 *
3951
 * Note: This function was previously named webform_clear_tokens, which
3952
 * conflicted with the webform_clear module, being called as hook_tokens.
3953
 *
3954
 * @param string $text
3955
 *   The text to have its tokens removed.
3956
 * @see token_replace()
3957
 */
3958
function webform_replace_tokens_clear($text) {
3959 ba09eb79 Assos Assos
  if (empty($text) || !webform_variable_get('webform_token_access')) {
3960 a45e4bc1 Assos Assos
    return $text;
3961 85ad3d82 Assos Assos
  }
3962
3963 a45e4bc1 Assos Assos
  $text_tokens = token_scan($text);
3964
  if (empty($text_tokens)) {
3965
    return $text;
3966 85ad3d82 Assos Assos
  }
3967
3968 a45e4bc1 Assos Assos
  $replacements = array();
3969
  foreach ($text_tokens as $type => $tokens) {
3970
    $replacements += array_fill_keys($tokens, '');
3971 85ad3d82 Assos Assos
  }
3972
3973 a45e4bc1 Assos Assos
  $tokens = array_keys($replacements);
3974
  $values = array_values($replacements);
3975 85ad3d82 Assos Assos
3976 a45e4bc1 Assos Assos
  return str_replace($tokens, $values, $text);
3977
}
3978
3979
/**
3980
 * Replace tokens within a URL, encoding the parts within the query string.
3981
 *
3982
 * @param string $redirect_url
3983
 *   The redirect URL, with everything other than tokens already URL encoded.
3984
 * @param $node
3985
 *   If replacing node-level tokens, the node for which tokens will be created.
3986
 * @param $submission
3987
 *   If replacing submission-level tokens, the submission for which tokens will
3988
 *   be created.
3989
 * @return array
3990
 *   An array of path and url() options, suitable for a redirect or drupal_goto.
3991
 */
3992
function webform_replace_url_tokens($redirect_url, $node = NULL, $submission = NULL) {
3993
  // Parse the url into its components.
3994
  $parsed_redirect_url = drupal_parse_url($redirect_url);
3995
  // Replace tokens in each component.
3996
  $parsed_redirect_url['path'] = webform_replace_tokens($parsed_redirect_url['path'], $node, $submission);
3997
  if (!empty($parsed_redirect_url['query'])) {
3998
    foreach ($parsed_redirect_url['query'] as $key => $value) {
3999
      $parsed_redirect_url['query'][$key] = trim(webform_replace_tokens($value, $node, $submission));
4000 85ad3d82 Assos Assos
    }
4001
  }
4002 a45e4bc1 Assos Assos
  $parsed_redirect_url['fragment'] = webform_replace_tokens($parsed_redirect_url['fragment'], $node, $submission);
4003
  // Determine whether the path is internal or external. Paths which contain the site's
4004
  // base url are still considered internal. #webform_external is private to webform.
4005
  $parsed_redirect_url['#webform_external'] = url_is_external($parsed_redirect_url['path']);
4006
  foreach (array(NULL, TRUE, FALSE) as $https) {
4007
    if (stripos($parsed_redirect_url['path'], url('', array('absolute' => TRUE, 'https' => $https))) === 0) {
4008
      $parsed_redirect_url['#webform_external'] = FALSE;
4009 85ad3d82 Assos Assos
    }
4010
  }
4011 a45e4bc1 Assos Assos
  // Return an array suitable for a form redirect or drupal_goto.
4012
  return array($parsed_redirect_url['path'], $parsed_redirect_url);
4013
}
4014 85ad3d82 Assos Assos
4015 a45e4bc1 Assos Assos
/**
4016
 * Replace tokens in descriptions and sanitize according to Webform settings.
4017
 */
4018
function webform_filter_descriptions($string, $node = NULL, $submission = NULL) {
4019
  return strlen($string) == 0 ? '' : webform_filter_xss(webform_replace_tokens($string, $node, $submission));
4020 85ad3d82 Assos Assos
}
4021
4022
/**
4023 a45e4bc1 Assos Assos
 * Deprecated! Use webform_filter_descriptions() instead.
4024
 *
4025
 * @deprecated
4026 85ad3d82 Assos Assos
 */
4027
function _webform_filter_descriptions($string, $node = NULL, $submission = NULL) {
4028 a45e4bc1 Assos Assos
  return webform_filter_descriptions($string, $node, $submission);
4029 85ad3d82 Assos Assos
}
4030
4031
/**
4032
 * Filter labels for display by running through XSS checks.
4033
 */
4034 a45e4bc1 Assos Assos
function webform_filter_xss($string) {
4035 85ad3d82 Assos Assos
  static $allowed_tags;
4036
  $allowed_tags = isset($allowed_tags) ? $allowed_tags : webform_variable_get('webform_allowed_tags');
4037
  return filter_xss($string, $allowed_tags);
4038
}
4039
4040 a45e4bc1 Assos Assos
/**
4041
 * Deprecated! Use webform_filter_xss() instead!
4042
 *
4043
 * @deprecated
4044
 */
4045
function _webform_filter_xss($string) {
4046
  return webform_filter_xss($string);
4047
}
4048 85ad3d82 Assos Assos
4049
/**
4050
 * Utility function to ensure that a webform record exists in the database.
4051
 *
4052
 * @param $node
4053
 *   The node object to check if a database entry exists.
4054
 * @return
4055
 *   This function should always return TRUE if no errors were encountered,
4056
 *   ensuring that a webform table row has been created. Will return FALSE if
4057
 *   a record does not exist and a new one could not be created.
4058
 */
4059
function webform_ensure_record(&$node) {
4060
  if (!$node->webform['record_exists']) {
4061
    // Even though webform_node_insert() would set this property to TRUE,
4062
    // we set record_exists to trigger a difference from the defaults.
4063
    $node->webform['record_exists'] = TRUE;
4064
    webform_node_insert($node);
4065
  }
4066
  return $node->webform['record_exists'];
4067
}
4068
4069
/**
4070
 * Utility function to check if a webform record is necessary in the database.
4071
 *
4072
 * If the node is no longer using any webform settings, this function will
4073
 * delete the settings from the webform table. Note that this function will NOT
4074
 * delete rows from the webform table if the node-type is exclusively used for
4075
 * webforms (per the "webform_node_types_primary" variable).
4076
 *
4077
 * @param $node
4078
 *   The node object to check if a database entry is still required.
4079
 * @return
4080
 *   Returns TRUE if the webform still has a record in the database. Returns
4081
 *   FALSE if the webform does not have a record or if the previously existing
4082
 *   record was just deleted.
4083
 */
4084
function webform_check_record(&$node) {
4085
  $webform = $node->webform;
4086
  $webform['record_exists'] = FALSE;
4087
  unset($webform['nid']);
4088
4089
  // Don't include empty values in the comparison, this makes it so modules that
4090
  // extend Webform with empty defaults won't affect cleanup of rows.
4091
  $webform = array_filter($webform);
4092
  $defaults = array_filter(webform_node_defaults());
4093
  if ($webform == $defaults && !in_array($node->type, webform_variable_get('webform_node_types_primary'))) {
4094
    webform_node_delete($node);
4095
    $node->webform = webform_node_defaults();
4096
  }
4097
  return $node->webform['record_exists'];
4098
}
4099
4100
/**
4101
 * Given a form_key and a list of form_key parents, determine the cid.
4102
 *
4103
 * @param $node
4104
 *   A fully loaded node object.
4105
 * @param $form_key
4106
 *   The form key for which we're finding a cid.
4107
 * @param $parent
4108
 *   The cid of the parent component.
4109
 */
4110
function webform_get_cid(&$node, $form_key, $pid) {
4111
  foreach ($node->webform['components'] as $cid => $component) {
4112
    if ($component['form_key'] == $form_key && $component['pid'] == $pid) {
4113
      return $cid;
4114
    }
4115
  }
4116
}
4117
4118
/**
4119 a45e4bc1 Assos Assos
 * Find the label of a given page based on page breaks.
4120
 *
4121
 * @param $webform
4122
 *   The webform node.
4123
 * @param $form_state
4124
 *   The form's state, if available
4125
 * @return array
4126
 *   An array of all page labels, indexed by page number.
4127
 */
4128
function webform_page_labels($node, $form_state = array()) {
4129
  $page_count = 1;
4130
  $page_labels = array();
4131
  $page_labels[0] = t($node->webform['progressbar_label_first']);
4132
  foreach ($node->webform['components'] as $component) {
4133
    if ($component['type'] == 'pagebreak') {
4134
      $page_labels[$page_count] = $component['name'];
4135
      $page_count++;
4136
    }
4137
  }
4138
  if ($node->webform['preview'] || !empty($form_state['webform']['preview'])) {
4139
    $page_labels[] = $node->webform['preview_title'] ? t($node->webform['preview_title']) : t('Preview');
4140
  }
4141
  if ($node->webform['progressbar_include_confirmation']) {
4142
    $page_labels[] = t($node->webform['progressbar_label_confirmation']);
4143
  }
4144
  return $page_labels;
4145
}
4146
4147
/**
4148
 * Retrieve a Drupal variable with the appropriate default value.
4149 85ad3d82 Assos Assos
 */
4150
function webform_variable_get($variable) {
4151
  switch ($variable) {
4152 a45e4bc1 Assos Assos
    case 'webform_blocks':
4153
      $result = variable_get('webform_blocks', array());
4154
      break;
4155
    case 'webform_tracking_mode':
4156
      $result = variable_get('webform_tracking_mode', 'cookie');
4157
      break;
4158 85ad3d82 Assos Assos
    case 'webform_allowed_tags':
4159
      $result = variable_get('webform_allowed_tags', array('a', 'em', 'strong', 'code', 'img'));
4160
      break;
4161 a45e4bc1 Assos Assos
    case 'webform_email_address_format':
4162
      $result = variable_get('webform_email_address_format', 'long');
4163
      break;
4164
    case 'webform_email_address_individual':
4165
      $result = variable_get('webform_email_address_individual', 0);
4166
      break;
4167 85ad3d82 Assos Assos
    case 'webform_default_from_name':
4168
      $result = variable_get('webform_default_from_name', variable_get('site_name', ''));
4169
      break;
4170
    case 'webform_default_from_address':
4171
      $result = variable_get('webform_default_from_address', variable_get('site_mail', ini_get('sendmail_from')));
4172
      break;
4173
    case 'webform_default_subject':
4174 a45e4bc1 Assos Assos
      $result = variable_get('webform_default_subject', t('Form submission from: [node:title]'));
4175
      break;
4176
    case 'webform_email_replyto':
4177
      $result = variable_get('webform_email_replyto', TRUE);
4178
      break;
4179
    case 'webform_email_html_capable':
4180
      $result = variable_get('webform_email_html_capable', FALSE);
4181
      break;
4182
    case 'webform_default_format':
4183
      $result = variable_get('webform_default_format', 0);
4184
      break;
4185
    case 'webform_format_override':
4186
      $result = variable_get('webform_format_override', 0);
4187
      break;
4188
    case 'webform_email_select_max':
4189
      $result = variable_get('webform_email_select_max', 50);
4190 85ad3d82 Assos Assos
      break;
4191
    case 'webform_node_types':
4192 a45e4bc1 Assos Assos
      $result = webform_node_types();
4193 85ad3d82 Assos Assos
      break;
4194
    case 'webform_node_types_primary':
4195
      $result = variable_get('webform_node_types_primary', array('webform'));
4196
      break;
4197 a45e4bc1 Assos Assos
    case 'webform_date_type':
4198
      $result = variable_get('webform_date_type', 'medium');
4199
      break;
4200
    case 'webform_export_format':
4201
      module_load_include('inc', 'webform', 'includes/webform.export');
4202
      $options = webform_export_list();
4203
      $result = variable_get('webform_export_format', 'excel');
4204
      $result = isset($options[$result]) ? $result : key($options);
4205
      break;
4206
    case 'webform_csv_delimiter':
4207
      $result = variable_get('webform_csv_delimiter', '\t');
4208
      break;
4209 01d522a6 Assos Assos
    case 'webform_csv_line_ending':
4210
      $result = variable_get('webform_csv_line_ending', "\n");
4211
      break;
4212 a45e4bc1 Assos Assos
    case 'webform_export_wordwrap':
4213
      $result = variable_get('webform_export_wordwrap', 0);
4214
      break;
4215
    case 'webform_excel_legacy_exporter':
4216
      $result = variable_get('webform_excel_legacy_exporter', 0);
4217
      break;
4218
    case 'webform_progressbar_style':
4219
      $result = variable_get('webform_progressbar_style', array('progressbar_bar', 'progressbar_pagebreak_labels', 'progressbar_include_confirmation'));
4220
      break;
4221
    case 'webform_progressbar_label_first':
4222
      $result = variable_get('webform_progressbar_label_first', t('Start'));
4223
      break;
4224
    case 'webform_progressbar_label_confirmation':
4225
      $result = variable_get('webform_progressbar_label_confirmation', t('Complete'));
4226
      break;
4227
    case 'webform_table':
4228
      $result = variable_get('webform_table', FALSE);
4229
      break;
4230
    case 'webform_submission_access_control':
4231
      $result = variable_get('webform_submission_access_control', 1);
4232
      break;
4233 ba09eb79 Assos Assos
    case 'webform_token_access':
4234
      $result = variable_get('webform_token_access', 1);
4235
      break;
4236 a45e4bc1 Assos Assos
    case 'webform_update_batch_size':
4237
      $result = variable_get('webform_update_batch_size', 100);
4238
      break;
4239
    case 'webform_disabled_components':
4240
      $result = variable_get('webform_disabled_components', array());
4241
      break;
4242 85ad3d82 Assos Assos
  }
4243
  return $result;
4244
}
4245
4246 a45e4bc1 Assos Assos
/**
4247
 * Output the contents of token help used throughout Webform.
4248
 *
4249
 * In earlier versions of Token, a fieldset is used to show all the tokens.
4250
 * Later versions now use a modal dialog that is accessed through a link. If
4251
 * Token module is not available, a message should be displayed.
4252
 */
4253 85ad3d82 Assos Assos
function theme_webform_token_help($variables) {
4254 ba09eb79 Assos Assos
  if (!webform_variable_get('webform_token_access')) {
4255
    return '';
4256
  }
4257 85ad3d82 Assos Assos
  $groups = $variables['groups'];
4258 a45e4bc1 Assos Assos
  module_load_include('inc', 'token', 'token.pages');
4259 85ad3d82 Assos Assos
4260 a45e4bc1 Assos Assos
  // Assume dialogs are not supported, show as a fieldset.
4261
  $help = array(
4262 85ad3d82 Assos Assos
    '#title' => t('Token values'),
4263
    '#type' => 'fieldset',
4264
    '#collapsible' => TRUE,
4265
    '#collapsed' => TRUE,
4266
    '#attributes' => array('class' => array('collapsible', 'collapsed')),
4267 a45e4bc1 Assos Assos
    'help' => array(
4268
      '#markup' => '<p>' . t('This field supports dynamic token values. Common values might be [current-user:mail] or [node:title].') . '</p>',
4269
    ),
4270
    'token_tree' => array(
4271
      '#theme' => 'token_tree',
4272
      '#token_types' => $groups,
4273
    ),
4274 85ad3d82 Assos Assos
  );
4275
4276 a45e4bc1 Assos Assos
  if (!module_exists('token')) {
4277
    // No token module at all. Display a simple suggestion to enable it.
4278
    $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>';
4279
    unset($help['token_tree']);
4280 85ad3d82 Assos Assos
  }
4281 a45e4bc1 Assos Assos
  elseif (function_exists('token_page_output_tree')) {
4282
    // Token supports dialogs: display simply as a link.
4283
    $help = $help['token_tree'];
4284
    $help['#dialog'] = TRUE;
4285 85ad3d82 Assos Assos
  }
4286
4287 a45e4bc1 Assos Assos
  return render($help);
4288
}
4289
4290
/**
4291
 * Convert a name into an identifier that is safe for machine names, classes,
4292
 * and other ASCII uses.
4293
 */
4294
function _webform_safe_name($name) {
4295
  $new = trim($name);
4296
  $new = _webform_transliterate($new);
4297
  $new = str_replace(array(' ', '-', '/'), array('_', '_', '_'), $new);
4298 85ad3d82 Assos Assos
  $new = drupal_strtolower($new);
4299
  $new = preg_replace('/[^a-z0-9_]/', '', $new);
4300
  return $new;
4301
}
4302
4303 a45e4bc1 Assos Assos
/**
4304
 * Transliterate common non-English characters to 7-bit ASCII.
4305
 */
4306
function _webform_transliterate($name) {
4307
  // If transliteration is available, use it to convert names to ASCII.
4308
  return function_exists('transliteration_get')
4309
            ? transliteration_get($name, '')
4310
            : str_replace(array('€', 'ƒ', 'Š', 'Ž', 'š', 'ž', 'Ÿ', '¢', '¥', 'µ', 'À', 'Á', 'Â', 'Ã', 'Ä', 'Å', 'Ç', 'È', 'É', 'Ê', 'Ë', 'Ì', 'Í', 'Î', 'Ï', 'Ñ', 'Ò', 'Ó', 'Ô', 'Õ', 'Ö', 'Ø', 'Ù', 'Ú', 'Û', 'Ü', 'Ý', 'à', 'á', 'â', 'ã', 'ä', 'å', 'ç', 'è', 'é', 'ê', 'ë', 'ì', 'í', 'î', 'ï', 'ñ', 'ò', 'ó', 'ô', 'õ', 'ö', 'ø', 'ù', 'ú', 'û', 'ü', 'ý', 'ÿ', 'Œ',  'œ',  'Æ',  'Ð',  'Þ',  'ß',  'æ',  'ð',  'þ'),
4311
                          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'),
4312
                          $name);
4313
}
4314
4315 85ad3d82 Assos Assos
/**
4316
 * Given an email address and a name, format an e-mail address.
4317
 *
4318 a45e4bc1 Assos Assos
 * The address can be the cid of a component with multiple values. When $single
4319
 * is true, a single address is return (the first of any multiples). When not
4320
 * true, an array of addresses is returned.
4321
 *
4322
 * Note that multiple names could be used with multiple addresses, but this
4323
 * capability is not currently possible with the webform UI. Separate names
4324
 * are only used with the From address, which is always single.
4325
 *
4326 85ad3d82 Assos Assos
 * @param $address
4327
 *   The e-mail address.
4328
 * @param $name
4329 a45e4bc1 Assos Assos
 *   The name to be used in the formatted address. If the address contains a
4330
 *   name in 'Some Name <somename@example.com>' format, $name is ignored.
4331 85ad3d82 Assos Assos
 * @param $node
4332
 *   The webform node if replacements will be done.
4333
 * @param $submission
4334
 *   The webform submission values if replacements will be done.
4335
 * @param $encode
4336
 *   Encode the text for use in an e-mail.
4337
 * @param $single
4338
 *   Force a single value to be returned, even if a component expands to
4339
 *   multiple addresses. This is useful to ensure a single e-mail will be
4340
 *   returned for the "From" address.
4341
 * @param $format
4342 a45e4bc1 Assos Assos
 *   The e-mail format, defaults to the site-wide setting. May be "short",
4343
 *   "long", or NULL for the system default.
4344
 * @param $mapping
4345
 *   A mapping array to be applied to the address values.
4346
 * @return string|array
4347
 *   The formatted e-mail address -- or addresses (if not $single)
4348 85ad3d82 Assos Assos
 */
4349 a45e4bc1 Assos Assos
function webform_format_email_address($address, $name, $node = NULL, $submission = NULL, $encode = TRUE, $single = TRUE, $format = NULL, $mapping = NULL) {
4350 85ad3d82 Assos Assos
  if (!isset($format)) {
4351 a45e4bc1 Assos Assos
    $format = webform_variable_get('webform_email_address_format');
4352 85ad3d82 Assos Assos
  }
4353
4354
  if ($name == 'default') {
4355
    $name = webform_variable_get('webform_default_from_name');
4356
  }
4357
  elseif (is_numeric($name) && isset($node->webform['components'][$name])) {
4358 a45e4bc1 Assos Assos
    if (isset($submission->data[$name])) {
4359
      $component = $node->webform['components'][$name];
4360
      $name = $submission->data[$name];
4361
4362
      // Convert the FROM name to be the label of select lists.
4363
      if (webform_component_implements($component['type'], 'options')) {
4364
        $options = webform_component_invoke($component['type'], 'options', $component);
4365
        foreach ($name as &$one_name) {
4366 01d522a6 Assos Assos
          $one_name = isset($options[$one_name]) ? $options[$one_name] : $one_name;
4367 a45e4bc1 Assos Assos
        }
4368
        unset($one_name); // Drop PHP reference.
4369
      }
4370 85ad3d82 Assos Assos
    }
4371
    else {
4372
      $name = t('Value of !component', array('!component' => $node->webform['components'][$name]['name']));
4373
    }
4374
  }
4375 a45e4bc1 Assos Assos
  elseif (!isset($name)) {
4376
    $name = '';
4377
  }
4378 85ad3d82 Assos Assos
4379
  if ($address == 'default') {
4380
    $address = webform_variable_get('webform_default_from_address');
4381
  }
4382
  elseif (is_numeric($address) && isset($node->webform['components'][$address])) {
4383 a45e4bc1 Assos Assos
    if (isset($submission->data[$address])) {
4384
      $values = $submission->data[$address];
4385 85ad3d82 Assos Assos
      $address = array();
4386
      foreach ($values as $value) {
4387 a45e4bc1 Assos Assos
        if (isset($mapping) && isset($mapping[$value])) {
4388
          $value = $mapping[$value];
4389
        }
4390 85ad3d82 Assos Assos
        $address = array_merge($address, explode(',', $value));
4391
      }
4392
    }
4393
    else {
4394
      $address = t('Value of "!component"', array('!component' => $node->webform['components'][$address]['name']));
4395
    }
4396
  }
4397
4398 a45e4bc1 Assos Assos
  // Convert single values to an array to simplify processing.
4399
  $address = is_array($address) ? $address : explode(',', $address);
4400
  $name = is_array($name) ? $name : array($name);
4401
  $name_shortage = count($address) - count($name);
4402
  if ($name_shortage > 0) {
4403
    $name += array_fill(count($name), $name_shortage, $name[0]);
4404 85ad3d82 Assos Assos
  }
4405
4406 a45e4bc1 Assos Assos
  foreach ($address as $key => $individual_address) {
4407
    $individual_address = webform_replace_tokens($individual_address, $node, $submission);
4408
    $email_parts = webform_parse_email_address($individual_address);
4409
    if ($format == 'long' && !empty($name[$key]) && !strlen($email_parts['name'])) {
4410
      $individual_name = $name[$key];
4411
      $individual_name = webform_replace_tokens($individual_name, $node, $submission);
4412
      if ($encode) {
4413
        $individual_name = mime_header_encode($individual_name);
4414
      }
4415
      $individual_name = trim($individual_name);
4416
      $individual_address = '"' . $individual_name . '" <' . $individual_address . '>';
4417 85ad3d82 Assos Assos
    }
4418 a45e4bc1 Assos Assos
    $address[$key] = $individual_address;
4419 85ad3d82 Assos Assos
  }
4420 a45e4bc1 Assos Assos
4421
  return $single ? reset($address) : $address;
4422
4423
}
4424
4425
/**
4426
 * Validates an email form element.
4427
 *
4428
 * @param string $emails
4429
 *   An email or list of comma-seperated email addresses. Passed by reference.
4430
 *   Empty emails will be eliminated, and mutiple addresses will be seperated
4431
 *   with a comma and space.
4432
 * @param string $form_name
4433
 *   The name of the form element to receive an error, in form_set_error format.
4434
 * @param boolean $allow_empty
4435
 *   TRUE if optional. FALSE if required.
4436
 * @param boolean $allow_multiple
4437
 *   TRUE if a list of emails is allowed. FALSE if only one.
4438
 * @param boolean $allow_tokens
4439
 *   TRUE if any token should be assumed to contain a valid e-mail address.
4440
 * @param string $format
4441
 *   'short', 'long', or NULL (for default) format. Long format has a name and
4442
 *   the address in angle brackets.
4443
 * @return integer|boolean
4444
 *   The number of valid addresses found, or FALSE for an invalid email found.
4445
 */
4446
function webform_email_validate(&$emails, $form_name, $allow_empty, $allow_multiple, $allow_tokens, $format = NULL) {
4447
  $nr_valid = webform_valid_email_address($emails, $allow_tokens, $format);
4448
  if ($nr_valid === FALSE) {
4449
    form_set_error($form_name, t('The entered e-mail address "@email" does not appear valid.', array('@email' => $emails)));
4450
  }
4451
  elseif ($nr_valid === 0 && !$allow_empty) {
4452
    form_set_error($form_name, t('When adding a new custom e-mail, the e-mail field is required.'));
4453
  }
4454
  elseif ($nr_valid > 1 && !$allow_multiple) {
4455
    form_set_error($form_name, t('Only one e-mail address is allowed.'));
4456 85ad3d82 Assos Assos
  }
4457 a45e4bc1 Assos Assos
  return $nr_valid;
4458
}
4459 85ad3d82 Assos Assos
4460 a45e4bc1 Assos Assos
/**
4461
 * Validates email address(es) with optional name(s).
4462
 *
4463
 * @param string $emails
4464
 *   An email address, a list of comma-separated email addresses. If all the
4465
 *   addresses are valid, the list of trimmed, non-empty emails is returned by
4466
 *   reference.
4467
 * @param boolean $allow_tokens
4468
 *   TRUE if any token should be assumed to contain a valid e-mail address.
4469
 * @param string $format
4470
 *   'short', 'long', or NULL (for default) format. Long format has a name and
4471
 *   the address in angle brackets.
4472
 * @return boolean|integer
4473
 *   Returns FALSE if an invalid e-mail address was found, 0 if no email
4474
 *   address(es) were found, or the number of valid e-mail addresses found.
4475
 */
4476
function webform_valid_email_address(&$emails, $allow_tokens = FALSE, $format = NULL) {
4477
  $email_array = array_filter(array_map('trim', explode(',', $emails)));
4478
  $count = 0;
4479
  foreach ($email_array as $email) {
4480 ba09eb79 Assos Assos
    if ($allow_tokens && webform_variable_get('webform_token_access')) {
4481 a45e4bc1 Assos Assos
      foreach (token_scan($email) as $tokens) {
4482
        foreach ($tokens as $token) {
4483
          $email = str_replace($token, 'admin@example.com', $email);
4484
        }
4485
      }
4486 85ad3d82 Assos Assos
    }
4487 a45e4bc1 Assos Assos
    $matches = webform_parse_email_address($email, $format);
4488
    if (!valid_email_address($matches['address'])) {
4489
      return FALSE;
4490
    }
4491
    $count++;
4492 85ad3d82 Assos Assos
  }
4493 a45e4bc1 Assos Assos
  $emails = implode(', ', $email_array);
4494
  return $count;
4495
}
4496
4497
/**
4498
 * Parses an e-mail address into name and address.
4499
 *
4500
 * @param string $email
4501
 *   The email address to be parsed, with an optional name.
4502
 * @param string $format
4503
 *   'short', 'long', or NULL (for default) format. Long format has a name and
4504
 *   the address in angle brackets.
4505
 * @return array
4506
 *   Associative array indexed by 'name' and 'address'.
4507
 */
4508
function webform_parse_email_address($email, $format = NULL) {
4509
  if (!$format) {
4510
    $format = webform_variable_get('webform_email_address_format');
4511
  }
4512
  if ($format == 'long') {
4513
    // Match e-mails of the form 'My Name <email@domain.com>' as follows:
4514
    // ^          = beginning of string
4515
    // "?         = optional quote
4516
    // ([^<]*?)   = match optional characters that aren't a < (non-greedy)
4517
    // "?         = optional quote
4518
    // SPACE*     = optional spaces
4519
    // (?:<(.*)>) = < matching stuff > (without the angle brakets)
4520
    // $          = end of string
4521
    preg_match('/^"?([^<]*?)"? *(?:<(.*)>)?$/', $email, $matches);
4522
    if (isset($matches[2]) && strlen($matches[2])) {
4523
      return array(
4524
        'name' => $matches[1],
4525
        'address' => $matches[2],
4526
      );
4527
    }
4528 85ad3d82 Assos Assos
  }
4529 a45e4bc1 Assos Assos
  return array(
4530
    'name' => '',
4531
    'address' => $email,
4532
  );
4533 85ad3d82 Assos Assos
}
4534
4535
/**
4536
 * Given an email subject, format it with any needed replacements.
4537
 */
4538
function webform_format_email_subject($subject, $node = NULL, $submission = NULL) {
4539
  if ($subject == 'default') {
4540
    $subject = webform_variable_get('webform_default_subject');
4541
  }
4542
  elseif (is_numeric($subject) && isset($node->webform['components'][$subject])) {
4543
    $component = $node->webform['components'][$subject];
4544 a45e4bc1 Assos Assos
    if (isset($submission->data[$subject])) {
4545 85ad3d82 Assos Assos
      $display_function = '_webform_display_' . $component['type'];
4546 a45e4bc1 Assos Assos
      $value = $submission->data[$subject];
4547 85ad3d82 Assos Assos
4548
      // Convert the value to a clean text representation if possible.
4549
      if (function_exists($display_function)) {
4550
        $display = $display_function($component, $value, 'text');
4551
        $display['#theme_wrappers'] = array();
4552
        $display['#webform_component'] = $component;
4553
        $subject = str_replace("\n", ' ', drupal_render($display));
4554
      }
4555
      else {
4556
        $subject = $value;
4557
      }
4558
    }
4559
    else {
4560
      $subject = t('Value of "!component"', array('!component' => $component['name']));
4561
    }
4562
  }
4563
4564
  // Convert arrays to strings (may happen if checkboxes are used as the value).
4565
  if (is_array($subject)) {
4566
    $subject = reset($subject);
4567
  }
4568
4569 a45e4bc1 Assos Assos
  return webform_replace_tokens($subject, $node, $submission);
4570 85ad3d82 Assos Assos
}
4571
4572
/**
4573
 * Convert an array of components into a tree
4574
 */
4575
function _webform_components_tree_build($src, &$tree, $parent, &$page_count) {
4576
  foreach ($src as $cid => $component) {
4577
    if ($component['pid'] == $parent) {
4578
      _webform_components_tree_build($src, $component, $cid, $page_count);
4579
      if ($component['type'] == 'pagebreak') {
4580
        $page_count++;
4581
      }
4582
      $tree['children'][$cid] = $component;
4583
      $tree['children'][$cid]['page_num'] = $page_count;
4584
    }
4585
  }
4586
  return $tree;
4587
}
4588
4589
/**
4590
 * Flatten a component tree into a flat list.
4591
 */
4592
function _webform_components_tree_flatten($tree) {
4593
  $components = array();
4594
  foreach ($tree as $cid => $component) {
4595
    if (isset($component['children'])) {
4596
      unset($component['children']);
4597
      $components[$cid] = $component;
4598
      // array_merge() can't be used here because the keys are numeric.
4599
      $children = _webform_components_tree_flatten($tree[$cid]['children']);
4600
      foreach ($children as $ccid => $ccomponent) {
4601
        $components[$ccid] = $ccomponent;
4602
      }
4603
    }
4604
    else {
4605
      $components[$cid] = $component;
4606
    }
4607
  }
4608
  return $components;
4609
}
4610
4611
/**
4612
 * Helper for the uasort in webform_tree_sort()
4613
 */
4614
function _webform_components_sort($a, $b) {
4615
  if ($a['weight'] == $b['weight']) {
4616
    return strcasecmp($a['name'], $b['name']);
4617
  }
4618
  return ($a['weight'] < $b['weight']) ? -1 : 1;
4619
}
4620
4621
/**
4622
 * Sort each level of a component tree by weight and name
4623
 */
4624
function _webform_components_tree_sort($tree) {
4625
  if (isset($tree['children']) && is_array($tree['children'])) {
4626
    $children = array();
4627
    uasort($tree['children'], '_webform_components_sort');
4628
    foreach ($tree['children'] as $cid => $component) {
4629
      $children[$cid] = _webform_components_tree_sort($component);
4630
    }
4631
    $tree['children'] = $children;
4632
  }
4633
  return $tree;
4634
}
4635
4636
/**
4637
 * Get a list of all available component definitions.
4638
 */
4639
function webform_components($include_disabled = FALSE, $reset = FALSE) {
4640 a45e4bc1 Assos Assos
  static $components, $enabled;
4641 85ad3d82 Assos Assos
4642
  if (!isset($components) || $reset) {
4643
    $components = array();
4644 a45e4bc1 Assos Assos
    $disabled = array_flip(webform_variable_get('webform_disabled_components'));
4645 85ad3d82 Assos Assos
    foreach (module_implements('webform_component_info') as $module) {
4646
      $module_components = module_invoke($module, 'webform_component_info');
4647
      foreach ($module_components as $type => $info) {
4648
        $module_components[$type]['module'] = $module;
4649
        $module_components[$type]['enabled'] = !array_key_exists($type, $disabled);
4650
      }
4651
      $components += $module_components;
4652
    }
4653
    drupal_alter('webform_component_info', $components);
4654 a45e4bc1 Assos Assos
    uasort($components, function($a, $b) {
4655
      return strnatcasecmp($a['label'], $b['label']);
4656
    });
4657
    $enabled = array_diff_key($components, $disabled);
4658 85ad3d82 Assos Assos
  }
4659
4660 a45e4bc1 Assos Assos
  return $include_disabled ? $components : $enabled;
4661 85ad3d82 Assos Assos
}
4662
4663
/**
4664
 * Build a list of components suitable for use as select list options.
4665
 */
4666
function webform_component_options($include_disabled = FALSE) {
4667
  $component_info = webform_components($include_disabled);
4668
  $options = array();
4669
  foreach ($component_info as $type => $info) {
4670
    $options[$type] = $info['label'];
4671
  }
4672
  return $options;
4673
}
4674
4675
/**
4676
 * Load a component file into memory.
4677
 *
4678
 * @param $component_type
4679
 *   The string machine name of a component.
4680
 */
4681
function webform_component_include($component_type) {
4682
  static $included = array();
4683
4684
  // No need to load components that have already been added once.
4685
  if (!isset($included[$component_type])) {
4686
    $components = webform_components(TRUE);
4687
    $included[$component_type] = TRUE;
4688
4689
    if (($info = $components[$component_type]) && isset($info['file'])) {
4690
      $pathinfo = pathinfo($info['file']);
4691
      $basename = basename($pathinfo['basename'], '.' . $pathinfo['extension']);
4692
      $path = (!empty($pathinfo['dirname']) ? $pathinfo['dirname'] . '/' : '') . $basename;
4693
      module_load_include($pathinfo['extension'], $info['module'], $path);
4694
    }
4695
  }
4696
}
4697
4698
/**
4699
 * Invoke a component callback.
4700
 *
4701
 * @param $type
4702
 *   The component type as a string.
4703
 * @param $callback
4704
 *   The callback to execute.
4705
 * @param ...
4706
 *   Any additional parameters required by the $callback.
4707
 */
4708
function webform_component_invoke($type, $callback) {
4709
  $args = func_get_args();
4710
  $type = array_shift($args);
4711
  $callback = array_shift($args);
4712
  $function = '_webform_' . $callback . '_' . $type;
4713
  webform_component_include($type);
4714
  if (function_exists($function)) {
4715
    return call_user_func_array($function, $args);
4716
  }
4717
}
4718
4719
/**
4720
 * Check if a component implements a particular hook.
4721
 *
4722
 * @param $type
4723
 *   The component type as a string.
4724
 * @param $callback
4725
 *   The callback to check.
4726
 */
4727
function webform_component_implements($type, $callback) {
4728
  $function = '_webform_' . $callback . '_' . $type;
4729
  webform_component_include($type);
4730
  return function_exists($function);
4731
}
4732
4733 a45e4bc1 Assos Assos
/**
4734
 * Form API #process function to expand a webform conditional element.
4735
 */
4736
function webform_conditional_expand($element) {
4737
  module_load_include('inc', 'webform', 'includes/webform.conditionals');
4738
  return _webform_conditional_expand($element);
4739
}
4740
4741
/**
4742
 * Add class and wrapper class attributes to an element.
4743
 */
4744
function _webform_component_classes(&$element, $component) {
4745
  if (isset($component['extra']['css_classes']) && drupal_strlen($component['extra']['css_classes'])) {
4746
    $element['#attributes']['class'] = isset($element['#attributes']['class']) ? $element['#attributes']['class'] : array();
4747
    $element['#attributes']['class'] = array_merge($element['#attributes']['class'], explode(' ' , $component['extra']['css_classes']));
4748
  }
4749
  if (isset($component['extra']['wrapper_classes']) && drupal_strlen($component['extra']['wrapper_classes'])) {
4750
    $element['#wrapper_attributes']['class'] = isset($element['#wrapper_attributes']['class']) ? $element['#wrapper_attributes']['class'] : array();
4751
    $element['#wrapper_attributes']['class'] = array_merge($element['#wrapper_attributes']['class'], explode(' ' , $component['extra']['wrapper_classes']));
4752
  }
4753
}
4754
4755 85ad3d82 Assos Assos
/**
4756
 * Disable the Drupal page cache.
4757
 */
4758
function webform_disable_page_cache() {
4759
  drupal_page_is_cacheable(FALSE);
4760
}
4761
4762
/**
4763
 * Set the necessary breadcrumb for the page we are on.
4764 c22e192e Assos Assos
 *
4765
 * @param object $node
4766
 *   The loaded webform node.
4767
 * @param boolean|object $submission
4768
 *   The submission if the current page is viewing or dealing with a submission,
4769
 *   or TRUE to just include the webform node in the breadcrumbs (used for
4770
 *   the submission completion confirmation page), or NULL for no extra
4771
 *   processing
4772 85ad3d82 Assos Assos
 */
4773
function webform_set_breadcrumb($node, $submission = NULL) {
4774 c22e192e Assos Assos
  $node_path = "node/{$node->nid}";
4775
4776
  // Set the href of the current menu item to be the node's path. This has two
4777 a45e4bc1 Assos Assos
  // effects. The active trail will be to the node's prefered menu tree
4778
  // location, expanding the menu as appropriate. And the breadcrumbs will be
4779
  // set as if the current page were under the node's preferred location.
4780 c22e192e Assos Assos
  // Note that menu_tree_set_path() could be used to set the path for the menu,
4781
  // but it will not affect the breadcrumbs when the webform is not in the
4782
  // default menu.
4783
  menu_set_item(NULL, array('href' => $node_path) + menu_get_item());
4784
4785
  if ($submission) {
4786
    $breadcrumb = menu_get_active_breadcrumb();
4787
4788
    // Append the node title (or its menu name), in case it isn't in the path already.
4789
    $active_trail = menu_get_active_trail();
4790
    $last_active = end($active_trail);
4791
    $breadcrumb[] = $last_active['href'] === $node_path && !empty($last_active['in_active_trail'])
4792
                      ? l($last_active['title'], $node_path, $last_active['localized_options'])
4793
                      : l($node->title, $node_path);
4794
4795
    // Setting the current menu href will cause the submission title and current
4796
    // tab (if not the default tab) to be added to the active path when the
4797
    // webform is in the default location in the menu (node/NID). The title
4798
    // is desirable, but the tab name (e.g. Edit or Delete) isn't.
4799
    if (preg_match('/href=".*"/', end($breadcrumb), $matches)) {
4800
      foreach ($breadcrumb as $index => $link) {
4801
        if (stripos($link, $matches[0]) !== FALSE) {
4802
          $breadcrumb = array_slice($breadcrumb, 0, $index + 1);
4803
          break;
4804
        }
4805
      }
4806 a45e4bc1 Assos Assos
  }
4807 c22e192e Assos Assos
4808
    // If the user is dealing with a submission, then the breadcrumb should
4809
    // be fudged to allow them to return to a likely list of webforms.
4810
    // Note that this isn't necessarily where they came from, but it's the
4811
    // best guess available.
4812
    if (is_object($submission)) {
4813 85ad3d82 Assos Assos
      if (webform_results_access($node)) {
4814 c22e192e Assos Assos
        $breadcrumb[] = l(t('Webform results'), $node_path . '/webform-results');
4815 85ad3d82 Assos Assos
      }
4816 3753f249 Assos Assos
      elseif (user_access('access own webform results')) {
4817 c22e192e Assos Assos
        $breadcrumb[] = l(t('Submissions'), $node_path . '/submissions');
4818 85ad3d82 Assos Assos
      }
4819
    }
4820
4821 c22e192e Assos Assos
    drupal_set_breadcrumb($breadcrumb);
4822
  }
4823 85ad3d82 Assos Assos
}
4824
4825
/**
4826
 * Convert an ISO 8601 date or time into an array.
4827
 *
4828
 * This converts full format dates or times. Either a date or time may be
4829
 * provided, in which case only those portions will be returned. Dashes and
4830
 * colons must be used, never implied.
4831
 *
4832
 * Formats:
4833
 * Dates: YYYY-MM-DD
4834
 * Times: HH:MM:SS
4835
 * Datetimes: YYYY-MM-DDTHH:MM:SS
4836
 *
4837
 * @param $string
4838
 *   An ISO 8601 date, time, or datetime.
4839
 * @param $type
4840
 *   If wanting only specific fields back, specify either "date" or "time".
4841
 *   Leaving empty will return an array with both date and time keys, even if
4842
 *   some are empty. Returns an array with the following keys:
4843
 *   - year
4844
 *   - month
4845
 *   - day
4846
 *   - hour (in 24hr notation)
4847
 *   - minute
4848
 *   - second
4849
 */
4850
function webform_date_array($string, $type = NULL) {
4851
  $pattern = '/((\d{4}?)-(\d{2}?)-(\d{2}?))?(T?(\d{2}?):(\d{2}?):(\d{2}?))?/';
4852
  $matches = array();
4853
  preg_match($pattern, $string, $matches);
4854
  $matches += array_fill(0, 9, '');
4855
4856
  $return = array();
4857
4858
  // Check for a date string.
4859
  if ($type == 'date' || !isset($type)) {
4860
    $return['year'] = $matches[2] !== '' ? (int) $matches[2] : '';
4861
    $return['month'] = $matches[3] !== '' ? (int) $matches[3] : '';
4862
    $return['day'] = $matches[4] !== '' ? (int) $matches[4] : '';
4863
  }
4864
4865
  // Check for a time string.
4866
  if ($type == 'time' || !isset($type)) {
4867
    $return['hour'] = $matches[6] !== '' ? (int) $matches[6] : '';
4868
    $return['minute'] = $matches[7] !== '' ? (int) $matches[7] : '';
4869
    $return['second'] = $matches[8] !== '' ? (int) $matches[8] : '';
4870
  }
4871
4872
  return $return;
4873
}
4874
4875
/**
4876
 * Convert an array of a date or time into an ISO 8601 compatible string.
4877
 *
4878
 * @param $array
4879
 *   The array to convert to a date or time string.
4880
 * @param $type
4881
 *   If wanting a specific string format back specify either "date" or "time".
4882
 *   Otherwise a full ISO 8601 date and time string will be returned.
4883
 */
4884
function webform_date_string($array, $type = NULL) {
4885
  $string = '';
4886
4887
  if ($type == 'date' || !isset($type)) {
4888
    $string .= empty($array['year']) ? '0000' : sprintf('%04d', $array['year']);
4889
    $string .= '-';
4890
    $string .= empty($array['month']) ? '00' : sprintf('%02d', $array['month']);
4891
    $string .= '-';
4892
    $string .= empty($array['day']) ? '00' : sprintf('%02d', $array['day']);
4893
  }
4894
4895
  if (!isset($type)) {
4896
    $string .= 'T';
4897
  }
4898
4899
  if ($type == 'time' || !isset($type)) {
4900
    $string .= empty($array['hour']) ? '00' :  sprintf('%02d', $array['hour']);
4901
    $string .= ':';
4902
    $string .= empty($array['minute']) ? '00' :  sprintf('%02d', $array['minute']);
4903
    $string .= ':';
4904
    $string .= empty($array['second']) ? '00' :  sprintf('%02d', $array['second']);
4905
  }
4906
4907
  return $string;
4908
}
4909
4910
/**
4911
 * Get a date format according to the site settings.
4912
 *
4913 a45e4bc1 Assos Assos
 * @param $type
4914
 *   A choice of 'short', 'medium', 'long' , or other user-defined date formats.
4915
 *   Use NULL for the webform-specific date format choosen in the webform
4916
 *   settings.
4917
 * @param array $exclude
4918
 *   An array containing 'day', 'month', and/or 'year' if they should be
4919
 *   removed from the format.
4920
 * @return string
4921
 *   A date/time format string.
4922 85ad3d82 Assos Assos
 */
4923 a45e4bc1 Assos Assos
function webform_date_format($type = NULL, $exclude = array()) {
4924
  static $formats = array();
4925
  $id = $type . ':' . implode('', $exclude);
4926
  if (!isset($formats[$id])) {
4927
    $type_name = $type ? $type : webform_variable_get('webform_date_type');
4928
4929 85ad3d82 Assos Assos
    // Format date according to site's given format.
4930 a45e4bc1 Assos Assos
    $format = variable_get('date_format_' . $type_name, 'D, m/d/Y');
4931
4932
    // Date/Time formatting characters
4933
    // WHAT           REQUIRED (at least 1) OPTIONAL (allowed but insufficient)
4934
    // ---------------------------------------------------------------------------
4935
    // Day-of-week                          DlNw
4936
    // Day            dj                    Stz
4937
    // Month          FmMn
4938
    // Year           oYy                   L
4939
    //
4940
    //                NOT ALLOWED
4941
    // --------------------------------------------------------------------------
4942
    // Time           aABgGhHisueIOPTZ
4943
    // Special        /.,-: <space>
4944
4945
    // Strip Time and Special characters from the beginning and end of format.
4946
    $date_format = trim($format, 'aABgGhHisueIOPTZ/.,-: ');
4947 85ad3d82 Assos Assos
4948
    // Ensure that a day, month, and year value are present. Use a default
4949 a45e4bc1 Assos Assos
    // format if all the values are not found. This regular expression uses
4950
    // (?= ), the positive lookahead assertion. It asserts that there are some
4951
    // optional characters (.*) followed by one of the day, month, or year
4952
    // characters. Because it is an assertion, it doesn't consume the
4953
    // characters, so the day, month, and year can be in any order.
4954
    if (!preg_match('/(?=.*[dj])(?=.*[FmMn])(?=.*[oYy])/', $date_format)) {
4955 85ad3d82 Assos Assos
      $date_format = 'm/d/Y';
4956
    }
4957
4958 a45e4bc1 Assos Assos
    // Remove any excluded portions.
4959
    $strip = array(
4960
      'day' => 'DlNwdjStz',
4961
      'month' => 'FmMn',
4962
      'year' => 'oYyL',
4963
    );
4964
    foreach ($exclude as $field) {
4965
      // Strip the format and any trailing /.,-: or space.
4966
      $date_format = preg_replace('#[' . $strip[$field] . ']+[/\.,\-: ]*#', '', $date_format);
4967
      $date_format = trim($date_format, '/.,-: ');
4968
    }
4969
4970
    $formats[$id] = $date_format;
4971
  }
4972
4973
  return $formats[$id];
4974 85ad3d82 Assos Assos
}
4975
4976
/**
4977
 * Return a date in the desired format taking into consideration user timezones.
4978
 */
4979 a45e4bc1 Assos Assos
function webform_strtodate($format, $string, $timezone_name = NULL, $reference_timestamp = NULL) {
4980 85ad3d82 Assos Assos
  global $user;
4981
4982
  // Adjust the time based on the user or site timezone.
4983
  if (variable_get('configurable_timezones', 1) && $timezone_name == 'user' && $user->uid) {
4984
    $timezone_name = isset($GLOBALS['user']->timezone) ? $GLOBALS['user']->timezone : 'UTC';
4985
  }
4986
  // If the timezone is still empty or not set, use the site timezone.
4987
  if (empty($timezone_name) || $timezone_name == 'user') {
4988
    $timezone_name = variable_get('date_default_timezone', 'UTC');
4989
  }
4990
4991
  if (!empty($timezone_name) && class_exists('DateTimeZone')) {
4992
    // Suppress errors if encountered during string conversion. Exceptions are
4993
    // only supported for DateTime in PHP 5.3 and higher.
4994
    try {
4995
      @$timezone = new DateTimeZone($timezone_name);
4996 a45e4bc1 Assos Assos
      if (isset($reference_timestamp)) {
4997
        // A reference for relative dates has been provided.
4998
        // 1) Convert the reference timestamp (in UTC) to a DateTime.
4999
        // 2) Set to time zone to the user or system timezone, recreating
5000
        //    the reference time in the appropriate time zone.
5001
        // 3) Set the time to midnight because when a non-referenced relative
5002
        //    date is created without a time, it is created at midnight (0:00).
5003
        // 4) Adjust to the specified relative (or absolute) time.
5004
5005
        @$datetime = new DateTime('@' . $reference_timestamp);
5006
        @$datetime->setTimezone($timezone)
5007
                  ->setTime(0, 0, 0)
5008
                  ->modify($string);
5009
      }
5010
      else {
5011
        @$datetime = new DateTime($string, $timezone);
5012
      }
5013 85ad3d82 Assos Assos
      return @$datetime->format($format);
5014
    }
5015
    catch (Exception $e) {
5016
      return '';
5017
    }
5018
  }
5019
  else {
5020 a45e4bc1 Assos Assos
    return date($format, isset($reference_timestamp) ? strtotime($string, $reference_timestamp) : strtotime($string));
5021 85ad3d82 Assos Assos
  }
5022
}
5023
5024
/**
5025
 * Get a timestamp in GMT time, ensuring timezone accuracy.
5026
 */
5027
function webform_strtotime($date) {
5028
  $current_tz = date_default_timezone_get();
5029
  date_default_timezone_set('UTC');
5030
  $timestamp = strtotime($date);
5031
  date_default_timezone_set($current_tz);
5032
  return $timestamp;
5033
}
5034
5035
/**
5036
 * Wrapper function for i18n_string() if i18nstrings enabled.
5037
 */
5038
function webform_tt($name, $string, $langcode = NULL, $update = FALSE) {
5039
  if (function_exists('i18n_string')) {
5040
    $options = array(
5041
      'langcode' => $langcode,
5042
      'update' => $update,
5043
    );
5044
    return i18n_string($name, $string, $options);
5045
  }
5046
  else {
5047
    return $string;
5048
  }
5049
}
5050
5051
/**
5052 a45e4bc1 Assos Assos
 * Returns an IP Address or anonymized IP Address for confidential webforms.
5053 85ad3d82 Assos Assos
 */
5054 a45e4bc1 Assos Assos
function webform_ip_address($node) {
5055
  return $node->webform['confidential'] ? t('(unknown)') : ip_address();
5056 85ad3d82 Assos Assos
}
5057
5058
/**
5059
 * Implements hook_views_api().
5060
 */
5061
function webform_views_api() {
5062
  return array(
5063 a45e4bc1 Assos Assos
    'api' => 3.0,
5064 85ad3d82 Assos Assos
    'path' => drupal_get_path('module', 'webform') . '/views',
5065
  );
5066
}
5067
5068 a45e4bc1 Assos Assos
/**
5069
 * Implements hook_views_default_views().
5070
 */
5071
function webform_views_default_views() {
5072
  $path = './' . drupal_get_path('module', 'webform') . '/views/default_views/*.inc';
5073
  $views = array();
5074
  foreach (glob($path) as $views_filename) {
5075
    require_once($views_filename);
5076
  }
5077
  return $views;
5078
}
5079
5080 85ad3d82 Assos Assos
/**
5081
 * Implements hook_field_extra_fields().
5082
 */
5083
function webform_field_extra_fields() {
5084
  $extra = array();
5085 a45e4bc1 Assos Assos
  foreach (webform_node_types() as $type) {
5086 85ad3d82 Assos Assos
    $extra['node'][$type]['display']['webform'] = array(
5087
      'label' => t('Webform'),
5088
      'description' => t('Webform client form.'),
5089
      'weight' => 10,
5090
    );
5091
  }
5092
  return $extra;
5093
}
5094
5095
/**
5096
 * Implements hook_mollom_form_list().
5097
 */
5098
function webform_mollom_form_list() {
5099
  $forms = array();
5100 a45e4bc1 Assos Assos
  $webform_types = webform_node_types();
5101 85ad3d82 Assos Assos
  if (empty($webform_types)) {
5102
    return $forms;
5103
  }
5104
5105 3753f249 Assos Assos
  $query = db_select('webform', 'w');
5106
  $query->innerJoin('node', 'n', 'n.nid = w.nid');
5107
  $query->fields('n', array('nid', 'title'));
5108
  $query->condition('n.type', $webform_types, 'IN');
5109
  $result = $query->execute();
5110 85ad3d82 Assos Assos
5111
  foreach ($result as $node) {
5112
    $form_id = 'webform_client_form_' . $node->nid;
5113
    $forms[$form_id] = array(
5114
      'title' => t('@name form', array('@name' => $node->title)),
5115
      'entity' => 'webform',
5116
      'delete form' => 'webform_submission_delete_form',
5117
    );
5118
  }
5119
  return $forms;
5120
}
5121
5122
/**
5123
 * Implements hook_mollom_form_info().
5124
 */
5125
function webform_mollom_form_info($form_id) {
5126
  module_load_include('inc', 'webform', 'includes/webform.components');
5127
5128
  $nid = drupal_substr($form_id, 20);
5129
  $node = node_load($nid);
5130
  $form_info = array(
5131
    'title' => t('@name form', array('@name' => $node->title)),
5132
    'mode' => MOLLOM_MODE_ANALYSIS,
5133
    'bypass access' => array('edit all webform submissions', 'edit any webform content'),
5134
    'entity' => 'webform',
5135
    'elements' => array(),
5136
    'mapping' => array(
5137
      'post_id' => 'details][sid',
5138
      'author_id' => 'details][uid',
5139
    ),
5140
  );
5141
  // Add components as elements.
5142
  // These components can be enabled for textual analysis (when not using a
5143
  // CAPTCHA-only protection) in Mollom's form configuration.
5144
  foreach ($node->webform['components'] as $cid => $component) {
5145
    if (webform_component_feature($component['type'], 'spam_analysis')) {
5146
      $parents = implode('][', webform_component_parent_keys($node, $component));
5147
      $form_info['elements']['submitted][' . $parents] = check_plain(t($component['name']));
5148
    }
5149
  }
5150
  // Assign field mappings based on webform configuration.
5151
  // Since multiple emails can be configured, we iterate over all and take
5152
  // over the assigned component for the field mapping in any email, unless
5153
  // we already assigned one. We are not interested in administratively
5154
  // configured static strings, only user-submitted values.
5155
  foreach ($node->webform['emails'] as $email) {
5156
    // Subject (post_title).
5157
    if (!isset($form_info['mapping']['post_title'])) {
5158
      $cid = $email['subject'];
5159
      if (is_numeric($cid)) {
5160
        $parents = implode('][', webform_component_parent_keys($node, $node->webform['components'][$cid]));
5161
        $form_info['mapping']['post_title'] = 'submitted][' . $parents;
5162
      }
5163
    }
5164
    // From name (author_name).
5165
    if (!isset($form_info['mapping']['author_name'])) {
5166
      $cid = $email['from_name'];
5167
      if (is_numeric($cid)) {
5168
        $parents = implode('][', webform_component_parent_keys($node, $node->webform['components'][$cid]));
5169
        $form_info['mapping']['author_name'] = 'submitted][' . $parents;
5170
      }
5171
    }
5172
    // From address (author_mail).
5173
    if (!isset($form_info['mapping']['author_mail'])) {
5174
      $cid = $email['from_address'];
5175
      if (is_numeric($cid)) {
5176
        $parents = implode('][', webform_component_parent_keys($node, $node->webform['components'][$cid]));
5177
        $form_info['mapping']['author_mail'] = 'submitted][' . $parents;
5178
      }
5179
    }
5180
  }
5181
5182
  return $form_info;
5183
}
5184 a45e4bc1 Assos Assos
5185
/**
5186
 * Implements hook_date_views_extra_tables().
5187
 */
5188
function webform_date_views_extra_tables() {
5189
  return array('webform_submissions' => 'webform_submissions');
5190
}
5191
5192
/**
5193
 * Returns the next serial number for a given node and increments the serial
5194
 * number.
5195
 *
5196
 * @param int $nid
5197
 *   The nid of the node.
5198
 *
5199
 * @return int
5200
 *   The next value of the serial number.
5201
 */
5202
function _webform_submission_serial_next_value($nid) {
5203
  // Use a transaction with SELECT ... FOR UPDATE to lock the row between
5204
  // the SELECT and the UPDATE, ensuring that multiple Webform submissions
5205
  // at the same time do not have duplicate numbers. FOR UPDATE must be inside
5206
  // a transaction. The return value of db_transaction() must be assigned or the
5207
  // transaction will commit immediately. The transaction will commit when $txn
5208
  // goes out-of-scope.
5209
  $txn = db_transaction();
5210
5211
  // Get the next_serial value.
5212
  $next_serial = db_select('webform', 'w')
5213
    // Only add FOR UPDATE when incrementing.
5214
    ->forUpdate()
5215
    ->fields('w', array('next_serial'))
5216
    ->condition('nid', $nid)
5217
    ->execute()
5218
    ->fetchField();
5219
5220
  // $next_serial must be greater than any existing serial number.
5221
  $next_serial = max($next_serial, _webform_submission_serial_next_value_used($nid));
5222
5223
  // Increment the next_value.
5224
  db_update('webform')
5225
    ->fields(array('next_serial' => $next_serial + 1))
5226
    ->condition('nid', $nid)
5227
    ->execute();
5228
5229
  return $next_serial;
5230
}
5231
5232
/**
5233
 * Returns the next serial number to be used, based upon actual submissions in
5234
 * the database.
5235
 *
5236
 * @param int $nid
5237
 *   The Node ID of the Webform.
5238
 *
5239
 * $return int
5240
 *   The largest serial number used by a submission for a given node, 1 when no
5241
 *   submissions.
5242
 */
5243
function _webform_submission_serial_next_value_used($nid) {
5244
  $max_serial = db_select('webform_submissions');
5245
  $max_serial->addExpression('MAX(serial)');
5246
  $max_serial = $max_serial
5247
    ->condition('nid', $nid)
5248
    ->execute()
5249
    ->fetchField();
5250
  // $max_serial will be a numeric string or NULL.
5251
  return $max_serial + 1;
5252
}
5253
5254
/**
5255
 * Alter the node before saving a clone.
5256
 *
5257
 * @param $node
5258
 *   Reference to the fully loaded node object being saved (the clone) that
5259
 *   can be altered as needed.
5260
 * @param array $context
5261
 *   An array of context describing the clone operation. The keys are:
5262
 *   - 'method' : Can be either 'prepopulate' or 'save-edit'.
5263
 *   - 'original_node' : The original fully loaded node object being cloned.
5264
 *
5265
 * @see clone_node_save()
5266
 * @see drupal_alter()
5267
 */
5268
function webform_clone_node_alter(&$node, $context) {
5269
  if (isset($node->webform)) {
5270
    $defaults = webform_node_defaults();
5271
    $node->webform['next_serial'] = $defaults['next_serial'];
5272
  }
5273
}
5274
5275
/**
5276
 * Check if the last form submission exceeded the servers max_input_vars
5277
 * limit and optionally preflight the current form to be returned in this
5278
 * request.
5279
 *
5280
 * @param array $form
5281
 *   Reference to the form, which will be changed if $parent_key is set.
5282
 * @param array $form_state
5283
 *   Form's state or NULL for no form state check.
5284
 * @param string $detect_key
5285
 *   A key that will always be present in the posted data when an actual form
5286
 *   submission has been made.
5287
 * @param string parent_key
5288
 *   Omit to not preflight the form, or the array key for the parent of where
5289
 *   the preflight warning should be inserted into the form.
5290
 */
5291
function webform_input_vars_check(&$form, $form_state, $detect_key, $parent_key = NULL) {
5292
  if (isset($parent_key)) {
5293
    $form['#pre_render'] = array('webform_pre_render_input_vars');
5294
    $form['#input_var_waring_parent'] = $parent_key;
5295
  }
5296
  if (!empty($form_state['input']) && key_exists($detect_key, $form_state['input']) && !key_exists('form_id', $form_state['input'])) {
5297
    // A form was submitted with POST, but the form_id was missing. The most likely cause of this
5298
    // is that the POST was truncated because PHP exceeded its max_input_vars limit.
5299
    $subs = array(
5300
      '@count' => webform_count_terminals($_POST),
5301
      '@limit' => (int)ini_get('max_input_vars'),
5302
    );
5303
    drupal_set_message(user_access('administer site configuration')
5304
                          ? 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)
5305
                          : t('This form could not be submitted because it exceeds the server configuration. Contact the administrator.'),
5306
                      'error');
5307
    watchdog('webform',
5308
             'POST truncated to @count input vars. PHP max_input_vars is @limit. Increase max_input_vars.',
5309
             $subs,
5310
             WATCHDOG_ERROR);
5311
  }
5312
}
5313
5314
/**
5315
 * Checks the number of input form elements on this page to ensure that the
5316
 * PHP max_input_vars limit is not exceeded.
5317
 *
5318
 * Install this function as a #pre_render function.
5319
 */
5320
function webform_pre_render_input_vars($element) {
5321
  // Determine the limit on input vars for this server configuration.
5322
  $limit = ini_get('max_input_vars');
5323
  if ($limit) {
5324
    // Estimate the number of input vars needed to see if the PHP limit has been exceeded.
5325
    $count = 1 + webform_count_input_vars($element); // Additional input_vars: op
5326
    if ($count > $limit * 0.95) {
5327
      $subs = array(
5328
        '@count' => $count,
5329
        '@limit' => $limit,
5330
      );
5331
      $warning = array(
5332
        '#markup' => '<div class="messages warning">' .
5333
                     (user_access('administer site configuration')
5334
                        ? t('This form contains @count input elements. PHP max_input_vars is @limit and should be increased.', $subs)
5335
                        : t('This form may be too long to work properly. Contact the administrator.'))
5336
                     . '</div>',
5337
        '#weight' => -1,
5338
      );
5339
      if ($element['#input_var_waring_parent']) {
5340
        $element[$element['#input_var_waring_parent']]['input_vars_warning'] = $warning;
5341
      }
5342
      else {
5343
        $element['input_vars_warning'] = $warning;
5344
      }
5345
      watchdog('webform',
5346
               'Page contains @count input elements but PHP max_input_vars is only @limit. Increase max_input_vars.',
5347
               $subs,
5348
               WATCHDOG_ERROR);
5349
    }
5350
  }
5351
  return $element;
5352
}
5353
5354
/**
5355
 * Counts the number of input form elements.
5356
 *
5357
 * Note that this is somewhat imprecise. The number of input vars returned in
5358
 * $_POST can vary with the form element. For example, a multiple-select
5359
 * listbox returns one input var for each selection actually made.
5360
 *
5361
 * The primary use for this count is for the conditionals page, where only
5362
 * select, textfield, hidden, and token elements are used. If a more accurate
5363
 * count for webform_client_form is needed, a mechanism to predict the number
5364
 * of input elements for each component type and each component instance would
5365
 * be needed.
5366
 *
5367
 * @param array $element
5368
 *   The form whose elements should be counted.
5369
 * @return integer
5370
 *   The number of elements in the form that will result in $_POST entries.
5371
 */
5372
function webform_count_input_vars($element) {
5373
  static $input_types = array(
5374
    'checkbox' => 1,
5375
    'date' => 1,
5376
    'file' => 1,
5377
    'managed_file' => 1,
5378
    'password' => 1,
5379
    'password_confirm' => 1,
5380
    'radios' => 1,
5381
    'select' => 1,
5382
    'textfield' => 1,
5383
    'textarea' => 1,
5384
    'token' => 1,
5385
    'weight' => 1,
5386
    'hidden' => 1,
5387
    'value' => 1,
5388
    'webform_email' => 1,
5389
    'webform_number' => 1,
5390
  );
5391
  $children = array_intersect_key($element, array_flip(element_children($element)));
5392
  return $children
5393
          ? array_reduce($children, function($carry, $item) {return $carry + webform_count_input_vars($item);}, 0)
5394
          : (isset($element['#type']) && isset($input_types[$element['#type']]) ? $input_types[$element['#type']] : 0);
5395
}
5396
5397
/**
5398
 * Counts terminals in an array. Useful for counting how many input_vars were
5399
 * returned in $_POST.
5400
 *
5401
 * @params $a
5402
 *   Array or array element to be counted
5403
 * @return integer
5404
 *   Number of non-array elements within $a.
5405
 */
5406
function webform_count_terminals($a) {
5407
  return is_array($a)
5408
            ? array_reduce($a, function($carry, $item) {return $carry + webform_count_terminals($item);}, 0)
5409
            : 1;
5410
}