profile.module 34.8 KB
Newer Older
Dries's avatar
 
Dries committed
1
<?php
Dries's avatar
Dries committed
2
// $Id$
Dries's avatar
 
Dries committed
3

Dries's avatar
 
Dries committed
4 5 6 7 8
/**
 * @file
 * Support for configurable user profiles.
 */

9
/**
10
 * Private field, content only available to privileged users.
11 12
 */
define('PROFILE_PRIVATE', 1);
13 14 15 16

/**
 * Public field, content shown on profile page but not used on member list pages.
 */
17
define('PROFILE_PUBLIC', 2);
18 19 20 21

/**
 * Public field, content shown on profile page and on member list pages.
 */
22
define('PROFILE_PUBLIC_LISTINGS', 3);
23 24 25 26

/**
 * Hidden profile field, only accessible by administrators, modules and themes.
 */
27
define('PROFILE_HIDDEN', 4);
28

Dries's avatar
 
Dries committed
29 30 31
/**
 * Implementation of hook_help().
 */
32 33
function profile_help($path, $arg) {
  switch ($path) {
34
    case 'admin/help#profile':
35
      $output = '<p>'. t('The profile module allows you to define custom fields (such as country, real name, age, ...) in the user profile. This permits users of a site to share more information about themselves, and can help community-based sites to organize users around profile fields.') .'</p>';
36 37 38 39 40 41 42 43 44 45 46
      $output .= t('<p>The following types of fields can be added to the user profile:</p>
<ul>
<li>single-line textfield</li>
<li>multi-line textfield</li>
<li>checkbox</li>
<li>list selection</li>
<li>freeform list</li>
<li>URL</li>
<li>date</li>
</ul>
');
47
      $output .= '<p>'. t('For more information please read the configuration and customization handbook <a href="@profile">Profile page</a>.', array('@profile' => 'http://drupal.org/handbook/modules/profile/')) .'</p>';
48
      return $output;
49
    case 'admin/user/profile':
50
      return '<p>'. t('Here you can define custom fields that users can fill in as part of their user profile (such as <em>country</em>, <em>real name</em>, <em>age</em>, ...).') .'</p>';
Dries's avatar
 
Dries committed
51 52 53
  }
}

54 55 56 57 58 59 60
/**
 * Implementation of hook_theme()
 */
function profile_theme() {
  return array(
    'profile_block' => array(
      'arguments' => array('account' => NULL, 'fields' => array()),
61
      'template' => 'profile-block',
62 63 64
    ),
    'profile_listing' => array(
      'arguments' => array('account' => NULL, 'fields' => array()),
65
      'template' => 'profile-listing',
66 67 68
    ),
    'profile_wrapper' => array(
      'arguments' => array('content' => NULL),
69
      'template' => 'profile-wrapper',
70 71
    )
  );
72 73
}

74 75 76
/**
 * Implementation of hook_menu().
 */
77 78
function profile_menu() {
  $items['profile'] = array(
79
    'title' => 'User list',
80 81 82 83 84
    'page callback' => 'profile_browse',
    'access arguments' => array('access user profiles'),
    'type' => MENU_SUGGESTED_ITEM,
  );
  $items['admin/user/profile'] = array(
85 86
    'title' => 'Profiles',
    'description' => 'Create customizable fields for your users.',
87 88 89
    'page callback' => 'profile_admin_overview',
  );
  $items['admin/user/profile/add'] = array(
90
    'title' => 'Add field',
91 92 93 94 95
    'page callback' => 'drupal_get_form',
    'page arguments' => array('profile_field_form'),
    'type' => MENU_CALLBACK,
  );
  $items['admin/user/profile/autocomplete'] = array(
96
    'title' => 'Profile category autocomplete',
97 98 99 100
    'page callback' => 'profile_admin_settings_autocomplete',
    'type' => MENU_CALLBACK,
  );
  $items['admin/user/profile/edit'] = array(
101
    'title' => 'Edit field',
102 103 104 105 106
    'page callback' => 'drupal_get_form',
    'page arguments' => array('profile_field_form'),
    'type' => MENU_CALLBACK,
  );
  $items['admin/user/profile/delete'] = array(
107
    'title' => 'Delete field',
108 109 110 111 112
    'page callback' => 'drupal_get_form',
    'page arguments' => array('profile_field_delete'),
    'type' => MENU_CALLBACK,
  );
  $items['profile/autocomplete'] = array(
113
    'title' => 'Profile autocomplete',
114
    'page callback' => 'profile_autocomplete',
115
    'access arguments' => array('access user profiles'),
116 117
    'type' => MENU_CALLBACK,
  );
118 119 120
  return $items;
}

121 122 123 124 125 126 127
/**
 * Implementation of hook_block().
 */
function profile_block($op = 'list', $delta = 0, $edit = array()) {

  if ($op == 'list') {
     $blocks[0]['info'] = t('Author information');
128
     $blocks[0]['cache'] = BLOCK_CACHE_PER_PAGE | BLOCK_CACHE_PER_ROLE;
129 130 131 132 133
     return $blocks;
  }
  else if ($op == 'configure' && $delta == 0) {
    // Compile a list of fields to show
    $fields = array();
134
    $result = db_query('SELECT name, title, weight, visibility FROM {profile_fields} WHERE visibility IN (%d, %d) ORDER BY weight', PROFILE_PUBLIC, PROFILE_PUBLIC_LISTINGS);
135 136 137 138
    while ($record = db_fetch_object($result)) {
      $fields[$record->name] = $record->title;
    }
    $fields['user_profile'] = t('Link to full user profile');
139 140 141 142
    $form['profile_block_author_fields'] = array('#type' => 'checkboxes',
      '#title' => t('Profile fields to display'),
      '#default_value' => variable_get('profile_block_author_fields', NULL),
      '#options' => $fields,
143
      '#description' => t('Select which profile fields you wish to display in the block. Only fields designated as public in the <a href="@profile-admin">profile field configuration</a> are available.', array('@profile-admin' => url('admin/user/profile'))),
144
    );
145
    return $form;
146 147 148 149 150 151
  }
  else if ($op == 'save' && $delta == 0) {
    variable_set('profile_block_author_fields', $edit['profile_block_author_fields']);
  }
  else if ($op == 'view') {
    if (user_access('access user profiles')) {
152
      $output = '';
153
      if ((arg(0) == 'node') && is_numeric(arg(1)) && (arg(2) == NULL)) {
154
        $node = node_load(arg(1));
155 156 157
        $account = user_load(array('uid' => $node->uid));

        if ($use_fields = variable_get('profile_block_author_fields', array())) {
158
          // Compile a list of fields to show.
159
          $fields = array();
160 161 162
          $result = db_query('SELECT name, title, type, visibility, weight FROM {profile_fields} WHERE visibility IN (%d, %d) ORDER BY weight', PROFILE_PUBLIC, PROFILE_PUBLIC_LISTINGS);
          while ($record = db_fetch_object($result)) {
            // Ensure that field is displayed only if it is among the defined block fields and, if it is private, the user has appropriate permissions.
163
            if (isset($use_fields[$record->name]) && $use_fields[$record->name]) {
164 165 166 167 168 169
              $fields[] = $record;
            }
          }
        }

        if ($fields) {
170 171
          $profile = _profile_update_user_fields($fields, $account);
          $output .= theme('profile_block', $account, $profile, TRUE);
172 173
        }

174
        if (isset($use_fields['user_profile']) && $use_fields['user_profile']) {
175
          $output .= '<div>'. l(t('View full user profile'), 'user/'. $account->uid) .'</div>';
176 177 178 179 180 181 182 183 184 185 186 187
        }
      }

      if ($output) {
         $block['subject'] = t('About %name', array('%name' => $account->name));
         $block['content'] = $output;
         return $block;
      }
    }
  }
}

Dries's avatar
 
Dries committed
188
/**
189
 * Implementation of hook_user().
Dries's avatar
 
Dries committed
190
 */
191 192 193 194 195
function profile_user($type, &$edit, &$user, $category = NULL) {
  switch ($type) {
    case 'load':
      return profile_load_profile($user);
    case 'register':
196
      return profile_form_profile($edit, $user, $category, TRUE);
197
    case 'update':
198
    return profile_save_profile($edit, $user, $category);
199
    case 'insert':
200
      return profile_save_profile($edit, $user, $category, TRUE);
201 202 203 204 205 206 207 208
    case 'view':
      return profile_view_profile($user);
    case 'form':
      return profile_form_profile($edit, $user, $category);
    case 'validate':
      return profile_validate_profile($edit, $category);
    case 'categories':
      return profile_categories();
209 210
    case 'delete':
      db_query('DELETE FROM {profile_values} WHERE uid = %d', $user->uid);
211 212
  }
}
Dries's avatar
 
Dries committed
213

214
/**
215
 * Menu callback: Generate a form to add/edit a user profile field.
216
 */
217
function profile_field_form(&$form_state, $arg = NULL) {
218 219 220
  if (arg(3) == 'edit') {
    if (is_numeric($arg)) {
      $fid = $arg;
221

222
      $edit = db_fetch_array(db_query('SELECT * FROM {profile_fields} WHERE fid = %d', $fid));
223

224 225 226 227 228 229 230 231
      if (!$edit) {
        drupal_not_found();
        return;
      }
      drupal_set_title(t('edit %title', array('%title' => $edit['title'])));
      $form['fid'] = array('#type' => 'value',
        '#value' => $fid,
      );
232
      $type = $edit['type'];
233
    }
234 235 236
    else {
      drupal_not_found();
      return;
237 238 239
    }
  }
  else {
240 241 242 243
    $types = _profile_field_types();
    if (!isset($types[$arg])) {
      drupal_not_found();
      return;
244
    }
245 246 247 248
    $type = $arg;
    drupal_set_title(t('add new %type', array('%type' => $types[$type])));
    $edit = array('name' => 'profile_');
    $form['type'] = array('#type' => 'value', '#value' => $type);
249
  }
250 251 252 253 254 255 256 257 258 259
  $edit += array(
    'category' => '',
    'title' => '',
    'explanation' => '',
    'weight' => '',
    'page' => '',
    'autocomplete' => '',
    'required' => '',
    'register' => '',
  );
260 261 262 263 264 265
  $form['fields'] = array('#type' => 'fieldset',
    '#title' => t('Field settings'),
  );
  $form['fields']['category'] = array('#type' => 'textfield',
    '#title' => t('Category'),
    '#default_value' => $edit['category'],
266
    '#autocomplete_path' => 'admin/user/profile/autocomplete',
267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305
    '#description' => t('The category the new field should be part of. Categories are used to group fields logically. An example category is "Personal information".'),
    '#required' => TRUE,
  );
  $form['fields']['title'] = array('#type' => 'textfield',
    '#title' => t('Title'),
    '#default_value' => $edit['title'],
    '#description' => t('The title of the new field. The title will be shown to the user. An example title is "Favorite color".'),
    '#required' => TRUE,
  );
  $form['fields']['name'] = array('#type' => 'textfield',
    '#title' => t('Form name'),
    '#default_value' => $edit['name'],
    '#description' => t('The name of the field. The form name is not shown to the user but used internally in the HTML code and URLs.
Unless you know what you are doing, it is highly recommended that you prefix the form name with <code>profile_</code> to avoid name clashes with other fields. Spaces or any other special characters except dash (-) and underscore (_) are not allowed. An example name is "profile_favorite_color" or perhaps just "profile_color".'),
    '#required' => TRUE,
  );
  $form['fields']['explanation'] = array('#type' => 'textarea',
    '#title' => t('Explanation'),
    '#default_value' => $edit['explanation'],
    '#description' => t('An optional explanation to go with the new field. The explanation will be shown to the user.'),
  );
  if ($type == 'selection') {
    $form['fields']['options'] = array('#type' => 'textarea',
      '#title' => t('Selection options'),
      '#default_value' => $edit['options'],
      '#description' => t('A list of all options. Put each option on a separate line. Example options are "red", "blue", "green", etc.'),
    );
  }
  $form['fields']['weight'] = array('#type' => 'weight',
    '#title' => t('Weight'),
    '#default_value' => $edit['weight'],
    '#delta' => 5,
    '#description' => t('The weights define the order in which the form fields are shown. Lighter fields "float up" towards the top of the category.'),
  );
  $form['fields']['visibility'] = array('#type' => 'radios',
    '#title' => t('Visibility'),
    '#default_value' => isset($edit['visibility']) ? $edit['visibility'] : PROFILE_PUBLIC,
    '#options' => array(PROFILE_HIDDEN => t('Hidden profile field, only accessible by administrators, modules and themes.'), PROFILE_PRIVATE => t('Private field, content only available to privileged users.'), PROFILE_PUBLIC => t('Public field, content shown on profile page but not used on member list pages.'), PROFILE_PUBLIC_LISTINGS => t('Public field, content shown on profile page and on member list pages.')),
  );
306
  if ($type == 'selection' || $type == 'list' || $type == 'textfield') {
307 308 309
    $form['fields']['page'] = array('#type' => 'textfield',
      '#title' => t('Page title'),
      '#default_value' => $edit['page'],
310
      '#description' => t('To enable browsing this field by value, enter a title for the resulting page. The word <code>%value</code> will be substituted with the corresponding value. An example page title is "People whose favorite color is %value". This is only applicable for a public field.'),
311 312
    );
  }
313
  else if ($type == 'checkbox') {
314 315 316
    $form['fields']['page'] = array('#type' => 'textfield',
      '#title' => t('Page title'),
      '#default_value' => $edit['page'],
317
      '#description' => t('To enable browsing this field by value, enter a title for the resulting page. An example page title is "People who are employed". This is only applicable for a public field.'),
318 319
    );
  }
320 321 322 323
  $form['fields']['autocomplete'] = array('#type' => 'checkbox',
    '#title' => t('Form will auto-complete while user is typing.'),
    '#default_value' => $edit['autocomplete'],
  );
324 325 326 327 328 329 330 331 332 333 334
  $form['fields']['required'] = array('#type' => 'checkbox',
    '#title' => t('The user must enter a value.'),
    '#default_value' => $edit['required'],
  );
  $form['fields']['register'] = array('#type' => 'checkbox',
    '#title' => t('Visible in user registration form.'),
    '#default_value' => $edit['register'],
  );
  $form['submit'] = array('#type' => 'submit',
    '#value' => t('Save field'),
  );
335
  return $form;
336 337
}

338 339 340
/**
 * Validate profile_field_form submissions.
 */
341
function profile_field_form_validate($form, &$form_state) {
342
  // Validate the 'field name':
343
  if (preg_match('/[^a-zA-Z0-9_-]/', $form_state['values']['name'])) {
344
    form_set_error('name', t('The specified form name contains one or more illegal characters. Spaces or any other special characters except dash (-) and underscore (_) are not allowed.'));
345 346
  }

347
  if (in_array($form_state['values']['name'], user_fields())) {
348 349 350
    form_set_error('name', t('The specified form name is reserved for use by Drupal.'));
  }
  // Validate the category:
351
  if (!$form_state['values']['category']) {
352 353
    form_set_error('category', t('You must enter a category.'));
  }
354
  if ($form_state['values']['category'] == 'account') {
355 356
    form_set_error('category', t('The specified category name is reserved for use by Drupal.'));
  }
357 358
  $args1 = array($form_state['values']['title'], $form_state['values']['category']);
  $args2 = array($form_state['values']['name']);
359
  $query_suffix = '';
Dries's avatar
Dries committed
360

361 362
  if (isset($form_state['values']['fid'])) {
    $args1[] = $args2[] = $form_state['values']['fid'];
363 364 365 366 367 368 369 370 371
    $query_suffix = ' AND fid != %d';
  }

  if (db_result(db_query("SELECT fid FROM {profile_fields} WHERE title = '%s' AND category = '%s'". $query_suffix, $args1))) {
    form_set_error('title', t('The specified title is already in use.'));
  }
  if (db_result(db_query("SELECT fid FROM {profile_fields} WHERE name = '%s'". $query_suffix, $args2))) {
    form_set_error('name', t('The specified name is already in use.'));
  }
372 373 374
}

/**
375
 * Process profile_field_form submissions.
376
 */
377 378 379
function profile_field_form_submit($form, &$form_state) {
  if (!isset($form_state['values']['fid'])) {
    db_query("INSERT INTO {profile_fields} (title, name, explanation, category, type, weight, required, register, visibility, autocomplete, options, page) VALUES ('%s', '%s', '%s', '%s', '%s', %d, %d, %d, %d, %d, '%s', '%s')", $form_state['values']['title'], $form_state['values']['name'], $form_state['values']['explanation'], $form_state['values']['category'], $form_state['values']['type'], $form_state['values']['weight'], $form_state['values']['required'], $form_state['values']['register'], $form_state['values']['visibility'], $form_state['values']['autocomplete'], $form_state['values']['options'], $form_state['values']['page']);
Dries's avatar
Dries committed
380

381
    drupal_set_message(t('The field has been created.'));
382
    watchdog('profile', 'Profile field %field added under category %category.', array('%field' => $form_state['values']['title'], '%category' => $form_state['values']['category']), WATCHDOG_NOTICE, l(t('view'), 'admin/user/profile'));
383 384
  }
  else {
385
    db_query("UPDATE {profile_fields} SET title = '%s', name = '%s', explanation = '%s', category = '%s', weight = %d, required = %d, register = %d, visibility = %d, autocomplete = %d, options = '%s', page = '%s' WHERE fid = %d", $form_state['values']['title'], $form_state['values']['name'], $form_state['values']['explanation'], $form_state['values']['category'], $form_state['values']['weight'], $form_state['values']['required'], $form_state['values']['register'], $form_state['values']['visibility'], $form_state['values']['autocomplete'], $form_state['values']['options'], $form_state['values']['page'], $form_state['values']['fid']);
386 387 388 389

    drupal_set_message(t('The field has been updated.'));
  }
  cache_clear_all();
390
  menu_rebuild();
Dries's avatar
Dries committed
391

392 393
  $form_state['redirect'] = 'admin/user/profile';
  return;
394 395 396 397 398
}

/**
 * Menu callback; deletes a field from all user profiles.
 */
399
function profile_field_delete(&$form_state, $fid) {
400 401 402 403
  $field = db_fetch_object(db_query("SELECT title FROM {profile_fields} WHERE fid = %d", $fid));
  if (!$field) {
    drupal_not_found();
    return;
404
  }
405 406
  $form['fid'] = array('#type' => 'value', '#value' => $fid);
  $form['title'] = array('#type' => 'value', '#value' => $field->title);
Dries's avatar
Dries committed
407

408
  return confirm_form($form,
409 410 411
    t('Are you sure you want to delete the field %field?', array('%field' => $field->title)), 'admin/user/profile',
    t('This action cannot be undone. If users have entered values into this field in their profile, these entries will also be deleted. If you want to keep the user-entered data, instead of deleting the field you may wish to <a href="@edit-field">edit this field</a> and change it to a hidden profile field so that it may only be accessed by administrators.', array('@edit-field' => url('admin/user/profile/edit/'. $fid))),
    t('Delete'), t('Cancel'));
412 413 414 415 416
}

/**
 * Process a field delete form submission.
 */
417 418 419
function profile_field_delete_submit($form, &$form_state) {
  db_query('DELETE FROM {profile_fields} WHERE fid = %d', $form_state['values']['fid']);
  db_query('DELETE FROM {profile_values} WHERE fid = %d', $form_state['values']['fid']);
Dries's avatar
Dries committed
420

421
  cache_clear_all();
Dries's avatar
Dries committed
422

423 424
  drupal_set_message(t('The field %field has been deleted.', array('%field' => $form_state['values']['title'])));
  watchdog('profile', 'Profile field %field deleted.', array('%field' => $form_state['values']['title']), WATCHDOG_NOTICE, l(t('view'), 'admin/user/profile'));
Dries's avatar
Dries committed
425

426 427
  $form_state['redirect'] = 'admin/user/profile';
  return;
428 429 430 431 432 433 434
}

/**
 * Menu callback; display a listing of all editable profile fields.
 */
function profile_admin_overview() {

435
  $result = db_query('SELECT title, name, type, category, fid FROM {profile_fields} ORDER BY category, weight');
436 437
  $rows = array();
  while ($field = db_fetch_object($result)) {
438
    $rows[] = array(check_plain($field->title), $field->name, _profile_field_types($field->type), $field->category, l(t('edit'), "admin/user/profile/edit/$field->fid"), l(t('delete'), "admin/user/profile/delete/$field->fid"));
439 440 441 442 443 444 445 446 447 448 449
  }
  if (count($rows) == 0) {
    $rows[] = array(array('data' => t('No fields defined.'), 'colspan' => '6'));
  }

  $header = array(t('Title'), t('Name'), t('Type'), t('Category'), array('data' => t('Operations'), 'colspan' => '2'));

  $output  = theme('table', $header, $rows);
  $output .= '<h2>'. t('Add new field') .'</h2>';
  $output .= '<ul>';
  foreach (_profile_field_types() as $key => $value) {
450
    $output .= '<li>'. l($value, "admin/user/profile/add/$key") .'</li>';
451 452 453 454
  }
  $output .= '</ul>';

  return $output;
Dries's avatar
 
Dries committed
455
}
Dries's avatar
 
Dries committed
456

Dries's avatar
 
Dries committed
457 458 459
/**
 * Menu callback; display a list of user information.
 */
Dries's avatar
 
Dries committed
460
function profile_browse() {
461 462
  // Ensure that the path is converted to 3 levels always.
  list(, $name, $value) = array_pad(explode('/', $_GET['q'], 3), 3, '');
Dries's avatar
 
Dries committed
463

Steven Wittens's avatar
Steven Wittens committed
464
  $field = db_fetch_object(db_query("SELECT DISTINCT(fid), type, title, page, visibility FROM {profile_fields} WHERE name = '%s'", $name));
Dries's avatar
 
Dries committed
465

Dries's avatar
 
Dries committed
466
  if ($name && $field->fid) {
467 468 469 470 471
    // Only allow browsing of fields that have a page title set.
    if (empty($field->page)) {
      drupal_not_found();
      return;
    }
472 473
    // Do not allow browsing of private and hidden fields by non-admins.
    if (!user_access('administer users') && ($field->visibility == PROFILE_PRIVATE || $field->visibility == PROFILE_HIDDEN)) {
Steven Wittens's avatar
Steven Wittens committed
474 475 476 477
       drupal_access_denied();
       return;
    }

478
    // Compile a list of fields to show.
Dries's avatar
 
Dries committed
479
    $fields = array();
480
    $result = db_query('SELECT name, title, type, weight, page FROM {profile_fields} WHERE fid != %d AND visibility = %d ORDER BY weight', $field->fid, PROFILE_PUBLIC_LISTINGS);
Dries's avatar
 
Dries committed
481 482 483
    while ($record = db_fetch_object($result)) {
      $fields[] = $record;
    }
Dries's avatar
 
Dries committed
484

Dries's avatar
Dries committed
485
    // Determine what query to use:
486
    $arguments = array($field->fid);
Dries's avatar
Dries committed
487 488 489 490
    switch ($field->type) {
      case 'checkbox':
        $query = 'v.value = 1';
        break;
491
      case 'textfield':
Dries's avatar
Dries committed
492
      case 'selection':
493 494
        $query = "v.value = '%s'";
        $arguments[] = $value;
Dries's avatar
Dries committed
495 496
        break;
      case 'list':
497 498
        $query = "v.value LIKE '%%%s%%'";
        $arguments[] = $value;
Dries's avatar
Dries committed
499
        break;
Steven Wittens's avatar
Steven Wittens committed
500 501 502
      default:
        drupal_not_found();
        return;
Dries's avatar
Dries committed
503 504
    }

Dries's avatar
 
Dries committed
505
    // Extract the affected users:
506
    $result = pager_query("SELECT u.uid, u.access FROM {users} u INNER JOIN {profile_values} v ON u.uid = v.uid WHERE v.fid = %d AND $query AND u.access != 0 AND u.status != 0 ORDER BY u.access DESC", 20, 0, NULL, $arguments);
Dries's avatar
 
Dries committed
507

508
    $content = '';
Dries's avatar
 
Dries committed
509
    while ($account = db_fetch_object($result)) {
510
      $account = user_load(array('uid' => $account->uid));
511
      $profile = _profile_update_user_fields($fields, $account);
512
      $content .= theme('profile_listing', $account, $profile);
Dries's avatar
 
Dries committed
513
    }
514
    $output = theme('profile_wrapper', $content);
Dries's avatar
 
Dries committed
515 516
    $output .= theme('pager', NULL, 20);

517
    if ($field->type == 'selection' || $field->type == 'list' || $field->type == 'textfield') {
Dries's avatar
Dries committed
518
      $title = strtr(check_plain($field->page), array('%value' => theme('placeholder', $value)));
Dries's avatar
 
Dries committed
519 520
    }
    else {
Dries's avatar
Dries committed
521
      $title = check_plain($field->page);
Dries's avatar
 
Dries committed
522 523
    }

524
    drupal_set_title($title);
Dries's avatar
 
Dries committed
525
    return $output;
Dries's avatar
 
Dries committed
526
  }
527
  else if ($name && !$field->fid) {
Dries's avatar
 
Dries committed
528
    drupal_not_found();
Dries's avatar
 
Dries committed
529
  }
Dries's avatar
 
Dries committed
530
  else {
531
    // Compile a list of fields to show.
Dries's avatar
 
Dries committed
532
    $fields = array();
533
    $result = db_query('SELECT name, title, type, weight, page FROM {profile_fields} WHERE visibility = %d ORDER BY category, weight', PROFILE_PUBLIC_LISTINGS);
Dries's avatar
 
Dries committed
534 535 536 537 538
    while ($record = db_fetch_object($result)) {
      $fields[] = $record;
    }

    // Extract the affected users:
539
    $result = pager_query('SELECT uid, access FROM {users} WHERE uid > 0 AND status != 0 AND access != 0 ORDER BY access DESC', 20, 0, NULL);
Dries's avatar
 
Dries committed
540

541
    $content = '';
Dries's avatar
 
Dries committed
542
    while ($account = db_fetch_object($result)) {
543
      $account = user_load(array('uid' => $account->uid));
544
      $profile = _profile_update_user_fields($fields, $account);
545
      $content .= theme('profile_listing', $account, $profile);
Dries's avatar
 
Dries committed
546
    }
547
    $output = theme('profile_wrapper', $content);
Dries's avatar
 
Dries committed
548 549
    $output .= theme('pager', NULL, 20);

550
    drupal_set_title(t('User list'));
Dries's avatar
 
Dries committed
551
    return $output;
Dries's avatar
 
Dries committed
552
  }
Dries's avatar
 
Dries committed
553
}
Dries's avatar
 
Dries committed
554

Dries's avatar
 
Dries committed
555
function profile_load_profile(&$user) {
Steven Wittens's avatar
Steven Wittens committed
556
  $result = db_query('SELECT f.name, f.type, v.value FROM {profile_fields} f INNER JOIN {profile_values} v ON f.fid = v.fid WHERE uid = %d', $user->uid);
Dries's avatar
 
Dries committed
557 558
  while ($field = db_fetch_object($result)) {
    if (empty($user->{$field->name})) {
Steven Wittens's avatar
Steven Wittens committed
559
      $user->{$field->name} = _profile_field_serialize($field->type) ? unserialize($field->value) : $field->value;
Dries's avatar
 
Dries committed
560
    }
Dries's avatar
 
Dries committed
561
  }
Dries's avatar
 
Dries committed
562 563
}

564 565
function profile_save_profile(&$edit, &$user, $category, $register = FALSE) {
  $result = _profile_get_fields($category, $register);
Dries's avatar
 
Dries committed
566
  while ($field = db_fetch_object($result)) {
Steven Wittens's avatar
Steven Wittens committed
567 568 569
    if (_profile_field_serialize($field->type)) {
       $edit[$field->name] = serialize($edit[$field->name]);
    }
570 571
    db_query("DELETE FROM {profile_values} WHERE fid = %d AND uid = %d", $field->fid, $user->uid);
    db_query("INSERT INTO {profile_values} (fid, uid, value) VALUES (%d, %d, '%s')", $field->fid, $user->uid, $edit[$field->name]);
572
    // Mark field as handled (prevents saving to user->data).
573
    $edit[$field->name] = NULL;
Dries's avatar
 
Dries committed
574
  }
Dries's avatar
 
Dries committed
575 576
}

577
function profile_view_field($user, $field) {
578 579 580 581 582 583
  // Only allow browsing of private fields for admins, if browsing is enabled,
  // and if a user has permission to view profiles. Note that this check is
  // necessary because a user may always see their own profile.
  $browse = user_access('access user profiles')
         && (user_access('administer users') || $field->visibility != PROFILE_PRIVATE)
         && !empty($field->page);
Steven Wittens's avatar
Steven Wittens committed
584

585
  if (isset($user->{$field->name}) && $value = $user->{$field->name}) {
586 587
    switch ($field->type) {
      case 'textarea':
588
        return check_markup($value);
589
      case 'textfield':
590
      case 'selection':
591
        return $browse ? l($value, 'profile/'. $field->name .'/'. $value) : check_plain($value);
592
      case 'checkbox':
593
        return $browse ? l($field->title, 'profile/'. $field->name) : check_plain($field->title);
594
      case 'url':
595
        return '<a href="'. check_url($value) .'">'. check_plain($value) .'</a>';
Steven Wittens's avatar
Steven Wittens committed
596
      case 'date':
597
        $format = substr(variable_get('date_format_short', 'm/d/Y - H:i'), 0, 5);
598
        // Note: Avoid PHP's date() because it does not handle dates before
Steven Wittens's avatar
Steven Wittens committed
599 600 601 602 603
        // 1970 on Windows. This would make the date field useless for e.g.
        // birthdays.
        $replace = array('d' => sprintf('%02d', $value['day']),
                         'j' => $value['day'],
                         'm' => sprintf('%02d', $value['month']),
604
                         'M' => map_month($value['month']),
605
                         'Y' => $value['year'],
606 607
                         'H:i' => NULL,
                         'g:ia' => NULL);
Steven Wittens's avatar
Steven Wittens committed
608
        return strtr($format, $replace);
609
      case 'list':
Dries's avatar
 
Dries committed
610
        $values = split("[,\n\r]", $value);
Dries's avatar
Dries committed
611 612
        $fields = array();
        foreach ($values as $value) {
Steven Wittens's avatar
Steven Wittens committed
613
          if ($value = trim($value)) {
614
            $fields[] = $browse ? l($value, 'profile/'. $field->name .'/'. $value) : check_plain($value);
Dries's avatar
Dries committed
615 616 617
          }
        }
        return implode(', ', $fields);
618 619 620 621
    }
  }
}

622
function profile_view_profile(&$user) {
Dries's avatar
 
Dries committed
623

624
  profile_load_profile($user);
Dries's avatar
 
Dries committed
625

Steven Wittens's avatar
Steven Wittens committed
626 627
  // Show private fields to administrators and people viewing their own account.
  if (user_access('administer users') || $GLOBALS['user']->uid == $user->uid) {
628
    $result = db_query('SELECT * FROM {profile_fields} WHERE visibility != %d ORDER BY category, weight', PROFILE_HIDDEN);
Steven Wittens's avatar
Steven Wittens committed
629 630
  }
  else {
631
    $result = db_query('SELECT * FROM {profile_fields} WHERE visibility != %d AND visibility != %d ORDER BY category, weight', PROFILE_PRIVATE, PROFILE_HIDDEN);
Steven Wittens's avatar
Steven Wittens committed
632 633
  }

634
  $fields = array();
Dries's avatar
 
Dries committed
635
  while ($field = db_fetch_object($result)) {
636
    if ($value = profile_view_field($user, $field)) {
637
      $title = ($field->type != 'checkbox') ? check_plain($field->title) : NULL;
638 639 640 641 642 643 644 645 646 647 648 649 650 651 652

      // Create a single fieldset for each category.
      if (!isset($user->content[$field->category])) {
        $user->content[$field->category] = array(
          '#type' => 'user_profile_category',
          '#title' => check_plain($field->category),
        );
      }

      $user->content[$field->category][$field->name] = array(
        '#type' => 'user_profile_item',
        '#title' => check_plain($title),
        '#value' => check_markup($value),
        '#weight' => $field->weight,
        '#attributes' => array('class' => 'profile-'. $field->name),
653
      );
Dries's avatar
 
Dries committed
654 655
    }
  }
Dries's avatar
 
Dries committed
656
}
Dries's avatar
 
Dries committed
657

658 659
function _profile_form_explanation($field) {
  $output = $field->explanation;
Dries's avatar
 
Dries committed
660

661
  if ($field->type == 'list') {
662
    $output .= ' '. t('Put each item on a separate line or separate them by commas. No HTML allowed.');
663 664 665 666 667 668 669 670 671
  }

  if ($field->visibility == PROFILE_PRIVATE) {
    $output .= ' '. t('The content of this field is kept private and will not be shown publicly.');
  }

  return $output;
}

672 673
function profile_form_profile($edit, $user, $category, $register = FALSE) {
  $result = _profile_get_fields($category, $register);
674
  $weight = 1;
675
  $fields = array();
Dries's avatar
 
Dries committed
676
  while ($field = db_fetch_object($result)) {
Dries's avatar
 
Dries committed
677
    $category = $field->category;
678
    if (!isset($fields[$category])) {
679
      $fields[$category] = array('#type' => 'fieldset', '#title' => $category, '#weight' => $weight++);
680
    }
Dries's avatar
 
Dries committed
681 682
    switch ($field->type) {
      case 'textfield':
683
      case 'url':
684
        $fields[$category][$field->name] = array('#type' => 'textfield',
685
          '#title' => check_plain($field->title),
686
          '#default_value' => isset($edit[$field->name]) ? $edit[$field->name] : '',
687 688
          '#maxlength' => 255,
          '#description' => _profile_form_explanation($field),
689
          '#required' => $field->required,
690
        );
691 692 693
        if ($field->autocomplete) {
          $fields[$category][$field->name]['#autocomplete_path'] = "profile/autocomplete/". $field->fid;
        }
Dries's avatar
 
Dries committed
694
        break;
695
      case 'textarea':
696
        $fields[$category][$field->name] = array('#type' => 'textarea',
697 698 699
          '#title' => check_plain($field->title),
          '#default_value' => $edit[$field->name],
          '#description' => _profile_form_explanation($field),
700
          '#required' => $field->required,
701
        );
Dries's avatar
Dries committed
702 703
        break;
      case 'list':
704
        $fields[$category][$field->name] = array('#type' => 'textarea',
705 706 707
          '#title' => check_plain($field->title),
          '#default_value' => $edit[$field->name],
          '#description' => _profile_form_explanation($field),
708
          '#required' => $field->required,
709
        );
Dries's avatar
 
Dries committed
710
        break;
711
      case 'checkbox':
712
        $fields[$category][$field->name] = array('#type' => 'checkbox',
713 714 715
          '#title' => check_plain($field->title),
          '#default_value' => $edit[$field->name],
          '#description' => _profile_form_explanation($field),
716
          '#required' => $field->required,
717
        );
Dries's avatar
 
Dries committed
718 719
        break;
      case 'selection':
720
        $options = $field->required ? array() : array('--');
Dries's avatar
 
Dries committed
721
        $lines = split("[,\n\r]", $field->options);
Dries's avatar
 
Dries committed
722 723 724 725 726
        foreach ($lines as $line) {
          if ($line = trim($line)) {
            $options[$line] = $line;
          }
        }
727
        $fields[$category][$field->name] = array('#type' => 'select',
728 729 730 731
          '#title' => check_plain($field->title),
          '#default_value' => $edit[$field->name],
          '#options' => $options,
          '#description' => _profile_form_explanation($field),
732
          '#required' => $field->required,
733
        );
Dries's avatar
 
Dries committed
734
        break;
Steven Wittens's avatar
Steven Wittens committed
735
      case 'date':
736
        $fields[$category][$field->name] = array('#type' => 'date',
737 738 739
          '#title' => check_plain($field->title),
          '#default_value' => $edit[$field->name],
          '#description' => _profile_form_explanation($field),
740
          '#required' => $field->required,
741
        );
Steven Wittens's avatar
Steven Wittens committed
742
        break;
Dries's avatar
 
Dries committed
743 744
    }
  }
745
  return $fields;
Dries's avatar
 
Dries committed
746 747
}

748 749 750 751
/**
 * Callback to allow autocomplete of profile text fields.
 */
function profile_autocomplete($field, $string) {
752
  $matches = array();
753 754 755 756 757 758
  if (db_result(db_query("SELECT COUNT(*) FROM {profile_fields} WHERE fid = %d AND autocomplete = 1", $field))) {
    $result = db_query_range("SELECT value FROM {profile_values} WHERE fid = %d AND LOWER(value) LIKE LOWER('%s%%') GROUP BY value ORDER BY value ASC", $field, $string, 0, 10);
    while ($data = db_fetch_object($result)) {
      $matches[$data->value] = check_plain($data->value);
    }
  }
759 760

  drupal_json($matches);
761 762
}

763 764 765
/**
 * Helper function: update an array of user fields by calling profile_view_field
 */
766
function _profile_update_user_fields($fields, $account) {
767
  foreach ($fields as $key => $field) {
768
    $fields[$key]->value = profile_view_field($account, $field);
769
  }
770
  return $fields;
771 772
}

773
function profile_validate_profile($edit, $category) {
774
  $result = _profile_get_fields($category);
775
  while ($field = db_fetch_object($result)) {
776
    if ($edit[$field->name]) {
Dries's avatar
 
Dries committed
777
      if ($field->type == 'url') {
778
        if (!valid_url($edit[$field->name], TRUE)) {
779
          form_set_error($field->name, t('The value provided for %field is not a valid URL.', array('%field' => $field->title)));
780
        }
781 782
      }
    }
783
    else if ($field->required && !user_access('administer users')) {
784
      form_set_error($field->name, t('The field %field is required.', array('%field' => $field->title)));
Dries's avatar
 
Dries committed
785
    }
786 787 788 789 790
  }

  return $edit;
}

791 792
function profile_categories() {
  $result = db_query("SELECT DISTINCT(category) FROM {profile_fields}");
793
  $data = array();
794
  while ($category = db_fetch_object($result)) {
795
    $data[] = array('name' => $category->category, 'title' => $category->category, 'weight' => 3);
796 797 798 799
  }
  return $data;
}

800 801 802 803 804 805 806 807 808 809
/**
 * Process variables for profile-block.tpl.php.
 *
 * The $variables array contains the following arguments:
 * - $account
 * - $fields
 *
 * @see profile-block.tpl.php
 */
function template_preprocess_profile_block(&$variables) {
810

811 812 813 814
  $variables['picture'] = theme('user_picture', $variables['account']);
  $variables['profile'] = array();
  // Supply filtered version of $fields that have values.
  foreach ($variables['fields'] as $field) {
815
    if ($field->value) {
816 817 818
      $variables['profile'][$field->name]->title = $field->title;
      $variables['profile'][$field->name]->value = $field->value;
      $variables['profile'][$field->name]->type = $field->type;
819 820 821 822 823
    }
  }

}

824 825 826 827 828 829 830 831 832 833
/**
 * Process variables for profile-listing.tpl.php.
 *
 * The $variables array contains the following arguments:
 * - $account
 * - $fields
 *
 * @see profile-listing.tpl.php
 */
function template_preprocess_profile_listing(&$variables) {
Dries's avatar
 
Dries committed
834

835 836 837 838 839
  $variables['picture'] = theme('user_picture', $variables['account']);
  $variables['name'] = theme('username', $variables['account']);
  $variables['profile'] = array();
  // Supply filtered version of $fields that have values.
  foreach ($variables['fields'] as $field) {
840
    if ($field->value) {
841 842 843
      $variables['profile'][$field->name]->title = $field->title;
      $variables['profile'][$field->name]->value = $field->value;
      $variables['profile'][$field->name]->type = $field->type;
Dries's avatar
 
Dries committed
844 845
    }
  }
Dries's avatar
 
Dries committed
846

847
}
Dries's avatar
 
Dries committed
848

849 850 851 852 853 854 855 856 857 858 859 860 861 862 863
/**
 * Process variables for profile-wrapper.tpl.php.
 *
 * The $variables array contains the following arguments:
 * - $content
 *
 * @see profile-wrapper.tpl.php
 */
function template_preprocess_profile_wrapper(&$variables) {
  $variables['current_field'] = '';
  if ($field = arg(1)) {
    $variables['current_field'] = $field;
    // Supply an alternate template suggestion based on the browsable field.
    $variables['template_files'][] = 'profile-wrapper-'. $field;
  }
Dries's avatar
 
Dries committed
864 865 866
}

function _profile_field_types($type = NULL) {
Steven Wittens's avatar
Steven Wittens committed
867 868 869 870 871 872 873
  $types = array('textfield' => t('single-line textfield'),
                 'textarea' => t('multi-line textfield'),
                 'checkbox' => t('checkbox'),
                 'selection' => t('list selection'),
                 'list' => t('freeform list'),
                 'url' => t('URL'),
                 'date' => t('date'));
Dries's avatar
 
Dries committed
874
  return isset($type) ? $types[$type] : $types;
Dries's avatar
 
Dries committed
875 876
}

Steven Wittens's avatar
Steven Wittens committed
877