realname.module 13.4 KB
Newer Older
1
<?php
2

3
4
5
6
/**
 * @file
 * Provides token-based name displays for users.
 *
7
 * @todo Add a 'view realname' permission enabled by default
8
9
10
11
12
13
14
 * @todo Allow users to login with their real name
 * @todo Disable the username field
 */

/**
 * @defgroup realname Real name API
 */
15

NancyDru's avatar
NancyDru committed
16
17
18
19
20
21
22
23
/**
 * Implements hook_help().
 */
function realname_help($path, $arg) {
  switch ($path) {
    // Main module help for the Realname module.
    case 'admin/config/people/realname':
    case 'admin/help#realname':
hass's avatar
hass committed
24
      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>';
NancyDru's avatar
NancyDru committed
25
26
27
  }
}

28
/**
29
 * Implements hook_permission().
30
 */
31
function realname_permission() {
32
33
34
35
36
  return array(
    'administer realname' => array(
      'title' => t('Administer Real Name'),
      'description' => t('Allows users to administer realname settings.'),
    ),
37
38
  );
}
39

40
/**
41
 * Implements hook_menu().
42
 */
43
44
45
function realname_menu() {
  $items['admin/config/people/realname'] = array(
    'title' => 'Real name',
46
    'description' => 'Use tokens to configure how user names are displayed.',
47
48
49
50
51
    'page callback' => 'drupal_get_form',
    'page arguments' => array('realname_settings_form'),
    'access arguments' => array('administer realname'),
    'file' => 'realname.admin.inc',
  );
52

53
54
  $items['realname/autocomplete'] = array(
    'page callback' => 'realname_autocomplete',
55
    'page arguments' => array('', '', 2),
56
57
58
    'access arguments' => array('access user profiles'),
    'type' => MENU_CALLBACK,
  );
59
60
61
62
63
64
65
  $items['realname/autocomplete/%/%/%'] = array(
    'page callback' => 'realname_autocomplete',
    'page arguments' => array(2, 3, 4),
    'access callback' => 'realname_autocomplete_access_callback',
    'access arguments' => array(2, 3, 4),
    'type' => MENU_CALLBACK,
  );
66

67
  return $items;
68
69
}

70
/**
71
 * Implements hook_menu_alter() to replace user autocomplete with realname autocomplete.
72
73
74
75
 */
function realname_menu_alter(&$items) {
  // Implement realname autocomplete.
  $items['user/autocomplete']['page callback'] = 'realname_autocomplete';
76
  $items['user/autocomplete']['page arguments'] = array('', '', 2);
77
78
}

79
/**
80
 * Implements hook_username_alter().
81
 */
82
function realname_username_alter(&$name, $account) {
83
  static $in_username_alter = FALSE;
84

85
86
87
88
89
90
  if (empty($account->uid)) {
    // Don't alter anonymous users or objects that do not have any user ID.
    return;
  }

  // Real name was loaded/generated via hook_user_load(), so re-use it.
91
  if (isset($account->realname)) {
92
    if (drupal_strlen($account->realname)) {
93
      // Only if the real name is a non-empty string is $name actually altered.
94
95
      $name = $account->realname;
    }
96
    return;
97
  }
98
99
100
101

  // Real name was not yet available for the account so we need to generate it.
  // Because tokens may call format_username() we need to prevent recursion.
  if (!$in_username_alter) {
102
    $in_username_alter = TRUE;
103
104

    // If $account->realname was undefined, then the user account object was
105
106
107
    // not properly loaded. We must enforce calling user_load().
    if ($realname_account = user_load($account->uid)) {
      realname_username_alter($name, $realname_account);
108
    }
109

110
    $in_username_alter = FALSE;
111
  }
112
113
114
}

/**
115
 * Implements hook_user_load().
116
 */
