Mercurial > hg > rr-repo
comparison sites/all/modules/references/user_reference/user_reference.module @ 4:ce11bbd8f642
added modules
author | danieleb <danielebarchiesi@me.com> |
---|---|
date | Thu, 19 Sep 2013 10:38:44 +0100 |
parents | |
children |
comparison
equal
deleted
inserted
replaced
3:b28be78d8160 | 4:ce11bbd8f642 |
---|---|
1 <?php | |
2 | |
3 /** | |
4 * @file | |
5 * Defines a field type for referencing a user from a node. | |
6 */ | |
7 | |
8 /** | |
9 * Implements hook_menu(). | |
10 */ | |
11 function user_reference_menu() { | |
12 $items['user_reference/autocomplete/%/%/%'] = array( | |
13 'page callback' => 'user_reference_autocomplete', | |
14 'page arguments' => array(2, 3, 4), | |
15 'access callback' => 'reference_autocomplete_access', | |
16 'access arguments' => array(2, 3, 4), | |
17 'type' => MENU_CALLBACK, | |
18 ); | |
19 return $items; | |
20 } | |
21 | |
22 /** | |
23 * Implements hook_field_info(). | |
24 */ | |
25 function user_reference_field_info() { | |
26 return array( | |
27 'user_reference' => array( | |
28 'label' => t('User reference'), | |
29 'description' => t('This field stores the ID of a related user as an integer value.'), | |
30 'settings' => array( | |
31 'referenceable_roles' => array(), | |
32 'referenceable_status' => array(), | |
33 'view' => array( | |
34 'view_name' => '', | |
35 'display_name' => '', | |
36 'args' => array(), | |
37 ), | |
38 ), | |
39 'default_widget' => 'user_reference_autocomplete', | |
40 'default_formatter' => 'user_reference_default', | |
41 // Support hook_entity_property_info() from contrib "Entity API". | |
42 'property_type' => 'user', | |
43 // Support default token formatter for field tokens. | |
44 'default_token_formatter' => 'user_reference_plain', | |
45 ), | |
46 ); | |
47 } | |
48 | |
49 /** | |
50 * Implements hook_field_settings_form(). | |
51 */ | |
52 function user_reference_field_settings_form($field, $instance, $has_data) { | |
53 $settings = $field['settings']; | |
54 | |
55 $form = array(); | |
56 $form['referenceable_roles'] = array( | |
57 '#type' => 'checkboxes', | |
58 '#title' => t('User roles that can be referenced'), | |
59 '#default_value' => is_array($settings['referenceable_roles']) | |
60 ? array_filter($settings['referenceable_roles']) | |
61 : array(), | |
62 '#options' => user_roles(TRUE), | |
63 ); | |
64 $form['referenceable_status'] = array( | |
65 '#type' => 'checkboxes', | |
66 '#title' => t('User status that can be referenced'), | |
67 '#default_value' => is_array($settings['referenceable_status']) | |
68 ? array_filter($settings['referenceable_status']) | |
69 : array(1), | |
70 '#options' => array(1 => t('Active'), 0 => t('Blocked')), | |
71 ); | |
72 | |
73 if (module_exists('views')) { | |
74 $view_settings = $settings['view']; | |
75 | |
76 $description = '<p>' . t('The list of users that can be referenced can provided by a view (Views module) using the "References" display type.') . '</p>'; | |
77 | |
78 // Special note for legacy fields migrated from D6. | |
79 if (!empty($view_settings['view_name']) && $view_settings['display_name'] == 'default') { | |
80 $description .= '<p><strong><span class="admin-missing">'. t("Important D6 migration note:") . '</span></strong>'; | |
81 $description .= '<br/>' . t("The field is currently configured to use the 'Master' display of the view %view_name.", array('%view_name' => $view_settings['view_name'])); | |
82 $description .= '<br/>' . t("It is highly recommended that you: <br/>- edit this view and create a new display using the 'References' display type, <br/>- update the field settings to explicitly select the correct view and display."); | |
83 $description .= '<br/>' . t("The field will work correctly until then, but submitting this form might inadvertently change the field settings.") . '</p>'; | |
84 } | |
85 | |
86 $form['view'] = array( | |
87 '#type' => 'fieldset', | |
88 '#title' => t('Views - Users that can be referenced'), | |
89 '#collapsible' => TRUE, | |
90 '#collapsed' => empty($view_settings['view_name']), | |
91 '#description' => $description, | |
92 ); | |
93 | |
94 $views_options = references_get_views_options('user'); | |
95 if ($views_options) { | |
96 // The value of the 'view_and_display' select below will need to be split | |
97 // into 'view_name' and 'view_display' in the final submitted values, so | |
98 // we massage the data at validate time on the wrapping element (not | |
99 // ideal). | |
100 $form['view']['#element_validate'] = array('_user_reference_view_settings_validate'); | |
101 | |
102 $views_options = array('' => '<' . t('none') . '>') + $views_options; | |
103 $default = empty($view_settings['view_name']) ? '' : $view_settings['view_name'] . ':' .$view_settings['display_name']; | |
104 $form['view']['view_and_display'] = array( | |
105 '#type' => 'select', | |
106 '#title' => t('View used to select the users'), | |
107 '#options' => $views_options, | |
108 '#default_value' => $default, | |
109 '#description' => '<p>' . t('Choose the view and display that select the nodes that can be referenced.<br />Only views with a display of type "References" are eligible.') . '</p>' . | |
110 t('Note:<ul><li>This will discard the "Referenceable Roles" and "Referenceable Status" settings above. Use the view\'s "filters" section instead.</li><li>Use the view\'s "fields" section to display additional informations about candidate users on user creation/edition form.</li><li>Use the view\'s "sort criteria" section to determine the order in which candidate users will be displayed.</li></ul>'), | |
111 ); | |
112 | |
113 $default = implode(', ', $view_settings['args']); | |
114 $form['view']['args'] = array( | |
115 '#type' => 'textfield', | |
116 '#title' => t('View arguments'), | |
117 '#default_value' => $default, | |
118 '#required' => FALSE, | |
119 '#description' => t('Provide a comma separated list of arguments to pass to the view.'), | |
120 ); | |
121 } | |
122 else { | |
123 $form['view']['no_view_help'] = array( | |
124 '#markup' => '<p>' . t('No eligible view was found.') .'</p>', | |
125 ); | |
126 } | |
127 } | |
128 | |
129 return $form; | |
130 } | |
131 | |
132 /** | |
133 * Validate callback for the 'view settings' fieldset. | |
134 * | |
135 * Puts back the various form values in the expected shape. | |
136 */ | |
137 function _user_reference_view_settings_validate($element, &$form_state, $form) { | |
138 // Split view name and display name from the 'view_and_display' value. | |
139 if (!empty($element['view_and_display']['#value'])) { | |
140 list($view, $display) = explode(':', $element['view_and_display']['#value']); | |
141 } | |
142 else { | |
143 $view = ''; | |
144 $display = ''; | |
145 } | |
146 | |
147 // Explode the 'args' string into an actual array. Beware, explode() turns an | |
148 // empty string into an array with one empty string. We'll need an empty array | |
149 // instead. | |
150 $args_string = trim($element['args']['#value']); | |
151 $args = ($args_string === '') ? array() : array_map('trim', explode(',', $args_string)); | |
152 | |
153 $value = array('view_name' => $view, 'display_name' => $display, 'args' => $args); | |
154 form_set_value($element, $value, $form_state); | |
155 } | |
156 | |
157 /** | |
158 * Implements hook_field_validate(). | |
159 * | |
160 * Possible error codes: | |
161 * - 'invalid_uid': uid is not valid for the field (not a valid user id, or the user is not referenceable). | |
162 */ | |
163 function user_reference_field_validate($entity_type, $entity, $field, $instance, $langcode, $items, &$errors) { | |
164 // Extract uids to check. | |
165 $ids = array(); | |
166 | |
167 // First check non-numeric uid's to avoid losing time with them. | |
168 foreach ($items as $delta => $item) { | |
169 if (is_array($item) && !empty($item['uid'])) { | |
170 if (is_numeric($item['uid'])) { | |
171 $ids[] = $item['uid']; | |
172 } | |
173 else { | |
174 $errors[$field['field_name']][$langcode][$delta][] = array( | |
175 'error' => 'invalid_uid', | |
176 'message' => t('%name: invalid input.', | |
177 array('%name' => $instance['label'])), | |
178 ); | |
179 } | |
180 } | |
181 } | |
182 // Prevent performance hog if there are no ids to check. | |
183 if ($ids) { | |
184 $options = array( | |
185 'ids' => $ids, | |
186 ); | |
187 $refs = user_reference_potential_references($field, $options); | |
188 foreach ($items as $delta => $item) { | |
189 if (is_array($item)) { | |
190 if (!empty($item['uid']) && !isset($refs[$item['uid']])) { | |
191 $errors[$field['field_name']][$langcode][$delta][] = array( | |
192 'error' => 'invalid_uid', | |
193 'message' => t("%name: this user can't be referenced.", | |
194 array('%name' => $instance['label'])), | |
195 ); | |
196 } | |
197 } | |
198 } | |
199 } | |
200 } | |
201 | |
202 /** | |
203 * Implements hook_field_prepare_view(). | |
204 */ | |
205 function user_reference_field_prepare_view($entity_type, $entities, $field, $instances, $langcode, &$items) { | |
206 $checked_ids = &drupal_static(__FUNCTION__, array()); | |
207 | |
208 // Set an 'access' property on each item (TRUE if the user exists). | |
209 | |
210 // Extract ids to check. | |
211 $ids = array(); | |
212 foreach ($items as $id => $entity_items) { | |
213 foreach ($entity_items as $delta => $item) { | |
214 if (is_array($item)) { | |
215 // Default to 'not accessible'. | |
216 $items[$id][$delta]['access'] = FALSE; | |
217 if (!empty($item['uid']) && is_numeric($item['uid'])) { | |
218 $ids[$item['uid']] = $item['uid']; | |
219 } | |
220 } | |
221 } | |
222 } | |
223 | |
224 if ($ids) { | |
225 // Load information about ids that we haven't already loaded during this | |
226 // page request. | |
227 $ids_to_check = array_diff($ids, array_keys($checked_ids)); | |
228 if (!empty($ids_to_check)) { | |
229 $query = db_select('users', 'u') | |
230 ->addMetaData('id', 'user_reference_field_prepare_view') | |
231 ->addMetaData('field', $field) | |
232 ->fields('u', array('uid')) | |
233 ->condition('u.uid', $ids_to_check, 'IN'); | |
234 $accessible_ids = $query->execute()->fetchAllAssoc('uid'); | |
235 | |
236 // Populate our static list so that we do not query on those ids again. | |
237 foreach ($ids_to_check as $id) { | |
238 $checked_ids[$id] = isset($accessible_ids[$id]); | |
239 } | |
240 } | |
241 | |
242 foreach ($items as $id => $entity_items) { | |
243 foreach ($entity_items as $delta => $item) { | |
244 if (is_array($item) && !empty($item['uid']) && !empty($checked_ids[$item['uid']])) { | |
245 $items[$id][$delta]['access'] = TRUE; | |
246 } | |
247 } | |
248 } | |
249 } | |
250 } | |
251 | |
252 /** | |
253 * Implements hook_field_is_empty(). | |
254 */ | |
255 function user_reference_field_is_empty($item, $field) { | |
256 return empty($item['uid']); | |
257 } | |
258 | |
259 /** | |
260 * Implements hook_field_formatter_info(). | |
261 */ | |
262 function user_reference_field_formatter_info() { | |
263 return array( | |
264 'user_reference_default' => array( | |
265 'label' => t('Default'), | |
266 'description' => t("Display the name of the referenced user as a link to the user's profile page."), | |
267 'field types' => array('user_reference'), | |
268 ), | |
269 'user_reference_plain' => array( | |
270 'label' => t('Plain text'), | |
271 'description' => t('Display the name of the referenced user as plain text.'), | |
272 'field types' => array('user_reference'), | |
273 ), | |
274 'user_reference_user' => array( | |
275 'label' => t('Rendered user'), | |
276 'description' => t('Display the referenced user in a specific view mode'), | |
277 'field types' => array('user_reference'), | |
278 'settings' => array('user_reference_view_mode' => 'full'), | |
279 ), | |
280 'user_reference_uid' => array( | |
281 'label' => t('User ID'), | |
282 'description' => t('Display the referenced user ID'), | |
283 'field types' => array('user_reference'), | |
284 ), | |
285 'user_reference_path' => array( | |
286 'label' => t('URL as plain text'), | |
287 'description' => t('Display the URL of the referenced user'), | |
288 'field types' => array('user_reference'), | |
289 'settings' => array( | |
290 'alias' => TRUE, | |
291 'absolute' => FALSE | |
292 ), | |
293 ), | |
294 ); | |
295 } | |
296 | |
297 /** | |
298 * Implements hook_field_formatter_settings_form(). | |
299 */ | |
300 function user_reference_field_formatter_settings_form($field, $instance, $view_mode, $form, &$form_state) { | |
301 $display = $instance['display'][$view_mode]; | |
302 $settings = $display['settings']; | |
303 | |
304 $element = array(); | |
305 | |
306 switch ($display['type']) { | |
307 case 'user_reference_user': | |
308 $entity_info = entity_get_info('user'); | |
309 $modes = $entity_info['view modes']; | |
310 $options = array(); | |
311 foreach ($modes as $name => $mode) { | |
312 $options[$name] = $mode['label']; | |
313 } | |
314 $element['user_reference_view_mode'] = array( | |
315 '#title' => t('View mode'), | |
316 '#type' => 'select', | |
317 '#options' => $options, | |
318 '#default_value' => $settings['user_reference_view_mode'], | |
319 // Never empty, so no #empty_option | |
320 ); | |
321 break; | |
322 | |
323 case 'user_reference_path': | |
324 $element['alias'] = array( | |
325 '#type' => 'checkbox', | |
326 '#title' => t('Display the aliased path (if exists) instead of the system path'), | |
327 '#default_value' => $settings['alias'], | |
328 ); | |
329 $element['absolute'] = array( | |
330 '#type' => 'checkbox', | |
331 '#title' => t('Display an absolute URL'), | |
332 '#default_value' => $settings['absolute'], | |
333 ); | |
334 break; | |
335 } | |
336 | |
337 return $element; | |
338 } | |
339 | |
340 /** | |
341 * Implements hook_field_formatter_settings_summary(). | |
342 */ | |
343 function user_reference_field_formatter_settings_summary($field, $instance, $view_mode) { | |
344 $display = $instance['display'][$view_mode]; | |
345 $settings = $display['settings']; | |
346 $summary = array(); | |
347 | |
348 switch ($display['type']) { | |
349 case 'user_reference_user': | |
350 $entity_info = entity_get_info('user'); | |
351 $modes = $entity_info['view modes']; | |
352 $mode = $modes[$settings['user_reference_view_mode']]['label']; | |
353 $summary[] = t('View mode: %mode', array('%mode' => $mode)); | |
354 break; | |
355 | |
356 case 'user_reference_path': | |
357 $summary[] = t('Aliased path: %yes_no', array('%yes_no' => $settings['alias'] ? t('Yes') : t('No'))); | |
358 $summary[] = t('Absolute URL: %yes_no', array('%yes_no' => $settings['absolute'] ? t('Yes') : t('No'))); | |
359 break; | |
360 } | |
361 | |
362 return implode('<br />', $summary); | |
363 } | |
364 | |
365 | |
366 /** | |
367 * Implements hook_field_formatter_prepare_view(). | |
368 * | |
369 * Preload all user referenced by items using 'full entity' formatters. | |
370 */ | |
371 function user_reference_field_formatter_prepare_view($entity_type, $entities, $field, $instances, $langcode, &$items, $displays) { | |
372 // Load the referenced users, except for the 'user_reference_uid' which does | |
373 // not need full objects. | |
374 | |
375 // Collect ids to load. | |
376 $ids = array(); | |
377 foreach ($displays as $id => $display) { | |
378 if ($display['type'] != 'user_reference_uid') { | |
379 foreach ($items[$id] as $delta => $item) { | |
380 if ($item['access']) { | |
381 $ids[$item['uid']] = $item['uid']; | |
382 } | |
383 } | |
384 } | |
385 } | |
386 $entities = user_load_multiple($ids); | |
387 | |
388 // Add the loaded user objects to the items. | |
389 foreach ($displays as $id => $display) { | |
390 if ($display['type'] != 'user_reference_uid') { | |
391 foreach ($items[$id] as $delta => $item) { | |
392 if ($item['access']) { | |
393 $items[$id][$delta]['user'] = $entities[$item['uid']]; | |
394 } | |
395 } | |
396 } | |
397 } | |
398 } | |
399 | |
400 /** | |
401 * Implements hook_field_formatter_view(). | |
402 */ | |
403 function user_reference_field_formatter_view($entity_type, $entity, $field, $instance, $langcode, $items, $display) { | |
404 $settings = $display['settings']; | |
405 $result = array(); | |
406 | |
407 // @todo Optimisation: use hook_field_formatter_prepare_view() to load | |
408 // user names or full user entities in 'multiple' mode. | |
409 | |
410 // Collect the list of user ids. | |
411 $uids = array(); | |
412 foreach ($items as $delta => $item) { | |
413 $uids[$item['uid']] = $item['uid']; | |
414 } | |
415 | |
416 switch ($display['type']) { | |
417 case 'user_reference_default': | |
418 case 'user_reference_plain': | |
419 foreach ($items as $delta => $item) { | |
420 if ($item['access']) { | |
421 $user = $item['user']; | |
422 $label = entity_label('user', $user); | |
423 if ($display['type'] == 'user_reference_default') { | |
424 $uri = entity_uri('user', $user); | |
425 $result[$delta] = array( | |
426 '#type' => 'link', | |
427 '#title' => $label, | |
428 '#href' => $uri['path'], | |
429 '#options' => $uri['options'], | |
430 ); | |
431 } | |
432 else { | |
433 $result[$delta] = array( | |
434 '#markup' => check_plain($label), | |
435 ); | |
436 } | |
437 } | |
438 } | |
439 break; | |
440 | |
441 case 'user_reference_user': | |
442 $view_mode = $display['settings']['user_reference_view_mode']; | |
443 // To prevent infinite recursion caused by reference cycles, we store | |
444 // diplayed accounts in a recursion queue. | |
445 $recursion_queue = &drupal_static(__FUNCTION__, array()); | |
446 | |
447 // If no 'referencing entity' is set, we are starting a new 'reference | |
448 // thread' and need to reset the queue. | |
449 // @todo Bug: $entity->referencing_entity on accounts referenced in a different | |
450 // thread on the page. E.g: 1 references 1+2 / 2 references 1+2 / visit homepage. | |
451 // We'd need a more accurate way... | |
452 if (!isset($entity->referencing_entity)) { | |
453 $recursion_queue = array(); | |
454 } | |
455 | |
456 // The recursion queue only needs to track nodes. | |
457 if ($entity_type == 'user') { | |
458 list($id) = entity_extract_ids($entity_type, $entity); | |
459 $recursion_queue[$id] = $id; | |
460 } | |
461 | |
462 // Check the recursion queue to determine which accounts should be fully | |
463 // displayed, and which accounts will only be displayed as a username. | |
464 $users_display = array(); | |
465 foreach ($items as $delta => $item) { | |
466 if ($item['access'] && !isset($recursion_queue[$item['uid']])) { | |
467 $users_display[$item['uid']] = $item['user']; | |
468 } | |
469 } | |
470 | |
471 // Load and build the fully displayed nodes. | |
472 if ($users_display) { | |
473 $users_built = array('users' => array('#sorted' => TRUE)); | |
474 foreach ($users_display as $uid => $account) { | |
475 $users_display[$uid]->referencing_entity = $entity; | |
476 $users_display[$uid]->referencing_field = $field['field_name']; | |
477 $users_built['users'][$account->uid] = user_view($account, $settings['user_reference_view_mode']); | |
478 } | |
479 } | |
480 | |
481 // Assemble the render array. | |
482 foreach ($items as $delta => $item) { | |
483 if ($item['access']) { | |
484 if (isset($users_display[$item['uid']])) { | |
485 $result[$delta] = $users_built['users'][$item['uid']]; | |
486 } | |
487 else { | |
488 $account = $item['user']; | |
489 $label = entity_label('user', $user); | |
490 $uri = entity_uri('user', $account); | |
491 $result[$delta] = array( | |
492 '#type' => 'link', | |
493 '#title' => $label, | |
494 '#href' => $uri['path'], | |
495 '#options' => $uri['options'], | |
496 ); | |
497 if (!$account->status) { | |
498 $result[$delta]['#prefix'] = '<span class="user-unpublished">'; | |
499 $result[$delta]['#suffix'] = '</span>'; | |
500 } | |
501 } | |
502 } | |
503 } | |
504 break; | |
505 | |
506 case 'user_reference_uid': | |
507 foreach ($items as $delta => $item) { | |
508 if ($item['access']) { | |
509 $result[$delta] = array( | |
510 '#markup' => $item['uid'], | |
511 ); | |
512 } | |
513 } | |
514 break; | |
515 | |
516 case 'user_reference_path': | |
517 foreach ($items as $delta => $item) { | |
518 if ($item['access']) { | |
519 $uri = entity_uri('user', $item['user']); | |
520 $options = array( | |
521 'absolute' => $settings['absolute'], | |
522 'alias' => !$settings['alias'], | |
523 ); | |
524 | |
525 $options += $uri['options']; | |
526 $result[$delta] = array( | |
527 '#markup' => url($uri['path'], $options), | |
528 ); | |
529 } | |
530 } | |
531 break; | |
532 } | |
533 | |
534 return $result; | |
535 } | |
536 | |
537 /** | |
538 * Helper function for widgets and formatters. | |
539 * | |
540 * Store user names collected in the curent request. | |
541 */ | |
542 function _user_reference_get_user_names($uids, $known_titles = array()) { | |
543 $titles = &drupal_static(__FUNCTION__, array()); | |
544 | |
545 // Save titles we receive. | |
546 $titles += $known_titles; | |
547 | |
548 // Collect nids to retrieve from database. | |
549 $uids_query = array(); | |
550 foreach ($uids as $uid) { | |
551 if (!isset($titles[$uid])) { | |
552 $uids_query[] = $uid; | |
553 } | |
554 } | |
555 if ($uids_query) { | |
556 $query = db_select('users', 'u') | |
557 ->fields('u', array('uid', 'name')) | |
558 ->condition('u.uid', $uids); | |
559 $titles += $query->execute()->fetchAllKeyed(); | |
560 } | |
561 | |
562 // Build the results array. | |
563 $return = array(); | |
564 foreach ($uids as $uid) { | |
565 $return[$uid] = isset($titles[$uid]) ? $titles[$uid] : ''; | |
566 } | |
567 | |
568 return $return; | |
569 } | |
570 | |
571 /** | |
572 * Implements hook_field_widget_info(). | |
573 */ | |
574 function user_reference_field_widget_info() { | |
575 return array( | |
576 'user_reference_autocomplete' => array( | |
577 'label' => t('Autocomplete text field'), | |
578 'description' => t('Display the list of referenceable users as a textfield with autocomplete behaviour.'), | |
579 'field types' => array('user_reference'), | |
580 'settings' => array( | |
581 'autocomplete_match' => 'contains', | |
582 'size' => 60, | |
583 'autocomplete_path' => 'user_reference/autocomplete', | |
584 ), | |
585 ), | |
586 ); | |
587 } | |
588 | |
589 /** | |
590 * Implements hook_field_widget_info_alter(). | |
591 */ | |
592 function user_reference_field_widget_info_alter(&$info) { | |
593 $info['options_select']['field types'][] = 'user_reference'; | |
594 $info['options_buttons']['field types'][] = 'user_reference'; | |
595 } | |
596 | |
597 /** | |
598 * Implements hook_field_widget_settings_form(). | |
599 */ | |
600 function user_reference_field_widget_settings_form($field, $instance) { | |
601 $widget = $instance['widget']; | |
602 $defaults = field_info_widget_settings($widget['type']); | |
603 $settings = array_merge($defaults, $widget['settings']); | |
604 | |
605 $form = array(); | |
606 if ($widget['type'] == 'user_reference_autocomplete') { | |
607 $form['autocomplete_match'] = array( | |
608 '#type' => 'select', | |
609 '#title' => t('Autocomplete matching'), | |
610 '#default_value' => $settings['autocomplete_match'], | |
611 '#options' => array( | |
612 'starts_with' => t('Starts with'), | |
613 'contains' => t('Contains'), | |
614 ), | |
615 '#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.'), | |
616 ); | |
617 $form['size'] = array( | |
618 '#type' => 'textfield', | |
619 '#title' => t('Size of textfield'), | |
620 '#default_value' => $settings['size'], | |
621 '#element_validate' => array('_element_validate_integer_positive'), | |
622 '#required' => TRUE, | |
623 ); | |
624 } | |
625 return $form; | |
626 } | |
627 | |
628 /** | |
629 * Implements hook_field_widget_form(). | |
630 */ | |
631 function user_reference_field_widget_form(&$form, &$form_state, $field, $instance, $langcode, $items, $delta, $element) { | |
632 switch ($instance['widget']['type']) { | |
633 case 'user_reference_autocomplete': | |
634 $element += array( | |
635 '#type' => 'textfield', | |
636 '#default_value' => isset($items[$delta]['uid']) ? $items[$delta]['uid'] : NULL, | |
637 '#autocomplete_path' => $instance['widget']['settings']['autocomplete_path'] . '/' . $instance['entity_type'] . '/' . $instance['bundle'] . '/' . $field['field_name'], | |
638 '#size' => $instance['widget']['settings']['size'], | |
639 '#element_validate' => array('user_reference_autocomplete_validate'), | |
640 '#value_callback' => 'user_reference_autocomplete_value', | |
641 ); | |
642 break; | |
643 } | |
644 return array('uid' => $element); | |
645 } | |
646 | |
647 /** | |
648 * Value callback for a user_reference autocomplete element. | |
649 * | |
650 * Substitute in the user name for the uid. | |
651 */ | |
652 function user_reference_autocomplete_value($element, $input = FALSE, $form_state) { | |
653 if ($input === FALSE) { | |
654 // We're building the displayed 'default value': expand the raw uid into | |
655 // "user name [uid:n]". | |
656 $uid = $element['#default_value']; | |
657 if (!empty($uid)) { | |
658 $q = db_select('users', 'u'); | |
659 $q->addField('u', 'name'); | |
660 | |
661 $q->condition('u.uid', $uid) | |
662 ->range(0, 1); | |
663 $result = $q->execute(); | |
664 // @todo If no result (user doesn't exist). | |
665 $value = $result->fetchField(); | |
666 $value .= ' [uid:' . $uid . ']'; | |
667 return $value; | |
668 } | |
669 } | |
670 } | |
671 | |
672 /** | |
673 * Validation callback for a user_reference autocomplete element. | |
674 */ | |
675 function user_reference_autocomplete_validate($element, &$form_state, $form) { | |
676 $field = field_widget_field($element, $form_state); | |
677 $instance = field_widget_instance($element, $form_state); | |
678 | |
679 $value = $element['#value']; | |
680 $uid = NULL; | |
681 | |
682 if (!empty($value)) { | |
683 // Check whether we have an explicit "[uid:n]" input. | |
684 preg_match('/^(?:\s*|(.*) )?\[\s*uid\s*:\s*(\d+)\s*\]$/', $value, $matches); | |
685 if (!empty($matches)) { | |
686 // Explicit uid. Check that the 'name' part matches the actual name for | |
687 // the uid. | |
688 list(, $name, $uid) = $matches; | |
689 if (!empty($name)) { | |
690 $names = _user_reference_get_user_names(array($uid)); | |
691 if ($name != $names[$uid]) { | |
692 form_error($element, t('%name: name mismatch. Please check your selection.', array('%name' => $instance['label']))); | |
693 } | |
694 } | |
695 } | |
696 else { | |
697 // No explicit uid (the submitted value was not populated by autocomplete | |
698 // selection). Get the uid of a referencable user from the entered name. | |
699 $options = array( | |
700 'string' => $value, | |
701 'match' => 'equals', | |
702 'limit' => 1, | |
703 ); | |
704 $references = user_reference_potential_references($field, $options); | |
705 if ($references) { | |
706 // @todo The best thing would be to present the user with an | |
707 // additional form, allowing the user to choose between valid | |
708 // candidates with the same name. ATM, we pick the first | |
709 // matching candidate... | |
710 $uid = key($references); | |
711 } | |
712 else { | |
713 form_error($element, t('%name: found no valid user with that name.', array('%name' => $instance['label']))); | |
714 } | |
715 } | |
716 } | |
717 | |
718 // Set the element's value as the user id that was extracted from the entered | |
719 // input. | |
720 form_set_value($element, $uid, $form_state); | |
721 } | |
722 | |
723 /** | |
724 * Implements hook_field_widget_error(). | |
725 */ | |
726 function user_reference_field_widget_error($element, $error, $form, &$form_state) { | |
727 form_error($element['uid'], $error['message']); | |
728 } | |
729 | |
730 /** | |
731 * Builds a list of referenceable users suitable for the '#option' FAPI property. | |
732 * | |
733 * Warning: the function does NOT take care of encoding or escaping the user | |
734 * names. Proper massaging needs to be performed by the caller, according to | |
735 * the destination FAPI '#type' (radios / checkboxes / select). | |
736 * | |
737 * @param $field | |
738 * The field definition. | |
739 * | |
740 * @return | |
741 * An array of referenceable user names, keyed by user id. | |
742 */ | |
743 function _user_reference_options($field, $flat = TRUE) { | |
744 $references = user_reference_potential_references($field); | |
745 | |
746 $options = array(); | |
747 foreach ($references as $key => $value) { | |
748 // The label, displayed in selects and checkboxes/radios, should have HTML | |
749 // entities unencoded. The widgets (core's options.module) take care of | |
750 // applying the relevant filters (strip_tags() or filter_xss()). | |
751 $label = html_entity_decode($value['rendered'], ENT_QUOTES); | |
752 if (empty($value['group']) || $flat) { | |
753 $options[$key] = $label; | |
754 } | |
755 else { | |
756 // The group name, displayed in selects, cannot contain tags, and should | |
757 // have HTML entities unencoded. | |
758 $group = html_entity_decode(strip_tags($value['group']), ENT_QUOTES); | |
759 $options[$group][$key] = $label; | |
760 } | |
761 } | |
762 | |
763 return $options; | |
764 } | |
765 | |
766 /** | |
767 * Retrieves an array of candidate referenceable users. | |
768 * | |
769 * This info is used in various places (aloowed values, autocomplete results, | |
770 * input validation...). Some of them only need the uids, others nid + names, | |
771 * others yet uid + names + rendered row (for display in widgets). | |
772 * The array we return contains all the potentially needed information, and lets | |
773 * consumers use the parts they actually need. | |
774 * | |
775 * @param $field | |
776 * The field definition. | |
777 * @param $options | |
778 * An array of options to limit the scope of the returned list. The following | |
779 * key/value pairs are accepted: | |
780 * - string: string to filter titles on (used by autocomplete). | |
781 * - match: operator to match the above string against, can be any of: | |
782 * 'contains', 'equals', 'starts_with'. Defaults to 'contains'. | |
783 * - ids: array of specific node ids to lookup. | |
784 * - limit: maximum size of the the result set. Defaults to 0 (no limit). | |
785 * | |
786 * @return | |
787 * An array of valid users in the form: | |
788 * array( | |
789 * uid => array( | |
790 * 'title' => The user name, | |
791 * 'rendered' => The text to display in widgets (can be HTML) | |
792 * ), | |
793 * ... | |
794 * ) | |
795 */ | |
796 function user_reference_potential_references($field, $options = array()) { | |
797 // Fill in default options. | |
798 $options += array( | |
799 'string' => '', | |
800 'match' => 'contains', | |
801 'ids' => array(), | |
802 'limit' => 0, | |
803 ); | |
804 | |
805 $results = &drupal_static(__FUNCTION__, array()); | |
806 | |
807 // Create unique id for static cache. | |
808 $cid = $field['field_name'] . ':' . $options['match'] . ':' | |
809 . ($options['string'] !== '' ? $options['string'] : implode('-', $options['ids'])) | |
810 . ':' . $options['limit']; | |
811 if (!isset($results[$cid])) { | |
812 $references = FALSE; | |
813 if (module_exists('views') && !empty($field['settings']['view']['view_name'])) { | |
814 $references = _user_reference_potential_references_views($field, $options); | |
815 } | |
816 | |
817 if ($references === FALSE) { | |
818 $references = _user_reference_potential_references_standard($field, $options); | |
819 } | |
820 | |
821 // Store the results. | |
822 $results[$cid] = !empty($references) ? $references : array(); | |
823 } | |
824 | |
825 return $results[$cid]; | |
826 } | |
827 | |
828 /** | |
829 * Helper function for user_reference_potential_references(). | |
830 * | |
831 * Case of Views-defined referenceable users. | |
832 */ | |
833 function _user_reference_potential_references_views($field, $options) { | |
834 $settings = $field['settings']['view']; | |
835 $options['title_field'] = 'name'; | |
836 return references_potential_references_view('user', $settings['view_name'], $settings['display_name'], $settings['args'], $options); | |
837 } | |
838 | |
839 /** | |
840 * Helper function for user_reference_potential_references(). | |
841 * | |
842 * List of referenceable users defined by user role and status. | |
843 */ | |
844 function _user_reference_potential_references_standard($field, $options) { | |
845 // Avoid useless work. | |
846 $filter_roles = array_filter($field['settings']['referenceable_roles']); | |
847 $filter_status = array_filter($field['settings']['referenceable_status']); | |
848 if (!count($filter_status) && !count($filter_roles)) { | |
849 return array(); | |
850 } | |
851 | |
852 $query = db_select('users', 'u') | |
853 // Select the whole record, so that format_username() has enough | |
854 // information. | |
855 ->fields('u') | |
856 ->addMetaData('id', ' _user_reference_potential_references_standard') | |
857 ->addMetaData('field', $field) | |
858 ->addMetaData('options', $options); | |
859 | |
860 // Enable this filter only if any statuses checked (and not both). | |
861 if (count($filter_status) == 1) { | |
862 $query->condition('u.status', array_keys($filter_status), 'IN'); | |
863 } | |
864 | |
865 // Skip filter when "authenticated user" choosen. | |
866 if ($filter_roles && !isset($filter_roles[DRUPAL_AUTHENTICATED_RID])) { | |
867 $query->join('users_roles', 'r', 'u.uid = r.uid'); | |
868 $query->condition('r.rid', array_keys($filter_roles), 'IN'); | |
869 } | |
870 | |
871 if ($options['string'] !== '') { | |
872 switch ($options['match']) { | |
873 case 'contains': | |
874 $query->condition('u.name', '%' . $options['string'] . '%', 'LIKE'); | |
875 break; | |
876 | |
877 case 'starts_with': | |
878 $query->condition('u.name', $options['string'] . '%', 'LIKE'); | |
879 break; | |
880 | |
881 case 'equals': | |
882 default: // no match type or incorrect match type: use "=" | |
883 $query->condition('u.name', $options['string'], '='); | |
884 break; | |
885 } | |
886 } | |
887 | |
888 if ($options['ids']) { | |
889 $query->condition('u.uid', $options['ids'], 'IN'); | |
890 } | |
891 | |
892 // Explicitly exclude the anonymous user. | |
893 $query->condition('u.uid', 0, '<>'); | |
894 | |
895 if ($options['limit']) { | |
896 $query->range(0, $options['limit']); | |
897 } | |
898 $query->orderBy('u.name'); | |
899 | |
900 $result = $query->execute()->fetchAll(); | |
901 $references = array(); | |
902 foreach ($result as $account) { | |
903 $references[$account->uid] = array( | |
904 'title' => $account->name, | |
905 'rendered' => check_plain(format_username($account)), | |
906 ); | |
907 } | |
908 return $references; | |
909 } | |
910 | |
911 /** | |
912 * Menu callback; Retrieve a pipe delimited string of autocomplete suggestions for existing users | |
913 */ | |
914 function user_reference_autocomplete($entity_type, $bundle, $field_name, $string = '') { | |
915 $field = field_info_field($field_name); | |
916 $instance = field_info_instance($entity_type, $field_name, $bundle); | |
917 | |
918 $options = array( | |
919 'string' => $string, | |
920 'match' => $instance['widget']['settings']['autocomplete_match'], | |
921 'limit' => 10, | |
922 ); | |
923 $references = user_reference_potential_references($field, $options); | |
924 | |
925 $matches = array(); | |
926 foreach ($references as $id => $row) { | |
927 // Markup is fine in autocompletion results (might happen when rendered | |
928 // through Views) but we want to remove hyperlinks. | |
929 $suggestion = preg_replace('/<a href="([^<]*)">([^<]*)<\/a>/', '$2', $row['rendered']); | |
930 // Remove link tags Add a class wrapper for a few required CSS overrides. | |
931 $matches[$row['title'] . " [uid:$id]"] = '<div class="reference-autocomplete">' . $suggestion . '</div>'; | |
932 } | |
933 drupal_json_output($matches); | |
934 } | |
935 | |
936 /** | |
937 * Implements hook_options_list(). | |
938 */ | |
939 function user_reference_options_list($field) { | |
940 return _user_reference_options($field, FALSE); | |
941 } | |
942 | |
943 /** | |
944 * Implementation of hook_user_load(). | |
945 */ | |
946 /*function user_reference_user_load($accounts) { | |
947 | |
948 // Only add links if we are on the user 'view' page. | |
949 if (arg(0) != 'user' || arg(2)) { | |
950 return; | |
951 } | |
952 | |
953 foreach ($accounts as $uid => $account) { | |
954 | |
955 // find CCK user_reference field tables | |
956 // search through them for matching user ids and load those nodes | |
957 $additions = array(); | |
958 $fields = field_info_instances('user'); | |
959 | |
960 // TODO : replace with field_attach_query() + synchronize with latest D6 code. | |
961 | |
962 // Find the table and columns to search through, if the same | |
963 // table comes up in more than one field type, we only need | |
964 // to search it once. | |
965 $search_tables = array(); | |
966 $search_links = array(); | |
967 foreach ($fields as $field) { | |
968 if ($field['type'] == 'user_reference' && !empty($field['widget']['reverse_link'])) { | |
969 $db_info = content_database_info($field); | |
970 $search_tables[$db_info['table']] = $db_info['columns']['uid']['column']; | |
971 $search_links[$db_info['table']] = $field['widget']['reverse_link']; | |
972 } | |
973 } | |
974 foreach ($search_tables as $table => $column) { | |
975 $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")); | |
976 while ($data = db_fetch_object($ids)) { | |
977 // TODO, do we really want a complete node_load() here? We only need the title to create a link. | |
978 $node = node_load($data->nid); | |
979 $node->reverse_link = $search_links[$table]; | |
980 $additions[$node->type][] = $node; | |
981 } | |
982 } | |
983 $accounts[$uid]->user_reference = $additions; | |
984 } | |
985 return; | |
986 }*/ | |
987 | |
988 /** | |
989 * Implementation of hook_user_view(). | |
990 */ | |
991 /*function user_reference_user_view($account, $view_mode, $langcode) { | |
992 if (!empty($account->user_reference)) { | |
993 $node_types = content_types(); | |
994 $additions = array(); | |
995 $values = array(); | |
996 foreach ($account->user_reference as $node_type => $nodes) { | |
997 foreach ($nodes as $node) { | |
998 if ($node->reverse_link) { | |
999 $values[$node_type][] = l($node->title, 'node/' . $node->nid); | |
1000 } | |
1001 } | |
1002 if (isset($values[$node_type])) { | |
1003 $additions[] = array( | |
1004 '#type' => 'user_profile_item', | |
1005 '#title' => check_plain($node_types[$node_type]['name']), | |
1006 '#value' => theme('item_list', $values[$node_type]), | |
1007 ); | |
1008 } | |
1009 } | |
1010 if ($additions) { | |
1011 $account->content['user_reference'] = $additions + array( | |
1012 '#type' => 'user_profile_category', | |
1013 '#attributes' => array('class' => array('user-member')), | |
1014 '#title' => t('Related content'), | |
1015 '#weight' => 10, | |
1016 ); | |
1017 } | |
1018 } | |
1019 }*/ | |
1020 | |
1021 /** | |
1022 * Implements hook_content_migrate_field_alter(). | |
1023 * | |
1024 * Use this to tweak the conversion of field settings | |
1025 * from the D6 style to the D7 style for specific | |
1026 * situations not handled by basic conversion, | |
1027 * as when field types or settings are changed. | |
1028 * | |
1029 * $field_value['widget_type'] is available to | |
1030 * see what widget type was originally used. | |
1031 */ | |
1032 function user_reference_content_migrate_field_alter(&$field_value, $instance_value) { | |
1033 switch ($field_value['module']) { | |
1034 case 'userreference': | |
1035 $field_value['module'] = 'user_reference'; | |
1036 $field_value['type'] = 'user_reference'; | |
1037 | |
1038 // Translate 'view' settings. | |
1039 $view_name = isset($field_value['settings']['advanced_view']) ? $field_value['settings']['advanced_view'] : ''; | |
1040 $view_args = isset($field_value['settings']['advanced_view_args']) ? $field_value['settings']['advanced_view_args'] : ''; | |
1041 $view_args = array_map('trim', explode(',', $view_args)); | |
1042 $field_value['settings']['view'] = array( | |
1043 'view_name' => $view_name, | |
1044 'display_name' => 'default', | |
1045 'args' => $view_args, | |
1046 ); | |
1047 if ($view_name) { | |
1048 $field_value['messages'][] = t("The field uses the view @view_name to determine referenceable users. You will need to manually edit the view and add a display of type 'References'.", array('@view_name' => $view_name)); | |
1049 } | |
1050 unset($field_value['settings']['advanced_view']); | |
1051 unset($field_value['settings']['advanced_view_args']); | |
1052 break; | |
1053 } | |
1054 } | |
1055 | |
1056 /** | |
1057 * Implements hook_content_migrate_instance_alter(). | |
1058 * | |
1059 * Use this to tweak the conversion of instance or widget settings | |
1060 * from the D6 style to the D7 style for specific | |
1061 * situations not handled by basic conversion, as when | |
1062 * formatter or widget names or settings are changed. | |
1063 */ | |
1064 function user_reference_content_migrate_instance_alter(&$instance_value, $field_value) { | |
1065 // The module name for the instance was corrected | |
1066 // by the change in user_reference_content_migrate_field_alter(). | |
1067 switch ($field_value['type']) { | |
1068 case 'userreference': | |
1069 // The formatter names changed, all are prefixed | |
1070 // with 'user_reference_'. | |
1071 foreach ($instance_value['display'] as $context => $settings) { | |
1072 $instance_value['display'][$context]['type'] = 'user_reference_' . $settings['type']; | |
1073 } | |
1074 // Massage the widget. | |
1075 switch ($instance_value['widget']['type']) { | |
1076 case 'userreference_autocomplete': | |
1077 $instance_value['widget']['type'] = 'user_reference_autocomplete'; | |
1078 $instance_value['widget']['module'] = 'user_reference'; | |
1079 break; | |
1080 case 'userreference_select': | |
1081 $instance_value['widget']['type'] = 'options_select'; | |
1082 $instance_value['widget']['module'] = 'options'; | |
1083 break; | |
1084 case 'userreference_buttons': | |
1085 $instance_value['widget']['type'] = 'options_buttons'; | |
1086 $instance_value['widget']['module'] = 'options'; | |
1087 } | |
1088 break; | |
1089 } | |
1090 } | |
1091 | |
1092 /** | |
1093 * Implements hook_field_views_data(). | |
1094 * | |
1095 * In addition to the default field information we add the relationship for | |
1096 * views to connect back to the users table. | |
1097 */ | |
1098 function user_reference_field_views_data($field) { | |
1099 // No module_load_include(): this hook is invoked from | |
1100 // views/modules/field.views.inc, which is where that function is defined. | |
1101 $data = field_views_field_default_views_data($field); | |
1102 | |
1103 $storage = $field['storage']['details']['sql']; | |
1104 | |
1105 foreach ($storage as $age => $table_data) { | |
1106 $table = key($table_data); | |
1107 $columns = current($table_data); | |
1108 $id_column = $columns['uid']; | |
1109 if (isset($data[$table])) { | |
1110 // Filter: swap the handler to the 'in' operator. The callback receives | |
1111 // the field name instead of the whole $field structure to keep views | |
1112 // data to a reasonable size. | |
1113 $data[$table][$id_column]['filter']['handler'] = 'views_handler_filter_in_operator'; | |
1114 $data[$table][$id_column]['filter']['options callback'] = 'user_reference_views_filter_options'; | |
1115 $data[$table][$id_column]['filter']['options arguments'] = array($field['field_name']); | |
1116 | |
1117 // Argument: display users.name in argument titles (handled in our custom | |
1118 // handler) and summary lists (handled by the base views_handler_argument | |
1119 // handler). | |
1120 // Both mechanisms rely on the 'name table' and 'name field' information | |
1121 // below, by joining to a separate copy of the base table from the field | |
1122 // data table. | |
1123 $data[$table][$id_column]['argument']['handler'] = 'references_handler_argument'; | |
1124 $data[$table][$id_column]['argument']['name table'] = $table . '_reference'; | |
1125 $data[$table][$id_column]['argument']['name field'] = 'name'; | |
1126 $data[$table . '_reference']['table']['join'][$table] = array( | |
1127 'left_field' => $id_column, | |
1128 'table' => 'users', | |
1129 'field' => 'uid', | |
1130 ); | |
1131 | |
1132 // Relationship. | |
1133 $data[$table][$id_column]['relationship'] = array( | |
1134 'handler' => 'references_handler_relationship', | |
1135 'base' => 'users', | |
1136 'base field' => 'uid', | |
1137 'field' => $id_column, | |
1138 'label' => $field['field_name'], | |
1139 'field_name' => $field['field_name'], | |
1140 ); | |
1141 } | |
1142 } | |
1143 | |
1144 return $data; | |
1145 } | |
1146 | |
1147 /** | |
1148 * Implements hook_field_views_data_views_data_alter(). | |
1149 */ | |
1150 function user_reference_field_views_data_views_data_alter(&$data, $field) { | |
1151 foreach ($field['bundles'] as $entity_type => $bundles) { | |
1152 $entity_info = entity_get_info($entity_type); | |
1153 $pseudo_field_name = 'reverse_' . $field['field_name'] . '_' . $entity_type; | |
1154 | |
1155 list($label, $all_labels) = field_views_field_label($field['field_name']); | |
1156 $entity = $entity_info['label']; | |
1157 if ($entity == t('Node')) { | |
1158 $entity = t('Content'); | |
1159 } | |
1160 | |
1161 // Only specify target entity type if the field is used in more than one. | |
1162 if (count($field['bundles']) > 1) { | |
1163 $title = t('@field (@field_name) - reverse (to @entity)', array('@entity' => $entity, '@field' => $label, '@field_name' => $field['field_name'])); | |
1164 } | |
1165 else { | |
1166 $title = t('@field (@field_name) - reverse', array('@entity' => $entity, '@field' => $label, '@field_name' => $field['field_name'])); | |
1167 } | |
1168 $data['users'][$pseudo_field_name]['relationship'] = array( | |
1169 'title' => $title, | |
1170 'help' => t('Relate each @entity referencing the user through @field.', array('@entity' => $entity, '@field' => $label)), | |
1171 'handler' => 'views_handler_relationship_entity_reverse', | |
1172 'field_name' => $field['field_name'], | |
1173 'field table' => _field_sql_storage_tablename($field), | |
1174 'field field' => $field['field_name'] . '_uid', | |
1175 'base' => $entity_info['base table'], | |
1176 'base field' => $entity_info['entity keys']['id'], | |
1177 'label' => t('!field_name', array('!field_name' => $field['field_name'])), | |
1178 ); | |
1179 } | |
1180 } | |
1181 | |
1182 /** | |
1183 * 'options callback' for the views_handler_filter_in_operator filter. | |
1184 * | |
1185 * @param $field_name | |
1186 * The field name. | |
1187 * | |
1188 * @return | |
1189 * The array of allowed options for the filter. | |
1190 */ | |
1191 function user_reference_views_filter_options($field_name) { | |
1192 $options = array(); | |
1193 | |
1194 if ($field = field_info_field($field_name)) { | |
1195 $options = _user_reference_options($field, TRUE); | |
1196 | |
1197 // The options are displayed in checkboxes within the filter admin form, and | |
1198 // in a select within an exposed filter. Checkboxes accept HTML, other | |
1199 // entities should be encoded; selects require the exact opposite: no HTML, | |
1200 // no encoding. We go for a middle ground: strip tags, leave entities | |
1201 // unencoded. | |
1202 foreach ($options as $key => $value) { | |
1203 $options[$key] = strip_tags($value); | |
1204 } | |
1205 } | |
1206 | |
1207 return $options; | |
1208 } |