Projet

Général

Profil

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

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

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