user_reference.module in References 7.2
Defines a field type for referencing a user from a node.
File
user_reference/user_reference.moduleView source
<?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) {
$path = drupal_get_path('module', 'references');
drupal_add_js($path . '/js/references.admin.js');
$settings = $field['settings'];
$options['user_reference_config_select_all_roles'] = t('Select all/none');
$options = $options + user_roles(TRUE);
$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' => $options,
);
$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'],
);
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.
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);
$router_item = menu_get_item($uri['path']);
if ($router_item && $router_item['access']) {
$result[$delta] = array(
'#type' => 'link',
'#title' => $label,
'#href' => $uri['path'],
'#options' => $uri['options'],
);
}
else {
$result[$delta] = array(
'#markup' => check_plain($label),
);
}
}
else {
$result[$delta] = array(
'#markup' => check_plain($label),
);
}
}
}
break;
case 'user_reference_user':
// 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',
'limit' => 10,
'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['limit'] = array(
'#type' => 'select',
'#title' => t('Dropdown limit'),
'#options' => drupal_map_assoc(array(
5,
10,
15,
20,
25,
30,
35,
40,
45,
50,
)),
'#default_value' => $settings['limit'],
'#required' => TRUE,
'#description' => t('Maximum number of matched dropdown items displayed on the form. Overrides limit setting of the view if used.'),
);
$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.
*
* @codingStandardsIgnoreStart
*/
function user_reference_autocomplete_value($element, $input = FALSE, $form_state) {
// @codingStandardsIgnoreEnd
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']);
}
/**
* Options.
*
* 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 array $field
* The field definition.
*
* @return array
* An array of referenceable user names, keyed by user id.
*
* @codingStandardsIgnoreStart
*/
function _user_reference_options($field, $flat = TRUE) {
// @codingStandardsIgnoreEnd
$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 array $field
* The field definition.
* @param array $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 array
* 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)
* ),
* ...
* )
*
* @codingStandardsIgnoreStart
*/
function user_reference_potential_references($field, $options = array()) {
// @codingStandardsIgnoreEnd
// Fill in default options.
$options += array(
'string' => '',
'match' => 'contains',
'ids' => array(),
'limit' => 25,
);
$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) {
$filter_roles = array();
// Avoid useless work.
if (is_array($field['settings']['referenceable_roles'])) {
$filter_roles = array_filter($field['settings']['referenceable_roles']);
}
// $field['settings']['referenceable_status'] may be an int/boolean on D6
// upgraded sites.
$filter_status = array();
if (is_array($field['settings']['referenceable_status'])) {
// Selects only items in array that are not empty (true, 1, string, etc.)
$filter_status = array_filter($field['settings']['referenceable_status']);
}
if (!count($filter_status) && !count($filter_roles)) {
return array();
}
$query = db_select('users', 'u')
->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':
// No match type or incorrect match type: use "=".
default:
$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 = '') {
$instance = field_info_instance($entity_type, $field_name, $bundle);
$field = field_info_field($field_name);
$options = array(
'string' => $string,
'match' => $instance['widget']['settings']['autocomplete_match'],
'limit' => $instance['widget']['settings']['limit'],
);
$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 $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) = 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: 'options callback' for the views_handler_filter_in_operator filter.
*
* @param string $field_name
* The field name.
*
* @return array
* 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;
}