commiting uncommited changes on live site
[weblabels.fsf.org.git] / crm.fsf.org / 20131203 / files / sites / all / modules-new / civicrm / drupal / modules / civicrm_contact_ref / civicrm_contact_ref.module
1 <?php
2
3 /**
4 * Implemetation of hook_menu, retrieve conatacts from civicrm for
5 * autocomplete widget.
6 */
7 function civicrm_contact_ref_menu() {
8 $items = array();
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'),
14 );
15
16 return $items;
17 }
18
19 /**
20 * Implemetation of hook_field_info
21 */
22 function civicrm_contact_ref_field_info() {
23 return array(
24 'civicrm_contact_ref_contact' =>
25 array(
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
31 ),
32 );
33 }
34
35 /**
36 * Implemetation of hook_field_widget_info
37 */
38 function civicrm_contact_ref_field_widget_info() {
39 return array(
40 'civicrm_contact_ref_autocomplete' =>
41 array(
42 'label' => t('Autocomplete text field'),
43 'field types' => array('civicrm_contact_ref_contact'),
44 'settings' => array(
45 'size' => 60,
46 'autocomplete_path' => 'civicrm_contact_ref/autocomplete',
47 ),
48 ),
49 );
50 }
51
52 /**
53 * Implemetation of hook_field_widget_info_alter
54 */
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';
58 }
59
60 /**
61 * Implements hook_options_list().
62 */
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);
66 }
67
68 /**
69 * Implements hook_field_is_empty().
70 */
71 function civicrm_contact_ref_field_is_empty($item, $field) {
72 return empty($item['contact_id']) ? TRUE : FALSE;
73 }
74
75 /**
76 * Implements hook_field_formatter_info().
77 */
78 function civicrm_contact_ref_field_formatter_info() {
79 return array(
80 'civicrm_contact_ref_link' => array(
81 'label' => t('Title (link)'),
82 'field types' => array('civicrm_contact_ref_contact'),
83 ),
84 'civicrm_contact_ref_plain' => array(
85 'label' => t('Title (plain)'),
86 'field types' => array('civicrm_contact_ref_contact'),
87 ),
88 'civicrm_contact_ref_raw' => array(
89 'label' => t('CID (raw)'),
90 'field types' => array('civicrm_contact_ref_contact'),
91 ),
92 );
93 }
94
95 /**
96 * Implements hook_field_formatter_view()
97 */
98 function civicrm_contact_ref_field_formatter_view($entity_type, $entity, $field, $instance, $langcode, $items, $display) {
99 $element = array();
100
101 // Terms whose tid is 'autocreate' do not exist
102 // yet and $item['taxonomy_term'] is not set. Theme such terms as
103 // just their name.
104
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',
113 array(
114 'query' =>
115 array(
116 'reset' => 1,
117 'cid' => "{$item['contact_id']}",
118 ),
119 )
120 ),
121 );
122 }
123 }
124 break;
125
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']),
130 );
131 }
132 break;
133
134 case 'civicrm_contact_ref_raw':
135 foreach ($items as $delta => $item) {
136 $element[$delta] = array(
137 '#markup' => $item['contact_id'],
138 );
139 }
140 break;
141 }
142
143 return $element;
144 }
145
146 /**
147 * Returns the set of valid civicrm contacts
148 *
149 * @param $field
150 * The field definition.
151 *
152 * @return
153 * The array of valid contacts.
154 */
155 function civicrm_contact_ref_allowed_values($field) {
156 $references = _civicrm_contact_ref_potential_references($field);
157
158 $options = array();
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']));
163 }
164 return $options;
165 }
166
167 /**
168 * Implements hook_field_widget_form().
169 */
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']}]";
175 }
176
177 $element += array(
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'),
184 );
185
186 return array('contact_id' => $element);
187 }
188
189 /**
190 * Implements hook_field_widget_error().
191 */
192 function civicrm_contact_ref_field_widget_error($element, $error, $form, &$form_state) {
193 form_error($element, $error['message']);
194 }
195
196 /**
197 * Implements hook_field_settings_form().
198 */
199 function civicrm_contact_ref_field_settings_form($field, $instance, $has_data) {
200
201 if (!civicrm_initialize()) {
202 return;
203 }
204
205 $settings = $field['settings'];
206
207 require_once 'CRM/Contact/BAO/ContactType.php';
208 $contactTypes = CRM_Contact_BAO_ContactType::basicTypePairs();
209 $form = array();
210 $form['allowed_values'] = array(
211 '#tree' => TRUE,
212 );
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(),
220 '#weight' => 1,
221 );
222 }
223
224 return $form;
225 }
226
227 /**
228 * Form element validate handler for civicrm contact autocomplete element.
229 */
230 function civicrm_contact_ref_autocomplete_validate($element, &$form_state) {
231
232 $field = field_info_field($element['#field_name']);
233 $field_key = $element['#columns'][0];
234 $delta = $element['#delta'];
235 $value = $element['#value'];
236
237 $cid = NULL;
238 if (!empty($value)) {
239 preg_match('/^(?:\s*|(.*) )?\[\s*cid\s*:\s*(\d+)\s*\]$/', $value, $matches);
240 if (!empty($matches)) {
241 // Explicit [cid:n].
242 $cid = $matches[2];
243 }
244 else {
245 // No explicit cid.
246 $cids = _civicrm_contact_ref_potential_references($field, $value, TRUE);
247 if (empty($cids)) {
248 form_error($element, t('%name: found no valid post with that title.', array('%name' => $element['#title'])));
249 }
250 else {
251 // TODO:
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));
256 }
257 }
258 }
259
260 form_set_value($element, $cid, $form_state);
261 }
262
263 /*
264 * Function to provide values for autocomplete civicrm contact
265 * element, depend upon entered string.
266 */
267 function civicrm_contact_ref_autocomplete_value($field_name, $string = '') {
268 if (!civicrm_initialize()) {
269 return;
270 }
271
272 $field = field_info_field($field_name);;
273 $matches = array();
274
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>';
279 }
280 drupal_json_output($matches);
281 }
282
283 /**
284 * Fetch an array of all candidate referenced nodes.
285 *
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.
291 *
292 * @param $field
293 * The field description.
294 * @param $string
295 * Optional string to filter titles on (used by autocomplete)
296 * @param $exact_string
297 * Optional: should the title filter be an exact match.
298 *
299 * @return
300 * An array of valid nodes in the form:
301 * array(
302 * nid => array(
303 * 'title' => The node title,
304 * 'rendered' => The text to display in widgets (can be HTML)
305 * ),
306 * ...
307 * )
308 */
309 function _civicrm_contact_ref_potential_references($field, $string = '', $exact_string = FALSE) {
310 static $results = array();
311
312 $references = _civicrm_contact_ref_potential_references_standard($field, $string, $exact_string);
313
314 // Store the results.
315 $results[$field['field_name']][$string][$exact_string] = $references;
316
317 return $results[$field['field_name']][$string][$exact_string];
318 }
319
320 /**
321 * Helper function for _civicrm_contact_ref_potential_references():
322 * referenceable nodes defined by content types.
323 */
324 function _civicrm_contact_ref_potential_references_standard($field, $string = '', $exact_string = FALSE, $limit = '10') {
325 $args = $whereClause = $contactTypes = $contactSubTypes = array();
326
327 if (!civicrm_initialize()) {
328 return;
329 }
330
331 require_once 'CRM/Contact/BAO/ContactType.php';
332 $basicTypes = CRM_Contact_BAO_ContactType::basicTypePairs();
333
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;
340 }
341 else {
342 $contactSubTypes = array_merge($contactSubTypes, array_keys($contactNames));
343 }
344 }
345 }
346 }
347
348 if (!empty($contactTypes)) {
349 $contactTypes = implode("','", $contactTypes);
350 $whereClause[] = "contact_type IN ( '{$contactTypes}' )";
351 }
352
353 if (!empty($contactSubTypes)) {
354 $contactSubTypes = implode("','", $contactSubTypes);
355 $whereClause[] = "contact_sub_type IN ( '{$contactSubTypes}' )";
356 }
357
358 $whereClause = empty($whereClause) ? '' : '(' . implode(' OR ', $whereClause) . ') AND';
359 $related_clause = "";
360
361 if (isset($string)) {
362 if ($exact_string) {
363 $string_clause = " AND sort_name = %1";
364 $args[] = $string;
365 }
366 else {
367 $string_clause = " AND sort_name LIKE %1";
368 $args[] = "%%" . $string . "%";
369 }
370 }
371
372 $q = "
373 SELECT sort_name, id
374 FROM civicrm_contact
375 WHERE $whereClause
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 '..%%'
384 AND is_deleted = 0
385 " . $string_clause . " LIMIT $limit";
386 $params = array(1 => array($args[0], "String"));
387 $dao = CRM_Core_DAO::executeQuery($q, $params);
388
389 $references = array();
390 while ($dao->fetch()) {
391 $references[$dao->id] = array(
392 'title' => $dao->sort_name,
393 'rendered' => $dao->sort_name,
394 );
395 }
396
397 return $references;
398 }
399
400 /**
401 * Helper function for formatters.
402 *
403 * Store node titles collected in the current request.
404 *
405 * @param int $cid
406 * @param string $field
407 * @return string|null
408 */
409 function _civicrm_contact_ref_titles($cid, $field = 'display_name') {
410 if (!civicrm_initialize() || !$cid) {
411 return NULL;
412 }
413 static $titles = array();
414
415 if (!isset($titles["{$cid}_{$field}"])) {
416 $q = "
417 SELECT {$field}
418 FROM civicrm_contact
419 WHERE id = %1";
420 $params = array(1 => array($cid, "Integer"));
421 $dao = CRM_Core_DAO::executeQuery($q, $params);
422
423 $titles["{$cid}_{$field}"] = $dao->fetch() ? $dao->$field : '';
424 }
425 return $titles["{$cid}_{$field}"];
426 }
427
428 /**
429 * Implements hook_field_views_data() to allow for views relationships
430 */
431 function civicrm_contact_ref_field_views_data($field) {
432 $data = field_views_field_default_views_data($field);
433 $storage = $field['storage']['details']['sql'];
434
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])) {
440 // Relationship.
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'],
448 );
449 }
450 }
451
452 return $data;
453 }
454
455 /**
456 * Implements hook_civicrm_merge().
457 * Update field data to reflect new cids when contacts are merged.
458 */
459 function civicrm_contact_ref_civicrm_merge($type, $data, $new_id = NULL, $old_id = NULL, $tables = NULL) {
460 $updated = FALSE;
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)
468 ->execute();
469 if ($result) {
470 $updated = TRUE;
471 }
472 }
473 }
474 if ($updated) {
475 cache_clear_all('*', 'cache_field', TRUE);
476 cache_clear_all('*', 'cache_page', TRUE);
477 }
478 }