Fix problems with autocomplete handling. It was doubled up in nodereference and a...
[project/cck.git] / 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/**
ea73d362
KS
10 * Implementation of hook_theme().
11 */
12function userreference_theme() {
13 return array(
14 'userreference_select' => array(
15 'arguments' => array('element' => NULL),
16 ),
17 'userreference_autocomplete' => array(
18 'arguments' => array('element' => NULL),
19 ),
20 );
21}
22
23/**
e1a2f8a6 24 * Implementation of hook_field_info().
e9b6b501
KS
25 *
26 * Here we indicate that the content module will use its default
27 * handling for the view of this field.
e1a2f8a6
JC
28 */
29function userreference_field_info() {
30 return array(
e9b6b501
KS
31 'userreference' => array(
32 'label' => 'User Reference',
481dd299 33 'description' => t('Store the id of a related user as an integer value.'),
e9b6b501 34 'callbacks' => array(
e9b6b501
KS
35 'tables' => CONTENT_CALLBACK_DEFAULT,
36 'arguments' => CONTENT_CALLBACK_DEFAULT,
37 ),
38 ),
e1a2f8a6
JC
39 );
40}
41
42/**
43 * Implementation of hook_field_settings().
44 */
e2c6522a 45function userreference_field_settings($op, $field) {
e1a2f8a6 46 switch ($op) {
bafef6a2
JC
47 case 'form':
48 $form = array();
49 $form['referenceable_roles'] = array(
50 '#type' => 'checkboxes',
51 '#title' => t('User roles that can be referenced'),
3973f7be 52 '#default_value' => is_array($field['referenceable_roles']) ? $field['referenceable_roles'] : array(),
bafef6a2
JC
53 '#options' => user_roles(1),
54 );
55 return $form;
56
57 case 'save':
58 return array('referenceable_roles');
59
4af0dd2f
JC
60 case 'database columns':
61 $columns = array(
01bf6a48
YC
62 'uid' => array('type' => 'int', 'unsigned' => TRUE, 'not null' => FALSE),
63 );
64 return $columns;
65
7056e59c
JC
66 case 'filters':
67 return array(
68 'default' => array(
69 'list' => '_userreference_filter_handler',
70 'list-type' => 'list',
71 'operator' => 'views_handler_operator_or',
72 'value-type' => 'array',
73 'extra' => array('field' => $field),
74 ),
75 );
e1a2f8a6
JC
76 }
77}
78
79/**
80 * Implementation of hook_field().
81 */
1fbe637d
KS
82function userreference_field($op, &$node, $field, &$items, $teaser, $page) {
83 switch ($op) {
84 case 'validate':
85 foreach ($items as $delta => $item) {
24caa5e4
KS
86 if (is_array($item) && !empty($item['error_field'])) {
87 $error_field = $item['error_field'];
88 unset($item['error_field']);
89 if (!empty($item['uid']) && !in_array($item['uid'], array_keys(_userreference_potential_references($field)))) {
90 form_set_error($error_field, t('%name : Invalid user.', array('%name' => t($field['widget']['label']))));
91 }
1fbe637d
KS
92 }
93 }
94 return;
6130c745
KS
95 }
96}
e9b6b501 97
6130c745
KS
98/**
99 * Implementation of hook_content_is_empty().
100 */
101function userreference_content_is_empty($item, $field) {
102 if (empty($item['uid'])) {
103 return TRUE;
1fbe637d 104 }
6130c745 105 return FALSE;
1fbe637d 106}
e1a2f8a6 107
319350d8 108/**
7e087396 109 * Implementation of hook_field_formatter_info().
319350d8 110 */
7e087396
KS
111function userreference_field_formatter_info() {
112 return array(
113 'default' => array(
114 'label' => 'Default',
115 'field types' => array('userreference'),
116 ),
117 'plain' => array(
118 'label' => 'Plain text',
119 'field types' => array('userreference'),
120 ),
121 );
122}
123
124/**
125 * Implementation of hook_field_formatter().
126 */
127function userreference_field_formatter($field, $item, $formatter, $node) {
128 $text = '';
129 if (isset($item['uid'])) {
130 $referenced_user = user_load(array('uid' => $item['uid']));
e411c26d 131 if ($referenced_user) {
7e087396 132 $text = theme('username', $referenced_user);
e411c26d 133 }
c82e814c 134 }
aa5d88fc 135
7e087396
KS
136 switch ($formatter) {
137 case 'plain':
138 return strip_tags($text);
aa5d88fc 139
7e087396
KS
140 default:
141 return $text;
142 }
319350d8
JC
143}
144
e1a2f8a6 145/**
1182d802 146 * Implementation of hook_widget_info().
e9b6b501
KS
147 *
148 * We need custom handling of multiple values for userreference_select
149 * because we need to combine them into a options list rather than
150 * display multiple elements.
151 *
152 * We will use the content module's default handling for default value.
153 *
154 * Callbacks can be omitted if default handing is used.
155 * They're included here just so this module can be used
156 * as an example for custom modules that might do things
157 * differently.
e1a2f8a6 158 */
1182d802
JC
159function userreference_widget_info() {
160 return array(
161 'userreference_select' => array(
162 'label' => 'Select List',
163 'field types' => array('userreference'),
a4418efc 164 'multiple values' => CONTENT_HANDLE_MODULE,
e9b6b501
KS
165 'callbacks' => array(
166 'default value' => CONTENT_CALLBACK_DEFAULT,
e9b6b501 167 ),
1182d802
JC
168 ),
169 'userreference_autocomplete' => array(
170 'label' => 'Autocomplete Text Field',
171 'field types' => array('userreference'),
a4418efc 172 'multiple values' => CONTENT_HANDLE_CORE,
e9b6b501
KS
173 'callbacks' => array(
174 'default value' => CONTENT_CALLBACK_DEFAULT,
e9b6b501 175 ),
1182d802
JC
176 ),
177 );
178}
e1a2f8a6 179
1182d802 180/**
ea73d362
KS
181 * Implementation of FAPI hook_elements().
182 *
183 * Any FAPI callbacks needed for individual widgets can be declared here,
184 * and the element will be passed to those callbacks for processing.
185 *
186 * Drupal will automatically theme the element using a theme with
187 * the same name as the hook_elements key.
188 *
189 * Autocomplete_path is not used by text_widget but other widgets can use it
190 * (see nodereference and userreference).
191 */
192function userreference_elements() {
193 return array(
194 'userreference_select' => array(
195 '#input' => TRUE,
196 '#columns' => array('uid'), '#delta' => 0,
197 '#process' => array('userreference_select_process'),
198 ),
199 'userreference_autocomplete' => array(
200 '#input' => TRUE,
201 '#columns' => array('name'), '#delta' => 0,
202 '#process' => array('userreference_autocomplete_process'),
203 '#autocomplete_path' => FALSE,
204 ),
205 );
206}
207
208/**
1182d802 209 * Implementation of hook_widget().
e9b6b501
KS
210 *
211 * Attach a single form element to the form. It will be built out and
212 * validated in the callback(s) listed in hook_elements. We build it
213 * out in the callbacks rather than here in hook_widget so it can be
214 * plugged into any module that can provide it with valid
215 * $field information.
216 *
217 * Content module will set the weight, field name and delta values
218 * for each form element. This is a change from earlier CCK versions
219 * where the widget managed its own multiple values.
220 *
221 * If there are multiple values for this field, the content module will
222 * call this function as many times as needed.
223 *
224 * @param $form
225 * the entire form array, $form['#node'] holds node information
226 * @param $form_state
227 * the form_state, $form_state['values'][$field['field_name']]
228 * holds the field's form values.
229 * @param $field
230 * the field array
231 * @param $items
232 * array of default values for this field
233 * @param $delta
234 * the order of this item in the array of subelements (0, 1, 2, etc)
235 *
236 * @return
237 * the form item for a single element for this field
1182d802 238 */
e9b6b501 239function userreference_widget(&$form, &$form_state, $field, $items, $delta = 0) {
e9b6b501
KS
240 switch ($field['widget']['type']) {
241 case 'userreference_select':
e9b6b501 242 $element = array(
ea73d362 243 '#type' => 'userreference_select',
e9b6b501
KS
244 '#default_value' => $items,
245 );
246 break;
1182d802 247
e9b6b501 248 case 'userreference_autocomplete':
e9b6b501 249 $element = array(
ea73d362 250 '#type' => 'userreference_autocomplete',
e9b6b501
KS
251 '#default_value' => isset($items[$delta]) ? $items[$delta] : NULL,
252 '#value_callback' => 'userreference_autocomplete_value',
e9b6b501
KS
253 );
254 break;
255 }
256 return $element;
257}
4af0dd2f 258
e9b6b501 259/**
92efa608
KS
260 * Value for a userreference autocomplete element.
261 *
262 * Substitute in the user name for the uid.
263 */
264function userreference_autocomplete_value($element, $edit = FALSE) {
265 if (func_num_args() == 1) {
266 $field_key = $element['#columns'][0];
267 if (!empty($element['#default_value'][$field_key])) {
268 $value = $value = db_result(db_query("SELECT name FROM {users} WHERE uid = '%d'", $element['#default_value'][$field_key]));
269 return array($field_key => $value);
270 }
271 return array($field_key => NULL);
272 }
273}
274
275/**
ea73d362
KS
276 * Process an individual element.
277 *
278 * Build the form element. When creating a form using FAPI #process,
279 * note that $element['#value'] is already set.
e9b6b501 280 *
32ead958 281 * The $fields array is in $form['#field_info'][$element['#field_name']].
e9b6b501 282 */
ea73d362
KS
283function userreference_select_process($element, $edit, $form_state, $form) {
284 // The userreference_select widget doesn't need to create its own
285 // element, it can wrap around the optionwidgets_select element.
286 // Add a validation step where the value can be unwrapped.
287 $field_key = $element['#columns'][0];
288 $element[$field_key] = array(
289 '#type' => 'optionwidgets_select',
443b2f04 290 '#default_value' => isset($element['#value']) ? $element['#value'] : '',
ea73d362 291 '#element_validate' => array('userreference_select_validate'),
9b5083a1 292 '#after_build' => array('userreference_after_build'),
8c06d539
KS
293
294 // The following values were set by the content module and need
295 // to be passed down to the nested element.
ea73d362
KS
296 '#field_name' => $element['#field_name'],
297 '#delta' => $element['#delta'],
298 '#columns' => $element['#columns'],
8c06d539 299 '#title' => $element['#title'],
ea73d362
KS
300 );
301 return $element;
302}
303
304/**
305 * Process an individual element.
306 *
307 * Build the form element. When creating a form using FAPI #process,
308 * note that $element['#value'] is already set.
309 *
32ead958 310 * The $fields array is in $form['#field_info'][$element['#field_name']].
ea73d362
KS
311 */
312function userreference_autocomplete_process($element, $edit, $form_state, $form) {
313 // The userreference autocomplete widget doesn't need to create its own
314 // element, it can wrap around the text_textfield element and add an autocomplete
315 // path and some extra processing to it.
316 // Add a validation step where the value can be unwrapped.
317 $field_key = $element['#columns'][0];
ea73d362
KS
318 $element[$field_key] = array(
319 '#type' => 'text_textfield',
92efa608 320 '#default_value' => isset($element['#value']) ? $element['#value'] : '',
ea73d362
KS
321 '#autocomplete_path' => 'user/autocomplete',
322 '#element_validate' => array('userreference_autocomplete_validate'),
9b5083a1 323 '#after_build' => array('userreference_after_build'),
8c06d539
KS
324
325 // The following values were set by the content module and need
326 // to be passed down to the nested element.
ea73d362
KS
327 '#field_name' => $element['#field_name'],
328 '#delta' => $element['#delta'],
329 '#columns' => $element['#columns'],
8c06d539 330 '#title' => $element['#title'],
ea73d362
KS
331 );
332 return $element;
e9b6b501 333}
1182d802 334
ea73d362 335/**
9b5083a1 336 * Afterbuild adjustment of the element.
ea73d362
KS
337 *
338 * Remove the wrapper layer and set the right element's value.
339 * Since we nested two elements, we ended up with a value like
9b5083a1
KS
340 * $element['#parents'][...]['nid']['nid'] and we need to make it back
341 * into $element['#parents'][...]['nid'].
ea73d362 342 */
9b5083a1
KS
343function userreference_after_build($element, &$form_state) {
344 array_pop($element['#parents']);
345 array_pop($element['#array_parents']);
346 return $element;
ea73d362
KS
347}
348
349/**
350 * Validate an autocomplete element.
351 *
352 * Remove the wrapper layer and set the right element's value.
353 */
354function userreference_autocomplete_validate($element, &$form_state) {
b09a2374
KS
355 $field_key = $element['#columns'][0];
356 $value = $element['#value'][$field_key];
357 if (!empty($value)) {
358 $value = db_result(db_query("SELECT uid FROM {users} WHERE name = '%s'", $value));
359 }
360 if (!empty($value)) {
ea73d362 361 form_set_value($element, $value, $form_state);
b09a2374 362 }
b09a2374
KS
363}
364
e9b6b501
KS
365/**
366 * Implementation of hook_allowed_values().
367 */
368function userreference_allowed_values($field) {
369 $options = _userreference_potential_references($field);
370 if (!$field['required']) {
371 $options = array('none' => t('<none>')) + $options;
1182d802 372 }
e9b6b501 373 return $options;
e1a2f8a6
JC
374}
375
376/**
377 * Fetch an array of all candidate referenced users, for use in presenting the selection form to the user.
378 */
379function _userreference_potential_references($field) {
7e087396 380 $roles = array();
24caa5e4 381 if (isset($field['referenceable_roles']) && is_array($field['referenceable_roles'])) {
b1a2c635
YC
382 // keep only selected checkboxes
383 $roles = array_filter($field['referenceable_roles']);
0a1f6f8b 384 // filter invalid values that seems to get through sometimes ??
b1a2c635 385 $roles = array_intersect(array_keys(user_roles(1)), $roles);
7e087396 386 }
0a1f6f8b 387 if (empty($roles) || in_array(DRUPAL_AUTHENTICATED_RID, $roles)) {
c7c1c5d9 388 $result = db_query('SELECT u.name, u.uid FROM {users} u WHERE uid > 0 ORDER BY u.name ASC');
7e087396
KS
389 }
390 else {
c7c1c5d9 391 $result = db_query('SELECT u.name, u.uid FROM {users} u LEFT JOIN {users_roles} r ON u.uid = r.uid WHERE u.uid > 0 AND r.rid IN ('. implode($roles, ',') .') ORDER BY u.name ASC');
c4d7be16 392 }
e1a2f8a6 393
0a1f6f8b 394 $users = array();
e1a2f8a6 395 while ($user = db_fetch_object($result)) {
7056e59c 396 $users[$user->uid] = $user->name;
e1a2f8a6 397 }
7056e59c 398 return $users;
e1a2f8a6 399}
0f6ca9bb 400
7056e59c
JC
401/**
402 * Provide a list of users to filter on.
403 */
404function _userreference_filter_handler($op, $filterinfo) {
405 $options = views_handler_filter_usercurrent();
7e087396 406 $options = $options + _userreference_potential_references($filterinfo['extra']['field']);
7056e59c 407 return $options;
ea73d362
KS
408}
409
410/**
411 * FAPI theme for an individual elements.
412 *
413 * The textfield or select is already rendered by the
414 * textfield or select themes and the html output
415 * lives in $element['#children']. Override this theme to
416 * make custom changes to the output.
417 *
418 * $element['#field_name'] contains the field name
419 * $element['#delta] is the position of this element in the group
420 */
421function theme_userreference_select($element) {
422 return $element['#children'];
423}
424
425function theme_userreference_autocomplete($element) {
426 return $element['#children'];
7e087396 427}