t('Userreference autocomplete'), 'page callback' => 'userreference_autocomplete', 'access arguments' => array('access content'), 'type' => MENU_CALLBACK ); return $items; } /** * Implementation of hook_theme(). */ function userreference_theme() { return array( 'userreference_select' => array( 'arguments' => array('element' => NULL), ), 'userreference_autocomplete' => array( 'arguments' => array('element' => NULL), ), 'userreference_formatter_default' => array( 'arguments' => array('element'), ), 'userreference_formatter_plain' => array( 'arguments' => array('element'), ), ); } /** * Implementation of hook_field_info(). * * Here we indicate that the content module will use its default * handling for the view of this field. */ function userreference_field_info() { return array( 'userreference' => array( 'label' => t('User Reference'), 'description' => t('Store the id of a related user as an integer value.'), 'callbacks' => array( 'tables' => CONTENT_CALLBACK_DEFAULT, 'arguments' => CONTENT_CALLBACK_DEFAULT, ), ), ); } /** * Implementation of hook_field_settings(). */ function userreference_field_settings($op, $field) { switch ($op) { case 'form': $form = array(); $form['referenceable_roles'] = array( '#type' => 'checkboxes', '#title' => t('User roles that can be referenced'), '#default_value' => is_array($field['referenceable_roles']) ? array_filter($field['referenceable_roles']) : array(), '#options' => user_roles(1), ); $form['referenceable_status'] = array( '#type' => 'checkboxes', '#title' => t('User status that can be referenced'), '#default_value' => is_array($field['referenceable_status']) ? array_filter($field['referenceable_status']) : array(1), '#options' => array(1 => t('Active'), 0 => t('Blocked')), ); return $form; case 'save': return array('referenceable_roles', 'referenceable_status'); case 'database columns': $columns = array( 'uid' => array('type' => 'int', 'unsigned' => TRUE, 'not null' => FALSE), ); return $columns; case 'views data': $data = content_views_field_views_data($field); $db_info = content_database_info($field); $table_alias = content_views_tablename($field); // Swap the filter handler to the 'in' operator. $data[$table_alias][$field['field_name'] .'_uid']['filter']['handler'] = 'views_handler_filter_many_to_one_content'; // Add a relationship for related user. $data[$table_alias][$field['field_name'] .'_uid']['relationship'] = array( 'base' => 'users', 'field' => $db_info['columns']['uid']['column'], 'handler' => 'views_handler_relationship', ); return $data; } } /** * Implementation of hook_field(). */ function userreference_field($op, &$node, $field, &$items, $teaser, $page) { switch ($op) { case 'validate': foreach ($items as $delta => $item) { if (is_array($item) && !empty($item['error_field'])) { $error_field = $item['error_field']; unset($item['error_field']); if (!empty($item['uid']) && !in_array($item['uid'], array_keys(_userreference_potential_references($field)))) { form_set_error($error_field, t('%name : Invalid user.', array('%name' => t($field['widget']['label'])))); } } } return; } } /** * Implementation of hook_content_is_empty(). */ function userreference_content_is_empty($item, $field) { if (empty($item['uid'])) { return TRUE; } return FALSE; } /** * Implementation of hook_field_formatter_info(). */ function userreference_field_formatter_info() { return array( 'default' => array( 'label' => t('Default'), 'field types' => array('userreference'), 'multiple values' => CONTENT_HANDLE_CORE, ), 'plain' => array( 'label' => t('Plain text'), 'field types' => array('userreference'), 'multiple values' => CONTENT_HANDLE_CORE, ), ); } /** * Theme function for 'default' userreference field formatter. */ function theme_userreference_formatter_default($element) { $output = ''; if (isset($element['#item']['uid']) && $account = user_load(array('uid' => $element['#item']['uid']))) { $output = theme('username', $account); } return $output; } /** * Theme function for 'plain' userreference field formatter. */ function theme_userreference_formatter_plain($element) { $output = ''; if (isset($element['#item']['uid']) && $account = user_load(array('uid' => $element['#item']['uid']))) { $output = $account->name; } return $output; } /** * Implementation of hook_widget_info(). * * We need custom handling of multiple values for userreference_select * because we need to combine them into a options list rather than * display multiple elements. * * We will use the content module's default handling for default value. * * Callbacks can be omitted if default handing is used. * They're included here just so this module can be used * as an example for custom modules that might do things * differently. */ function userreference_widget_info() { return array( 'userreference_select' => array( 'label' => t('Select List'), 'field types' => array('userreference'), 'multiple values' => CONTENT_HANDLE_MODULE, 'callbacks' => array( 'default value' => CONTENT_CALLBACK_DEFAULT, ), ), 'userreference_autocomplete' => array( 'label' => t('Autocomplete Text Field'), 'field types' => array('userreference'), 'multiple values' => CONTENT_HANDLE_CORE, 'callbacks' => array( 'default value' => CONTENT_CALLBACK_DEFAULT, ), ), ); } /** * Implementation of FAPI hook_elements(). * * Any FAPI callbacks needed for individual widgets can be declared here, * and the element will be passed to those callbacks for processing. * * Drupal will automatically theme the element using a theme with * the same name as the hook_elements key. * * Autocomplete_path is not used by text_widget but other widgets can use it * (see nodereference and userreference). */ function userreference_elements() { return array( 'userreference_select' => array( '#input' => TRUE, '#columns' => array('uid'), '#delta' => 0, '#process' => array('userreference_select_process'), ), 'userreference_autocomplete' => array( '#input' => TRUE, '#columns' => array('name'), '#delta' => 0, '#process' => array('userreference_autocomplete_process'), '#autocomplete_path' => FALSE, ), ); } /** * Implementation of hook_widget_settings(). */ function userreference_widget_settings($op, $field) { switch ($op) { case 'form': $form = array(); $form['reverse_link'] = array( '#type' => 'radios', '#title' => t('Reverse Link'), '#default_value' => isset($field['reverse_link']) ? $field['reverse_link'] : 0, '#options' => array(0 => t('No'), 1 => t('Yes')), '#required' => TRUE, '#description' => t('If selected, a reverse link back to the referencing node will displayed on the referenced user record.'), ); return $form; break; case 'save': return array('reverse_link'); break; } } /** * Implementation of hook_widget(). * * Attach a single form element to the form. It will be built out and * validated in the callback(s) listed in hook_elements. We build it * out in the callbacks rather than here in hook_widget so it can be * plugged into any module that can provide it with valid * $field information. * * Content module will set the weight, field name and delta values * for each form element. This is a change from earlier CCK versions * where the widget managed its own multiple values. * * If there are multiple values for this field, the content module will * call this function as many times as needed. * * @param $form * the entire form array, $form['#node'] holds node information * @param $form_state * the form_state, $form_state['values'][$field['field_name']] * holds the field's form values. * @param $field * the field array * @param $items * array of default values for this field * @param $delta * the order of this item in the array of subelements (0, 1, 2, etc) * * @return * the form item for a single element for this field */ function userreference_widget(&$form, &$form_state, $field, $items, $delta = 0) { switch ($field['widget']['type']) { case 'userreference_select': $element = array( '#type' => 'userreference_select', '#default_value' => $items, ); break; case 'userreference_autocomplete': $element = array( '#type' => 'userreference_autocomplete', '#default_value' => isset($items[$delta]) ? $items[$delta] : NULL, '#value_callback' => 'userreference_autocomplete_value', ); break; } return $element; } /** * Value for a userreference autocomplete element. * * Substitute in the user name for the uid. */ function userreference_autocomplete_value($element, $edit = FALSE) { $field_key = $element['#columns'][0]; if (!empty($element['#default_value'][$field_key])) { $value = db_result(db_query("SELECT name FROM {users} WHERE uid = '%d'", $element['#default_value'][$field_key])); return array($field_key => $value); } return array($field_key => NULL); } /** * Process an individual element. * * Build the form element. When creating a form using FAPI #process, * note that $element['#value'] is already set. * * The $fields array is in $form['#field_info'][$element['#field_name']]. */ function userreference_select_process($element, $edit, $form_state, $form) { // The userreference_select widget doesn't need to create its own // element, it can wrap around the optionwidgets_select element. // Add a validation step where the value can be unwrapped. $field_key = $element['#columns'][0]; $element[$field_key] = array( '#type' => 'optionwidgets_select', '#default_value' => isset($element['#value']) ? $element['#value'] : '', '#element_validate' => array('optionwidgets_validate', 'userreference_select_validate'), // The following values were set by the content module and need // to be passed down to the nested element. '#field_name' => $element['#field_name'], '#delta' => $element['#delta'], '#columns' => $element['#columns'], '#title' => $element['#title'], '#required' => $element['#required'], '#description' => $element['#description'], ); return $element; } /** * Process an individual element. * * Build the form element. When creating a form using FAPI #process, * note that $element['#value'] is already set. * * The $fields array is in $form['#field_info'][$element['#field_name']]. */ function userreference_autocomplete_process($element, $edit, $form_state, $form) { // The userreference autocomplete widget doesn't need to create its own // element, it can wrap around the text_textfield element and add an autocomplete // path and some extra processing to it. // Add a validation step where the value can be unwrapped. $field_key = $element['#columns'][0]; $element[$field_key] = array( '#type' => 'text_textfield', '#default_value' => isset($element['#value']) ? $element['#value'] : '', '#autocomplete_path' => 'userreference/autocomplete/'. $element['#field_name'], '#element_validate' => array('userreference_autocomplete_validate'), // The following values were set by the content module and need // to be passed down to the nested element. '#field_name' => $element['#field_name'], '#delta' => $element['#delta'], '#columns' => $element['#columns'], '#title' => $element['#title'], '#required' => $element['#required'], '#description' => $element['#description'], ); return $element; } /** * Afterbuild adjustment of the element. * * Remove the wrapper layer and set the right element's value. */ function userreference_select_validate($element, &$form_state) { $field_key = $element['#columns'][0]; array_pop($element['#parents']); form_set_value($element, $form_state['values'][$element['#field_name']][$field_key], $form_state); } /** * Validate an autocomplete element. * * Remove the wrapper layer and set the right element's value. */ function userreference_autocomplete_validate($element, &$form_state) { $field_key = $element['#columns'][0]; $user = $element['#value'][$field_key]; $uid = NULL; if (!empty($user)) { $uid = db_result(db_query("SELECT uid FROM {users} WHERE name = '%s'", $user)); } form_set_value($element, $uid, $form_state); } /** * Implementation of hook_allowed_values(). */ function userreference_allowed_values($field) { $options = _userreference_potential_references($field); if (!$field['required']) { $options = array('none' => '<'. t('none') .'>') + $options; } return $options; } /** * Menu callback; Retrieve a pipe delimited string of autocomplete suggestions for existing users */ function userreference_autocomplete($field_name, $string = '') { $fields = content_fields(); $field = $fields[$field_name]; $matches = array(); if ($string) { foreach (_userreference_potential_references($field, $string) as $uid => $name) { $matches[$name] = check_plain($name); } } drupal_json($matches); } /** * Fetch an array of all candidate referenced users, for use in presenting the selection form to the user. */ function _userreference_potential_references($field, $string = '') { $where = array(); $args = array(); $join = array(); if (!empty($string)) { $where[] = "LOWER(name) LIKE LOWER('%s%%')"; $args[] = $string; } else { $where[] = "u.uid > 0"; } $roles = array(); if (isset($field['referenceable_roles']) && is_array($field['referenceable_roles'])) { // keep only selected checkboxes $roles = array_filter($field['referenceable_roles']); // filter invalid values that seems to get through sometimes ?? $roles = array_intersect(array_keys(user_roles(1)), $roles); } if (!empty($roles) && !in_array(DRUPAL_AUTHENTICATED_RID, $roles)) { $where[] = "r.rid IN (". implode($roles, ',') .")"; $join[] = 'LEFT JOIN {users_roles} r ON u.uid = r.uid'; } $status = array(); if (isset($field['referenceable_status']) && is_array($field['referenceable_status'])) { // keep only selected checkboxes $status = array_filter($field['referenceable_status']); } if (!empty($status)) { // Limit query if only one status should be referenced. if (count($status) == 1) { $where[] = "u.status = ". array_pop($status); } } $users = array(); $result = db_query('SELECT u.name, u.uid FROM {users} u '. implode(' ', $join) .' WHERE '. implode(' AND ', $where) .' ORDER BY u.name ASC', $args); while ($user = db_fetch_object($result)) { $users[$user->uid] = $user->name; } return $users; } /** * Provide a list of users to filter on. */ function _userreference_filter_handler($op, $filterinfo) { $options = views_handler_filter_usercurrent(); $options = $options + _userreference_potential_references($filterinfo['extra']['field']); return $options; } /** * Implementation of hook_user(). */ function userreference_user($type, &$edit, &$account) { switch ($type) { case 'load': // find CCK userreference field tables // search through them for matching user ids and load those nodes $additions = array(); $types = content_types(); // Find the table and columns to search through, if the same // table comes up in more than one content type, we only need // to search it once. $search_tables = array(); $search_links = array(); foreach ($types as $type_name => $type) { foreach ($type['fields'] as $field) { if ($field['type'] == 'userreference') { $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; } } $account->userreference = $additions; return; break; case 'view': if (!empty($account->userreference)) { $node_types = content_types(); $additions = array(); $values = array(); foreach ($account->userreference 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' => $node_types[$node_type]['name'], '#value' => theme('item_list', $values[$node_type]), ); } } if ($additions) { $account->content['userreference'] = $additions + array( '#type' => 'user_profile_category', '#attributes' => array('class' => 'user-member'), '#title' => t('Related Content'), '#weight' => 10, ); } } break; } } /** * FAPI theme for an individual elements. * * The textfield or select is already rendered by the * textfield or select themes and the HTML output * lives in $element['#children']. Override this theme to * make custom changes to the output. * * $element['#field_name'] contains the field name * $element['#delta] is the position of this element in the group */ function theme_userreference_select($element) { return $element['#children']; } function theme_userreference_autocomplete($element) { return $element['#children']; }