Index: userreference.module
===================================================================
--- userreference.module	(revision 31)
+++ userreference.module	(working copy)
@@ -1,613 +1,750 @@
-<?php
-// $Id: userreference.module,v 1.106.2.7 2008/07/07 13:15:31 yched Exp $
-
-/**
- * @file
- * Defines a field type for referencing a user from a node.
- */
-
-/**
- * Implementation of hook_menu().
- */
-function userreference_menu() {
-  $items = array();
-  $items['userreference/autocomplete'] = array(
-    'title' => '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' => isset($field['referenceable_roles']) && 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' => isset($field['referenceable_status']) && 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)) {
-          $error_element = isset($item['_error_element']) ? $item['_error_element'] : '';
-          unset($item['_error_element']);
-          if (!empty($item['uid']) && !array_key_exists($item['uid'], _userreference_potential_references($field, '', $item['uid']))) {
-            form_set_error($error_element, 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' => 'checkbox',
-        '#title' => t('Reverse link'),
-        '#default_value' => isset($field['reverse_link']) ? $field['reverse_link'] : 0,
-        '#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.
-    '#title' => $element['#title'],
-    '#required' => $element['#required'],
-    '#description' => $element['#description'],
-    '#field_name' => $element['#field_name'],
-    '#type_name' => $element['#type_name'],
-    '#delta' => $element['#delta'],
-    '#columns' => $element['#columns'],
-  );
-  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.
-    '#title' => $element['#title'],
-    '#required' => $element['#required'],
-    '#description' => $element['#description'],
-    '#field_name' => $element['#field_name'],
-    '#type_name' => $element['#type_name'],
-    '#delta' => $element['#delta'],
-    '#columns' => $element['#columns'],
-  );
-
-  // Used so that hook_field('validate') knows where to flag an error.
-  $element['_error_element'] = array(
-    '#type' => 'value',
-    // TODO: why do we need to repeat $field_key twice ?
-    '#value' => implode('][', array_merge($element['#parents'], array($field_key, $field_key))),
-  );
-  return $element;
-}
-
-/**
- * Afterbuild adjustment of the element.
- *
- * Remove the wrapper layer and set the right element's value.
- * We don't know exactly where this element is, so we drill down
- * through the element until we get to our key.
- */
-function userreference_select_validate($element, &$form_state) {
-  $field_key  = $element['#columns'][0];
-  $new_parents = array();
-  $value = $form_state['values'];
-  foreach ($element['#parents'] as $parent) {
-    $value = $value[$parent];
-    if ($parent == $field_key) {
-      $element['#parents'] = $new_parents;
-      form_set_value($element, $value, $form_state);
-      break;
-    }
-    $new_parents[] = $parent;
-  }
-}
-
-/**
- * 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) {
-  return _userreference_potential_references($field);
-}
-
-/**
- * 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 = '', $uid = NULL) {
-  $where = array();
-  $args = array();
-  $join = array();
-
-  if (!empty($string)) {
-    $where[] = "LOWER(name) LIKE LOWER('%s%%')";
-    $args[] = $string;
-  }
-  elseif (!empty($uid) && is_numeric($uid)) {
-    $where[] = "u.uid = $uid";
-  }
-  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':
-      // Only add links if we are on the user 'view' page.
-      if (arg(0) != 'user' || arg(2)) {
-        return;
-      }
-      // 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) {
-          // Only add tables when reverse link has been selected.
-          if ($field['type'] == 'userreference' && !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;
-        }
-      }
-      $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'];
+<?php
+// $Id: userreference.module,v 1.106.2.7 2008/07/07 13:15:31 yched Exp $
+
+/**
+ * @file
+ * Defines a field type for referencing a user from a node.
+ */
+
+/**
+ * Implementation of hook_menu().
+ */
+function userreference_menu() {
+  $items = array();
+  $items['userreference/autocomplete'] = array(
+    'title' => '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' => isset($field['referenceable_roles']) && 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' => isset($field['referenceable_status']) && is_array($field['referenceable_status']) ? array_filter($field['referenceable_status']) : array(1),
+        '#options' => array(1 => t('Active'), 0 => t('Blocked')),
+      );
+      if (module_exists('views')) {
+        $views = array('--' => '--');
+        $all_views = views_get_all_views();
+        foreach ($all_views as $view) {
+          // Only 'node' views that have fields will work for our purpose.
+          if ($view->base_table == 'users' && !empty($view->display['default']->display_options['fields'])) {
+            if ($view->type == 'Default') {
+              $views[t('Default Views')][$view->name] = $view->name;
+            }
+            else {
+              $views[t('Existing Views')][$view->name] = $view->name;
+            }
+          }
+        }
+
+        if (count($views) > 1) {
+          $form['advanced'] = array(
+             '#type' => 'fieldset',
+             '#title' => t('Advanced - Users that can be referenced (View)'),
+             '#collapsible' => TRUE,
+             '#collapsed' => !isset($field['advanced_view']) || $field['advanced_view'] == '--',
+           );
+          $form['advanced']['advanced_view'] = array(
+            '#type' => 'select',
+            '#title' => t('View used to select the users'),
+            '#options' => $views,
+            '#default_value' => isset($field['advanced_view']) ? $field['advanced_view'] : '--',
+            '#description' => t('Choose the "Views module" view that selects the users that can be referenced.<br />Note:<ul><li>Only views that have fields will work for this purpose.</li><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>'),
+          );
+          $form['advanced']['advanced_view_args'] = array(
+            '#type' => 'textfield',
+            '#title' => t('View arguments'),
+            '#default_value' => isset($field['advanced_view_args']) ? $field['advanced_view_args'] : '',
+            '#required' => FALSE,
+            '#description' => t('Provide a comma separated list of arguments to pass to the view.'),
+          );
+        }
+      }
+      return $form;
+
+    case 'save':
+			$settings = array('referenceable_roles', 'referenceable_status');
+      if (module_exists('views')) {
+        $settings[] = 'advanced_view';
+        $settings[] = 'advanced_view_args';
+      }			
+			return $settings;
+
+    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)) {
+          $error_element = isset($item['_error_element']) ? $item['_error_element'] : '';
+          unset($item['_error_element']);
+          if (!empty($item['uid']) && !array_key_exists($item['uid'], _userreference_potential_references($field, '', $item['uid']))) {
+            form_set_error($error_element, 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' => 'checkbox',
+        '#title' => t('Reverse link'),
+        '#default_value' => isset($field['reverse_link']) ? $field['reverse_link'] : 0,
+        '#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.
+    '#title' => $element['#title'],
+    '#required' => $element['#required'],
+    '#description' => $element['#description'],
+    '#field_name' => $element['#field_name'],
+    '#type_name' => $element['#type_name'],
+    '#delta' => $element['#delta'],
+    '#columns' => $element['#columns'],
+  );
+  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.
+    '#title' => $element['#title'],
+    '#required' => $element['#required'],
+    '#description' => $element['#description'],
+    '#field_name' => $element['#field_name'],
+    '#type_name' => $element['#type_name'],
+    '#delta' => $element['#delta'],
+    '#columns' => $element['#columns'],
+  );
+
+  // Used so that hook_field('validate') knows where to flag an error.
+  $element['_error_element'] = array(
+    '#type' => 'value',
+    // TODO: why do we need to repeat $field_key twice ?
+    '#value' => implode('][', array_merge($element['#parents'], array($field_key, $field_key))),
+  );
+  return $element;
+}
+
+/**
+ * Afterbuild adjustment of the element.
+ *
+ * Remove the wrapper layer and set the right element's value.
+ * We don't know exactly where this element is, so we drill down
+ * through the element until we get to our key.
+ */
+function userreference_select_validate($element, &$form_state) {
+  $field_key  = $element['#columns'][0];
+  $new_parents = array();
+  $value = $form_state['values'];
+  foreach ($element['#parents'] as $parent) {
+    $value = $value[$parent];
+    if ($parent == $field_key) {
+      $element['#parents'] = $new_parents;
+      form_set_value($element, $value, $form_state);
+      break;
+    }
+    $new_parents[] = $parent;
+  }
+}
+
+/**
+ * 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) {
+  $references = _userreference_potential_references($field);
+
+  $options = array();
+  foreach ($references as $key => $value) {
+    // Views theming runs check_plain (htmlentities) on the values.
+    // We reverse that with html_entity_decode.
+    $options[$key] = html_entity_decode(strip_tags($value['rendered']));
+  }
+  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 = '', $uid = NULL) {
+  static $results = array();
+
+  if (!isset($results[$field['field_name']][$string][$uid])) {
+    $references = FALSE;
+    if (module_exists('views') && !empty($field['advanced_view']) && $field['advanced_view'] != '--') {
+      $references = _userreference_potential_references_views($field, $string, $uid);
+    }
+    // If the view doesn't exist, we got FALSE, and fallback to the regular 'standard mode'.
+
+    if ($references === FALSE) {
+      $references = _userreference_potential_references_standard($field, $string, $uid);
+    }
+
+    // Store the results.
+    $results[$field['field_name']][$string][$uid] = $references;
+  }
+
+  return $results[$field['field_name']][$string][$uid];
+}
+
+/**
+ * Helper function for _userreference_potential_references():
+ * referenceable users defined by user role and status
+ */
+function _userreference_potential_references_standard($field, $string = '', $uid = NULL) {
+  $where = array();
+  $args = array();
+  $join = array();
+
+  if (!empty($string)) {
+    $where[] = "LOWER(name) LIKE LOWER('%s%%')";
+    $args[] = $string;
+  }
+  elseif (!empty($uid) && is_numeric($uid)) {
+    $where[] = "u.uid = $uid";
+  }
+  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;
+}
+
+/**
+ * Helper function for _userreference_potential_references():
+ * case of Views-defined referenceable users.
+ */
+function _userreference_potential_references_views($field, $string = '', $uid = NULL) {
+  $view_name = $field['advanced_view'];
+
+  if ($view = views_get_view($view_name)) {
+    // We add a display, and let it derive from the 'default' display.
+    // TODO: We should let the user pick a display in the fields settings - sort of requires AHAH...
+    $display = $view->add_display('userreference_simple');
+    $view->set_display($display);
+
+    // TODO from merlinofchaos on IRC : arguments using summary view can defeat the style setting.
+    // We might also need to check if there's an argument, and set *its* style_plugin as well.
+    $view->display_handler->set_option('style_plugin', 'userreference_php_array');
+    $view->display_handler->set_option('row_plugin', 'fields');
+
+    // Get arguments for the view.
+    if (!empty($field['advanced_view_args'])) {
+      // TODO: Support Tokens using token.module ?
+      $view_args = array_map('trim', explode(',', $field['advanced_view_args']));
+    }
+    else {
+      $view_args = array();
+    }
+
+    // We do need title field, so add it if not present (unlikely, but...)
+    $fields = $view->get_items('field', $display);
+    if (!isset($fields['name'])) {
+      $view->add_item($display, 'field', 'node', 'name');
+    }
+    if ($string !== '') {
+      // Add an instance of the 'title' filter.
+      $options = array('value' => $string, 'operator' => 'contains');
+      $id = $view->add_item($display, 'filter', 'node', 'name', $options);
+    }
+
+    // If not set, make all fields inline and define a separator.
+    $options = $view->display_handler->get_option('row_options');
+    if (empty($options['inline'])) {
+      $options['inline'] = drupal_map_assoc(array_keys($view->get_items('field', $display)));
+    }
+    if (empty($options['separator'])) {
+      $options['separator'] = '-';
+    }
+    $view->display_handler->set_option('row_options', $options);
+
+    // Make sure the query is not cached
+    $view->is_cacheable = FALSE;
+
+    // Get the results.
+    $result = $view->execute_display($display, $view_args);
+  }
+  else {
+    $result = FALSE;
+  }
+
+  return $result;
+}
+
+/**
+ * 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':
+      // Only add links if we are on the user 'view' page.
+      if (arg(0) != 'user' || arg(2)) {
+        return;
+      }
+      // 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) {
+          // Only add tables when reverse link has been selected.
+          if ($field['type'] == 'userreference' && !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;
+        }
+      }
+      $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'];
 }
\ No newline at end of file
Index: userreference.views.inc
===================================================================
--- userreference.views.inc	(revision 0)
+++ userreference.views.inc	(revision 0)
@@ -0,0 +1,104 @@
+<?php
+// $Id: userreference.views.inc,v 1.1.2.3 2008/08/07 21:23:00 yched Exp $
+
+/**
+ * @file
+ * Interface between userreference.module and views.module.
+ */
+function userreference_views_plugins() {
+  $path = drupal_get_path('module', 'userreference');
+  $plugins = array(
+    'module' => 'userreference', // This just tells our themes are elsewhere.
+    'display' => array(
+      'userreference_simple' => array(
+        // Those strings are not translated for now.
+        // We'll need to change that if / when we remove 'no ui'
+        'title' => 'Simple', // TODO: better name ? (currently not displayed anyway)
+        'help' => 'Destination-agnostic display. Mostly useful for programmatic views.',
+        'no ui' => TRUE, // Programmatic use only.
+        'handler' => 'userreference_plugin_display_simple',
+        'uses hook menu' => FALSE,
+        'use ajax' => FALSE,
+        'use pager' => FALSE,
+        'accept attachments' => FALSE,
+      ),
+    ),
+    'style' => array(
+      'userreference_php_array' => array(
+        // Those strings are not translated for now.
+        // We'll need to change that if / when we remove 'no ui'
+        'title' => 'Results array (with title)',
+        'help' => 'Returns the view as a PHP array of titles + rendered rows.',
+        'handler' => 'userreference_plugin_style_php_array',
+        'no ui' => TRUE, // Programmatic use only.
+        'uses row plugin' => TRUE,
+        'uses fields' => TRUE,
+        'type' => 'userref',
+        'even empty' => TRUE,
+      ),
+    ),
+  );
+  return $plugins;
+}
+
+/**
+ * Display plugin
+ */
+class userreference_plugin_display_simple extends views_plugin_display {
+  function execute() {
+    return $this->view->render($this->display->id);
+  }
+
+  function render() {
+    return !empty($this->view->result) || !empty($this->view->style_plugin->definition['even empty']) ? $this->view->style_plugin->render($this->view->result) : '';
+  }
+}
+
+/**
+ * Style plugin that simply return a PHP array of themed rows.
+ */
+class userreference_plugin_style_php_array extends views_plugin_style {
+  function render() {
+    $results = array();
+
+    // Group the rows according to the grouping field, if specified.
+    $sets = $this->render_grouping($this->view->result, $this->options['grouping']);
+
+    $base_field = $this->view->base_field;
+    $title_field = $this->view->field['name']->field_alias;
+
+    // TODO : We don't display grouping info for now.
+    // Could be useful for select widget, though.
+    foreach ($sets as $name => $records) {
+      foreach ($records as $label => $row) {
+        $results[$row->{$base_field}] = array(
+          'title' => $row->{$title_field},
+          'rendered' => $this->row_plugin->render($row),
+        );
+      }
+    }
+    return $results;
+  }
+
+//  function render() {
+//    $results = array();
+//    if (empty($this->row_plugin)) {
+//      vpr('userreference_plugin_style_results_array: Missing row plugin');
+//      return $results;
+//    }
+//
+//    // Group the rows according to the grouping field, if specified.
+//    $sets = $this->render_grouping($this->view->result, $this->options['grouping']);
+//
+//    // Render each group separately.
+//    $base_field = $this->view->base_field;
+//    foreach ($sets as $title => $records) {
+//      $rows = array();
+//      foreach ($records as $label => $row) {
+//        $rows[$row->{$base_field}] = $this->row_plugin->render($row);
+//      }
+//      $results[$title] = $rows;
+//    }
+//    return $results;
+//  }
+}
