Mercurial > hg > rr-repo
diff 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 |
line wrap: on
line diff
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/sites/all/modules/references/user_reference/user_reference.module Thu Sep 19 10:38:44 2013 +0100 @@ -0,0 +1,1208 @@ +<?php + +/** + * @file + * Defines a field type for referencing a user from a node. + */ + +/** + * Implements hook_menu(). + */ +function user_reference_menu() { + $items['user_reference/autocomplete/%/%/%'] = array( + 'page callback' => 'user_reference_autocomplete', + 'page arguments' => array(2, 3, 4), + 'access callback' => 'reference_autocomplete_access', + 'access arguments' => array(2, 3, 4), + 'type' => MENU_CALLBACK, + ); + return $items; +} + +/** + * Implements hook_field_info(). + */ +function user_reference_field_info() { + return array( + 'user_reference' => array( + 'label' => t('User reference'), + 'description' => t('This field stores the ID of a related user as an integer value.'), + 'settings' => array( + 'referenceable_roles' => array(), + 'referenceable_status' => array(), + 'view' => array( + 'view_name' => '', + 'display_name' => '', + 'args' => array(), + ), + ), + 'default_widget' => 'user_reference_autocomplete', + 'default_formatter' => 'user_reference_default', + // Support hook_entity_property_info() from contrib "Entity API". + 'property_type' => 'user', + // Support default token formatter for field tokens. + 'default_token_formatter' => 'user_reference_plain', + ), + ); +} + +/** + * Implements hook_field_settings_form(). + */ +function user_reference_field_settings_form($field, $instance, $has_data) { + $settings = $field['settings']; + + $form = array(); + $form['referenceable_roles'] = array( + '#type' => 'checkboxes', + '#title' => t('User roles that can be referenced'), + '#default_value' => is_array($settings['referenceable_roles']) + ? array_filter($settings['referenceable_roles']) + : array(), + '#options' => user_roles(TRUE), + ); + $form['referenceable_status'] = array( + '#type' => 'checkboxes', + '#title' => t('User status that can be referenced'), + '#default_value' => is_array($settings['referenceable_status']) + ? array_filter($settings['referenceable_status']) + : array(1), + '#options' => array(1 => t('Active'), 0 => t('Blocked')), + ); + + if (module_exists('views')) { + $view_settings = $settings['view']; + + $description = '<p>' . t('The list of users that can be referenced can provided by a view (Views module) using the "References" display type.') . '</p>'; + + // Special note for legacy fields migrated from D6. + if (!empty($view_settings['view_name']) && $view_settings['display_name'] == 'default') { + $description .= '<p><strong><span class="admin-missing">'. t("Important D6 migration note:") . '</span></strong>'; + $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'])); + $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."); + $description .= '<br/>' . t("The field will work correctly until then, but submitting this form might inadvertently change the field settings.") . '</p>'; + } + + $form['view'] = array( + '#type' => 'fieldset', + '#title' => t('Views - Users that can be referenced'), + '#collapsible' => TRUE, + '#collapsed' => empty($view_settings['view_name']), + '#description' => $description, + ); + + $views_options = references_get_views_options('user'); + if ($views_options) { + // The value of the 'view_and_display' select below will need to be split + // into 'view_name' and 'view_display' in the final submitted values, so + // we massage the data at validate time on the wrapping element (not + // ideal). + $form['view']['#element_validate'] = array('_user_reference_view_settings_validate'); + + $views_options = array('' => '<' . t('none') . '>') + $views_options; + $default = empty($view_settings['view_name']) ? '' : $view_settings['view_name'] . ':' .$view_settings['display_name']; + $form['view']['view_and_display'] = array( + '#type' => 'select', + '#title' => t('View used to select the users'), + '#options' => $views_options, + '#default_value' => $default, + '#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>' . + 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>'), + ); + + $default = implode(', ', $view_settings['args']); + $form['view']['args'] = array( + '#type' => 'textfield', + '#title' => t('View arguments'), + '#default_value' => $default, + '#required' => FALSE, + '#description' => t('Provide a comma separated list of arguments to pass to the view.'), + ); + } + else { + $form['view']['no_view_help'] = array( + '#markup' => '<p>' . t('No eligible view was found.') .'</p>', + ); + } + } + + return $form; +} + +/** + * Validate callback for the 'view settings' fieldset. + * + * Puts back the various form values in the expected shape. + */ +function _user_reference_view_settings_validate($element, &$form_state, $form) { + // Split view name and display name from the 'view_and_display' value. + if (!empty($element['view_and_display']['#value'])) { + list($view, $display) = explode(':', $element['view_and_display']['#value']); + } + else { + $view = ''; + $display = ''; + } + + // Explode the 'args' string into an actual array. Beware, explode() turns an + // empty string into an array with one empty string. We'll need an empty array + // instead. + $args_string = trim($element['args']['#value']); + $args = ($args_string === '') ? array() : array_map('trim', explode(',', $args_string)); + + $value = array('view_name' => $view, 'display_name' => $display, 'args' => $args); + form_set_value($element, $value, $form_state); +} + +/** + * Implements hook_field_validate(). + * + * Possible error codes: + * - 'invalid_uid': uid is not valid for the field (not a valid user id, or the user is not referenceable). + */ +function user_reference_field_validate($entity_type, $entity, $field, $instance, $langcode, $items, &$errors) { + // Extract uids to check. + $ids = array(); + + // First check non-numeric uid's to avoid losing time with them. + foreach ($items as $delta => $item) { + if (is_array($item) && !empty($item['uid'])) { + if (is_numeric($item['uid'])) { + $ids[] = $item['uid']; + } + else { + $errors[$field['field_name']][$langcode][$delta][] = array( + 'error' => 'invalid_uid', + 'message' => t('%name: invalid input.', + array('%name' => $instance['label'])), + ); + } + } + } + // Prevent performance hog if there are no ids to check. + if ($ids) { + $options = array( + 'ids' => $ids, + ); + $refs = user_reference_potential_references($field, $options); + foreach ($items as $delta => $item) { + if (is_array($item)) { + if (!empty($item['uid']) && !isset($refs[$item['uid']])) { + $errors[$field['field_name']][$langcode][$delta][] = array( + 'error' => 'invalid_uid', + 'message' => t("%name: this user can't be referenced.", + array('%name' => $instance['label'])), + ); + } + } + } + } +} + +/** + * Implements hook_field_prepare_view(). + */ +function user_reference_field_prepare_view($entity_type, $entities, $field, $instances, $langcode, &$items) { + $checked_ids = &drupal_static(__FUNCTION__, array()); + + // Set an 'access' property on each item (TRUE if the user exists). + + // Extract ids to check. + $ids = array(); + foreach ($items as $id => $entity_items) { + foreach ($entity_items as $delta => $item) { + if (is_array($item)) { + // Default to 'not accessible'. + $items[$id][$delta]['access'] = FALSE; + if (!empty($item['uid']) && is_numeric($item['uid'])) { + $ids[$item['uid']] = $item['uid']; + } + } + } + } + + if ($ids) { + // Load information about ids that we haven't already loaded during this + // page request. + $ids_to_check = array_diff($ids, array_keys($checked_ids)); + if (!empty($ids_to_check)) { + $query = db_select('users', 'u') + ->addMetaData('id', 'user_reference_field_prepare_view') + ->addMetaData('field', $field) + ->fields('u', array('uid')) + ->condition('u.uid', $ids_to_check, 'IN'); + $accessible_ids = $query->execute()->fetchAllAssoc('uid'); + + // Populate our static list so that we do not query on those ids again. + foreach ($ids_to_check as $id) { + $checked_ids[$id] = isset($accessible_ids[$id]); + } + } + + foreach ($items as $id => $entity_items) { + foreach ($entity_items as $delta => $item) { + if (is_array($item) && !empty($item['uid']) && !empty($checked_ids[$item['uid']])) { + $items[$id][$delta]['access'] = TRUE; + } + } + } + } +} + +/** + * Implements hook_field_is_empty(). + */ +function user_reference_field_is_empty($item, $field) { + return empty($item['uid']); +} + +/** + * Implements hook_field_formatter_info(). + */ +function user_reference_field_formatter_info() { + return array( + 'user_reference_default' => array( + 'label' => t('Default'), + 'description' => t("Display the name of the referenced user as a link to the user's profile page."), + 'field types' => array('user_reference'), + ), + 'user_reference_plain' => array( + 'label' => t('Plain text'), + 'description' => t('Display the name of the referenced user as plain text.'), + 'field types' => array('user_reference'), + ), + 'user_reference_user' => array( + 'label' => t('Rendered user'), + 'description' => t('Display the referenced user in a specific view mode'), + 'field types' => array('user_reference'), + 'settings' => array('user_reference_view_mode' => 'full'), + ), + 'user_reference_uid' => array( + 'label' => t('User ID'), + 'description' => t('Display the referenced user ID'), + 'field types' => array('user_reference'), + ), + 'user_reference_path' => array( + 'label' => t('URL as plain text'), + 'description' => t('Display the URL of the referenced user'), + 'field types' => array('user_reference'), + 'settings' => array( + 'alias' => TRUE, + 'absolute' => FALSE + ), + ), + ); +} + +/** + * Implements hook_field_formatter_settings_form(). + */ +function user_reference_field_formatter_settings_form($field, $instance, $view_mode, $form, &$form_state) { + $display = $instance['display'][$view_mode]; + $settings = $display['settings']; + + $element = array(); + + switch ($display['type']) { + case 'user_reference_user': + $entity_info = entity_get_info('user'); + $modes = $entity_info['view modes']; + $options = array(); + foreach ($modes as $name => $mode) { + $options[$name] = $mode['label']; + } + $element['user_reference_view_mode'] = array( + '#title' => t('View mode'), + '#type' => 'select', + '#options' => $options, + '#default_value' => $settings['user_reference_view_mode'], + // Never empty, so no #empty_option + ); + break; + + case 'user_reference_path': + $element['alias'] = array( + '#type' => 'checkbox', + '#title' => t('Display the aliased path (if exists) instead of the system path'), + '#default_value' => $settings['alias'], + ); + $element['absolute'] = array( + '#type' => 'checkbox', + '#title' => t('Display an absolute URL'), + '#default_value' => $settings['absolute'], + ); + break; + } + + return $element; +} + +/** + * Implements hook_field_formatter_settings_summary(). + */ +function user_reference_field_formatter_settings_summary($field, $instance, $view_mode) { + $display = $instance['display'][$view_mode]; + $settings = $display['settings']; + $summary = array(); + + switch ($display['type']) { + case 'user_reference_user': + $entity_info = entity_get_info('user'); + $modes = $entity_info['view modes']; + $mode = $modes[$settings['user_reference_view_mode']]['label']; + $summary[] = t('View mode: %mode', array('%mode' => $mode)); + break; + + case 'user_reference_path': + $summary[] = t('Aliased path: %yes_no', array('%yes_no' => $settings['alias'] ? t('Yes') : t('No'))); + $summary[] = t('Absolute URL: %yes_no', array('%yes_no' => $settings['absolute'] ? t('Yes') : t('No'))); + break; + } + + return implode('<br />', $summary); +} + + +/** + * Implements hook_field_formatter_prepare_view(). + * + * Preload all user referenced by items using 'full entity' formatters. + */ +function user_reference_field_formatter_prepare_view($entity_type, $entities, $field, $instances, $langcode, &$items, $displays) { + // Load the referenced users, except for the 'user_reference_uid' which does + // not need full objects. + + // Collect ids to load. + $ids = array(); + foreach ($displays as $id => $display) { + if ($display['type'] != 'user_reference_uid') { + foreach ($items[$id] as $delta => $item) { + if ($item['access']) { + $ids[$item['uid']] = $item['uid']; + } + } + } + } + $entities = user_load_multiple($ids); + + // Add the loaded user objects to the items. + foreach ($displays as $id => $display) { + if ($display['type'] != 'user_reference_uid') { + foreach ($items[$id] as $delta => $item) { + if ($item['access']) { + $items[$id][$delta]['user'] = $entities[$item['uid']]; + } + } + } + } +} + +/** + * Implements hook_field_formatter_view(). + */ +function user_reference_field_formatter_view($entity_type, $entity, $field, $instance, $langcode, $items, $display) { + $settings = $display['settings']; + $result = array(); + + // @todo Optimisation: use hook_field_formatter_prepare_view() to load + // user names or full user entities in 'multiple' mode. + + // Collect the list of user ids. + $uids = array(); + foreach ($items as $delta => $item) { + $uids[$item['uid']] = $item['uid']; + } + + switch ($display['type']) { + case 'user_reference_default': + case 'user_reference_plain': + foreach ($items as $delta => $item) { + if ($item['access']) { + $user = $item['user']; + $label = entity_label('user', $user); + if ($display['type'] == 'user_reference_default') { + $uri = entity_uri('user', $user); + $result[$delta] = array( + '#type' => 'link', + '#title' => $label, + '#href' => $uri['path'], + '#options' => $uri['options'], + ); + } + else { + $result[$delta] = array( + '#markup' => check_plain($label), + ); + } + } + } + break; + + case 'user_reference_user': + $view_mode = $display['settings']['user_reference_view_mode']; + // To prevent infinite recursion caused by reference cycles, we store + // diplayed accounts in a recursion queue. + $recursion_queue = &drupal_static(__FUNCTION__, array()); + + // If no 'referencing entity' is set, we are starting a new 'reference + // thread' and need to reset the queue. + // @todo Bug: $entity->referencing_entity on accounts referenced in a different + // thread on the page. E.g: 1 references 1+2 / 2 references 1+2 / visit homepage. + // We'd need a more accurate way... + if (!isset($entity->referencing_entity)) { + $recursion_queue = array(); + } + + // The recursion queue only needs to track nodes. + if ($entity_type == 'user') { + list($id) = entity_extract_ids($entity_type, $entity); + $recursion_queue[$id] = $id; + } + + // Check the recursion queue to determine which accounts should be fully + // displayed, and which accounts will only be displayed as a username. + $users_display = array(); + foreach ($items as $delta => $item) { + if ($item['access'] && !isset($recursion_queue[$item['uid']])) { + $users_display[$item['uid']] = $item['user']; + } + } + + // Load and build the fully displayed nodes. + if ($users_display) { + $users_built = array('users' => array('#sorted' => TRUE)); + foreach ($users_display as $uid => $account) { + $users_display[$uid]->referencing_entity = $entity; + $users_display[$uid]->referencing_field = $field['field_name']; + $users_built['users'][$account->uid] = user_view($account, $settings['user_reference_view_mode']); + } + } + + // Assemble the render array. + foreach ($items as $delta => $item) { + if ($item['access']) { + if (isset($users_display[$item['uid']])) { + $result[$delta] = $users_built['users'][$item['uid']]; + } + else { + $account = $item['user']; + $label = entity_label('user', $user); + $uri = entity_uri('user', $account); + $result[$delta] = array( + '#type' => 'link', + '#title' => $label, + '#href' => $uri['path'], + '#options' => $uri['options'], + ); + if (!$account->status) { + $result[$delta]['#prefix'] = '<span class="user-unpublished">'; + $result[$delta]['#suffix'] = '</span>'; + } + } + } + } + break; + + case 'user_reference_uid': + foreach ($items as $delta => $item) { + if ($item['access']) { + $result[$delta] = array( + '#markup' => $item['uid'], + ); + } + } + break; + + case 'user_reference_path': + foreach ($items as $delta => $item) { + if ($item['access']) { + $uri = entity_uri('user', $item['user']); + $options = array( + 'absolute' => $settings['absolute'], + 'alias' => !$settings['alias'], + ); + + $options += $uri['options']; + $result[$delta] = array( + '#markup' => url($uri['path'], $options), + ); + } + } + break; + } + + return $result; +} + +/** + * Helper function for widgets and formatters. + * + * Store user names collected in the curent request. + */ +function _user_reference_get_user_names($uids, $known_titles = array()) { + $titles = &drupal_static(__FUNCTION__, array()); + + // Save titles we receive. + $titles += $known_titles; + + // Collect nids to retrieve from database. + $uids_query = array(); + foreach ($uids as $uid) { + if (!isset($titles[$uid])) { + $uids_query[] = $uid; + } + } + if ($uids_query) { + $query = db_select('users', 'u') + ->fields('u', array('uid', 'name')) + ->condition('u.uid', $uids); + $titles += $query->execute()->fetchAllKeyed(); + } + + // Build the results array. + $return = array(); + foreach ($uids as $uid) { + $return[$uid] = isset($titles[$uid]) ? $titles[$uid] : ''; + } + + return $return; +} + +/** + * Implements hook_field_widget_info(). + */ +function user_reference_field_widget_info() { + return array( + 'user_reference_autocomplete' => array( + 'label' => t('Autocomplete text field'), + 'description' => t('Display the list of referenceable users as a textfield with autocomplete behaviour.'), + 'field types' => array('user_reference'), + 'settings' => array( + 'autocomplete_match' => 'contains', + 'size' => 60, + 'autocomplete_path' => 'user_reference/autocomplete', + ), + ), + ); +} + +/** + * Implements hook_field_widget_info_alter(). + */ +function user_reference_field_widget_info_alter(&$info) { + $info['options_select']['field types'][] = 'user_reference'; + $info['options_buttons']['field types'][] = 'user_reference'; +} + +/** + * Implements hook_field_widget_settings_form(). + */ +function user_reference_field_widget_settings_form($field, $instance) { + $widget = $instance['widget']; + $defaults = field_info_widget_settings($widget['type']); + $settings = array_merge($defaults, $widget['settings']); + + $form = array(); + if ($widget['type'] == 'user_reference_autocomplete') { + $form['autocomplete_match'] = array( + '#type' => 'select', + '#title' => t('Autocomplete matching'), + '#default_value' => $settings['autocomplete_match'], + '#options' => array( + 'starts_with' => t('Starts with'), + 'contains' => t('Contains'), + ), + '#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.'), + ); + $form['size'] = array( + '#type' => 'textfield', + '#title' => t('Size of textfield'), + '#default_value' => $settings['size'], + '#element_validate' => array('_element_validate_integer_positive'), + '#required' => TRUE, + ); + } + return $form; +} + +/** + * Implements hook_field_widget_form(). + */ +function user_reference_field_widget_form(&$form, &$form_state, $field, $instance, $langcode, $items, $delta, $element) { + switch ($instance['widget']['type']) { + case 'user_reference_autocomplete': + $element += array( + '#type' => 'textfield', + '#default_value' => isset($items[$delta]['uid']) ? $items[$delta]['uid'] : NULL, + '#autocomplete_path' => $instance['widget']['settings']['autocomplete_path'] . '/' . $instance['entity_type'] . '/' . $instance['bundle'] . '/' . $field['field_name'], + '#size' => $instance['widget']['settings']['size'], + '#element_validate' => array('user_reference_autocomplete_validate'), + '#value_callback' => 'user_reference_autocomplete_value', + ); + break; + } + return array('uid' => $element); +} + +/** + * Value callback for a user_reference autocomplete element. + * + * Substitute in the user name for the uid. + */ +function user_reference_autocomplete_value($element, $input = FALSE, $form_state) { + if ($input === FALSE) { + // We're building the displayed 'default value': expand the raw uid into + // "user name [uid:n]". + $uid = $element['#default_value']; + if (!empty($uid)) { + $q = db_select('users', 'u'); + $q->addField('u', 'name'); + + $q->condition('u.uid', $uid) + ->range(0, 1); + $result = $q->execute(); + // @todo If no result (user doesn't exist). + $value = $result->fetchField(); + $value .= ' [uid:' . $uid . ']'; + return $value; + } + } +} + +/** + * Validation callback for a user_reference autocomplete element. + */ +function user_reference_autocomplete_validate($element, &$form_state, $form) { + $field = field_widget_field($element, $form_state); + $instance = field_widget_instance($element, $form_state); + + $value = $element['#value']; + $uid = NULL; + + if (!empty($value)) { + // Check whether we have an explicit "[uid:n]" input. + preg_match('/^(?:\s*|(.*) )?\[\s*uid\s*:\s*(\d+)\s*\]$/', $value, $matches); + if (!empty($matches)) { + // Explicit uid. Check that the 'name' part matches the actual name for + // the uid. + list(, $name, $uid) = $matches; + if (!empty($name)) { + $names = _user_reference_get_user_names(array($uid)); + if ($name != $names[$uid]) { + form_error($element, t('%name: name mismatch. Please check your selection.', array('%name' => $instance['label']))); + } + } + } + else { + // No explicit uid (the submitted value was not populated by autocomplete + // selection). Get the uid of a referencable user from the entered name. + $options = array( + 'string' => $value, + 'match' => 'equals', + 'limit' => 1, + ); + $references = user_reference_potential_references($field, $options); + if ($references) { + // @todo The best thing would be to present the user with an + // additional form, allowing the user to choose between valid + // candidates with the same name. ATM, we pick the first + // matching candidate... + $uid = key($references); + } + else { + form_error($element, t('%name: found no valid user with that name.', array('%name' => $instance['label']))); + } + } + } + + // Set the element's value as the user id that was extracted from the entered + // input. + form_set_value($element, $uid, $form_state); +} + +/** + * Implements hook_field_widget_error(). + */ +function user_reference_field_widget_error($element, $error, $form, &$form_state) { + form_error($element['uid'], $error['message']); +} + +/** + * Builds a list of referenceable users suitable for the '#option' FAPI property. + * + * Warning: the function does NOT take care of encoding or escaping the user + * names. Proper massaging needs to be performed by the caller, according to + * the destination FAPI '#type' (radios / checkboxes / select). + * + * @param $field + * The field definition. + * + * @return + * An array of referenceable user names, keyed by user id. + */ +function _user_reference_options($field, $flat = TRUE) { + $references = user_reference_potential_references($field); + + $options = array(); + foreach ($references as $key => $value) { + // The label, displayed in selects and checkboxes/radios, should have HTML + // entities unencoded. The widgets (core's options.module) take care of + // applying the relevant filters (strip_tags() or filter_xss()). + $label = html_entity_decode($value['rendered'], ENT_QUOTES); + if (empty($value['group']) || $flat) { + $options[$key] = $label; + } + else { + // The group name, displayed in selects, cannot contain tags, and should + // have HTML entities unencoded. + $group = html_entity_decode(strip_tags($value['group']), ENT_QUOTES); + $options[$group][$key] = $label; + } + } + + return $options; +} + +/** + * Retrieves an array of candidate referenceable users. + * + * This info is used in various places (aloowed values, autocomplete results, + * input validation...). Some of them only need the uids, others nid + names, + * others yet uid + names + rendered row (for display in widgets). + * The array we return contains all the potentially needed information, and lets + * consumers use the parts they actually need. + * + * @param $field + * The field definition. + * @param $options + * An array of options to limit the scope of the returned list. The following + * key/value pairs are accepted: + * - string: string to filter titles on (used by autocomplete). + * - match: operator to match the above string against, can be any of: + * 'contains', 'equals', 'starts_with'. Defaults to 'contains'. + * - ids: array of specific node ids to lookup. + * - limit: maximum size of the the result set. Defaults to 0 (no limit). + * + * @return + * An array of valid users in the form: + * array( + * uid => array( + * 'title' => The user name, + * 'rendered' => The text to display in widgets (can be HTML) + * ), + * ... + * ) + */ +function user_reference_potential_references($field, $options = array()) { + // Fill in default options. + $options += array( + 'string' => '', + 'match' => 'contains', + 'ids' => array(), + 'limit' => 0, + ); + + $results = &drupal_static(__FUNCTION__, array()); + + // Create unique id for static cache. + $cid = $field['field_name'] . ':' . $options['match'] . ':' + . ($options['string'] !== '' ? $options['string'] : implode('-', $options['ids'])) + . ':' . $options['limit']; + if (!isset($results[$cid])) { + $references = FALSE; + if (module_exists('views') && !empty($field['settings']['view']['view_name'])) { + $references = _user_reference_potential_references_views($field, $options); + } + + if ($references === FALSE) { + $references = _user_reference_potential_references_standard($field, $options); + } + + // Store the results. + $results[$cid] = !empty($references) ? $references : array(); + } + + return $results[$cid]; +} + +/** + * Helper function for user_reference_potential_references(). + * + * Case of Views-defined referenceable users. + */ +function _user_reference_potential_references_views($field, $options) { + $settings = $field['settings']['view']; + $options['title_field'] = 'name'; + return references_potential_references_view('user', $settings['view_name'], $settings['display_name'], $settings['args'], $options); +} + +/** + * Helper function for user_reference_potential_references(). + * + * List of referenceable users defined by user role and status. + */ +function _user_reference_potential_references_standard($field, $options) { + // Avoid useless work. + $filter_roles = array_filter($field['settings']['referenceable_roles']); + $filter_status = array_filter($field['settings']['referenceable_status']); + if (!count($filter_status) && !count($filter_roles)) { + return array(); + } + + $query = db_select('users', 'u') + // Select the whole record, so that format_username() has enough + // information. + ->fields('u') + ->addMetaData('id', ' _user_reference_potential_references_standard') + ->addMetaData('field', $field) + ->addMetaData('options', $options); + + // Enable this filter only if any statuses checked (and not both). + if (count($filter_status) == 1) { + $query->condition('u.status', array_keys($filter_status), 'IN'); + } + + // Skip filter when "authenticated user" choosen. + if ($filter_roles && !isset($filter_roles[DRUPAL_AUTHENTICATED_RID])) { + $query->join('users_roles', 'r', 'u.uid = r.uid'); + $query->condition('r.rid', array_keys($filter_roles), 'IN'); + } + + if ($options['string'] !== '') { + switch ($options['match']) { + case 'contains': + $query->condition('u.name', '%' . $options['string'] . '%', 'LIKE'); + break; + + case 'starts_with': + $query->condition('u.name', $options['string'] . '%', 'LIKE'); + break; + + case 'equals': + default: // no match type or incorrect match type: use "=" + $query->condition('u.name', $options['string'], '='); + break; + } + } + + if ($options['ids']) { + $query->condition('u.uid', $options['ids'], 'IN'); + } + + // Explicitly exclude the anonymous user. + $query->condition('u.uid', 0, '<>'); + + if ($options['limit']) { + $query->range(0, $options['limit']); + } + $query->orderBy('u.name'); + + $result = $query->execute()->fetchAll(); + $references = array(); + foreach ($result as $account) { + $references[$account->uid] = array( + 'title' => $account->name, + 'rendered' => check_plain(format_username($account)), + ); + } + return $references; +} + +/** + * Menu callback; Retrieve a pipe delimited string of autocomplete suggestions for existing users + */ +function user_reference_autocomplete($entity_type, $bundle, $field_name, $string = '') { + $field = field_info_field($field_name); + $instance = field_info_instance($entity_type, $field_name, $bundle); + + $options = array( + 'string' => $string, + 'match' => $instance['widget']['settings']['autocomplete_match'], + 'limit' => 10, + ); + $references = user_reference_potential_references($field, $options); + + $matches = array(); + foreach ($references as $id => $row) { + // Markup is fine in autocompletion results (might happen when rendered + // through Views) but we want to remove hyperlinks. + $suggestion = preg_replace('/<a href="([^<]*)">([^<]*)<\/a>/', '$2', $row['rendered']); + // Remove link tags Add a class wrapper for a few required CSS overrides. + $matches[$row['title'] . " [uid:$id]"] = '<div class="reference-autocomplete">' . $suggestion . '</div>'; + } + drupal_json_output($matches); +} + +/** + * Implements hook_options_list(). + */ +function user_reference_options_list($field) { + return _user_reference_options($field, FALSE); +} + +/** + * Implementation of hook_user_load(). + */ +/*function user_reference_user_load($accounts) { + + // Only add links if we are on the user 'view' page. + if (arg(0) != 'user' || arg(2)) { + return; + } + + foreach ($accounts as $uid => $account) { + + // find CCK user_reference field tables + // search through them for matching user ids and load those nodes + $additions = array(); + $fields = field_info_instances('user'); + + // TODO : replace with field_attach_query() + synchronize with latest D6 code. + + // Find the table and columns to search through, if the same + // table comes up in more than one field type, we only need + // to search it once. + $search_tables = array(); + $search_links = array(); + foreach ($fields as $field) { + if ($field['type'] == 'user_reference' && !empty($field['widget']['reverse_link'])) { + $db_info = content_database_info($field); + $search_tables[$db_info['table']] = $db_info['columns']['uid']['column']; + $search_links[$db_info['table']] = $field['widget']['reverse_link']; + } + } + foreach ($search_tables as $table => $column) { + $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")); + while ($data = db_fetch_object($ids)) { + // TODO, do we really want a complete node_load() here? We only need the title to create a link. + $node = node_load($data->nid); + $node->reverse_link = $search_links[$table]; + $additions[$node->type][] = $node; + } + } + $accounts[$uid]->user_reference = $additions; + } + return; +}*/ + +/** + * Implementation of hook_user_view(). + */ +/*function user_reference_user_view($account, $view_mode, $langcode) { + if (!empty($account->user_reference)) { + $node_types = content_types(); + $additions = array(); + $values = array(); + foreach ($account->user_reference as $node_type => $nodes) { + foreach ($nodes as $node) { + if ($node->reverse_link) { + $values[$node_type][] = l($node->title, 'node/' . $node->nid); + } + } + if (isset($values[$node_type])) { + $additions[] = array( + '#type' => 'user_profile_item', + '#title' => check_plain($node_types[$node_type]['name']), + '#value' => theme('item_list', $values[$node_type]), + ); + } + } + if ($additions) { + $account->content['user_reference'] = $additions + array( + '#type' => 'user_profile_category', + '#attributes' => array('class' => array('user-member')), + '#title' => t('Related content'), + '#weight' => 10, + ); + } + } +}*/ + +/** + * Implements hook_content_migrate_field_alter(). + * + * Use this to tweak the conversion of field settings + * from the D6 style to the D7 style for specific + * situations not handled by basic conversion, + * as when field types or settings are changed. + * + * $field_value['widget_type'] is available to + * see what widget type was originally used. + */ +function user_reference_content_migrate_field_alter(&$field_value, $instance_value) { + switch ($field_value['module']) { + case 'userreference': + $field_value['module'] = 'user_reference'; + $field_value['type'] = 'user_reference'; + + // Translate 'view' settings. + $view_name = isset($field_value['settings']['advanced_view']) ? $field_value['settings']['advanced_view'] : ''; + $view_args = isset($field_value['settings']['advanced_view_args']) ? $field_value['settings']['advanced_view_args'] : ''; + $view_args = array_map('trim', explode(',', $view_args)); + $field_value['settings']['view'] = array( + 'view_name' => $view_name, + 'display_name' => 'default', + 'args' => $view_args, + ); + if ($view_name) { + $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)); + } + unset($field_value['settings']['advanced_view']); + unset($field_value['settings']['advanced_view_args']); + break; + } +} + +/** + * Implements hook_content_migrate_instance_alter(). + * + * Use this to tweak the conversion of instance or widget settings + * from the D6 style to the D7 style for specific + * situations not handled by basic conversion, as when + * formatter or widget names or settings are changed. + */ +function user_reference_content_migrate_instance_alter(&$instance_value, $field_value) { + // The module name for the instance was corrected + // by the change in user_reference_content_migrate_field_alter(). + switch ($field_value['type']) { + case 'userreference': + // The formatter names changed, all are prefixed + // with 'user_reference_'. + foreach ($instance_value['display'] as $context => $settings) { + $instance_value['display'][$context]['type'] = 'user_reference_' . $settings['type']; + } + // Massage the widget. + switch ($instance_value['widget']['type']) { + case 'userreference_autocomplete': + $instance_value['widget']['type'] = 'user_reference_autocomplete'; + $instance_value['widget']['module'] = 'user_reference'; + break; + case 'userreference_select': + $instance_value['widget']['type'] = 'options_select'; + $instance_value['widget']['module'] = 'options'; + break; + case 'userreference_buttons': + $instance_value['widget']['type'] = 'options_buttons'; + $instance_value['widget']['module'] = 'options'; + } + break; + } +} + +/** + * Implements hook_field_views_data(). + * + * In addition to the default field information we add the relationship for + * views to connect back to the users table. + */ +function user_reference_field_views_data($field) { + // No module_load_include(): this hook is invoked from + // views/modules/field.views.inc, which is where that function is defined. + $data = field_views_field_default_views_data($field); + + $storage = $field['storage']['details']['sql']; + + foreach ($storage as $age => $table_data) { + $table = key($table_data); + $columns = current($table_data); + $id_column = $columns['uid']; + if (isset($data[$table])) { + // Filter: swap the handler to the 'in' operator. The callback receives + // the field name instead of the whole $field structure to keep views + // data to a reasonable size. + $data[$table][$id_column]['filter']['handler'] = 'views_handler_filter_in_operator'; + $data[$table][$id_column]['filter']['options callback'] = 'user_reference_views_filter_options'; + $data[$table][$id_column]['filter']['options arguments'] = array($field['field_name']); + + // Argument: display users.name in argument titles (handled in our custom + // handler) and summary lists (handled by the base views_handler_argument + // handler). + // Both mechanisms rely on the 'name table' and 'name field' information + // below, by joining to a separate copy of the base table from the field + // data table. + $data[$table][$id_column]['argument']['handler'] = 'references_handler_argument'; + $data[$table][$id_column]['argument']['name table'] = $table . '_reference'; + $data[$table][$id_column]['argument']['name field'] = 'name'; + $data[$table . '_reference']['table']['join'][$table] = array( + 'left_field' => $id_column, + 'table' => 'users', + 'field' => 'uid', + ); + + // Relationship. + $data[$table][$id_column]['relationship'] = array( + 'handler' => 'references_handler_relationship', + 'base' => 'users', + 'base field' => 'uid', + 'field' => $id_column, + 'label' => $field['field_name'], + 'field_name' => $field['field_name'], + ); + } + } + + return $data; +} + +/** + * Implements hook_field_views_data_views_data_alter(). + */ +function user_reference_field_views_data_views_data_alter(&$data, $field) { + foreach ($field['bundles'] as $entity_type => $bundles) { + $entity_info = entity_get_info($entity_type); + $pseudo_field_name = 'reverse_' . $field['field_name'] . '_' . $entity_type; + + list($label, $all_labels) = field_views_field_label($field['field_name']); + $entity = $entity_info['label']; + if ($entity == t('Node')) { + $entity = t('Content'); + } + + // Only specify target entity type if the field is used in more than one. + if (count($field['bundles']) > 1) { + $title = t('@field (@field_name) - reverse (to @entity)', array('@entity' => $entity, '@field' => $label, '@field_name' => $field['field_name'])); + } + else { + $title = t('@field (@field_name) - reverse', array('@entity' => $entity, '@field' => $label, '@field_name' => $field['field_name'])); + } + $data['users'][$pseudo_field_name]['relationship'] = array( + 'title' => $title, + 'help' => t('Relate each @entity referencing the user through @field.', array('@entity' => $entity, '@field' => $label)), + 'handler' => 'views_handler_relationship_entity_reverse', + 'field_name' => $field['field_name'], + 'field table' => _field_sql_storage_tablename($field), + 'field field' => $field['field_name'] . '_uid', + 'base' => $entity_info['base table'], + 'base field' => $entity_info['entity keys']['id'], + 'label' => t('!field_name', array('!field_name' => $field['field_name'])), + ); + } +} + +/** + * 'options callback' for the views_handler_filter_in_operator filter. + * + * @param $field_name + * The field name. + * + * @return + * The array of allowed options for the filter. + */ +function user_reference_views_filter_options($field_name) { + $options = array(); + + if ($field = field_info_field($field_name)) { + $options = _user_reference_options($field, TRUE); + + // The options are displayed in checkboxes within the filter admin form, and + // in a select within an exposed filter. Checkboxes accept HTML, other + // entities should be encoded; selects require the exact opposite: no HTML, + // no encoding. We go for a middle ground: strip tags, leave entities + // unencoded. + foreach ($options as $key => $value) { + $options[$key] = strip_tags($value); + } + } + + return $options; +}