Projet

Général

Profil

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

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

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