117
function realname_user_load($accounts) {
118
119
120
  $realnames = realname_load_multiple($accounts);
  foreach ($realnames as $uid => $realname) {
    $accounts[$uid]->realname = $realname;
121
122
123
124
  }
}

/**
125
 * Implements hook_user_update().
126
 */
127
function realname_user_update(&$edit, $account, $category) {
128
129
  // Since user data may have changed, update the realname.
  realname_update($account);
130
131
}

132
133
134
135
136
137
138
139
140
141
/**
 * Implements hook_profile2_update().
 */
function realname_profile2_update($profile) {
  $account = user_load($profile->uid);
  if (is_object($account)) {
    realname_update($account);
  }
}

142
143
144
/**
 * Implements hook_user_delete().
 */
145
function realname_user_delete($account) {
146
147
  realname_delete($account->uid);
}
148

149
150
151
152
153
154
155
156
157
158
159
160
161
162
/**
 * Implements hook_user_operations().
 */
function realname_user_operations() {
  $operations['realname_update'] = array(
    'label' => t('Update real name'),
    'callback' => 'realname_user_operations_realname_update',
  );
  return $operations;
}

/**
 * Callback function for admin mass generating user real names.
 *
163
 * @param array $uids
164
165
 *   An array of user IDs.
 */
166
function realname_user_operations_realname_update($uids) {
167
168
  $accounts = user_load_multiple($uids);
  foreach ($accounts as $account) {
169
    realname_update($account);
170
171
172
  }
}

173
174
175
176
177
178
179
180
181
182
183
184
185
/**
 * Implements hook_action_info().
 */
function realname_action_info() {
  $info['realname_action_realname_update'] = array(
    'type' => 'user',
    'label' => t('Update real name'),
    'configurable' => FALSE,
  );

  return $info;
}

186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
/**
 * Implements hook_rules_action_info().
 */
function realname_rules_action_info() {
  $items['user_realname_update'] = array(
    'label' => t('Update real name'),
    'base' => 'realname_action_realname_update',
    'parameter' => array(
      'account' => array(
        'type' => 'user',
        'label' => t('User'),
        'save' => FALSE,
      ),
    ),
    'group' => t('User'),
    'access callback' => 'rules_user_integration_access',
  );
  return $items;
}

206
207
208
209
/**
 * Action callback to update a user's realname.
 */
function realname_action_realname_update($account, $context = array()) {
210
  realname_update($account);
211
212
}

213
214
215
216
217
218
219
/**
 * Implements hook_views_api().
 */
function realname_views_api() {
  return array('api' => 3);
}

220
221
222
223
224
/**
 * @addtogroup realname
 * @{
 */

225
/**
226
 * Loads a real name.
227
 *
228
 * @param object $account
229
 *   A user account object.
230
231
 *
 * @return mixed
232
 *   The user's generated real name.
233
 */
234
function realname_load($account) {
235
236
237
  $realnames = realname_load_multiple(array($account->uid => $account));
  return reset($realnames);
}
238

239
240
241
/**
 * Loads multiple real names.
 *
242
 * @param array $accounts
243
 *   An array of user account objects keyed by user ID.
244
 * @return array
245
246
 *   An array of real names keyed by user ID.
 */
247
function realname_load_multiple($accounts) {
248
249
250
251
252
253
254
255
256
  $realnames = &drupal_static(__FUNCTION__, array());

  if ($new_accounts = array_diff_key($accounts, $realnames)) {
    // Attempt to fetch realnames from the database first.
    $realnames += db_query("SELECT uid, realname FROM {realname} WHERE uid IN (:uids)", array(':uids' => array_keys($new_accounts)))->fetchAllKeyed();

    // For each account that was not present in the database, generate its
    // real name.
    foreach ($new_accounts as $uid => $account) {
257
      if (!isset($realnames[$uid])) {
258
        $realnames[$uid] = realname_update($account);
259
      }
260
    }
261
262
  }

263
  return array_intersect_key($realnames, $accounts);
264
265
}

