Projet

Général

Profil

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

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

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