commiting uncommited changes on live site
[weblabels.fsf.org.git] / crm.fsf.org / 20131203 / files / sites / all / modules-new / webform_civicrm / webform_civicrm.module
1 <?php
2
3 /**
4 * @file
5 * Webform CiviCRM Integration Module:
6 * Links webform submissions to contacts in a CiviCRM database.
7 * @author Coleman Watts
8 */
9
10 /**
11 * The versions of CiviCRM and WebForm. Min is >=. Max is <. FALSE = no MAX
12 */
13 define('WEBFORM_CIVICRM_CIVICRM_VERSION_MIN', '4.4');
14 define('WEBFORM_CIVICRM_CIVICRM_VERSION_MAX', FALSE);
15
16 define('WEBFORM_CIVICRM_WEBFORM_VERSION', '4.12');
17
18 /**
19 * Implements hook_menu().
20 *
21 * @return array
22 */
23 function webform_civicrm_menu() {
24 $items = array();
25 $items['node/%webform_menu/civicrm'] = array(
26 'title' => 'CiviCRM',
27 'page callback' => 'drupal_get_form',
28 'page arguments' => array('wf_crm_configure_form', 1),
29 'access callback' => 'wf_crm_admin_access',
30 'access arguments' => array(1),
31 'file' => 'includes/wf_crm_admin_form.inc',
32 'weight' => 3,
33 'type' => MENU_LOCAL_TASK,
34 );
35 $items['webform-civicrm/js/%'] = array(
36 'page callback' => 'wf_crm_ajax',
37 'file' => 'includes/wf_crm_webform_ajax.inc',
38 'access callback' => TRUE,
39 'page arguments' => array(2),
40 'type' => MENU_CALLBACK,
41 );
42 $items['webform-civicrm/help/%'] = array(
43 'page callback' => 'wf_crm_admin_help',
44 'file' => 'includes/wf_crm_admin_help.inc',
45 'access arguments' => array('access CiviCRM'),
46 'page arguments' => array(2),
47 'type' => MENU_CALLBACK,
48 );
49 return $items;
50 }
51
52 /**
53 * Access callback to determine if user can see the CiviCRM tab of a webform.
54 *
55 * @param object $node
56 * @return bool
57 */
58 function wf_crm_admin_access($node) {
59 return (node_access('update', $node) && user_access('access CiviCRM'));
60 }
61
62 /**
63 * Implements hook_form_alter().
64 */
65 function webform_civicrm_form_alter(&$form, &$form_state, $form_id) {
66 // Alter back-end webform component edit forms
67 if ($form_id == 'webform_component_edit_form') {
68 if (substr($form['form_key']['#default_value'], 0, 7) == 'civicrm') {
69 form_load_include($form_state, 'inc', 'webform_civicrm', 'includes/wf_crm_admin_component');
70 $admin_form = new wf_crm_admin_component($form, $form_state);
71 $admin_form->alterForm();
72 }
73 if ($form['type']['#value'] == 'pagebreak') {
74 form_load_include($form_state, 'inc', 'webform_civicrm', 'includes/wf_crm_admin_component');
75 $admin_form = new wf_crm_admin_component($form, $form_state);
76 $admin_form->adjustPageBreak();
77 }
78 }
79
80 // Alter front-end of webforms
81 elseif (strpos($form_id, 'webform_client_form_') !== FALSE
82 && !empty($form['#node']->webform_civicrm)) {
83 form_load_include($form_state, 'inc', 'webform_civicrm', 'includes/wf_crm_webform_preprocess');
84 $processor = new wf_crm_webform_preprocess($form, $form_state);
85 $processor->alterForm();
86 }
87
88 // Validation for webform components tab
89 elseif ($form_id == 'webform_components_form') {
90 form_load_include($form_state, 'inc', 'webform_civicrm', 'includes/wf_crm_admin_component');
91 $form['#validate'][] = 'wf_crm_components_form_validate';
92 if (empty($form_state['input'])) {
93 wf_crm_admin_component::checkBillingPagination($form['#node']);
94 }
95 }
96 }
97
98 /**
99 * Implements hook_node_load().
100 *
101 * @param array $nodes
102 */
103 function webform_civicrm_node_load($nodes, $types) {
104 $db = db_query('SELECT * FROM {webform_civicrm_forms} WHERE nid IN(:nids)', array(':nids' => array_keys($nodes)));
105 foreach ($db as $settings) {
106 $node = &$nodes[$settings->nid];
107 $settings->data = unserialize($settings->data);
108 $node->webform_civicrm = (array) $settings;
109 // Allow a component widget to be changed
110 if (!empty($_GET['type']) && arg(0) == 'node' && arg(1) == $node->nid && arg(3) == 'components') {
111 if (!empty($node->webform['components'][arg(4)]) && array_key_exists($_GET['type'], webform_components())) {
112 $node->webform['components'][arg(4)]['type'] = $_GET['type'];
113 webform_component_defaults($node->webform['components'][arg(4)]);
114 if ($_GET['type'] == 'select') {
115 module_load_include('inc', 'webform_civicrm', 'includes/utils');
116 civicrm_initialize();
117 $node->webform['components'][arg(4)]['extra']['items'] = wf_crm_array2str(wf_crm_field_options($node->webform['components'][arg(4)], 'component_insert', $node->webform_civicrm['data']));
118 }
119 }
120 }
121 }
122 }
123
124 /**
125 * Implements hook_node_insert().
126 * Preserve webform_civicrm data when cloning or importing a node
127 *
128 * @param object $node
129 */
130 function webform_civicrm_node_insert($node) {
131 if (isset($node->webform_civicrm)) {
132 $node->webform_civicrm['nid'] = $node->nid;
133 drupal_write_record('webform_civicrm_forms', $node->webform_civicrm);
134 }
135 }
136
137 /**
138 * Implements hook_node_delete().
139 *
140 * @param object $node
141 */
142 function webform_civicrm_node_delete($node) {
143 if (!empty($node->webform)) {
144 db_delete('webform_civicrm_forms')
145 ->condition('nid', $node->nid)
146 ->execute();
147 // Submissions have already been deleted from webform_submissions table
148 // So we'll do the opposite of a join to find them
149 db_delete('webform_civicrm_submissions')
150 ->where('sid NOT IN (SELECT sid FROM {webform_submissions})')
151 ->execute();
152 }
153 }
154
155 /**
156 * Implements hook_theme().
157 *
158 * @return array
159 */
160 function webform_civicrm_theme() {
161 $theme = array(
162 'webform_civicrm_options_table' => array(
163 'render element' => 'element',
164 'file' => 'includes/wf_crm_admin_form.inc',
165 ),
166 'display_civicrm_contact' => array(
167 'render element' => 'element',
168 'file' => 'includes/contact_component.inc',
169 ),
170 'static_contact_element' => array(
171 'render element' => 'element',
172 'file' => 'includes/contact_component.inc',
173 ),
174 );
175 return $theme;
176 }
177
178 /**
179 * Implements hook_webform_component_info().
180 *
181 * @return array
182 */
183 function webform_civicrm_webform_component_info() {
184 return array(
185 'civicrm_contact' => array(
186 'label' => t('CiviCRM Contact'),
187 'description' => t('Choose existing contact.'),
188 'features' => array(
189 'email_name' => TRUE,
190 ),
191 'file' => 'includes/contact_component.inc',
192 ),
193 );
194 }
195
196 /**
197 * Implements hook_webform_submission_presave().
198 * Uses cached instance of wf_crm_webform_postprocess that was created during validation.
199 */
200 function webform_civicrm_webform_submission_presave($node, &$submission) {
201 if (!empty($node->webform_civicrm)) {
202 module_load_include('inc', 'webform_civicrm', 'includes/wf_crm_webform_postprocess');
203 $processor = wf_crm_webform_postprocess::singleton($node);
204 $processor->preSave($submission);
205 }
206 }
207
208 /**
209 * Implements hook_webform_submission_insert().
210 * Uses cached instance of wf_crm_webform_postprocess that was created during validation.
211 */
212 function webform_civicrm_webform_submission_insert($node, $submission) {
213 if (!empty($node->webform_civicrm)) {
214 $processor = wf_crm_webform_postprocess::singleton($node);
215 $processor->postSave($submission);
216 }
217 }
218
219 /**
220 * Implements hook_webform_submission_update().
221 * Uses cached instance of wf_crm_webform_postprocess that was created during validation.
222 */
223 function webform_civicrm_webform_submission_update($node, $submission) {
224 if (!empty($node->webform_civicrm)) {
225 $processor = wf_crm_webform_postprocess::singleton($node);
226 $processor->postSave($submission);
227 }
228 }
229
230 /**
231 * Implements hook_webform_submission_delete().
232 */
233 function webform_civicrm_webform_submission_delete($node, $submission) {
234 db_delete('webform_civicrm_submissions')
235 ->condition('sid', $submission->sid)
236 ->execute();
237 }
238
239 /**
240 * Implements hook_webform_submission_load().
241 * Add CiviCRM contact info to submission objects.
242 */
243 function webform_civicrm_webform_submission_load(&$submissions) {
244 if (empty($submissions)) {
245 return;
246 }
247 $db = db_query('SELECT * FROM {webform_civicrm_submissions} WHERE sid IN (' . implode(',', array_keys($submissions)) . ')');
248 $contacts = array();
249 foreach ($db as $row) {
250 $data = unserialize($row->civicrm_data) + array('contact' => array());
251 if ($row->contact_id) {
252 foreach (explode('-', trim($row->contact_id, '-')) as $c => $cid) {
253 $data['contact'][$c + 1]['id'] = $cid;
254 $data['contact'][$c + 1]['display_name'] = '';
255 if ($c == 0 && $cid) {
256 $contacts[$cid] = '';
257 }
258 }
259 }
260 $submissions[$row->sid]->civicrm = $data;
261 }
262 if ($contacts) {
263 // Retrieve contact names and add to submission objects
264 civicrm_initialize();
265 $sql = 'SELECT id, display_name FROM civicrm_contact WHERE id IN (' . implode(',', array_keys($contacts)) . ')';
266
267 $dao = CRM_Core_DAO::executeQuery($sql);
268 while ($dao->fetch()) {
269 $contacts[$dao->id] = $dao->display_name;
270 }
271 foreach ($submissions as &$s) {
272 if (!empty($s->civicrm['contact'][1]['id'])) {
273 $s->civicrm['contact'][1]['display_name'] = $contacts[$s->civicrm['contact'][1]['id']];
274 }
275 }
276 }
277 }
278
279 /**
280 * Implements hook_webform_submission_render_alter().
281 * Add display name to title while viewing a submission.
282 */
283 function webform_civicrm_webform_submission_render_alter(&$sub) {
284 if (!empty($sub['#submission']->civicrm['contact'][1]['display_name']) && empty($sub['#email']) && $sub['#format'] == 'html') {
285 drupal_set_title(t('Submission #!num by @name', array('!num' => $sub['#submission']->sid, '@name' => $sub['#submission']->civicrm['contact'][1]['display_name'])));
286 }
287 }
288
289 /**
290 * Implements hook_webform_submission_actions().
291 * Add links to view contact & activity.
292 */
293 function webform_civicrm_webform_submission_actions($node, $submission) {
294 $actions = array();
295 if (!empty($node->webform_civicrm)
296 && !empty($submission->civicrm)
297 && webform_results_access($node)
298 && user_access('access CiviCRM')) {
299 $data = $submission->civicrm;
300 if (!empty($data['contact'][1]['display_name'])) {
301 $actions['civicrm_action contact_view'] = array(
302 'title' => t('View @name', array('@name' => $data['contact'][1]['display_name'])),
303 'href' => 'civicrm/contact/view',
304 'query' => array('reset' => 1, 'cid' => $data['contact'][1]['id']),
305 );
306 if (!empty($data['activity'][1]['id'])) {
307 $actions['civicrm_action activity_view'] = array(
308 'title' => t('View Activity'),
309 'href' => 'civicrm/activity',
310 'query' => array('action' => 'view', 'reset' => 1, 'cid' => $data['contact'][1]['id'], 'id' => $data['activity'][1]['id']),
311 );
312 }
313 if (!empty($data['contribution'][1]['id'])) {
314 $actions['civicrm_action contribution_view'] = array(
315 'title' => t('View Contribution'),
316 'href' => 'civicrm/contact/view/contribution',
317 'query' => array('action' => 'view', 'reset' => 1, 'cid' => $data['contact'][1]['id'], 'id' => $data['contribution'][1]['id']),
318 );
319 }
320 }
321 }
322 return $actions;
323 }
324
325 /**
326 * Implements hook_civicrm_merge().
327 * Update submission data to reflect new cids when contacts are merged.
328 */
329 function webform_civicrm_civicrm_merge($type, $data, $new_id = NULL, $old_id = NULL, $tables = NULL) {
330 if (!empty($new_id) && !empty($old_id) && $type == 'sqls') {
331 // Update civicrm submissions table
332 db_update('webform_civicrm_submissions')
333 ->expression('contact_id', 'REPLACE(contact_id, :old, :new)', array(':old' => '-' . $old_id . '-', ':new' => '-' . $new_id . '-'))
334 ->condition('contact_id', '%-' . $old_id . '-%', 'LIKE')
335 ->execute();
336 // Update contact reference field data
337 db_query("UPDATE {webform_submitted_data} d, {webform_component} c SET d.data = :new
338 WHERE d.data = :old AND d.cid = c.cid AND d.nid = c.nid AND c.type = 'civicrm_contact'",
339 array(':new' => $new_id, ':old' => $old_id)
340 );
341 }
342 }
343
344 /**
345 * Implements hook_admin_paths().
346 */
347 function webform_civicrm_admin_paths() {
348 return array('node/*/civicrm' => TRUE);
349 }
350
351 /**
352 * Implements hook_help().
353 */
354 function webform_civicrm_help($section) {
355 if ($section == 'admin/help#webform_civicrm') {
356 // Return a line-break version of the module README.txt
357 return nl2br(file_get_contents(drupal_get_path('module', 'webform_civicrm') . '/README.txt'));
358 }
359 }
360
361 /**
362 * Implements hook_webform_component_presave().
363 * Alter form keys when cloning a contact.
364 */
365 function webform_civicrm_webform_component_presave(&$component) {
366 if ($c = wf_crm_contact_clone_storage()) {
367 $component['form_key'] = str_replace($c['old'], $c['new'], $component['form_key']);
368 if ($component['type'] == 'civicrm_contact') {
369 // Only contact 1 can be the current user
370 if (wf_crm_aval($component, 'extra:default') == 'user') {
371 unset($component['extra']['default']);
372 }
373 }
374 }
375 }
376
377 /**
378 * Implements hook_preprocess_HOOK().
379 * Add CiviCRM names to webform submission results table.
380 */
381 function webform_civicrm_preprocess_webform_results_submissions(&$vars) {
382 if (count($vars['table']['#rows']) && !empty($vars['node']->webform_civicrm) && webform_results_access($vars['node'])) {
383 module_load_include('inc', 'webform_civicrm', 'includes/utils');
384 $access = user_access('access CiviCRM');
385 $temp = $vars['table']['#header'];
386 $vars['table']['#header'] = array();
387 // Move contact col to position 2
388 foreach ($temp as $k => $v) {
389 $vars['table']['#header'][] = $v;
390 if ($k == 1) {
391 $vars['table']['#header'][] = wf_crm_contact_label(1, $vars['node']->webform_civicrm['data']);
392 }
393 }
394 foreach ($vars['table']['#rows'] as &$row) {
395 $name = '';
396 // Get submission id from url
397 preg_match('#/submission/(\d+)#', $row[4], $preg);
398 $sid = $preg[1];
399 if (!empty($vars['submissions'][$sid]->civicrm['contact'][1])) {
400 $data = $vars['submissions'][$sid]->civicrm;
401 $name = $data['contact'][1]['display_name'];
402 if ($name !== '' && $access) {
403 $name = l($name, 'civicrm/contact/view', array(
404 'query' => array('reset' => 1, 'cid' => $data['contact'][1]['id']),
405 'attributes' => array('title' => t('View CiviCRM contact')),
406 'alias' => TRUE,
407 ));
408 }
409 }
410 $temp = $row;
411 $row = array();
412 // Move name to position 2
413 foreach ($temp as $k => $v) {
414 $row[] = $v;
415 if ($k == 1) {
416 $row[] = $name;
417 }
418 }
419 }
420 }
421 }
422
423 /**
424 * Implements hook_preprocess_HOOK().
425 */
426 function webform_civicrm_preprocess_webform_components_form(&$vars) {
427 module_load_include('inc', 'webform_civicrm', 'includes/wf_crm_admin_component');
428 wf_crm_admin_component::preprocessComponentsForm($vars['form'], $vars['rows'], $vars['form']['#node']);
429 }
430
431 /**
432 * Implements hook_civicrm_alterPaymentProcessorParams().
433 *
434 * Legacy handling for paypal.
435 * We use it to override the return url so that the user gets redirected to the right place from paypal.
436 *
437 * Remove when dropping support for CiviCRM 4.6 and below.
438 */
439 function webform_civicrm_civicrm_alterPaymentProcessorParams($paymentObj, $rawParams, &$cookedParams) {
440 if (!empty($rawParams['webform_redirect_cancel']) && !empty($rawParams['webform_redirect_success'])
441 && !empty($cookedParams['return']) && !empty($cookedParams['cancel_return'])
442 ) {
443 $cookedParams['return'] = $rawParams['webform_redirect_success'];
444 $cookedParams['cancel_return'] = $rawParams['webform_redirect_cancel'];
445 }
446 }
447
448 /**
449 * Return a value from nested arrays or objects.
450 *
451 * @param array|object $haystack
452 * The array to search
453 * @param string $keys
454 * Pass a single key, or multiple keys separated by : to get a nested value
455 * @param mixed $default
456 * Value to return if given array key does not exist
457 * @param bool $strict
458 * Should we use empty or isset to determine if array key exists?
459 *
460 * @return mixed
461 * found value or default
462 */
463 function wf_crm_aval($haystack, $keys, $default = NULL, $strict = FALSE) {
464 foreach (explode(':', $keys) as $key) {
465 if (is_object($haystack)) {
466 $haystack = (array) $haystack;
467 }
468 if (!is_array($haystack) || !isset($haystack[$key]) || (empty($haystack[$key]) && $default !== NULL && !$strict)) {
469 return $default;
470 }
471 $haystack = $haystack[$key];
472 }
473 // $haystack has been reduced down to the item we want
474 return $haystack;
475 }
476
477 /**
478 * Store info while a clone operation is running.
479 *
480 * @param array $input
481 * Data to store
482 * @return array
483 */
484 function wf_crm_contact_clone_storage($input = NULL) {
485 static $storage = NULL;
486 if ($input) {
487 $storage = $input;
488 }
489 return $storage;
490 }
491
492 /**
493 * Clone a contact via webform.
494 * This submit handler is called when cloning a contact's fieldset
495 */
496 function wf_crm_contact_clone($form, $form_state) {
497 form_load_include($form_state, 'inc', 'webform_civicrm', 'includes/utils');
498 $fid = $form['form_key']['#default_value'];
499 list(, $old) = wf_crm_explode_key($fid);
500 $node = node_load($form['nid']['#value']);
501 $settings = $node->webform_civicrm;
502 $new = count($settings['data']['contact']) + 1;
503 // Clone contact
504 $settings['data']['contact'][$new] = $settings['data']['contact'][$old];
505 // Set label
506 $settings['data']['contact'][$new]['contact'][1]['webform_label'] = $form_state['input']['name'];
507 $storage = array(
508 'old' => array("civicrm_{$old}_contact_"),
509 'new' => array("civicrm_{$new}_contact_"),
510 );
511 // Clone participant if registering separately
512 if (wf_crm_aval($settings['data'], 'participant_reg_type') == 'separate') {
513 $settings['data']['participant'][$new] = $settings['data']['participant'][$old];
514 $storage['old'][] = "civicrm_{$old}_participant_";
515 $storage['new'][] = "civicrm_{$new}_participant_";
516 }
517 drupal_write_record('webform_civicrm_forms', $settings, 'nid');
518 // Store data to rewrite form keys
519 wf_crm_contact_clone_storage($storage);
520 }
521
522 /**
523 * Validation callback for webform submissions.
524 */
525 function wf_crm_validate($form, &$form_state) {
526 form_load_include($form_state, 'inc', 'webform_civicrm', 'includes/wf_crm_webform_postprocess');
527 $processor = wf_crm_webform_postprocess::singleton($form['#node']);
528 $processor->validate($form, $form_state);
529 }
530
531 /**
532 * Checks dependencies.
533 *
534 * @return array
535 * Array with TRUE/FALSE for each dependency.
536 *
537 * @see webform_civicrm_requirements
538 */
539 function _webform_civicrm_status() {
540 $status = array();
541 $status['webform_civicrm'] = FALSE;
542
543 $civicrm = system_get_info('module', 'civicrm');
544 $webform = system_get_info('module', 'webform');
545
546 if (version_compare($civicrm['version'], WEBFORM_CIVICRM_CIVICRM_VERSION_MIN, '>=') &&
547 version_compare($webform['version'], WEBFORM_CIVICRM_WEBFORM_VERSION, '>=')) {
548 $status['webform_civicrm'] = TRUE;
549 }
550
551 // If there is a max version of CiviCRM supported, check it too.
552 if (WEBFORM_CIVICRM_CIVICRM_VERSION_MAX && version_compare($civicrm['version'], WEBFORM_CIVICRM_CIVICRM_VERSION_MAX, '>=')) {
553 $status['webform_civicrm'] = FALSE;
554 }
555
556 return $status;
557 }