4 * Implemetation of hook_menu, retrieve conatacts from civicrm for
7 function civicrm_contact_ref_menu() {
9 $items['civicrm_contact_ref/autocomplete'] = array(
10 'title' => t('Contacts'),
11 'page callback' => 'civicrm_contact_ref_autocomplete_value',
12 'type' => MENU_CALLBACK,
13 'access arguments' => array('access content'),
20 * Implemetation of hook_field_info
22 function civicrm_contact_ref_field_info() {
24 'civicrm_contact_ref_contact' =>
26 'label' => t('CiviCRM Contact'),
27 'description' => t('Reference a CiviCRM contact.'),
28 'default_widget' => 'options_select',
29 'default_formatter' => 'civicrm_contact_ref_link',
30 'property_type' => 'integer', // note that this property is used by entity.module
36 * Implemetation of hook_field_widget_info
38 function civicrm_contact_ref_field_widget_info() {
40 'civicrm_contact_ref_autocomplete' =>
42 'label' => t('Autocomplete text field'),
43 'field types' => array('civicrm_contact_ref_contact'),
46 'autocomplete_path' => 'civicrm_contact_ref/autocomplete',
53 * Implemetation of hook_field_widget_info_alter
55 function civicrm_contact_ref_field_widget_info_alter(&$info) {
56 $info['options_select']['field types'][] = 'civicrm_contact_ref_contact';
57 $info['options_buttons']['field types'][] = 'civicrm_contact_ref_contact';
61 * Implements hook_options_list().
63 function civicrm_contact_ref_options_list($field) {
64 $function = !empty($field['settings']['options_list_callback']) ? $field['settings']['options_list_callback'] : 'civicrm_contact_ref_allowed_values';
65 return $function($field);
69 * Implements hook_field_is_empty().
71 function civicrm_contact_ref_field_is_empty($item, $field) {
72 return empty($item['contact_id']) ? TRUE : FALSE;
76 * Implements hook_field_formatter_info().
78 function civicrm_contact_ref_field_formatter_info() {
80 'civicrm_contact_ref_link' => array(
81 'label' => t('Title (link)'),
82 'field types' => array('civicrm_contact_ref_contact'),
84 'civicrm_contact_ref_plain' => array(
85 'label' => t('Title (plain)'),
86 'field types' => array('civicrm_contact_ref_contact'),
88 'civicrm_contact_ref_raw' => array(
89 'label' => t('CID (raw)'),
90 'field types' => array('civicrm_contact_ref_contact'),
96 * Implements hook_field_formatter_view()
98 function civicrm_contact_ref_field_formatter_view($entity_type, $entity, $field, $instance, $langcode, $items, $display) {
101 // Terms whose tid is 'autocreate' do not exist
102 // yet and $item['taxonomy_term'] is not set. Theme such terms as
105 switch ($display['type']) {
106 case 'civicrm_contact_ref_link':
107 foreach ($items as $delta => $item) {
108 if (isset($item['contact_id']) && is_numeric($item['contact_id'])) {
109 $title = _civicrm_contact_ref_titles($item['contact_id']);
110 $element[$delta] = array(
111 '#markup' => l($title,
112 'civicrm/contact/view',
117 'cid' => "{$item['contact_id']}",
126 case 'civicrm_contact_ref_plain':
127 foreach ($items as $delta => $item) {
128 $element[$delta] = array(
129 '#markup' => _civicrm_contact_ref_titles($item['contact_id']),
134 case 'civicrm_contact_ref_raw':
135 foreach ($items as $delta => $item) {
136 $element[$delta] = array(
137 '#markup' => $item['contact_id'],
147 * Returns the set of valid civicrm contacts
150 * The field definition.
153 * The array of valid contacts.
155 function civicrm_contact_ref_allowed_values($field) {
156 $references = _civicrm_contact_ref_potential_references($field);
159 foreach ($references as $key => $value) {
160 // Views theming runs check_plain (htmlentities) on the values.
161 // We reverse that with html_entity_decode.
162 $options[$key] = html_entity_decode(strip_tags($value['rendered']));
168 * Implements hook_field_widget_form().
170 function civicrm_contact_ref_field_widget_form(&$form, &$form_state, $field, $instance, $langcode, $items, $delta, $element) {
171 $defaultValues = array();
172 foreach ($items as $item) {
173 $sort_name = _civicrm_contact_ref_titles($item['contact_id'], 'sort_name');
174 $defaultValues[] = "$sort_name [cid:{$item['contact_id']}]";
178 '#type' => 'textfield',
179 '#autocomplete_path' => $instance['widget']['settings']['autocomplete_path'] . '/' . $field['field_name'],
180 '#size' => $instance['widget']['settings']['size'],
181 '#maxlength' => 1024,
182 '#default_value' => empty($defaultValues[$delta]) ? '' : $defaultValues[$delta],
183 '#element_validate' => array('civicrm_contact_ref_autocomplete_validate'),
186 return array('contact_id' => $element);
190 * Implements hook_field_widget_error().
192 function civicrm_contact_ref_field_widget_error($element, $error, $form, &$form_state) {
193 form_error($element, $error['message']);
197 * Implements hook_field_settings_form().
199 function civicrm_contact_ref_field_settings_form($field, $instance, $has_data) {
201 if (!civicrm_initialize()) {
205 $settings = $field['settings'];
207 require_once 'CRM/Contact/BAO/ContactType.php';
208 $contactTypes = CRM_Contact_BAO_ContactType::basicTypePairs();
210 $form['allowed_values'] = array(
213 foreach ($contactTypes as $name => $label) {
214 $subTypes = CRM_Contact_BAO_ContactType::subTypePairs($name);
215 $form['allowed_values'][$name] = array(
216 '#type' => 'checkboxes',
217 '#title' => ts('CiviCRM Contact Type %1', array(1 => $label)),
218 '#options' => CRM_Utils_Array::crmArrayMerge(array($name => $label), $subTypes),
219 '#default_value' => isset($settings['allowed_values']) ? $settings['allowed_values'][$name] : array(),
228 * Form element validate handler for civicrm contact autocomplete element.
230 function civicrm_contact_ref_autocomplete_validate($element, &$form_state) {
232 $field = field_info_field($element['#field_name']);
233 $field_key = $element['#columns'][0];
234 $delta = $element['#delta'];
235 $value = $element['#value'];
238 if (!empty($value)) {
239 preg_match('/^(?:\s*|(.*) )?\[\s*cid\s*:\s*(\d+)\s*\]$/', $value, $matches);
240 if (!empty($matches)) {
246 $cids = _civicrm_contact_ref_potential_references($field, $value, TRUE);
248 form_error($element, t('%name: found no valid post with that title.', array('%name' => $element['#title'])));
252 // the best thing would be to present the user with an additional form,
253 // allowing the user to choose between valid candidates with the same title
254 // ATM, we pick the first matching candidate...
255 $cid = array_shift(array_keys($cids));
260 form_set_value($element, $cid, $form_state);
264 * Function to provide values for autocomplete civicrm contact
265 * element, depend upon entered string.
267 function civicrm_contact_ref_autocomplete_value($field_name, $string = '') {
268 if (!civicrm_initialize()) {
272 $field = field_info_field($field_name);;
275 $references = _civicrm_contact_ref_potential_references($field, $string);
276 foreach ($references as $id => $row) {
277 // Add a class wrapper for a few required CSS overrides.
278 $matches["{$row['title']} [cid:{$id}]"] = '<div class="reference-autocomplete">' . $row['rendered'] . '</div>';
280 drupal_json_output($matches);
284 * Fetch an array of all candidate referenced nodes.
286 * This info is used in various places (aloowed values, autocomplete results,
287 * input validation...). Some of them only need the nids, others nid + titles,
288 * others yet nid + titles + rendered row (for display in widgets).
289 * The array we return contains all the potentially needed information, and lets
290 * consumers use the parts they actually need.
293 * The field description.
295 * Optional string to filter titles on (used by autocomplete)
296 * @param $exact_string
297 * Optional: should the title filter be an exact match.
300 * An array of valid nodes in the form:
303 * 'title' => The node title,
304 * 'rendered' => The text to display in widgets (can be HTML)
309 function _civicrm_contact_ref_potential_references($field, $string = '', $exact_string = FALSE) {
310 static $results = array();
312 $references = _civicrm_contact_ref_potential_references_standard($field, $string, $exact_string);
314 // Store the results.
315 $results[$field['field_name']][$string][$exact_string] = $references;
317 return $results[$field['field_name']][$string][$exact_string];
321 * Helper function for _civicrm_contact_ref_potential_references():
322 * referenceable nodes defined by content types.
324 function _civicrm_contact_ref_potential_references_standard($field, $string = '', $exact_string = FALSE, $limit = '10') {
325 $args = $whereClause = $contactTypes = $contactSubTypes = array();
327 if (!civicrm_initialize()) {
331 require_once 'CRM/Contact/BAO/ContactType.php';
332 $basicTypes = CRM_Contact_BAO_ContactType::basicTypePairs();
334 foreach ($basicTypes as $name => $label) {
335 if (isset($field['settings']['allowed_values']) && is_array($field['settings']['allowed_values'][$name])) {
336 $contactNames = array_filter($field['settings']['allowed_values'][$name]);
337 if (!empty($contactNames)) {
338 if (in_array($name, $contactNames)) {
339 $contactTypes[] = $name;
342 $contactSubTypes = array_merge($contactSubTypes, array_keys($contactNames));
348 if (!empty($contactTypes)) {
349 $contactTypes = implode("','", $contactTypes);
350 $whereClause[] = "contact_type IN ( '{$contactTypes}' )";
353 if (!empty($contactSubTypes)) {
354 $contactSubTypes = implode("','", $contactSubTypes);
355 $whereClause[] = "contact_sub_type IN ( '{$contactSubTypes}' )";
358 $whereClause = empty($whereClause) ? '' : '(' . implode(' OR ', $whereClause) . ') AND';
359 $related_clause = "";
361 if (isset($string)) {
363 $string_clause = " AND sort_name = %1";
367 $string_clause = " AND sort_name LIKE %1";
368 $args[] = "%%" . $string . "%";
376 sort_name IS NOT NULL
377 AND sort_name NOT LIKE ''
378 AND sort_name NOT LIKE '<Last>%%'
379 AND sort_name NOT LIKE '%@%%'
380 AND sort_name NOT LIKE '--%%'
381 AND sort_name NOT LIKE '- -%%'
382 AND sort_name NOT LIKE ',%%'
383 AND sort_name NOT LIKE '..%%'
385 " . $string_clause . " LIMIT $limit";
386 $params = array(1 => array($args[0], "String"));
387 $dao = CRM_Core_DAO::executeQuery($q, $params);
389 $references = array();
390 while ($dao->fetch()) {
391 $references[$dao->id] = array(
392 'title' => $dao->sort_name,
393 'rendered' => $dao->sort_name,
401 * Helper function for formatters.
403 * Store node titles collected in the current request.
406 * @param string $field
407 * @return string|null
409 function _civicrm_contact_ref_titles($cid, $field = 'display_name') {
410 if (!civicrm_initialize() || !$cid) {
413 static $titles = array();
415 if (!isset($titles["{$cid}_{$field}"])) {
420 $params = array(1 => array($cid, "Integer"));
421 $dao = CRM_Core_DAO::executeQuery($q, $params);
423 $titles["{$cid}_{$field}"] = $dao->fetch() ? $dao->$field : '';
425 return $titles["{$cid}_{$field}"];
429 * Implements hook_field_views_data() to allow for views relationships
431 function civicrm_contact_ref_field_views_data($field) {
432 $data = field_views_field_default_views_data($field);
433 $storage = $field['storage']['details']['sql'];
435 foreach ($storage as $age => $table_data) {
436 $table = key($table_data);
437 $columns = current($table_data);
438 $id_column = $columns['contact_id'];
439 if (isset($data[$table])) {
441 $data[$table][$id_column]['relationship'] = array(
442 'handler' => 'views_handler_relationship',
443 'base' => 'civicrm_contact',
444 'base field' => 'id',
445 'field' => $id_column,
446 'label' => $field['field_name'],
447 'field_name' => $field['field_name'],
456 * Implements hook_civicrm_merge().
457 * Update field data to reflect new cids when contacts are merged.
459 function civicrm_contact_ref_civicrm_merge($type, $data, $new_id = NULL, $old_id = NULL, $tables = NULL) {
461 if (!empty($new_id) && !empty($old_id) && $type == 'sqls' && $tables === FALSE) {
462 // Update contact reference field data
463 $db = db_query("SELECT field_name FROM {field_config} WHERE module = 'civicrm_contact_ref'");
464 foreach ($db as $table) {
465 $result = db_update("field_data_{$table->field_name}")
466 ->fields(array("{$table->field_name}_contact_id" => $new_id))
467 ->condition("{$table->field_name}_contact_id", $old_id)
475 cache_clear_all('*', 'cache_field', TRUE);
476 cache_clear_all('*', 'cache_page', TRUE);