266
267
268
/**
 * Update the realname for a user account.
 *
269
 * @param object $account
270
271
 *   A user account object.
 *
272
273
 * @return string
 *
274
275
276
277
278
279
280
281
282
283
 * @see hook_realname_pattern_alter()
 * @see hook_realname_alter()
 * @see hook_realname_update()
 */
function realname_update($account) {
  // Get the default pattern and allow other modules to alter it.
  $pattern = variable_get('realname_pattern', '[user:name-raw]');
  drupal_alter('realname_pattern', $pattern, $account);

  // Perform token replacement on the real name pattern.
284
  $realname = token_replace($pattern, array('user' => $account), array('clear' => TRUE, 'sanitize' => FALSE));
285
286
287
288
289
290

  // Remove any HTML tags.
  $realname = strip_tags(decode_entities($realname));

  // Remove double spaces (if a token had no value).
  $realname = preg_replace('/ {2,}/', ' ', $realname);
291
292

  // The name must be trimmed to 255 characters before inserting into the database.
293
  $realname = truncate_utf8(trim($realname), 255);
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313

  // Allow other modules to alter the generated realname.
  drupal_alter('realname', $realname, $account);

  // Save to the database and the static cache.
  db_merge('realname')
    ->key(array('uid' => $account->uid))
    ->fields(array(
      'realname' => $realname,
      'created' => REQUEST_TIME,
    ))
    ->execute();

  // Allow modules to react to the realname being updated.
  module_invoke_all('realname_update', $realname, $account);

  $account->realname = $realname;
  return $realname;
}

314
/**
315
 * Delete a real name.
316
 *
317
 * @param int $uid
318
 *   A user ID.
319
 */
320
321
function realname_delete($uid) {
  return realname_delete_multiple(array($uid));
322
323
324
}

/**
325
326
 * Delete multiple real names.
 *
327
 * @param array $uids
328
 *   An array of user IDs.
329
 */
330
function realname_delete_multiple($uids) {
331
332
  db_delete('realname')->condition('uid', $uids, 'IN')->execute();
  drupal_static_reset('realname_load_multiple');
333
  entity_get_controller('user')->resetCache($uids);
334
335
336
}

/**
337
 * Delete all real names.
338
 */
339
function realname_delete_all() {
340
  db_truncate('realname')->execute();
341
  drupal_static_reset('realname_load_multiple');
342
  entity_get_controller('user')->resetCache();
343
}
344
345
346
347

/**
 * @} End of "addtogroup realname".
 */
348
349
350
351
352

/**
 * Menu callback; Retrieve a JSON object containing autocomplete suggestions
 * for existing users based on their generated real names.
 */
353
354
function realname_autocomplete($field_name, $entity_type, $bundle_name, $string = '') {
  $instance = field_info_instance($entity_type, $field_name, $bundle_name);
355
356
  $matches = array();

357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
  // User entityreference autocomplete fields.
  if ($instance['widget']['type'] == 'entityreference_autocomplete' && !empty($string)) {
    $query = db_select('users', 'u');
    $query->leftJoin('realname', 'rn', 'u.uid = rn.uid');
    $query->fields('u', array('uid', 'name'));
    if ($instance['widget']['settings']['match_operator'] == 'CONTAINS') {
      $query->condition(db_or()
        ->condition('rn.realname', '%' . db_like($string) . '%', 'LIKE')
        ->condition('u.name', '%' . db_like($string) . '%', 'LIKE')
      );
    }
    else {
      $query->condition(db_or()
        ->condition('rn.realname', db_like($string) . '%', 'LIKE')
        ->condition('u.name', db_like($string) . '%', 'LIKE')
      );
    }
    $query->range(0, 10);
    $uids = $query->execute()->fetchCol();
    $accounts = user_load_multiple($uids);

    foreach ($accounts as $account) {
379
      $matches[t('!account (@uid)', array('!account' => format_username($account), '@uid' => $account->uid))] = t('!realname (@username)', array('!realname' => format_username($account), '@username' => $account->name));
380
381
382
383
    }
  }
  // User autocomplete fields.
  elseif (!empty($string)) {
384
385
    $query = db_select('users', 'u');
    $query->leftJoin('realname', 'rn', 'u.uid = rn.uid');
386
387
388
389
390
    $query->fields('u', array('uid', 'name'));
    $query->condition(db_or()
      ->condition('rn.realname', db_like($string) . '%', 'LIKE')
      ->condition('u.name', db_like($string) . '%', 'LIKE')
    );
391
392
393
394
395
    $query->range(0, 10);
    $uids = $query->execute()->fetchCol();
    $accounts = user_load_multiple($uids);

    foreach ($accounts as $account) {
396
      $matches[$account->name] = t('!realname (@username)', array('!realname' => format_username($account), '@username' => $account->name));
397
398
399
400
401
    }
  }

  drupal_json_output($matches);
}
402

