comparison sites/all/modules/references/user_reference/user_reference.module @ 4:ce11bbd8f642

added modules
author danieleb <danielebarchiesi@me.com>
date Thu, 19 Sep 2013 10:38:44 +0100
parents
children
comparison
equal deleted inserted replaced
3:b28be78d8160 4:ce11bbd8f642
1 <?php
2
3 /**
4 * @file
5 * Defines a field type for referencing a user from a node.
6 */
7
8 /**
9 * Implements hook_menu().
10 */
11 function user_reference_menu() {
12 $items['user_reference/autocomplete/%/%/%'] = array(
13 'page callback' => 'user_reference_autocomplete',
14 'page arguments' => array(2, 3, 4),
15 'access callback' => 'reference_autocomplete_access',
16 'access arguments' => array(2, 3, 4),
17 'type' => MENU_CALLBACK,
18 );
19 return $items;
20 }
21
22 /**
23 * Implements hook_field_info().
24 */
25 function user_reference_field_info() {
26 return array(
27 'user_reference' => array(
28 'label' => t('User reference'),
29 'description' => t('This field stores the ID of a related user as an integer value.'),
30 'settings' => array(
31 'referenceable_roles' => array(),
32 'referenceable_status' => array(),
33 'view' => array(
34 'view_name' => '',
35 'display_name' => '',
36 'args' => array(),
37 ),
38 ),
39 'default_widget' => 'user_reference_autocomplete',
40 'default_formatter' => 'user_reference_default',
41 // Support hook_entity_property_info() from contrib "Entity API".
42 'property_type' => 'user',
43 // Support default token formatter for field tokens.
44 'default_token_formatter' => 'user_reference_plain',
45 ),
46 );
47 }
48
49 /**
50 * Implements hook_field_settings_form().
51 */
52 function user_reference_field_settings_form($field, $instance, $has_data) {
53 $settings = $field['settings'];
54
55 $form = array();
56 $form['referenceable_roles'] = array(
57 '#type' => 'checkboxes',
58 '#title' => t('User roles that can be referenced'),
59 '#default_value' => is_array($settings['referenceable_roles'])
60 ? array_filter($settings['referenceable_roles'])
61 : array(),
62 '#options' => user_roles(TRUE),
63 );
64 $form['referenceable_status'] = array(
65 '#type' => 'checkboxes',
66 '#title' => t('User status that can be referenced'),
67 '#default_value' => is_array($settings['referenceable_status'])
68 ? array_filter($settings['referenceable_status'])
69 : array(1),
70 '#options' => array(1 => t('Active'), 0 => t('Blocked')),
71 );
72
73 if (module_exists('views')) {
74 $view_settings = $settings['view'];
75
76 $description = '<p>' . t('The list of users that can be referenced can provided by a view (Views module) using the "References" display type.') . '</p>';
77
78 // Special note for legacy fields migrated from D6.
79 if (!empty($view_settings['view_name']) && $view_settings['display_name'] == 'default') {
80 $description .= '<p><strong><span class="admin-missing">'. t("Important D6 migration note:") . '</span></strong>';
81 $description .= '<br/>' . t("The field is currently configured to use the 'Master' display of the view %view_name.", array('%view_name' => $view_settings['view_name']));
82 $description .= '<br/>' . t("It is highly recommended that you: <br/>- edit this view and create a new display using the 'References' display type, <br/>- update the field settings to explicitly select the correct view and display.");
83 $description .= '<br/>' . t("The field will work correctly until then, but submitting this form might inadvertently change the field settings.") . '</p>';
84 }
85
86 $form['view'] = array(
87 '#type' => 'fieldset',
88 '#title' => t('Views - Users that can be referenced'),
89 '#collapsible' => TRUE,
90 '#collapsed' => empty($view_settings['view_name']),
91 '#description' => $description,
92 );
93
94 $views_options = references_get_views_options('user');
95 if ($views_options) {
96 // The value of the 'view_and_display' select below will need to be split
97 // into 'view_name' and 'view_display' in the final submitted values, so
98 // we massage the data at validate time on the wrapping element (not
99 // ideal).
100 $form['view']['#element_validate'] = array('_user_reference_view_settings_validate');
101
102 $views_options = array('' => '<' . t('none') . '>') + $views_options;
103 $default = empty($view_settings['view_name']) ? '' : $view_settings['view_name'] . ':' .$view_settings['display_name'];
104 $form['view']['view_and_display'] = array(
105 '#type' => 'select',
106 '#title' => t('View used to select the users'),
107 '#options' => $views_options,
108 '#default_value' => $default,
109 '#description' => '<p>' . t('Choose the view and display that select the nodes that can be referenced.<br />Only views with a display of type "References" are eligible.') . '</p>' .
110 t('Note:<ul><li>This will discard the "Referenceable Roles" and "Referenceable Status" settings above. Use the view\'s "filters" section instead.</li><li>Use the view\'s "fields" section to display additional informations about candidate users on user creation/edition form.</li><li>Use the view\'s "sort criteria" section to determine the order in which candidate users will be displayed.</li></ul>'),
111 );
112
113 $default = implode(', ', $view_settings['args']);
114 $form['view']['args'] = array(
115 '#type' => 'textfield',
116 '#title' => t('View arguments'),
117 '#default_value' => $default,
118 '#required' => FALSE,
119 '#description' => t('Provide a comma separated list of arguments to pass to the view.'),
120 );
121 }
122 else {
123 $form['view']['no_view_help'] = array(
124 '#markup' => '<p>' . t('No eligible view was found.') .'</p>',
125 );
126 }
127 }
128
129 return $form;
130 }
131
132 /**
133 * Validate callback for the 'view settings' fieldset.
134 *
135 * Puts back the various form values in the expected shape.
136 */
137 function _user_reference_view_settings_validate($element, &$form_state, $form) {
138 // Split view name and display name from the 'view_and_display' value.
139 if (!empty($element['view_and_display']['#value'])) {
140 list($view, $display) = explode(':', $element['view_and_display']['#value']);
141 }
142 else {
143 $view = '';
144 $display = '';
145 }
146
147 // Explode the 'args' string into an actual array. Beware, explode() turns an
148 // empty string into an array with one empty string. We'll need an empty array
149 // instead.
150 $args_string = trim($element['args']['#value']);
151 $args = ($args_string === '') ? array() : array_map('trim', explode(',', $args_string));
152
153 $value = array('view_name' => $view, 'display_name' => $display, 'args' => $args);
154 form_set_value($element, $value, $form_state);
155 }
156
157 /**
158 * Implements hook_field_validate().
159 *
160 * Possible error codes:
161 * - 'invalid_uid': uid is not valid for the field (not a valid user id, or the user is not referenceable).
162 */
163 function user_reference_field_validate($entity_type, $entity, $field, $instance, $langcode, $items, &$errors) {
164 // Extract uids to check.
165 $ids = array();
166
167 // First check non-numeric uid's to avoid losing time with them.
168 foreach ($items as $delta => $item) {
169 if (is_array($item) && !empty($item['uid'])) {
170 if (is_numeric($item['uid'])) {
171 $ids[] = $item['uid'];
172 }
173 else {
174 $errors[$field['field_name']][$langcode][$delta][] = array(
175 'error' => 'invalid_uid',
176 'message' => t('%name: invalid input.',
177 array('%name' => $instance['label'])),
178 );
179 }
180 }
181 }
182 // Prevent performance hog if there are no ids to check.
183 if ($ids) {
184 $options = array(
185 'ids' => $ids,
186 );
187 $refs = user_reference_potential_references($field, $options);
188 foreach ($items as $delta => $item) {
189 if (is_array($item)) {
190 if (!empty($item['uid']) && !isset($refs[$item['uid']])) {
191 $errors[$field['field_name']][$langcode][$delta][] = array(
192 'error' => 'invalid_uid',
193 'message' => t("%name: this user can't be referenced.",
194 array('%name' => $instance['label'])),
195 );
196 }
197 }
198 }
199 }
200 }
201
202 /**
203 * Implements hook_field_prepare_view().
204 */
205 function user_reference_field_prepare_view($entity_type, $entities, $field, $instances, $langcode, &$items) {
206 $checked_ids = &drupal_static(__FUNCTION__, array());
207
208 // Set an 'access' property on each item (TRUE if the user exists).
209
210 // Extract ids to check.
211 $ids = array();
212 foreach ($items as $id => $entity_items) {
213 foreach ($entity_items as $delta => $item) {
214 if (is_array($item)) {
215 // Default to 'not accessible'.
216 $items[$id][$delta]['access'] = FALSE;
217 if (!empty($item['uid']) && is_numeric($item['uid'])) {
218 $ids[$item['uid']] = $item['uid'];
219 }
220 }
221 }
222 }
223
224 if ($ids) {
225 // Load information about ids that we haven't already loaded during this
226 // page request.
227 $ids_to_check = array_diff($ids, array_keys($checked_ids));
228 if (!empty($ids_to_check)) {
229 $query = db_select('users', 'u')
230 ->addMetaData('id', 'user_reference_field_prepare_view')
231 ->addMetaData('field', $field)
232 ->fields('u', array('uid'))
233 ->condition('u.uid', $ids_to_check, 'IN');
234 $accessible_ids = $query->execute()->fetchAllAssoc('uid');
235
236 // Populate our static list so that we do not query on those ids again.
237 foreach ($ids_to_check as $id) {
238 $checked_ids[$id] = isset($accessible_ids[$id]);
239 }
240 }
241
242 foreach ($items as $id => $entity_items) {
243 foreach ($entity_items as $delta => $item) {
244 if (is_array($item) && !empty($item['uid']) && !empty($checked_ids[$item['uid']])) {
245 $items[$id][$delta]['access'] = TRUE;
246 }
247 }
248 }
249 }
250 }
251
252 /**
253 * Implements hook_field_is_empty().
254 */
255 function user_reference_field_is_empty($item, $field) {
256 return empty($item['uid']);
257 }
258
259 /**
260 * Implements hook_field_formatter_info().
261 */
262 function user_reference_field_formatter_info() {
263 return array(
264 'user_reference_default' => array(
265 'label' => t('Default'),
266 'description' => t("Display the name of the referenced user as a link to the user's profile page."),
267 'field types' => array('user_reference'),
268 ),
269 'user_reference_plain' => array(
270 'label' => t('Plain text'),
271 'description' => t('Display the name of the referenced user as plain text.'),
272 'field types' => array('user_reference'),
273 ),
274 'user_reference_user' => array(
275 'label' => t('Rendered user'),
276 'description' => t('Display the referenced user in a specific view mode'),
277 'field types' => array('user_reference'),
278 'settings' => array('user_reference_view_mode' => 'full'),
279 ),
280 'user_reference_uid' => array(
281 'label' => t('User ID'),
282 'description' => t('Display the referenced user ID'),
283 'field types' => array('user_reference'),
284 ),
285 'user_reference_path' => array(
286 'label' => t('URL as plain text'),
287 'description' => t('Display the URL of the referenced user'),
288 'field types' => array('user_reference'),
289 'settings' => array(
290 'alias' => TRUE,
291 'absolute' => FALSE
292 ),
293 ),
294 );
295 }
296
297 /**
298 * Implements hook_field_formatter_settings_form().
299 */
300 function user_reference_field_formatter_settings_form($field, $instance, $view_mode, $form, &$form_state) {
301 $display = $instance['display'][$view_mode];
302 $settings = $display['settings'];
303
304 $element = array();
305
306 switch ($display['type']) {
307 case 'user_reference_user':
308 $entity_info = entity_get_info('user');
309 $modes = $entity_info['view modes'];
310 $options = array();
311 foreach ($modes as $name => $mode) {
312 $options[$name] = $mode['label'];
313 }
314 $element['user_reference_view_mode'] = array(
315 '#title' => t('View mode'),
316 '#type' => 'select',
317 '#options' => $options,
318 '#default_value' => $settings['user_reference_view_mode'],
319 // Never empty, so no #empty_option
320 );
321 break;
322
323 case 'user_reference_path':
324 $element['alias'] = array(
325 '#type' => 'checkbox',
326 '#title' => t('Display the aliased path (if exists) instead of the system path'),
327 '#default_value' => $settings['alias'],
328 );
329 $element['absolute'] = array(
330 '#type' => 'checkbox',
331 '#title' => t('Display an absolute URL'),
332 '#default_value' => $settings['absolute'],
333 );
334 break;
335 }
336
337 return $element;
338 }
339
340 /**
341 * Implements hook_field_formatter_settings_summary().
342 */
343 function user_reference_field_formatter_settings_summary($field, $instance, $view_mode) {
344 $display = $instance['display'][$view_mode];
345 $settings = $display['settings'];
346 $summary = array();
347
348 switch ($display['type']) {
349 case 'user_reference_user':
350 $entity_info = entity_get_info('user');
351 $modes = $entity_info['view modes'];
352 $mode = $modes[$settings['user_reference_view_mode']]['label'];
353 $summary[] = t('View mode: %mode', array('%mode' => $mode));
354 break;
355
356 case 'user_reference_path':
357 $summary[] = t('Aliased path: %yes_no', array('%yes_no' => $settings['alias'] ? t('Yes') : t('No')));
358 $summary[] = t('Absolute URL: %yes_no', array('%yes_no' => $settings['absolute'] ? t('Yes') : t('No')));
359 break;
360 }
361
362 return implode('<br />', $summary);
363 }
364
365
366 /**
367 * Implements hook_field_formatter_prepare_view().
368 *
369 * Preload all user referenced by items using 'full entity' formatters.
370 */
371 function user_reference_field_formatter_prepare_view($entity_type, $entities, $field, $instances, $langcode, &$items, $displays) {
372 // Load the referenced users, except for the 'user_reference_uid' which does
373 // not need full objects.
374
375 // Collect ids to load.
376 $ids = array();
377 foreach ($displays as $id => $display) {
378 if ($display['type'] != 'user_reference_uid') {
379 foreach ($items[$id] as $delta => $item) {
380 if ($item['access']) {
381 $ids[$item['uid']] = $item['uid'];
382 }
383 }
384 }
385 }
386 $entities = user_load_multiple($ids);
387
388 // Add the loaded user objects to the items.
389 foreach ($displays as $id => $display) {
390 if ($display['type'] != 'user_reference_uid') {
391 foreach ($items[$id] as $delta => $item) {
392 if ($item['access']) {
393 $items[$id][$delta]['user'] = $entities[$item['uid']];
394 }
395 }
396 }
397 }
398 }
399
400 /**
401 * Implements hook_field_formatter_view().
402 */
403 function user_reference_field_formatter_view($entity_type, $entity, $field, $instance, $langcode, $items, $display) {
404 $settings = $display['settings'];
405 $result = array();
406
407 // @todo Optimisation: use hook_field_formatter_prepare_view() to load
408 // user names or full user entities in 'multiple' mode.
409
410 // Collect the list of user ids.
411 $uids = array();
412 foreach ($items as $delta => $item) {
413 $uids[$item['uid']] = $item['uid'];
414 }
415
416 switch ($display['type']) {
417 case 'user_reference_default':
418 case 'user_reference_plain':
419 foreach ($items as $delta => $item) {
420 if ($item['access']) {
421 $user = $item['user'];
422 $label = entity_label('user', $user);
423 if ($display['type'] == 'user_reference_default') {
424 $uri = entity_uri('user', $user);
425 $result[$delta] = array(
426 '#type' => 'link',
427 '#title' => $label,
428 '#href' => $uri['path'],
429 '#options' => $uri['options'],
430 );
431 }
432 else {
433 $result[$delta] = array(
434 '#markup' => check_plain($label),
435 );
436 }
437 }
438 }
439 break;
440
441 case 'user_reference_user':
442 $view_mode = $display['settings']['user_reference_view_mode'];
443 // To prevent infinite recursion caused by reference cycles, we store
444 // diplayed accounts in a recursion queue.
445 $recursion_queue = &drupal_static(__FUNCTION__, array());
446
447 // If no 'referencing entity' is set, we are starting a new 'reference
448 // thread' and need to reset the queue.
449 // @todo Bug: $entity->referencing_entity on accounts referenced in a different
450 // thread on the page. E.g: 1 references 1+2 / 2 references 1+2 / visit homepage.
451 // We'd need a more accurate way...
452 if (!isset($entity->referencing_entity)) {
453 $recursion_queue = array();
454 }
455
456 // The recursion queue only needs to track nodes.
457 if ($entity_type == 'user') {
458 list($id) = entity_extract_ids($entity_type, $entity);
459 $recursion_queue[$id] = $id;
460 }
461
462 // Check the recursion queue to determine which accounts should be fully
463 // displayed, and which accounts will only be displayed as a username.
464 $users_display = array();
465 foreach ($items as $delta => $item) {
466 if ($item['access'] && !isset($recursion_queue[$item['uid']])) {
467 $users_display[$item['uid']] = $item['user'];
468 }
469 }
470
471 // Load and build the fully displayed nodes.
472 if ($users_display) {
473 $users_built = array('users' => array('#sorted' => TRUE));
474 foreach ($users_display as $uid => $account) {
475 $users_display[$uid]->referencing_entity = $entity;
476 $users_display[$uid]->referencing_field = $field['field_name'];
477 $users_built['users'][$account->uid] = user_view($account, $settings['user_reference_view_mode']);
478 }
479 }
480
481 // Assemble the render array.
482 foreach ($items as $delta => $item) {
483 if ($item['access']) {
484 if (isset($users_display[$item['uid']])) {
485 $result[$delta] = $users_built['users'][$item['uid']];
486 }
487 else {
488 $account = $item['user'];
489 $label = entity_label('user', $user);
490 $uri = entity_uri('user', $account);
491 $result[$delta] = array(
492 '#type' => 'link',
493 '#title' => $label,
494 '#href' => $uri['path'],
495 '#options' => $uri['options'],
496 );
497 if (!$account->status) {
498 $result[$delta]['#prefix'] = '<span class="user-unpublished">';
499 $result[$delta]['#suffix'] = '</span>';
500 }
501 }
502 }
503 }
504 break;
505
506 case 'user_reference_uid':
507 foreach ($items as $delta => $item) {
508 if ($item['access']) {
509 $result[$delta] = array(
510 '#markup' => $item['uid'],
511 );
512 }
513 }
514 break;
515
516 case 'user_reference_path':
517 foreach ($items as $delta => $item) {
518 if ($item['access']) {
519 $uri = entity_uri('user', $item['user']);
520 $options = array(
521 'absolute' => $settings['absolute'],
522 'alias' => !$settings['alias'],
523 );
524
525 $options += $uri['options'];
526 $result[$delta] = array(
527 '#markup' => url($uri['path'], $options),
528 );
529 }
530 }
531 break;
532 }
533
534 return $result;
535 }
536
537 /**
538 * Helper function for widgets and formatters.
539 *
540 * Store user names collected in the curent request.
541 */
542 function _user_reference_get_user_names($uids, $known_titles = array()) {
543 $titles = &drupal_static(__FUNCTION__, array());
544
545 // Save titles we receive.
546 $titles += $known_titles;
547
548 // Collect nids to retrieve from database.
549 $uids_query = array();
550 foreach ($uids as $uid) {
551 if (!isset($titles[$uid])) {
552 $uids_query[] = $uid;
553 }
554 }
555 if ($uids_query) {
556 $query = db_select('users', 'u')
557 ->fields('u', array('uid', 'name'))
558 ->condition('u.uid', $uids);
559 $titles += $query->execute()->fetchAllKeyed();
560 }
561
562 // Build the results array.
563 $return = array();
564 foreach ($uids as $uid) {
565 $return[$uid] = isset($titles[$uid]) ? $titles[$uid] : '';
566 }
567
568 return $return;
569 }
570
571 /**
572 * Implements hook_field_widget_info().
573 */
574 function user_reference_field_widget_info() {
575 return array(
576 'user_reference_autocomplete' => array(
577 'label' => t('Autocomplete text field'),
578 'description' => t('Display the list of referenceable users as a textfield with autocomplete behaviour.'),
579 'field types' => array('user_reference'),
580 'settings' => array(
581 'autocomplete_match' => 'contains',
582 'size' => 60,
583 'autocomplete_path' => 'user_reference/autocomplete',
584 ),
585 ),
586 );
587 }
588
589 /**
590 * Implements hook_field_widget_info_alter().
591 */
592 function user_reference_field_widget_info_alter(&$info) {
593 $info['options_select']['field types'][] = 'user_reference';
594 $info['options_buttons']['field types'][] = 'user_reference';
595 }
596
597 /**
598 * Implements hook_field_widget_settings_form().
599 */
600 function user_reference_field_widget_settings_form($field, $instance) {
601 $widget = $instance['widget'];
602 $defaults = field_info_widget_settings($widget['type']);
603 $settings = array_merge($defaults, $widget['settings']);
604
605 $form = array();
606 if ($widget['type'] == 'user_reference_autocomplete') {
607 $form['autocomplete_match'] = array(
608 '#type' => 'select',
609 '#title' => t('Autocomplete matching'),
610 '#default_value' => $settings['autocomplete_match'],
611 '#options' => array(
612 'starts_with' => t('Starts with'),
613 'contains' => t('Contains'),
614 ),
615 '#description' => t('Select the method used to collect autocomplete suggestions. Note that <em>Contains</em> can cause performance issues on sites with thousands of users.'),
616 );
617 $form['size'] = array(
618 '#type' => 'textfield',
619 '#title' => t('Size of textfield'),
620 '#default_value' => $settings['size'],
621 '#element_validate' => array('_element_validate_integer_positive'),
622 '#required' => TRUE,
623 );
624 }
625 return $form;
626 }
627
628 /**
629 * Implements hook_field_widget_form().
630 */
631 function user_reference_field_widget_form(&$form, &$form_state, $field, $instance, $langcode, $items, $delta, $element) {
632 switch ($instance['widget']['type']) {
633 case 'user_reference_autocomplete':
634 $element += array(
635 '#type' => 'textfield',
636 '#default_value' => isset($items[$delta]['uid']) ? $items[$delta]['uid'] : NULL,
637 '#autocomplete_path' => $instance['widget']['settings']['autocomplete_path'] . '/' . $instance['entity_type'] . '/' . $instance['bundle'] . '/' . $field['field_name'],
638 '#size' => $instance['widget']['settings']['size'],
639 '#element_validate' => array('user_reference_autocomplete_validate'),
640 '#value_callback' => 'user_reference_autocomplete_value',
641 );
642 break;
643 }
644 return array('uid' => $element);
645 }
646
647 /**
648 * Value callback for a user_reference autocomplete element.
649 *
650 * Substitute in the user name for the uid.
651 */
652 function user_reference_autocomplete_value($element, $input = FALSE, $form_state) {
653 if ($input === FALSE) {
654 // We're building the displayed 'default value': expand the raw uid into
655 // "user name [uid:n]".
656 $uid = $element['#default_value'];
657 if (!empty($uid)) {
658 $q = db_select('users', 'u');
659 $q->addField('u', 'name');
660
661 $q->condition('u.uid', $uid)
662 ->range(0, 1);
663 $result = $q->execute();
664 // @todo If no result (user doesn't exist).
665 $value = $result->fetchField();
666 $value .= ' [uid:' . $uid . ']';
667 return $value;
668 }
669 }
670 }
671
672 /**
673 * Validation callback for a user_reference autocomplete element.
674 */
675 function user_reference_autocomplete_validate($element, &$form_state, $form) {
676 $field = field_widget_field($element, $form_state);
677 $instance = field_widget_instance($element, $form_state);
678
679 $value = $element['#value'];
680 $uid = NULL;
681
682 if (!empty($value)) {
683 // Check whether we have an explicit "[uid:n]" input.
684 preg_match('/^(?:\s*|(.*) )?\[\s*uid\s*:\s*(\d+)\s*\]$/', $value, $matches);
685 if (!empty($matches)) {
686 // Explicit uid. Check that the 'name' part matches the actual name for
687 // the uid.
688 list(, $name, $uid) = $matches;
689 if (!empty($name)) {
690 $names = _user_reference_get_user_names(array($uid));
691 if ($name != $names[$uid]) {
692 form_error($element, t('%name: name mismatch. Please check your selection.', array('%name' => $instance['label'])));
693 }
694 }
695 }
696 else {
697 // No explicit uid (the submitted value was not populated by autocomplete
698 // selection). Get the uid of a referencable user from the entered name.
699 $options = array(
700 'string' => $value,
701 'match' => 'equals',
702 'limit' => 1,
703 );
704 $references = user_reference_potential_references($field, $options);
705 if ($references) {
706 // @todo The best thing would be to present the user with an
707 // additional form, allowing the user to choose between valid
708 // candidates with the same name. ATM, we pick the first
709 // matching candidate...
710 $uid = key($references);
711 }
712 else {
713 form_error($element, t('%name: found no valid user with that name.', array('%name' => $instance['label'])));
714 }
715 }
716 }
717
718 // Set the element's value as the user id that was extracted from the entered
719 // input.
720 form_set_value($element, $uid, $form_state);
721 }
722
723 /**
724 * Implements hook_field_widget_error().
725 */
726 function user_reference_field_widget_error($element, $error, $form, &$form_state) {
727 form_error($element['uid'], $error['message']);
728 }
729
730 /**
731 * Builds a list of referenceable users suitable for the '#option' FAPI property.
732 *
733 * Warning: the function does NOT take care of encoding or escaping the user
734 * names. Proper massaging needs to be performed by the caller, according to
735 * the destination FAPI '#type' (radios / checkboxes / select).
736 *
737 * @param $field
738 * The field definition.
739 *
740 * @return
741 * An array of referenceable user names, keyed by user id.
742 */
743 function _user_reference_options($field, $flat = TRUE) {
744 $references = user_reference_potential_references($field);
745
746 $options = array();
747 foreach ($references as $key => $value) {
748 // The label, displayed in selects and checkboxes/radios, should have HTML
749 // entities unencoded. The widgets (core's options.module) take care of
750 // applying the relevant filters (strip_tags() or filter_xss()).
751 $label = html_entity_decode($value['rendered'], ENT_QUOTES);
752 if (empty($value['group']) || $flat) {
753 $options[$key] = $label;
754 }
755 else {
756 // The group name, displayed in selects, cannot contain tags, and should
757 // have HTML entities unencoded.
758 $group = html_entity_decode(strip_tags($value['group']), ENT_QUOTES);
759 $options[$group][$key] = $label;
760 }
761 }
762
763 return $options;
764 }
765
766 /**
767 * Retrieves an array of candidate referenceable users.
768 *
769 * This info is used in various places (aloowed values, autocomplete results,
770 * input validation...). Some of them only need the uids, others nid + names,
771 * others yet uid + names + rendered row (for display in widgets).
772 * The array we return contains all the potentially needed information, and lets
773 * consumers use the parts they actually need.
774 *
775 * @param $field
776 * The field definition.
777 * @param $options
778 * An array of options to limit the scope of the returned list. The following
779 * key/value pairs are accepted:
780 * - string: string to filter titles on (used by autocomplete).
781 * - match: operator to match the above string against, can be any of:
782 * 'contains', 'equals', 'starts_with'. Defaults to 'contains'.
783 * - ids: array of specific node ids to lookup.
784 * - limit: maximum size of the the result set. Defaults to 0 (no limit).
785 *
786 * @return
787 * An array of valid users in the form:
788 * array(
789 * uid => array(
790 * 'title' => The user name,
791 * 'rendered' => The text to display in widgets (can be HTML)
792 * ),
793 * ...
794 * )
795 */
796 function user_reference_potential_references($field, $options = array()) {
797 // Fill in default options.
798 $options += array(
799 'string' => '',
800 'match' => 'contains',
801 'ids' => array(),
802 'limit' => 0,
803 );
804
805 $results = &drupal_static(__FUNCTION__, array());
806
807 // Create unique id for static cache.
808 $cid = $field['field_name'] . ':' . $options['match'] . ':'
809 . ($options['string'] !== '' ? $options['string'] : implode('-', $options['ids']))
810 . ':' . $options['limit'];
811 if (!isset($results[$cid])) {
812 $references = FALSE;
813 if (module_exists('views') && !empty($field['settings']['view']['view_name'])) {
814 $references = _user_reference_potential_references_views($field, $options);
815 }
816
817 if ($references === FALSE) {
818 $references = _user_reference_potential_references_standard($field, $options);
819 }
820
821 // Store the results.
822 $results[$cid] = !empty($references) ? $references : array();
823 }
824
825 return $results[$cid];
826 }
827
828 /**
829 * Helper function for user_reference_potential_references().
830 *
831 * Case of Views-defined referenceable users.
832 */
833 function _user_reference_potential_references_views($field, $options) {
834 $settings = $field['settings']['view'];
835 $options['title_field'] = 'name';
836 return references_potential_references_view('user', $settings['view_name'], $settings['display_name'], $settings['args'], $options);
837 }
838
839 /**
840 * Helper function for user_reference_potential_references().
841 *
842 * List of referenceable users defined by user role and status.
843 */
844 function _user_reference_potential_references_standard($field, $options) {
845 // Avoid useless work.
846 $filter_roles = array_filter($field['settings']['referenceable_roles']);
847 $filter_status = array_filter($field['settings']['referenceable_status']);
848 if (!count($filter_status) && !count($filter_roles)) {
849 return array();
850 }
851
852 $query = db_select('users', 'u')
853 // Select the whole record, so that format_username() has enough
854 // information.
855 ->fields('u')
856 ->addMetaData('id', ' _user_reference_potential_references_standard')
857 ->addMetaData('field', $field)
858 ->addMetaData('options', $options);
859
860 // Enable this filter only if any statuses checked (and not both).
861 if (count($filter_status) == 1) {
862 $query->condition('u.status', array_keys($filter_status), 'IN');
863 }
864
865 // Skip filter when "authenticated user" choosen.
866 if ($filter_roles && !isset($filter_roles[DRUPAL_AUTHENTICATED_RID])) {
867 $query->join('users_roles', 'r', 'u.uid = r.uid');
868 $query->condition('r.rid', array_keys($filter_roles), 'IN');
869 }
870
871 if ($options['string'] !== '') {
872 switch ($options['match']) {
873 case 'contains':
874 $query->condition('u.name', '%' . $options['string'] . '%', 'LIKE');
875 break;
876
877 case 'starts_with':
878 $query->condition('u.name', $options['string'] . '%', 'LIKE');
879 break;
880
881 case 'equals':
882 default: // no match type or incorrect match type: use "="
883 $query->condition('u.name', $options['string'], '=');
884 break;
885 }
886 }
887
888 if ($options['ids']) {
889 $query->condition('u.uid', $options['ids'], 'IN');
890 }
891
892 // Explicitly exclude the anonymous user.
893 $query->condition('u.uid', 0, '<>');
894
895 if ($options['limit']) {
896 $query->range(0, $options['limit']);
897 }
898 $query->orderBy('u.name');
899
900 $result = $query->execute()->fetchAll();
901 $references = array();
902 foreach ($result as $account) {
903 $references[$account->uid] = array(
904 'title' => $account->name,
905 'rendered' => check_plain(format_username($account)),
906 );
907 }
908 return $references;
909 }
910
911 /**
912 * Menu callback; Retrieve a pipe delimited string of autocomplete suggestions for existing users
913 */
914 function user_reference_autocomplete($entity_type, $bundle, $field_name, $string = '') {
915 $field = field_info_field($field_name);
916 $instance = field_info_instance($entity_type, $field_name, $bundle);
917
918 $options = array(
919 'string' => $string,
920 'match' => $instance['widget']['settings']['autocomplete_match'],
921 'limit' => 10,
922 );
923 $references = user_reference_potential_references($field, $options);
924
925 $matches = array();
926 foreach ($references as $id => $row) {
927 // Markup is fine in autocompletion results (might happen when rendered
928 // through Views) but we want to remove hyperlinks.
929 $suggestion = preg_replace('/<a href="([^<]*)">([^<]*)<\/a>/', '$2', $row['rendered']);
930 // Remove link tags Add a class wrapper for a few required CSS overrides.
931 $matches[$row['title'] . " [uid:$id]"] = '<div class="reference-autocomplete">' . $suggestion . '</div>';
932 }
933 drupal_json_output($matches);
934 }
935
936 /**
937 * Implements hook_options_list().
938 */
939 function user_reference_options_list($field) {
940 return _user_reference_options($field, FALSE);
941 }
942
943 /**
944 * Implementation of hook_user_load().
945 */
946 /*function user_reference_user_load($accounts) {
947
948 // Only add links if we are on the user 'view' page.
949 if (arg(0) != 'user' || arg(2)) {
950 return;
951 }
952
953 foreach ($accounts as $uid => $account) {
954
955 // find CCK user_reference field tables
956 // search through them for matching user ids and load those nodes
957 $additions = array();
958 $fields = field_info_instances('user');
959
960 // TODO : replace with field_attach_query() + synchronize with latest D6 code.
961
962 // Find the table and columns to search through, if the same
963 // table comes up in more than one field type, we only need
964 // to search it once.
965 $search_tables = array();
966 $search_links = array();
967 foreach ($fields as $field) {
968 if ($field['type'] == 'user_reference' && !empty($field['widget']['reverse_link'])) {
969 $db_info = content_database_info($field);
970 $search_tables[$db_info['table']] = $db_info['columns']['uid']['column'];
971 $search_links[$db_info['table']] = $field['widget']['reverse_link'];
972 }
973 }
974 foreach ($search_tables as $table => $column) {
975 $ids = db_query(db_rewrite_sql("SELECT DISTINCT(n.nid) FROM {node} n LEFT JOIN {". $table ."} f ON n.vid = f.vid WHERE f.". $column ."=". $account->uid. " AND n.status = 1"));
976 while ($data = db_fetch_object($ids)) {
977 // TODO, do we really want a complete node_load() here? We only need the title to create a link.
978 $node = node_load($data->nid);
979 $node->reverse_link = $search_links[$table];
980 $additions[$node->type][] = $node;
981 }
982 }
983 $accounts[$uid]->user_reference = $additions;
984 }
985 return;
986 }*/
987
988 /**
989 * Implementation of hook_user_view().
990 */
991 /*function user_reference_user_view($account, $view_mode, $langcode) {
992 if (!empty($account->user_reference)) {
993 $node_types = content_types();
994 $additions = array();
995 $values = array();
996 foreach ($account->user_reference as $node_type => $nodes) {
997 foreach ($nodes as $node) {
998 if ($node->reverse_link) {
999 $values[$node_type][] = l($node->title, 'node/' . $node->nid);
1000 }
1001 }
1002 if (isset($values[$node_type])) {
1003 $additions[] = array(
1004 '#type' => 'user_profile_item',
1005 '#title' => check_plain($node_types[$node_type]['name']),
1006 '#value' => theme('item_list', $values[$node_type]),
1007 );
1008 }
1009 }
1010 if ($additions) {
1011 $account->content['user_reference'] = $additions + array(
1012 '#type' => 'user_profile_category',
1013 '#attributes' => array('class' => array('user-member')),
1014 '#title' => t('Related content'),
1015 '#weight' => 10,
1016 );
1017 }
1018 }
1019 }*/
1020
1021 /**
1022 * Implements hook_content_migrate_field_alter().
1023 *
1024 * Use this to tweak the conversion of field settings
1025 * from the D6 style to the D7 style for specific
1026 * situations not handled by basic conversion,
1027 * as when field types or settings are changed.
1028 *
1029 * $field_value['widget_type'] is available to
1030 * see what widget type was originally used.
1031 */
1032 function user_reference_content_migrate_field_alter(&$field_value, $instance_value) {
1033 switch ($field_value['module']) {
1034 case 'userreference':
1035 $field_value['module'] = 'user_reference';
1036 $field_value['type'] = 'user_reference';
1037
1038 // Translate 'view' settings.
1039 $view_name = isset($field_value['settings']['advanced_view']) ? $field_value['settings']['advanced_view'] : '';
1040 $view_args = isset($field_value['settings']['advanced_view_args']) ? $field_value['settings']['advanced_view_args'] : '';
1041 $view_args = array_map('trim', explode(',', $view_args));
1042 $field_value['settings']['view'] = array(
1043 'view_name' => $view_name,
1044 'display_name' => 'default',
1045 'args' => $view_args,
1046 );
1047 if ($view_name) {
1048 $field_value['messages'][] = t("The field uses the view @view_name to determine referenceable users. You will need to manually edit the view and add a display of type 'References'.", array('@view_name' => $view_name));
1049 }
1050 unset($field_value['settings']['advanced_view']);
1051 unset($field_value['settings']['advanced_view_args']);
1052 break;
1053 }
1054 }
1055
1056 /**
1057 * Implements hook_content_migrate_instance_alter().
1058 *
1059 * Use this to tweak the conversion of instance or widget settings
1060 * from the D6 style to the D7 style for specific
1061 * situations not handled by basic conversion, as when
1062 * formatter or widget names or settings are changed.
1063 */
1064 function user_reference_content_migrate_instance_alter(&$instance_value, $field_value) {
1065 // The module name for the instance was corrected
1066 // by the change in user_reference_content_migrate_field_alter().
1067 switch ($field_value['type']) {
1068 case 'userreference':
1069 // The formatter names changed, all are prefixed
1070 // with 'user_reference_'.
1071 foreach ($instance_value['display'] as $context => $settings) {
1072 $instance_value['display'][$context]['type'] = 'user_reference_' . $settings['type'];
1073 }
1074 // Massage the widget.
1075 switch ($instance_value['widget']['type']) {
1076 case 'userreference_autocomplete':
1077 $instance_value['widget']['type'] = 'user_reference_autocomplete';
1078 $instance_value['widget']['module'] = 'user_reference';
1079 break;
1080 case 'userreference_select':
1081 $instance_value['widget']['type'] = 'options_select';
1082 $instance_value['widget']['module'] = 'options';
1083 break;
1084 case 'userreference_buttons':
1085 $instance_value['widget']['type'] = 'options_buttons';
1086 $instance_value['widget']['module'] = 'options';
1087 }
1088 break;
1089 }
1090 }
1091
1092 /**
1093 * Implements hook_field_views_data().
1094 *
1095 * In addition to the default field information we add the relationship for
1096 * views to connect back to the users table.
1097 */
1098 function user_reference_field_views_data($field) {
1099 // No module_load_include(): this hook is invoked from
1100 // views/modules/field.views.inc, which is where that function is defined.
1101 $data = field_views_field_default_views_data($field);
1102
1103 $storage = $field['storage']['details']['sql'];
1104
1105 foreach ($storage as $age => $table_data) {
1106 $table = key($table_data);
1107 $columns = current($table_data);
1108 $id_column = $columns['uid'];
1109 if (isset($data[$table])) {
1110 // Filter: swap the handler to the 'in' operator. The callback receives
1111 // the field name instead of the whole $field structure to keep views
1112 // data to a reasonable size.
1113 $data[$table][$id_column]['filter']['handler'] = 'views_handler_filter_in_operator';
1114 $data[$table][$id_column]['filter']['options callback'] = 'user_reference_views_filter_options';
1115 $data[$table][$id_column]['filter']['options arguments'] = array($field['field_name']);
1116
1117 // Argument: display users.name in argument titles (handled in our custom
1118 // handler) and summary lists (handled by the base views_handler_argument
1119 // handler).
1120 // Both mechanisms rely on the 'name table' and 'name field' information
1121 // below, by joining to a separate copy of the base table from the field
1122 // data table.
1123 $data[$table][$id_column]['argument']['handler'] = 'references_handler_argument';
1124 $data[$table][$id_column]['argument']['name table'] = $table . '_reference';
1125 $data[$table][$id_column]['argument']['name field'] = 'name';
1126 $data[$table . '_reference']['table']['join'][$table] = array(
1127 'left_field' => $id_column,
1128 'table' => 'users',
1129 'field' => 'uid',
1130 );
1131
1132 // Relationship.
1133 $data[$table][$id_column]['relationship'] = array(
1134 'handler' => 'references_handler_relationship',
1135 'base' => 'users',
1136 'base field' => 'uid',
1137 'field' => $id_column,
1138 'label' => $field['field_name'],
1139 'field_name' => $field['field_name'],
1140 );
1141 }
1142 }
1143
1144 return $data;
1145 }
1146
1147 /**
1148 * Implements hook_field_views_data_views_data_alter().
1149 */
1150 function user_reference_field_views_data_views_data_alter(&$data, $field) {
1151 foreach ($field['bundles'] as $entity_type => $bundles) {
1152 $entity_info = entity_get_info($entity_type);
1153 $pseudo_field_name = 'reverse_' . $field['field_name'] . '_' . $entity_type;
1154
1155 list($label, $all_labels) = field_views_field_label($field['field_name']);
1156 $entity = $entity_info['label'];
1157 if ($entity == t('Node')) {
1158 $entity = t('Content');
1159 }
1160
1161 // Only specify target entity type if the field is used in more than one.
1162 if (count($field['bundles']) > 1) {
1163 $title = t('@field (@field_name) - reverse (to @entity)', array('@entity' => $entity, '@field' => $label, '@field_name' => $field['field_name']));
1164 }
1165 else {
1166 $title = t('@field (@field_name) - reverse', array('@entity' => $entity, '@field' => $label, '@field_name' => $field['field_name']));
1167 }
1168 $data['users'][$pseudo_field_name]['relationship'] = array(
1169 'title' => $title,
1170 'help' => t('Relate each @entity referencing the user through @field.', array('@entity' => $entity, '@field' => $label)),
1171 'handler' => 'views_handler_relationship_entity_reverse',
1172 'field_name' => $field['field_name'],
1173 'field table' => _field_sql_storage_tablename($field),
1174 'field field' => $field['field_name'] . '_uid',
1175 'base' => $entity_info['base table'],
1176 'base field' => $entity_info['entity keys']['id'],
1177 'label' => t('!field_name', array('!field_name' => $field['field_name'])),
1178 );
1179 }
1180 }
1181
1182 /**
1183 * 'options callback' for the views_handler_filter_in_operator filter.
1184 *
1185 * @param $field_name
1186 * The field name.
1187 *
1188 * @return
1189 * The array of allowed options for the filter.
1190 */
1191 function user_reference_views_filter_options($field_name) {
1192 $options = array();
1193
1194 if ($field = field_info_field($field_name)) {
1195 $options = _user_reference_options($field, TRUE);
1196
1197 // The options are displayed in checkboxes within the filter admin form, and
1198 // in a select within an exposed filter. Checkboxes accept HTML, other
1199 // entities should be encoded; selects require the exact opposite: no HTML,
1200 // no encoding. We go for a middle ground: strip tags, leave entities
1201 // unencoded.
1202 foreach ($options as $key => $value) {
1203 $options[$key] = strip_tags($value);
1204 }
1205 }
1206
1207 return $options;
1208 }