5 * Webform CiviCRM Integration Module:
6 * Links webform submissions to contacts in a CiviCRM database.
7 * @author Coleman Watts
11 * The versions of CiviCRM and WebForm. Min is >=. Max is <. FALSE = no MAX
13 define('WEBFORM_CIVICRM_CIVICRM_VERSION_MIN', '4.4');
14 define('WEBFORM_CIVICRM_CIVICRM_VERSION_MAX', FALSE);
16 define('WEBFORM_CIVICRM_WEBFORM_VERSION', '4.12');
19 * Implements hook_menu().
23 function webform_civicrm_menu() {
25 $items['node/%webform_menu/civicrm'] = array(
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',
33 'type' => MENU_LOCAL_TASK,
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,
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,
53 * Access callback to determine if user can see the CiviCRM tab of a webform.
58 function wf_crm_admin_access($node) {
59 return (node_access('update', $node) && user_access('access CiviCRM'));
63 * Implements hook_form_alter().
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();
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();
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();
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']);
99 * Implements hook_node_load().
101 * @param array $nodes
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']));
125 * Implements hook_node_insert().
126 * Preserve webform_civicrm data when cloning or importing a node
128 * @param object $node
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);
138 * Implements hook_node_delete().
140 * @param object $node
142 function webform_civicrm_node_delete($node) {
143 if (!empty($node->webform)) {
144 db_delete('webform_civicrm_forms')
145 ->condition('nid', $node->nid)
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})')
156 * Implements hook_theme().
160 function webform_civicrm_theme() {
162 'webform_civicrm_options_table' => array(
163 'render element' => 'element',
164 'file' => 'includes/wf_crm_admin_form.inc',
166 'display_civicrm_contact' => array(
167 'render element' => 'element',
168 'file' => 'includes/contact_component.inc',
170 'static_contact_element' => array(
171 'render element' => 'element',
172 'file' => 'includes/contact_component.inc',
179 * Implements hook_webform_component_info().
183 function webform_civicrm_webform_component_info() {
185 'civicrm_contact' => array(
186 'label' => t('CiviCRM Contact'),
187 'description' => t('Choose existing contact.'),
189 'email_name' => TRUE,
191 'file' => 'includes/contact_component.inc',
197 * Implements hook_webform_submission_presave().
198 * Uses cached instance of wf_crm_webform_postprocess that was created during validation.
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);
209 * Implements hook_webform_submission_insert().
210 * Uses cached instance of wf_crm_webform_postprocess that was created during validation.
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);
220 * Implements hook_webform_submission_update().
221 * Uses cached instance of wf_crm_webform_postprocess that was created during validation.
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);
231 * Implements hook_webform_submission_delete().
233 function webform_civicrm_webform_submission_delete($node, $submission) {
234 db_delete('webform_civicrm_submissions')
235 ->condition('sid', $submission->sid)
240 * Implements hook_webform_submission_load().
241 * Add CiviCRM contact info to submission objects.
243 function webform_civicrm_webform_submission_load(&$submissions) {
244 if (empty($submissions)) {
247 $db = db_query('SELECT * FROM {webform_civicrm_submissions} WHERE sid IN (' . implode(',', array_keys($submissions)) . ')');
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] = '';
260 $submissions[$row->sid]->civicrm = $data;
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)) . ')';
267 $dao = CRM_Core_DAO::executeQuery($sql);
268 while ($dao->fetch()) {
269 $contacts[$dao->id] = $dao->display_name;
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']];
280 * Implements hook_webform_submission_render_alter().
281 * Add display name to title while viewing a submission.
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'])));
290 * Implements hook_webform_submission_actions().
291 * Add links to view contact & activity.
293 function webform_civicrm_webform_submission_actions($node, $submission) {
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']),
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']),
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']),
326 * Implements hook_civicrm_merge().
327 * Update submission data to reflect new cids when contacts are merged.
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')
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)
345 * Implements hook_admin_paths().
347 function webform_civicrm_admin_paths() {
348 return array('node/*/civicrm' => TRUE);
352 * Implements hook_help().
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'));
362 * Implements hook_webform_component_presave().
363 * Alter form keys when cloning a contact.
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']);
378 * Implements hook_preprocess_HOOK().
379 * Add CiviCRM names to webform submission results table.
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;
391 $vars['table']['#header'][] = wf_crm_contact_label(1, $vars['node']->webform_civicrm['data']);
394 foreach ($vars['table']['#rows'] as &$row) {
396 // Get submission id from url
397 preg_match('#/submission/(\d+)#', $row[4], $preg);
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')),
412 // Move name to position 2
413 foreach ($temp as $k => $v) {
424 * Implements hook_preprocess_HOOK().
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']);
432 * Implements hook_civicrm_alterPaymentProcessorParams().
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.
437 * Remove when dropping support for CiviCRM 4.6 and below.
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'])
443 $cookedParams['return'] = $rawParams['webform_redirect_success'];
444 $cookedParams['cancel_return'] = $rawParams['webform_redirect_cancel'];
449 * Return a value from nested arrays or objects.
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?
461 * found value or default
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;
468 if (!is_array($haystack) || !isset($haystack[$key]) || (empty($haystack[$key]) && $default !== NULL && !$strict)) {
471 $haystack = $haystack[$key];
473 // $haystack has been reduced down to the item we want
478 * Store info while a clone operation is running.
480 * @param array $input
484 function wf_crm_contact_clone_storage($input = NULL) {
485 static $storage = NULL;
493 * Clone a contact via webform.
494 * This submit handler is called when cloning a contact's fieldset
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;
504 $settings['data']['contact'][$new] = $settings['data']['contact'][$old];
506 $settings['data']['contact'][$new]['contact'][1]['webform_label'] = $form_state['input']['name'];
508 'old' => array("civicrm_{$old}_contact_"),
509 'new' => array("civicrm_{$new}_contact_"),
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_";
517 drupal_write_record('webform_civicrm_forms', $settings, 'nid');
518 // Store data to rewrite form keys
519 wf_crm_contact_clone_storage($storage);
523 * Validation callback for webform submissions.
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);
532 * Checks dependencies.
535 * Array with TRUE/FALSE for each dependency.
537 * @see webform_civicrm_requirements
539 function _webform_civicrm_status() {
541 $status['webform_civicrm'] = FALSE;
543 $civicrm = system_get_info('module', 'civicrm');
544 $webform = system_get_info('module', 'webform');
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;
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;