realname.module 11.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
16

/**
17
 * Implements hook_permission().
18
 */
19
20
21
22
23
24
function realname_permission() {
  $permissions['administer realname'] = array(
    'title' => t('Administer Real Name configuration.'),
  );
  return $permissions;
}
25

26
/**
27
 * Implements hook_menu().
28
 */
29
30
31
function realname_menu() {
  $items['admin/config/people/realname'] = array(
    'title' => 'Real name',
32
    'description' => 'Use tokens to configure how user names are displayed.',
33
34
35
36
37
    'page callback' => 'drupal_get_form',
    'page arguments' => array('realname_settings_form'),
    'access arguments' => array('administer realname'),
    'file' => 'realname.admin.inc',
  );
38

39
40
  $items['realname/autocomplete'] = array(
    'page callback' => 'realname_autocomplete',
41
    'page arguments' => array('', '', 2),
42
43
44
    'access arguments' => array('access user profiles'),
    'type' => MENU_CALLBACK,
  );
45
46
47
48
49
50
51
  $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,
  );
52

53
  return $items;
54
55
}

56
/**
57
 * Implements hook_menu_alter() to replace user autocomplete with realname autocomplete.
58
59
60
61
 */
function realname_menu_alter(&$items) {
  // Implement realname autocomplete.
  $items['user/autocomplete']['page callback'] = 'realname_autocomplete';
62
  $items['user/autocomplete']['page arguments'] = array('', '', 2);
63
64
}

65
/**
66
 * Implements hook_username_alter().
67
 */
68
function realname_username_alter(&$name, $account) {
69
  static $in_username_alter = FALSE;
70

71
72
73
74
75
76
  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.
77
  if (isset($account->realname)) {
78
    if (drupal_strlen($account->realname)) {
79
      // Only if the real name is a non-empty string is $name actually altered.
80
81
      $name = $account->realname;
    }
82
    return;
83
  }
84
85
86
87

  // 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) {
88
    $in_username_alter = TRUE;
89
90

    // If $account->realname was undefined, then the user account object was
91
92
93
    // not properly loaded. We must enforce calling user_load().
    if ($realname_account = user_load($account->uid)) {
      realname_username_alter($name, $realname_account);
94
    }
95

96
    $in_username_alter = FALSE;
97
  }
98
99
100
}

/**
101
 * Implements hook_user_load().
102
 */
103
104
105
106
function realname_user_load(array $accounts) {
  $realnames = realname_load_multiple($accounts);
  foreach ($realnames as $uid => $realname) {
    $accounts[$uid]->realname = $realname;
107
108
109
110
  }
}

/**
111
 * Implements hook_user_update().
112
 */
113
114
115
function realname_user_update(array &$edit, stdClass $account, $category) {
  // Since user data may have changed, delete the existing realname.
  realname_delete($account->uid);
116
117
}

118
119
120
121
122
123
/**
 * Implements hook_user_delete().
 */
function realname_user_delete(stdClass $account) {
  realname_delete($account->uid);
}
124

125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
/**
 * 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.
 *
 * @param $uids
 *   An array of user IDs.
 */
function realname_user_operations_realname_update(array $uids) {
  $accounts = user_load_multiple($uids);
  foreach ($accounts as $account) {
145
    realname_delete($account->uid);
146
147
148
  }
}

149
150
151
152
153
154
155
156
157
158
159
160
161
/**
 * 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;
}

162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
/**
 * 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;
}

182
183
184
185
186
187
188
/**
 * Action callback to update a user's realname.
 */
function realname_action_realname_update($account, $context = array()) {
  realname_delete($account->uid);
}

189
190
191
192
193
194
195
/**
 * Implements hook_views_api().
 */
function realname_views_api() {
  return array('api' => 3);
}

196
197
198
199
200
/**
 * @addtogroup realname
 * @{
 */

201
/**
202
 * Loads a real name.
203
 *
204
205
 * @param $uid
 *   A user account object.
206
 * @return
207
 *   The user's generated real name.
208
 */
209
210
211
212
function realname_load(stdClass $account) {
  $realnames = realname_load_multiple(array($account->uid => $account));
  return reset($realnames);
}
213

214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
/**
 * Loads multiple real names.
 *
 * @param $accounts
 *   An array of user account objects keyed by user ID.
 * @return
 *   An array of real names keyed by user ID.
 */
function realname_load_multiple(array $accounts) {
  $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) {
232
      if (!isset($realnames[$uid])) {
233
        $realnames[$uid] = realname_update($account);
234
      }
235
    }
236
237
  }

238
  return array_intersect_key($realnames, $accounts);
239
240
}

241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
/**
 * Update the realname for a user account.
 *
 * @param $account
 *   A user account object.
 *
 * @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.
257
  $realname = token_replace($pattern, array('user' => $account), array('clear' => TRUE, 'sanitize' => FALSE));
258
259
260
261
262
263

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

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

  // The name must be trimmed to 255 characters before inserting into the database.
266
  $realname = truncate_utf8(trim($realname), 255);
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286

  // 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;
}

287
/**
288
 * Delete a real name.
289
 *
290
291
 * @param $uid
 *   A user ID.
292
 */
293
294
function realname_delete($uid) {
  return realname_delete_multiple(array($uid));
295
296
297
}

/**
298
299
300
301
 * Delete multiple real names.
 *
 * @param $uids
 *   An array of user IDs.
302
 */
303
304
305
function realname_delete_multiple(array $uids) {
  db_delete('realname')->condition('uid', $uids, 'IN')->execute();
  drupal_static_reset('realname_load_multiple');
306
  entity_get_controller('user')->resetCache($uids);
307
308
309
}

/**
310
 * Delete all real names.
311
 */
312
313
314
function realname_delete_all() {
  db_delete('realname')->execute();
  drupal_static_reset('realname_load_multiple');
315
  entity_get_controller('user')->resetCache();
316
}
317
318
319
320

/**
 * @} End of "addtogroup realname".
 */
321
322
323
324
325

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

330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
  // 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) {
      if ($matches[$account->name] != $account->name) {
        $matches[t('!account (@uid)', array('!account' => format_username($account), '@uid' => $account->uid))] = t('!realname (@username)', array('!realname' => format_username($account), '@username' => $account->name));
      }
    }
  }
  // User autocomplete fields.
  elseif (!empty($string)) {
359
360
    $query = db_select('users', 'u');
    $query->leftJoin('realname', 'rn', 'u.uid = rn.uid');
361
362
363
364
365
    $query->fields('u', array('uid', 'name'));
    $query->condition(db_or()
      ->condition('rn.realname', db_like($string) . '%', 'LIKE')
      ->condition('u.name', db_like($string) . '%', 'LIKE')
    );
366
367
368
369
370
    $query->range(0, 10);
    $uids = $query->execute()->fetchCol();
    $accounts = user_load_multiple($uids);

    foreach ($accounts as $account) {
371
      if ($matches[$account->name] != $account->name) {
372
        $matches[$account->name] = t('!realname (@username)', array('!realname' => format_username($account), '@username' => $account->name));
373
      }
374
375
376
377
378
    }
  }

  drupal_json_output($matches);
}
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414

/**
 * 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'];
    }
  }
}

/**
 * Menu Access callback for the autocomplete widget.
 *
 * @param $field_name
 *   The name of the entity-reference field.
 * @param $entity_type
 *   The entity type.
 * @param $bundle_name
 *   The bundle name.
 * @return
 *   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;
}