hass's avatar
hass committed
403
404
405
/**
 * Menu Access callback for the autocomplete widget.
 *
406
 * @param string $field_name
hass's avatar
hass committed
407
 *   The name of the entity-reference field.
408
 * @param string $entity_type
hass's avatar
hass committed
409
 *   The entity type.
410
 * @param string $bundle_name
hass's avatar
hass committed
411
 *   The bundle name.
412
413
 *
 * @return boolean
hass's avatar
hass committed
414
415
416
417
418
419
420
421
422
423
424
425
 *   True if user can access this menu item.
 */
function realname_autocomplete_access_callback($field_name, $entity_type, $bundle_name) {
  $field = field_info_field($field_name);
  $instance = field_info_instance($entity_type, $field_name, $bundle_name);

  if (!$field || !$instance || $field['type'] != 'entityreference' || !field_access('edit', $field, $entity_type)) {
    return FALSE;
  }
  return TRUE;
}

426
427
428
429
430
431
432
433
434
435
436
437
438
439
/**
 * Implements hook_widget_form_alter().
 *
 * Overrides entityreference autocomplete fields that point to an entity
 * of type 'user'.
 */
function realname_field_widget_form_alter(&$element, &$form_state, $context) {
  if ($context['field']['type'] == 'entityreference') {
    if ($context['field']['settings']['target_type'] == 'user') {
      $element['target_id']['#autocomplete_path'] = 'realname/autocomplete/' . $context['field']['field_name'] . '/' . $context['instance']['entity_type'] . '/' . $context['instance']['bundle'];
    }
  }
}

440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
/**
 * Implements hook_form_FORM_ID_alter().
 */
function realname_form_user_admin_settings_alter(&$form, &$form_state, $form_id) {
  $form['email_admin_created']['user_mail_register_admin_created_body']['#element_validate'][] = 'realname_username_raw_token_validate';
  $form['email_no_approval_required']['user_mail_register_no_approval_required_body']['#element_validate'][] = 'realname_username_raw_token_validate';
  $form['email_activated']['settings']['user_mail_status_activated_body']['#element_validate'][] = 'realname_username_raw_token_validate';
}

/**
 * E-mail settings form validation callback.
 */
function realname_username_raw_token_validate(&$element, &$form_state) {
  $value = isset($element['#value']) ? $element['#value'] : $element['#default_value'];

455
456
457
458
459
  // Warns the site administrator that [user:name-raw], required to log in, is
  // missing. You can force the deactivation of this message by setting the
  // variable `realname_suppress_user_name_mail_validation` to TRUE (or any
  // value that converts to TRUE, like '1').
  if (!variable_get('realname_suppress_user_name_mail_validation', FALSE) && strpos($value, '[user:name-raw]') === FALSE) {
460
    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');
461
462
463
464
  }

  return $element;
}