Revert #780566 by stella, add drupal_alter() for content_allowed_values lists created...
[project/cck.git] / modules / userreference / userreference.module
CommitLineData
e1a2f8a6
JC
1<?php
2// $Id$
3
4/**
5 * @file
0f6ca9bb 6 * Defines a field type for referencing a user from a node.
e1a2f8a6
JC
7 */
8
9/**
1db0519e
KS
10 * Implementation of hook_menu().
11 */
12function userreference_menu() {
13 $items = array();
14 $items['userreference/autocomplete'] = array(
c56d4554 15 'title' => 'Userreference autocomplete',
1db0519e
KS
16 'page callback' => 'userreference_autocomplete',
17 'access arguments' => array('access content'),
18 'type' => MENU_CALLBACK
19 );
20 return $items;
21}
22
23/**
ea73d362
KS
24 * Implementation of hook_theme().
25 */
26function userreference_theme() {
27 return array(
28 'userreference_select' => array(
29 'arguments' => array('element' => NULL),
30 ),
4ae62d83
YC
31 'userreference_buttons' => array(
32 'arguments' => array('element' => NULL),
33 ),
ea73d362
KS
34 'userreference_autocomplete' => array(
35 'arguments' => array('element' => NULL),
36 ),
e74ada9a
YC
37 'userreference_formatter_default' => array(
38 'arguments' => array('element'),
39 ),
40 'userreference_formatter_plain' => array(
41 'arguments' => array('element'),
42 ),
ea73d362
KS
43 );
44}
45
46/**
aff1f800
YC
47 * Implementaion of hook_ctools_plugin_directory().
48 */
49function userreference_ctools_plugin_directory($module, $plugin) {
50 if ($module == 'ctools' && $plugin == 'relationships') {
51 return 'panels/' . $plugin;
52 }
53}
54
55/**
e1a2f8a6
JC
56 * Implementation of hook_field_info().
57 */
58function userreference_field_info() {
59 return array(
e9b6b501 60 'userreference' => array(
2400f882
KS
61 'label' => t('User reference'),
62 'description' => t('Store the ID of a related user as an integer value.'),
c5482d81 63// 'content_icon' => 'icon_content_noderef.png',
6d7a097b 64 ),
e1a2f8a6
JC
65 );
66}
67
68/**
69 * Implementation of hook_field_settings().
70 */
e2c6522a 71function userreference_field_settings($op, $field) {
e1a2f8a6 72 switch ($op) {
bafef6a2
JC
73 case 'form':
74 $form = array();
75 $form['referenceable_roles'] = array(
76 '#type' => 'checkboxes',
77 '#title' => t('User roles that can be referenced'),
1aed1a15 78 '#default_value' => isset($field['referenceable_roles']) && is_array($field['referenceable_roles']) ? array_filter($field['referenceable_roles']) : array(),
bafef6a2
JC
79 '#options' => user_roles(1),
80 );
1db0519e 81 $form['referenceable_status'] = array(
47abf3a7 82 '#type' => 'radios',
1db0519e 83 '#title' => t('User status that can be referenced'),
01c8a292 84 '#default_value' => isset($field['referenceable_status']) ? $field['referenceable_status'] : '',
47abf3a7 85 '#options' => array('' => t('All users'), 1 => t('Active users'), 0 => t('Blocked users')),
1db0519e 86 );
2a1f05cb
YC
87 if (module_exists('views')) {
88 $views = array('--' => '--');
89 $all_views = views_get_all_views();
90 foreach ($all_views as $view) {
47c2cc9e 91 // Only 'users' views that have fields will work for our purpose.
2a1f05cb
YC
92 if ($view->base_table == 'users' && !empty($view->display['default']->display_options['fields'])) {
93 if ($view->type == 'Default') {
94 $views[t('Default Views')][$view->name] = $view->name;
95 }
96 else {
97 $views[t('Existing Views')][$view->name] = $view->name;
98 }
99 }
100 }
101
0e968023
YC
102 $form['advanced'] = array(
103 '#type' => 'fieldset',
104 '#title' => t('Advanced - Users that can be referenced (View)'),
105 '#collapsible' => TRUE,
106 '#collapsed' => !isset($field['advanced_view']) || $field['advanced_view'] == '--',
107 );
2a1f05cb 108 if (count($views) > 1) {
2a1f05cb
YC
109 $form['advanced']['advanced_view'] = array(
110 '#type' => 'select',
111 '#title' => t('View used to select the users'),
112 '#options' => $views,
113 '#default_value' => isset($field['advanced_view']) ? $field['advanced_view'] : '--',
0e968023
YC
114 '#description' => t('<p>Choose the "Views module" view that selects the users that can be referenced.<br />Note:</p>') .
115 t('<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>'),
2a1f05cb
YC
116 );
117 $form['advanced']['advanced_view_args'] = array(
118 '#type' => 'textfield',
119 '#title' => t('View arguments'),
120 '#default_value' => isset($field['advanced_view_args']) ? $field['advanced_view_args'] : '',
121 '#required' => FALSE,
122 '#description' => t('Provide a comma separated list of arguments to pass to the view.'),
123 );
124 }
0e968023
YC
125 else {
126 $form['advanced']['no_view_help'] = array(
127 '#value' => t('<p>The list of user that can be referenced can be based on a "Views module" view but no appropriate views were found. <br />Note:</p>') .
128 t('<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>'),
129 );
130 }
2a1f05cb 131 }
bafef6a2
JC
132 return $form;
133
134 case 'save':
2a1f05cb
YC
135 $settings = array('referenceable_roles', 'referenceable_status');
136 if (module_exists('views')) {
137 $settings[] = 'advanced_view';
138 $settings[] = 'advanced_view_args';
139 }
140 return $settings;
bafef6a2 141
4af0dd2f
JC
142 case 'database columns':
143 $columns = array(
972214b8 144 'uid' => array('type' => 'int', 'unsigned' => TRUE, 'not null' => FALSE, 'index' => TRUE),
01bf6a48
YC
145 );
146 return $columns;
147
330807d0
KS
148 case 'views data':
149 $data = content_views_field_views_data($field);
150 $db_info = content_database_info($field);
f33cc12c
KS
151 $table_alias = content_views_tablename($field);
152
47f94b0d 153 // Filter : swap the handler to the 'in' operator.
bea5ae87 154 $data[$table_alias][$field['field_name'] .'_uid']['filter']['handler'] = 'content_handler_filter_many_to_one';
017108ce
YC
155 // Argument: get the user name for summaries.
156 // We need to join a new instance of the users table.
157 $data["users_$table_alias"]['table']['join']['node'] = array(
158 'table' => 'users',
159 'field' => 'uid',
160 'left_table' => $table_alias,
161 'left_field' => $field['field_name'] .'_uid',
162 );
2a63f940 163 $data[$table_alias][$field['field_name'] .'_uid']['argument']['handler'] = 'content_handler_argument_reference';
017108ce 164 $data[$table_alias][$field['field_name'] .'_uid']['argument']['name table'] = "users_$table_alias";
d0371d94 165 $data[$table_alias][$field['field_name'] .'_uid']['argument']['name field'] = 'name';
47f94b0d 166 // Relationship: Add a relationship for related user.
f33cc12c 167 $data[$table_alias][$field['field_name'] .'_uid']['relationship'] = array(
330807d0
KS
168 'base' => 'users',
169 'field' => $db_info['columns']['uid']['column'],
0bb3edd5 170 'handler' => 'content_handler_relationship',
f458bc00 171 'label' => t($field['widget']['label']),
b6ca681f 172 'content_field_name' => $field['field_name'],
330807d0
KS
173 );
174 return $data;
175
e1a2f8a6
JC
176 }
177}
178
179/**
180 * Implementation of hook_field().
181 */
1fbe637d
KS
182function userreference_field($op, &$node, $field, &$items, $teaser, $page) {
183 switch ($op) {
184 case 'validate':
14ddf71e
YC
185 // Extract uids to check.
186 $ids = array();
187 foreach ($items as $delta => $item) {
91a7f662
YC
188 if (is_array($item) && !empty($item['uid'])) {
189 if (is_numeric($item['uid'])) {
190 $ids[] = $item['uid'];
191 }
192 else {
193 $error_element = isset($item['_error_element']) ? $item['_error_element'] : '';
194 if (is_array($item) && isset($item['_error_element'])) unset($item['_error_element']);
195 form_set_error($error_element, t('%name: invalid input.', array('%name' => t($field['widget']['label']))));
196 }
14ddf71e
YC
197 }
198 }
91a7f662
YC
199 // Prevent performance hog if there are no ids to check.
200 if ($ids) {
201 $refs = _userreference_potential_references($field, '', NULL, $ids);
202 foreach ($items as $delta => $item) {
203 if (is_array($item)) {
204 $error_element = isset($item['_error_element']) ? $item['_error_element'] : '';
205 if (is_array($item) && isset($item['_error_element'])) unset($item['_error_element']);
206 if (!empty($item['uid']) && !isset($refs[$item['uid']])) {
207 form_set_error($error_element, t('%name: invalid user.', array('%name' => t($field['widget']['label']))));
208 }
24caa5e4 209 }
1fbe637d
KS
210 }
211 }
14ddf71e 212 return $items;
6130c745
KS
213 }
214}
e9b6b501 215
6130c745
KS
216/**
217 * Implementation of hook_content_is_empty().
218 */
219function userreference_content_is_empty($item, $field) {
220 if (empty($item['uid'])) {
221 return TRUE;
1fbe637d 222 }
6130c745 223 return FALSE;
1fbe637d 224}
e1a2f8a6 225
319350d8 226/**
7e087396 227 * Implementation of hook_field_formatter_info().
319350d8 228 */
7e087396
KS
229function userreference_field_formatter_info() {
230 return array(
231 'default' => array(
d4353143 232 'label' => t('Default'),
7e087396 233 'field types' => array('userreference'),
089838e7 234 'multiple values' => CONTENT_HANDLE_CORE,
7e087396
KS
235 ),
236 'plain' => array(
d4353143 237 'label' => t('Plain text'),
7e087396 238 'field types' => array('userreference'),
089838e7 239 'multiple values' => CONTENT_HANDLE_CORE,
7e087396
KS
240 ),
241 );
242}
243
244/**
e74ada9a 245 * Theme function for 'default' userreference field formatter.
7e087396 246 */
e74ada9a
YC
247function theme_userreference_formatter_default($element) {
248 $output = '';
bb471092
YC
249
250 if (isset($element['#item']['uid']) && $account = user_load(array('uid' => $element['#item']['uid']))) {
251 $output = theme('username', $account);
c82e814c 252 }
e74ada9a
YC
253 return $output;
254}
aa5d88fc 255
e74ada9a
YC
256/**
257 * Theme function for 'plain' userreference field formatter.
258 */
259function theme_userreference_formatter_plain($element) {
260 $output = '';
bb471092 261 if (isset($element['#item']['uid']) && $account = user_load(array('uid' => $element['#item']['uid']))) {
e74ada9a 262 $output = $account->name;
7e087396 263 }
e74ada9a 264 return $output;
319350d8
JC
265}
266
e1a2f8a6 267/**
1182d802 268 * Implementation of hook_widget_info().
e9b6b501 269 *
4ae62d83
YC
270 * We need custom handling of multiple values for the userreference_select
271 * widget because we need to combine them into a options list rather
272 * than display multiple elements.
e9b6b501
KS
273 *
274 * We will use the content module's default handling for default value.
275 *
276 * Callbacks can be omitted if default handing is used.
277 * They're included here just so this module can be used
278 * as an example for custom modules that might do things
279 * differently.
e1a2f8a6 280 */
1182d802
JC
281function userreference_widget_info() {
282 return array(
283 'userreference_select' => array(
2400f882 284 'label' => t('Select list'),
1182d802 285 'field types' => array('userreference'),
a4418efc 286 'multiple values' => CONTENT_HANDLE_MODULE,
e9b6b501
KS
287 'callbacks' => array(
288 'default value' => CONTENT_CALLBACK_DEFAULT,
4ae62d83
YC
289 ),
290 ),
291 'userreference_buttons' => array(
292 'label' => t('Check boxes/radio buttons'),
293 'field types' => array('userreference'),
294 'multiple values' => CONTENT_HANDLE_MODULE,
295 'callbacks' => array(
296 'default value' => CONTENT_CALLBACK_DEFAULT,
297 ),
1182d802
JC
298 ),
299 'userreference_autocomplete' => array(
2400f882 300 'label' => t('Autocomplete text field'),
1182d802 301 'field types' => array('userreference'),
a4418efc 302 'multiple values' => CONTENT_HANDLE_CORE,
e9b6b501
KS
303 'callbacks' => array(
304 'default value' => CONTENT_CALLBACK_DEFAULT,
4ae62d83 305 ),
1182d802
JC
306 ),
307 );
308}
e1a2f8a6 309
1182d802 310/**
ea73d362
KS
311 * Implementation of FAPI hook_elements().
312 *
313 * Any FAPI callbacks needed for individual widgets can be declared here,
314 * and the element will be passed to those callbacks for processing.
315 *
316 * Drupal will automatically theme the element using a theme with
317 * the same name as the hook_elements key.
318 *
319 * Autocomplete_path is not used by text_widget but other widgets can use it
320 * (see nodereference and userreference).
321 */
322function userreference_elements() {
323 return array(
324 'userreference_select' => array(
325 '#input' => TRUE,
326 '#columns' => array('uid'), '#delta' => 0,
327 '#process' => array('userreference_select_process'),
4ae62d83
YC
328 ),
329 'userreference_buttons' => array(
330 '#input' => TRUE,
331 '#columns' => array('uid'), '#delta' => 0,
332 '#process' => array('userreference_buttons_process'),
333 ),
ea73d362
KS
334 'userreference_autocomplete' => array(
335 '#input' => TRUE,
336 '#columns' => array('name'), '#delta' => 0,
337 '#process' => array('userreference_autocomplete_process'),
338 '#autocomplete_path' => FALSE,
339 ),
340 );
341}
342
343/**
5e218086
KS
344 * Implementation of hook_widget_settings().
345 */
14ddf71e 346function userreference_widget_settings($op, $widget) {
5e218086
KS
347 switch ($op) {
348 case 'form':
349 $form = array();
14ddf71e 350 $match = isset($widget['autocomplete_match']) ? $widget['autocomplete_match'] : 'contains';
0a6df163 351 $size = (isset($widget['size']) && is_numeric($widget['size'])) ? $widget['size'] : 60;
14ddf71e
YC
352 if ($widget['type'] == 'userreference_autocomplete') {
353 $form['autocomplete_match'] = array(
354 '#type' => 'select',
355 '#title' => t('Autocomplete matching'),
356 '#default_value' => $match,
357 '#options' => array(
358 'starts_with' => t('Starts with'),
359 'contains' => t('Contains'),
360 ),
361 '#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.'),
362 );
0a6df163
YC
363 $form['size'] = array(
364 '#type' => 'textfield',
365 '#title' => t('Size of textfield'),
366 '#default_value' => $size,
367 '#element_validate' => array('_element_validate_integer_positive'),
368 '#required' => TRUE,
369 );
14ddf71e
YC
370 }
371 else {
372 $form['autocomplete_match'] = array('#type' => 'hidden', '#value' => $match);
bdb1b457 373 $form['size'] = array('#type' => 'hidden', '#value' => $size);
14ddf71e 374 }
5e218086 375 $form['reverse_link'] = array(
eeb67ed7 376 '#type' => 'checkbox',
2400f882 377 '#title' => t('Reverse link'),
2eb42d57 378 '#default_value' => isset($widget['reverse_link']) ? $widget['reverse_link'] : 0,
5e218086
KS
379 '#description' => t('If selected, a reverse link back to the referencing node will displayed on the referenced user record.'),
380 );
381 return $form;
5e218086
KS
382
383 case 'save':
bdb1b457 384 return array('autocomplete_match', 'size', 'reverse_link');
5e218086
KS
385 }
386}
387
388/**
1182d802 389 * Implementation of hook_widget().
e9b6b501
KS
390 *
391 * Attach a single form element to the form. It will be built out and
392 * validated in the callback(s) listed in hook_elements. We build it
393 * out in the callbacks rather than here in hook_widget so it can be
394 * plugged into any module that can provide it with valid
395 * $field information.
396 *
397 * Content module will set the weight, field name and delta values
398 * for each form element. This is a change from earlier CCK versions
399 * where the widget managed its own multiple values.
400 *
401 * If there are multiple values for this field, the content module will
402 * call this function as many times as needed.
403 *
404 * @param $form
405 * the entire form array, $form['#node'] holds node information
406 * @param $form_state
407 * the form_state, $form_state['values'][$field['field_name']]
408 * holds the field's form values.
409 * @param $field
410 * the field array
411 * @param $items
412 * array of default values for this field
413 * @param $delta
414 * the order of this item in the array of subelements (0, 1, 2, etc)
415 *
416 * @return
417 * the form item for a single element for this field
1182d802 418 */
e9b6b501 419function userreference_widget(&$form, &$form_state, $field, $items, $delta = 0) {
e9b6b501
KS
420 switch ($field['widget']['type']) {
421 case 'userreference_select':
e9b6b501 422 $element = array(
ea73d362 423 '#type' => 'userreference_select',
e9b6b501
KS
424 '#default_value' => $items,
425 );
426 break;
1182d802 427
4ae62d83
YC
428 case 'userreference_buttons':
429 $element = array(
430 '#type' => 'userreference_buttons',
431 '#default_value' => $items,
432 );
433 break;
434
e9b6b501 435 case 'userreference_autocomplete':
e9b6b501 436 $element = array(
ea73d362 437 '#type' => 'userreference_autocomplete',
e9b6b501
KS
438 '#default_value' => isset($items[$delta]) ? $items[$delta] : NULL,
439 '#value_callback' => 'userreference_autocomplete_value',
e9b6b501
KS
440 );
441 break;
442 }
443 return $element;
444}
4af0dd2f 445
e9b6b501 446/**
92efa608
KS
447 * Value for a userreference autocomplete element.
448 *
449 * Substitute in the user name for the uid.
450 */
451function userreference_autocomplete_value($element, $edit = FALSE) {
77684855
KS
452 $field_key = $element['#columns'][0];
453 if (!empty($element['#default_value'][$field_key])) {
505c571a 454 $value = db_result(db_query("SELECT name FROM {users} WHERE uid = '%d'", $element['#default_value'][$field_key]));
77684855 455 return array($field_key => $value);
92efa608 456 }
77684855 457 return array($field_key => NULL);
92efa608
KS
458}
459
460/**
ea73d362
KS
461 * Process an individual element.
462 *
463 * Build the form element. When creating a form using FAPI #process,
464 * note that $element['#value'] is already set.
e9b6b501 465 *
32ead958 466 * The $fields array is in $form['#field_info'][$element['#field_name']].
e9b6b501 467 */
ea73d362
KS
468function userreference_select_process($element, $edit, $form_state, $form) {
469 // The userreference_select widget doesn't need to create its own
470 // element, it can wrap around the optionwidgets_select element.
471 // Add a validation step where the value can be unwrapped.
472 $field_key = $element['#columns'][0];
473 $element[$field_key] = array(
474 '#type' => 'optionwidgets_select',
443b2f04 475 '#default_value' => isset($element['#value']) ? $element['#value'] : '',
8c06d539
KS
476 // The following values were set by the content module and need
477 // to be passed down to the nested element.
8c06d539 478 '#title' => $element['#title'],
341f4230 479 '#required' => $element['#required'],
7dbb8603 480 '#description' => $element['#description'],
07c92176
YC
481 '#field_name' => $element['#field_name'],
482 '#type_name' => $element['#type_name'],
483 '#delta' => $element['#delta'],
484 '#columns' => $element['#columns'],
ea73d362 485 );
617d3264
KS
486 if (empty($element[$field_key]['#element_validate'])) {
487 $element[$field_key]['#element_validate'] = array();
488 }
489 array_unshift($element[$field_key]['#element_validate'], 'userreference_optionwidgets_validate');
ea73d362
KS
490 return $element;
491}
492
493/**
494 * Process an individual element.
495 *
496 * Build the form element. When creating a form using FAPI #process,
497 * note that $element['#value'] is already set.
498 *
32ead958 499 * The $fields array is in $form['#field_info'][$element['#field_name']].
ea73d362 500 */
4ae62d83
YC
501function userreference_buttons_process($element, $edit, $form_state, $form) {
502 // The userreference_select widget doesn't need to create its own
503 // element, it can wrap around the optionwidgets_select element.
504 // Add a validation step where the value can be unwrapped.
505 $field_key = $element['#columns'][0];
506 $element[$field_key] = array(
507 '#type' => 'optionwidgets_buttons',
508 '#default_value' => isset($element['#value']) ? $element['#value'] : '',
4ae62d83
YC
509 // The following values were set by the content module and need
510 // to be passed down to the nested element.
511 '#title' => $element['#title'],
512 '#required' => $element['#required'],
513 '#description' => $element['#description'],
514 '#field_name' => $element['#field_name'],
515 '#type_name' => $element['#type_name'],
516 '#delta' => $element['#delta'],
517 '#columns' => $element['#columns'],
518 );
617d3264
KS
519 if (empty($element[$field_key]['#element_validate'])) {
520 $element[$field_key]['#element_validate'] = array();
521 }
522 array_unshift($element[$field_key]['#element_validate'], 'userreference_optionwidgets_validate');
4ae62d83
YC
523 return $element;
524}
525
526/**
527 * Process an individual element.
528 *
529 * Build the form element. When creating a form using FAPI #process,
530 * note that $element['#value'] is already set.
531 *
532 */
ea73d362
KS
533function userreference_autocomplete_process($element, $edit, $form_state, $form) {
534 // The userreference autocomplete widget doesn't need to create its own
535 // element, it can wrap around the text_textfield element and add an autocomplete
536 // path and some extra processing to it.
537 // Add a validation step where the value can be unwrapped.
538 $field_key = $element['#columns'][0];
1db0519e 539
ea73d362
KS
540 $element[$field_key] = array(
541 '#type' => 'text_textfield',
92efa608 542 '#default_value' => isset($element['#value']) ? $element['#value'] : '',
1db0519e 543 '#autocomplete_path' => 'userreference/autocomplete/'. $element['#field_name'],
8c06d539
KS
544 // The following values were set by the content module and need
545 // to be passed down to the nested element.
8c06d539 546 '#title' => $element['#title'],
341f4230 547 '#required' => $element['#required'],
7dbb8603 548 '#description' => $element['#description'],
07c92176
YC
549 '#field_name' => $element['#field_name'],
550 '#type_name' => $element['#type_name'],
551 '#delta' => $element['#delta'],
552 '#columns' => $element['#columns'],
ea73d362 553 );
617d3264
KS
554 if (empty($element[$field_key]['#element_validate'])) {
555 $element[$field_key]['#element_validate'] = array();
556 }
557 array_unshift($element[$field_key]['#element_validate'], 'userreference_autocomplete_validate');
811e23ab 558
452a390c
KS
559 // Used so that hook_field('validate') knows where to flag an error.
560 $element['_error_element'] = array(
561 '#type' => 'value',
d1eac231 562 // Wrapping the element around a text_textfield element creates a
617d3264 563 // nested element, so the final id will look like 'field-name-0-uid-uid'.
452a390c
KS
564 '#value' => implode('][', array_merge($element['#parents'], array($field_key, $field_key))),
565 );
ea73d362 566 return $element;
e9b6b501 567}
1182d802 568
ea73d362 569/**
4ae62d83 570 * Validate a select/buttons element.
ea73d362
KS
571 *
572 * Remove the wrapper layer and set the right element's value.
d30e72da
KS
573 * We don't know exactly where this element is, so we drill down
574 * through the element until we get to our key.
d1eac231 575 *
617d3264
KS
576 * We use $form_state['values'] instead of $element['#value']
577 * to be sure we have the most accurate value when other modules
578 * like optionwidgets are using #element_validate to alter the value.
ea73d362 579 */
4ae62d83 580function userreference_optionwidgets_validate($element, &$form_state) {
1db0519e 581 $field_key = $element['#columns'][0];
14ddf71e 582
d30e72da 583 $value = $form_state['values'];
14ddf71e 584 $new_parents = array();
d30e72da
KS
585 foreach ($element['#parents'] as $parent) {
586 $value = $value[$parent];
617d3264
KS
587 // Use === to be sure we get right results if parent is a zero (delta) value.
588 if ($parent === $field_key) {
d30e72da 589 $element['#parents'] = $new_parents;
eeb67ed7 590 form_set_value($element, $value, $form_state);
d30e72da
KS
591 break;
592 }
593 $new_parents[] = $parent;
594 }
ea73d362
KS
595}
596
597/**
598 * Validate an autocomplete element.
599 *
600 * Remove the wrapper layer and set the right element's value.
d1eac231 601 * This will move the nested value at 'field-name-0-uid-uid'
617d3264 602 * back to its original location, 'field-name-0-uid'.
ea73d362
KS
603 */
604function userreference_autocomplete_validate($element, &$form_state) {
14ddf71e
YC
605 $field_name = $element['#field_name'];
606 $type_name = $element['#type_name'];
607 $field = content_fields($field_name, $type_name);
608 $field_key = $element['#columns'][0];
609 $value = $element['#value'][$field_key];
1db0519e 610 $uid = NULL;
14ddf71e
YC
611 if (!empty($value)) {
612 $reference = _userreference_potential_references($field, $value, 'equals', NULL, 1);
613 if (empty($reference)) {
614 form_error($element[$field_key], t('%name: found no valid user with that name.', array('%name' => t($field['widget']['label']))));
615 }
616 else {
617 $uid = key($reference);
618 }
b09a2374 619 }
b903d4d0 620 form_set_value($element, $uid, $form_state);
b09a2374
KS
621}
622
e9b6b501
KS
623/**
624 * Implementation of hook_allowed_values().
625 */
626function userreference_allowed_values($field) {
2a1f05cb
YC
627 $references = _userreference_potential_references($field);
628
629 $options = array();
630 foreach ($references as $key => $value) {
948f3ff9 631 $options[$key] = $value['rendered'];
2a1f05cb 632 }
d0c6d4fe 633
2a1f05cb 634 return $options;
e1a2f8a6
JC
635}
636
637/**
2a1f05cb
YC
638 * Fetch an array of all candidate referenced users.
639 *
640 * This info is used in various places (aloowed values, autocomplete results,
641 * input validation...). Some of them only need the uids, others nid + names,
642 * others yet uid + names + rendered row (for display in widgets).
643 * The array we return contains all the potentially needed information, and lets
644 * consumers use the parts they actually need.
645 *
646 * @param $field
647 * The field description.
648 * @param $string
14ddf71e
YC
649 * Optional string to filter usernames on (used by autocomplete)
650 * @param $match
651 * Operator to match filtered name against, can be any of:
652 * 'contains', 'equals', 'starts_with'
653 * @param $ids
654 * Optional user ids to lookup (the $string and $match arguments will be
655 * ignored).
656 * @param $limit
657 * If non-zero, limit the size of the result set.
2a1f05cb
YC
658 *
659 * @return
14ddf71e 660 * An array of valid users in the form:
2a1f05cb
YC
661 * array(
662 * uid => array(
663 * 'title' => The user name,
664 * 'rendered' => The text to display in widgets (can be HTML)
665 * ),
666 * ...
667 * )
1db0519e 668 */
14ddf71e 669function _userreference_potential_references($field, $string = '', $match = 'contains', $ids = array(), $limit = NULL) {
2a1f05cb 670 static $results = array();
1db0519e 671
14ddf71e
YC
672 // Create unique id for static cache.
673 $cid = $field['field_name'] .':'. $match .':'. ($string !== '' ? $string : implode('-', $ids)) .':'. $limit;
674 if (!isset($results[$cid])) {
2a1f05cb
YC
675 $references = FALSE;
676 if (module_exists('views') && !empty($field['advanced_view']) && $field['advanced_view'] != '--') {
14ddf71e 677 $references = _userreference_potential_references_views($field, $string, $match, $ids, $limit);
2a1f05cb
YC
678 }
679 // If the view doesn't exist, we got FALSE, and fallback to the regular 'standard mode'.
680
681 if ($references === FALSE) {
14ddf71e 682 $references = _userreference_potential_references_standard($field, $string, $match, $ids, $limit);
1db0519e 683 }
2a1f05cb
YC
684
685 // Store the results.
0311a4d5 686 $results[$cid] = !empty($references) ? $references : array();
1db0519e 687 }
2a1f05cb 688
14ddf71e 689 return $results[$cid];
1db0519e
KS
690}
691
692/**
2a1f05cb
YC
693 * Helper function for _userreference_potential_references():
694 * case of Views-defined referenceable users.
e1a2f8a6 695 */
14ddf71e 696function _userreference_potential_references_views($field, $string = '', $match = 'contains', $ids = array(), $limit = NULL) {
2a1f05cb
YC
697 $view_name = $field['advanced_view'];
698
699 if ($view = views_get_view($view_name)) {
700 // We add a display, and let it derive from the 'default' display.
701 // TODO: We should let the user pick a display in the fields settings - sort of requires AHAH...
14ddf71e 702 $display = $view->add_display('content_references');
2a1f05cb
YC
703 $view->set_display($display);
704
705 // TODO from merlinofchaos on IRC : arguments using summary view can defeat the style setting.
706 // We might also need to check if there's an argument, and set *its* style_plugin as well.
d49aafb0 707 $view->display_handler->set_option('style_plugin', 'content_php_array_autocomplete');
2a1f05cb 708 $view->display_handler->set_option('row_plugin', 'fields');
d49aafb0
YC
709 // Used in content_plugin_style_php_array::render(), to get
710 // the 'field' to be used as title.
711 $view->display_handler->set_option('content_title_field', 'name');
2a1f05cb 712
14ddf71e
YC
713 // Additional options to let content_plugin_display_references::query()
714 // narrow the results.
715 $options = array(
716 'table' => 'users',
717 'field_string' => 'name',
718 'string' => $string,
719 'match' => $match,
720 'field_id' => 'uid',
721 'ids' => $ids,
722 );
723 $view->display_handler->set_option('content_options', $options);
724
725 // TODO : for consistency, a fair amount of what's below
726 // should be moved to content_plugin_display_references
727
728 // Limit result set size.
7bb93942
YC
729 $limit = isset($limit) ? $limit : 0;
730 $view->display_handler->set_option('items_per_page', $limit);
14ddf71e 731
2a1f05cb
YC
732 // Get arguments for the view.
733 if (!empty($field['advanced_view_args'])) {
734 // TODO: Support Tokens using token.module ?
735 $view_args = array_map('trim', explode(',', $field['advanced_view_args']));
736 }
737 else {
738 $view_args = array();
739 }
740
741 // We do need name field, so add it if not present (unlikely, but...)
742 $fields = $view->get_items('field', $display);
743 if (!isset($fields['name'])) {
744 $view->add_item($display, 'field', 'users', 'name');
745 }
2a1f05cb
YC
746
747 // If not set, make all fields inline and define a separator.
748 $options = $view->display_handler->get_option('row_options');
749 if (empty($options['inline'])) {
750 $options['inline'] = drupal_map_assoc(array_keys($view->get_items('field', $display)));
751 }
752 if (empty($options['separator'])) {
753 $options['separator'] = '-';
754 }
755 $view->display_handler->set_option('row_options', $options);
756
757 // Make sure the query is not cached
758 $view->is_cacheable = FALSE;
759
760 // Get the results.
761 $result = $view->execute_display($display, $view_args);
762 }
763 else {
764 $result = FALSE;
765 }
766
767 return $result;
768}
769
770/**
771 * Helper function for _userreference_potential_references():
772 * referenceable users defined by user role and status
773 */
14ddf71e 774function _userreference_potential_references_standard($field, $string = '', $match = 'contains', $ids = array(), $limit = NULL) {
1db0519e
KS
775 $where = array();
776 $args = array();
777 $join = array();
778
63eb190a 779 if ($string !== '') {
121f60a6
KS
780 $like = $GLOBALS["db_type"] == 'pgsql' ? "ILIKE" : "LIKE";
781 $match_clauses = array(
782 'contains' => "$like '%%%s%%'",
14ddf71e 783 'equals' => "= '%s'",
121f60a6 784 'starts_with' => "$like '%s%%'",
14ddf71e
YC
785 );
786 $where[] = 'u.name '. (isset($match_operators[$match]) ? $match_operators[$match] : $match_operators['contains']);
1db0519e
KS
787 $args[] = $string;
788 }
14ddf71e
YC
789 elseif ($ids) {
790 $where[] = 'u.uid IN (' . db_placeholders($ids) . ')';
791 $args = array_merge($args, $ids);
e8b5d64c 792 }
1db0519e
KS
793 else {
794 $where[] = "u.uid > 0";
795 }
796
7e087396 797 $roles = array();
24caa5e4 798 if (isset($field['referenceable_roles']) && is_array($field['referenceable_roles'])) {
b1a2c635
YC
799 // keep only selected checkboxes
800 $roles = array_filter($field['referenceable_roles']);
0a1f6f8b 801 // filter invalid values that seems to get through sometimes ??
b1a2c635 802 $roles = array_intersect(array_keys(user_roles(1)), $roles);
7e087396 803 }
1db0519e
KS
804 if (!empty($roles) && !in_array(DRUPAL_AUTHENTICATED_RID, $roles)) {
805 $where[] = "r.rid IN (". implode($roles, ',') .")";
806 $join[] = 'LEFT JOIN {users_roles} r ON u.uid = r.uid';
7e087396 807 }
1db0519e 808
47abf3a7
MFM
809 if (isset($field['referenceable_status']) && is_numeric($field['referenceable_status'])) {
810 $where[] = 'u.status = %d';
811 $args[] = $field['referenceable_status'];
c4d7be16 812 }
e1a2f8a6 813
0a1f6f8b 814 $users = array();
14ddf71e
YC
815 $where_clause = $where ? 'WHERE ('. implode(') AND (', $where) .')' : '';
816 $result = db_query('SELECT u.name, u.uid FROM {users} u '. implode(' ', $join) ." $where_clause ORDER BY u.name ASC", $args);
e1a2f8a6 817 while ($user = db_fetch_object($result)) {
e7e792a6
YC
818 $users[$user->uid] = array(
819 'title' => $user->name,
948f3ff9 820 'rendered' => check_plain($user->name),
e7e792a6 821 );
e1a2f8a6 822 }
7056e59c 823 return $users;
e1a2f8a6 824}
0f6ca9bb 825
7056e59c 826/**
2a1f05cb
YC
827 * Menu callback; Retrieve a pipe delimited string of autocomplete suggestions for existing users
828 */
829function userreference_autocomplete($field_name, $string = '') {
830 $fields = content_fields();
831 $field = $fields[$field_name];
14ddf71e 832 $match = isset($field['widget']['autocomplete_match']) ? $field['widget']['autocomplete_match'] : 'contains';
2a1f05cb
YC
833 $matches = array();
834
14ddf71e 835 $references = _userreference_potential_references($field, $string, $match, array(), 10);
2a1f05cb
YC
836 foreach ($references as $id => $row) {
837 // Add a class wrapper for a few required CSS overrides.
838 $matches[$row['title']] = '<div class="reference-autocomplete">'. $row['rendered'] . '</div>';
839 }
840 drupal_json($matches);
841}
842
843/**
5e218086
KS
844 * Implementation of hook_user().
845 */
03537a4e
KS
846function userreference_user($type, &$edit, &$account) {
847 switch ($type) {
5e218086 848 case 'load':
eeb67ed7 849 // Only add links if we are on the user 'view' page.
28d724d1
KS
850 if (arg(0) != 'user' || arg(2)) {
851 return;
852 }
48c9a6da 853 // find CCK userreference field tables
5e218086
KS
854 // search through them for matching user ids and load those nodes
855 $additions = array();
32f7c0e0
KS
856 $types = content_types();
857
858 // Find the table and columns to search through, if the same
859 // table comes up in more than one content type, we only need
860 // to search it once.
861 $search_tables = array();
32f7c0e0 862 foreach ($types as $type_name => $type) {
2e55329a 863 foreach ($type['fields'] as $field) {
28d724d1
KS
864 // Only add tables when reverse link has been selected.
865 if ($field['type'] == 'userreference' && !empty($field['widget']['reverse_link'])) {
32f7c0e0 866 $db_info = content_database_info($field);
5f1f1dec 867 $search_tables[$db_info['table']][] = $db_info['columns']['uid']['column'];
58f22d79 868 }
5e218086
KS
869 }
870 }
5f1f1dec
YC
871 foreach ($search_tables as $table => $columns) {
872 foreach ($columns as $column) {
873 $ids = db_query(db_rewrite_sql("SELECT DISTINCT(n.nid), n.title, n.type FROM {node} n LEFT JOIN {". $table ."} f ON n.vid = f.vid WHERE f.". $column ."=". $account->uid. " AND n.status = 1"));
874 while ($data = db_fetch_object($ids)) {
875 $additions[$data->type][$data->nid] = $data->title;
876 }
32f7c0e0
KS
877 }
878 }
03537a4e 879 $account->userreference = $additions;
5e218086
KS
880 break;
881
882 case 'view':
07af053f
YC
883 if (!empty($account->userreference)) {
884 $node_types = content_types();
885 $additions = array();
03537a4e 886 $values = array();
07af053f 887 foreach ($account->userreference as $node_type => $nodes) {
7533592b
YC
888 foreach ($nodes as $nid => $title) {
889 $values[$node_type][] = l($title, 'node/'. $nid);
5e218086 890 }
07af053f
YC
891 if (isset($values[$node_type])) {
892 $additions[] = array(
893 '#type' => 'user_profile_item',
4b205056 894 '#title' => check_plain($node_types[$node_type]['name']),
07af053f
YC
895 '#value' => theme('item_list', $values[$node_type]),
896 );
897 }
898 }
899 if ($additions) {
900 $account->content['userreference'] = $additions + array(
901 '#type' => 'user_profile_category',
902 '#attributes' => array('class' => 'user-member'),
2400f882 903 '#title' => t('Related content'),
07af053f
YC
904 '#weight' => 10,
905 );
5e218086 906 }
5e218086 907 }
5e218086
KS
908 break;
909 }
910}
911
ea73d362
KS
912/**
913 * FAPI theme for an individual elements.
914 *
915 * The textfield or select is already rendered by the
4ae62d83 916 * textfield or select themes and the html output
ea73d362
KS
917 * lives in $element['#children']. Override this theme to
918 * make custom changes to the output.
919 *
920 * $element['#field_name'] contains the field name
921 * $element['#delta] is the position of this element in the group
922 */
923function theme_userreference_select($element) {
924 return $element['#children'];
925}
926
4ae62d83
YC
927function theme_userreference_buttons($element) {
928 return $element['#children'];
929}
930
ea73d362
KS
931function theme_userreference_autocomplete($element) {
932 return $element['#children'];
a6e65d38 933}