realname.module 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516
  1. <?php
  2. /**
  3. * @file
  4. * Provides token-based name displays for users.
  5. *
  6. * @todo Add a 'view realname' permission enabled by default
  7. * @todo Allow users to login with their real name
  8. * @todo Disable the username field
  9. */
  10. /**
  11. * @defgroup realname Real name API
  12. */
  13. /**
  14. * Implements hook_help().
  15. */
  16. function realname_help($path, $arg) {
  17. switch ($path) {
  18. // Main module help for the Realname module.
  19. case 'admin/config/people/realname':
  20. case 'admin/help#realname':
  21. return '<p>' . t("A Real Name is what the site developer decides that users' names should look like. It is constructed from various tokens that are available within the site.") . '</p>';
  22. }
  23. }
  24. /**
  25. * Implements hook_permission().
  26. */
  27. function realname_permission() {
  28. return array(
  29. 'administer realname' => array(
  30. 'title' => t('Administer Real Name'),
  31. 'description' => t('Allows users to administer realname settings.'),
  32. ),
  33. );
  34. }
  35. /**
  36. * Implements hook_menu().
  37. */
  38. function realname_menu() {
  39. $items['admin/config/people/realname'] = array(
  40. 'title' => 'Real name',
  41. 'description' => 'Use tokens to configure how user names are displayed.',
  42. 'page callback' => 'drupal_get_form',
  43. 'page arguments' => array('realname_settings_form'),
  44. 'access arguments' => array('administer realname'),
  45. 'file' => 'realname.admin.inc',
  46. );
  47. $items['realname/autocomplete'] = array(
  48. 'page callback' => 'realname_autocomplete',
  49. 'page arguments' => array('', '', 2),
  50. 'access arguments' => array('access user profiles'),
  51. 'type' => MENU_CALLBACK,
  52. );
  53. $items['realname/autocomplete/%/%/%'] = array(
  54. 'page callback' => 'realname_autocomplete',
  55. 'page arguments' => array(2, 3, 4),
  56. 'access callback' => 'realname_autocomplete_access_callback',
  57. 'access arguments' => array(2, 3, 4),
  58. 'type' => MENU_CALLBACK,
  59. );
  60. return $items;
  61. }
  62. /**
  63. * Implements hook_menu_alter().
  64. *
  65. * Replace user autocomplete with realname autocomplete.
  66. */
  67. function realname_menu_alter(&$items) {
  68. // Implement realname autocomplete.
  69. $items['user/autocomplete']['page callback'] = 'realname_autocomplete';
  70. $items['user/autocomplete']['page arguments'] = array('', '', 2);
  71. }
  72. /**
  73. * Implements hook_field_extra_fields().
  74. */
  75. function realname_field_extra_fields() {
  76. $fields['user']['user']['display']['realname'] = array(
  77. 'label' => t('Real name'),
  78. 'description' => t('Real name'),
  79. 'weight' => -1,
  80. );
  81. return $fields;
  82. }
  83. /**
  84. * Implements hook_username_alter().
  85. */
  86. function realname_username_alter(&$name, $account) {
  87. static $in_username_alter = FALSE;
  88. if (empty($account->uid)) {
  89. // Don't alter anonymous users or objects that do not have any user ID.
  90. return;
  91. }
  92. // Real name was loaded/generated via hook_user_load(), so re-use it.
  93. if (isset($account->realname)) {
  94. if (drupal_strlen($account->realname)) {
  95. // Only if the real name is a non-empty string is $name actually altered.
  96. $name = $account->realname;
  97. }
  98. return;
  99. }
  100. // Real name was not yet available for the account so we need to generate it.
  101. // Because tokens may call format_username() we need to prevent recursion.
  102. if (!$in_username_alter) {
  103. $in_username_alter = TRUE;
  104. // If $account->realname was undefined, then the user account object was
  105. // not properly loaded. We must enforce calling user_load().
  106. if ($realname_account = user_load($account->uid)) {
  107. realname_username_alter($name, $realname_account);
  108. }
  109. $in_username_alter = FALSE;
  110. }
  111. }
  112. /**
  113. * Implements hook_user_load().
  114. */
  115. function realname_user_load($accounts) {
  116. $realnames = realname_load_multiple($accounts);
  117. foreach ($realnames as $uid => $realname) {
  118. $accounts[$uid]->realname = $realname;
  119. }
  120. }
  121. /**
  122. * Implements hook_user_update().
  123. */
  124. function realname_user_update(&$edit, $account, $category) {
  125. // Since user data may have changed, update the realname and its cache.
  126. $realnames = &drupal_static('realname_load_multiple', array());
  127. $realnames[$account->uid] = realname_update($account);
  128. }
  129. /**
  130. * Implements hook_profile2_update().
  131. */
  132. function realname_profile2_update($profile) {
  133. $account = user_load($profile->uid);
  134. if (is_object($account)) {
  135. realname_update($account);
  136. }
  137. }
  138. /**
  139. * Implements hook_user_delete().
  140. */
  141. function realname_user_delete($account) {
  142. realname_delete($account->uid);
  143. }
  144. /**
  145. * Implements hook_user_view().
  146. */
  147. function realname_user_view($account, $view_mode, $langcode) {
  148. if (user_view_access($account)) {
  149. $markup = l($account->realname, 'user/' . $account->uid);
  150. }
  151. else {
  152. $markup = check_plain($account->realname);
  153. }
  154. $account->content['realname'] = array(
  155. '#entity_type' => 'custom',
  156. '#bundle' => 'custom',
  157. '#field_type' => 'text',
  158. '#theme' => 'field',
  159. '#field_name' => 'realname',
  160. '#title' => t('Real name'),
  161. '#label_display' => 'inline',
  162. '#items' => array(TRUE),
  163. 0 => array('#markup' => $markup),
  164. );
  165. }
  166. /**
  167. * Implements hook_user_operations().
  168. */
  169. function realname_user_operations() {
  170. $operations['realname_update'] = array(
  171. 'label' => t('Update real name'),
  172. 'callback' => 'realname_user_operations_realname_update',
  173. );
  174. return $operations;
  175. }
  176. /**
  177. * Callback function for admin mass generating user real names.
  178. *
  179. * @param array $uids
  180. * An array of user IDs.
  181. */
  182. function realname_user_operations_realname_update(array $uids) {
  183. $accounts = user_load_multiple($uids);
  184. foreach ($accounts as $account) {
  185. realname_update($account);
  186. }
  187. }
  188. /**
  189. * Implements hook_action_info().
  190. */
  191. function realname_action_info() {
  192. $info['realname_action_realname_update'] = array(
  193. 'type' => 'user',
  194. 'label' => t('Update real name'),
  195. 'configurable' => FALSE,
  196. );
  197. return $info;
  198. }
  199. /**
  200. * Implements hook_rules_action_info().
  201. */
  202. function realname_rules_action_info() {
  203. $items['user_realname_update'] = array(
  204. 'label' => t('Update real name'),
  205. 'base' => 'realname_action_realname_update',
  206. 'parameter' => array(
  207. 'account' => array(
  208. 'type' => 'user',
  209. 'label' => t('User'),
  210. 'save' => FALSE,
  211. ),
  212. ),
  213. 'group' => t('User'),
  214. 'access callback' => 'rules_user_integration_access',
  215. );
  216. return $items;
  217. }
  218. /**
  219. * Action callback to update a user's realname.
  220. */
  221. function realname_action_realname_update($account, $context = array()) {
  222. realname_update($account);
  223. }
  224. /**
  225. * Implements hook_views_api().
  226. */
  227. function realname_views_api() {
  228. return array('api' => 3);
  229. }
  230. /**
  231. * @addtogroup realname
  232. * @{
  233. */
  234. /**
  235. * Loads a real name.
  236. *
  237. * @param object $account
  238. * A user account object.
  239. *
  240. * @return mixed
  241. * The user's generated real name.
  242. */
  243. function realname_load($account) {
  244. $realnames = realname_load_multiple(array($account->uid => $account));
  245. return reset($realnames);
  246. }
  247. /**
  248. * Loads multiple real names.
  249. *
  250. * @param array $accounts
  251. * An array of user account objects keyed by user ID.
  252. *
  253. * @return array
  254. * An array of real names keyed by user ID.
  255. */
  256. function realname_load_multiple(array $accounts) {
  257. $realnames = &drupal_static(__FUNCTION__, array());
  258. if ($new_accounts = array_diff_key($accounts, $realnames)) {
  259. // Attempt to fetch realnames from the database first.
  260. $realnames += db_query("SELECT uid, realname FROM {realname} WHERE uid IN (:uids)", array(':uids' => array_keys($new_accounts)))->fetchAllKeyed();
  261. // For each account that was not present in the database, generate its
  262. // real name.
  263. foreach ($new_accounts as $uid => $account) {
  264. if (!isset($realnames[$uid])) {
  265. $realnames[$uid] = realname_update($account);
  266. }
  267. }
  268. }
  269. return array_intersect_key($realnames, $accounts);
  270. }
  271. /**
  272. * Update the realname for a user account.
  273. *
  274. * @param object $account
  275. * A user account object.
  276. *
  277. * @return string
  278. * A string with the real name.
  279. *
  280. * @see hook_realname_pattern_alter()
  281. * @see hook_realname_alter()
  282. * @see hook_realname_update()
  283. */
  284. function realname_update($account) {
  285. // Get the default pattern and allow other modules to alter it.
  286. $pattern = variable_get('realname_pattern', '[user:name-raw]');
  287. drupal_alter('realname_pattern', $pattern, $account);
  288. // Perform token replacement on the real name pattern.
  289. $realname = token_replace($pattern, array('user' => $account), array('clear' => TRUE, 'sanitize' => FALSE));
  290. // Remove any HTML tags.
  291. $realname = strip_tags(decode_entities($realname));
  292. // Remove double spaces (if a token had no value).
  293. $realname = preg_replace('/ {2,}/', ' ', $realname);
  294. // Allow other modules to alter the generated realname.
  295. drupal_alter('realname', $realname, $account);
  296. // The name must be trimmed to 255 characters before inserting into the
  297. // database.
  298. $realname = truncate_utf8(trim($realname), 255);
  299. // Save to the database and the static cache.
  300. db_merge('realname')
  301. ->key(array('uid' => $account->uid))
  302. ->fields(array(
  303. 'realname' => $realname,
  304. 'created' => REQUEST_TIME,
  305. ))
  306. ->execute();
  307. // Allow modules to react to the realname being updated.
  308. module_invoke_all('realname_update', $realname, $account);
  309. // Only if the real name is a non-empty string the name is altered.
  310. if (drupal_strlen($realname)) {
  311. $account->realname = $realname;
  312. }
  313. // Clear the entity cache.
  314. entity_get_controller('user')->resetCache(array($account->uid));
  315. return $realname;
  316. }
  317. /**
  318. * Delete a real name.
  319. *
  320. * @param int $uid
  321. * A user ID.
  322. */
  323. function realname_delete($uid) {
  324. return realname_delete_multiple(array($uid));
  325. }
  326. /**
  327. * Delete multiple real names.
  328. *
  329. * @param array $uids
  330. * An array of user IDs.
  331. */
  332. function realname_delete_multiple(array $uids) {
  333. db_delete('realname')->condition('uid', $uids, 'IN')->execute();
  334. drupal_static_reset('realname_load_multiple');
  335. entity_get_controller('user')->resetCache($uids);
  336. }
  337. /**
  338. * Delete all real names.
  339. */
  340. function realname_delete_all() {
  341. db_truncate('realname')->execute();
  342. drupal_static_reset('realname_load_multiple');
  343. entity_get_controller('user')->resetCache();
  344. }
  345. /**
  346. * @} End of "addtogroup realname".
  347. */
  348. /**
  349. * Menu callback.
  350. *
  351. * Retrieve a JSON object containing autocomplete suggestions
  352. * for existing users based on their generated real names.
  353. */
  354. function realname_autocomplete($field_name, $entity_type, $bundle_name, $string = '') {
  355. $instance = field_info_instance($entity_type, $field_name, $bundle_name);
  356. $matches = array();
  357. // User entityreference autocomplete fields.
  358. if ($instance['widget']['type'] == 'entityreference_autocomplete' && !empty($string)) {
  359. $query = db_select('users', 'u');
  360. $query->leftJoin('realname', 'rn', 'u.uid = rn.uid');
  361. $query->fields('u', array('uid', 'name'));
  362. if ($instance['widget']['settings']['match_operator'] == 'CONTAINS') {
  363. $query->condition(db_or()
  364. ->condition('rn.realname', '%' . db_like($string) . '%', 'LIKE')
  365. ->condition('u.name', '%' . db_like($string) . '%', 'LIKE')
  366. );
  367. }
  368. else {
  369. $query->condition(db_or()
  370. ->condition('rn.realname', db_like($string) . '%', 'LIKE')
  371. ->condition('u.name', db_like($string) . '%', 'LIKE')
  372. );
  373. }
  374. $query->range(0, 10);
  375. $uids = $query->execute()->fetchCol();
  376. $accounts = user_load_multiple($uids);
  377. foreach ($accounts as $account) {
  378. $matches[t('!account (@uid)', array('!account' => format_username($account), '@uid' => $account->uid))] = t('!realname (@username)', array('!realname' => format_username($account), '@username' => $account->name));
  379. }
  380. }
  381. // User autocomplete fields.
  382. elseif (!empty($string)) {
  383. $query = db_select('users', 'u');
  384. $query->leftJoin('realname', 'rn', 'u.uid = rn.uid');
  385. $query->fields('u', array('uid', 'name'));
  386. $query->condition(db_or()
  387. ->condition('rn.realname', db_like($string) . '%', 'LIKE')
  388. ->condition('u.name', db_like($string) . '%', 'LIKE')
  389. );
  390. $query->range(0, 10);
  391. $uids = $query->execute()->fetchCol();
  392. $accounts = user_load_multiple($uids);
  393. foreach ($accounts as $account) {
  394. $matches[$account->name] = t('!realname (@username)', array('!realname' => format_username($account), '@username' => $account->name));
  395. }
  396. }
  397. drupal_json_output($matches);
  398. }
  399. /**
  400. * Menu Access callback for the autocomplete widget.
  401. *
  402. * @param string $field_name
  403. * The name of the entity-reference field.
  404. * @param string $entity_type
  405. * The entity type.
  406. * @param string $bundle_name
  407. * The bundle name.
  408. *
  409. * @return bool
  410. * True if user can access this menu item.
  411. */
  412. function realname_autocomplete_access_callback($field_name, $entity_type, $bundle_name) {
  413. $field = field_info_field($field_name);
  414. $instance = field_info_instance($entity_type, $field_name, $bundle_name);
  415. if (!$field || !$instance || $field['type'] != 'entityreference' || !field_access('edit', $field, $entity_type)) {
  416. return FALSE;
  417. }
  418. return TRUE;
  419. }
  420. /**
  421. * Implements hook_widget_form_alter().
  422. *
  423. * Overrides entityreference autocomplete fields that point to an entity
  424. * of type 'user'.
  425. */
  426. function realname_field_widget_form_alter(&$element, &$form_state, $context) {
  427. if ($context['field']['type'] == 'entityreference') {
  428. if ($context['field']['settings']['target_type'] == 'user') {
  429. $element['target_id']['#autocomplete_path'] = 'realname/autocomplete/' . $context['field']['field_name'] . '/' . $context['instance']['entity_type'] . '/' . $context['instance']['bundle'];
  430. }
  431. }
  432. }
  433. /**
  434. * Implements hook_form_FORM_ID_alter().
  435. */
  436. function realname_form_user_admin_settings_alter(&$form, &$form_state, $form_id) {
  437. $form['email_admin_created']['user_mail_register_admin_created_body']['#element_validate'][] = 'realname_username_raw_token_validate';
  438. $form['email_no_approval_required']['user_mail_register_no_approval_required_body']['#element_validate'][] = 'realname_username_raw_token_validate';
  439. $form['email_activated']['settings']['user_mail_status_activated_body']['#element_validate'][] = 'realname_username_raw_token_validate';
  440. }
  441. /**
  442. * E-mail settings form validation callback.
  443. */
  444. function realname_username_raw_token_validate(&$element, &$form_state) {
  445. $value = isset($element['#value']) ? $element['#value'] : $element['#default_value'];
  446. // Warns the site administrator that [user:name-raw], required to log in, is
  447. // missing. You can force the deactivation of this message by setting the
  448. // variable `realname_suppress_user_name_mail_validation` to TRUE (or any
  449. // value that converts to TRUE, like '1').
  450. if (!variable_get('realname_suppress_user_name_mail_validation', FALSE) && strpos($value, '[user:name-raw]') === FALSE) {
  451. drupal_set_message(t('The %element-title does not contain the <code>[user:name-raw]</code> token, which is necessary when using username for login.', array('%element-title' => $element['#title'])), 'warning');
  452. }
  453. return $element;
  454. }