2 // $Id: profile.module 144 2007-03-28 07:52:20Z thierry $
6 * Support for configurable user profiles.
10 * Flags to define the visibility of a profile field.
12 define('PROFILE_PRIVATE', 1);
13 define('PROFILE_PUBLIC', 2);
14 define('PROFILE_PUBLIC_LISTINGS', 3);
15 define('PROFILE_HIDDEN', 4);
18 * Implementation of hook_help().
20 function profile_help($section) {
22 case 'admin/help#profile':
23 $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>';
24 $output .= t('<p>The following types of fields can be added to the user profile:</p>
26 <li>single-line textfield</li>
27 <li>multi-line textfield</li>
29 <li>list selection</li>
30 <li>freeform list</li>
35 $output .= t('<p>You can</p>
37 <li>view user <a href="%profile">profiles</a>.</li>
38 <li>administer profile settings: <a href="%admin-settings-profile">administer >> settings >> profiles</a>.</li>
40 ', array('%profile' => url('profile'), '%admin-settings-profile' => url('admin/settings/profile')));
41 $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>';
43 case 'admin/modules#description':
44 return t('Supports configurable user profiles.');
45 case 'admin/settings/profile':
46 return t('<p>Here you can define custom fields that users can fill in in their user profile (such as <em>country</em>, <em>real name</em>, <em>age</em>, ...).</p>');
51 * Implementation of hook_menu().
53 function profile_menu($may_cache) {
57 $items[] = array('path' => 'profile',
58 'title' => t('user list'),
59 'callback' => 'profile_browse',
60 'access' => user_access('access user profiles'),
61 'type' => MENU_SUGGESTED_ITEM);
62 $items[] = array('path' => 'admin/settings/profile',
63 'title' => t('profiles'),
64 'callback' => 'profile_admin_overview');
65 $items[] = array('path' => 'admin/settings/profile/add',
66 'title' => t('add field'),
67 'callback' => 'profile_field_form',
68 'type' => MENU_CALLBACK);
69 $items[] = array('path' => 'admin/settings/profile/edit',
70 'title' => t('edit field'),
71 'callback' => 'profile_field_form',
72 'type' => MENU_CALLBACK);
73 $items[] = array('path' => 'admin/settings/profile/delete',
74 'title' => t('delete field'),
75 'callback' => 'profile_field_delete',
76 'type' => MENU_CALLBACK);
83 * Implementation of hook_block().
85 function profile_block($op = 'list', $delta = 0, $edit = array()) {
88 $blocks[0]['info'] = t('Author information');
92 else if ($op == 'configure' && $delta == 0) {
93 // Compile a list of fields to show
95 $result = db_query('SELECT name, title, weight, visibility FROM {profile_fields} WHERE visibility IN (%d, %d) ORDER BY weight', PROFILE_PUBLIC, PROFILE_PUBLIC_LISTINGS);
96 while ($record = db_fetch_object($result)) {
97 $fields[$record->name] = $record->title;
99 $fields['user_profile'] = t('Link to full user profile');
100 $form['profile_block_author_fields'] = array('#type' => 'checkboxes',
101 '#title' => t('Profile fields to display'),
102 '#default_value' => variable_get('profile_block_author_fields', NULL),
103 '#options' => $fields,
104 '#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/settings/profile'))),
108 else if ($op == 'save' && $delta == 0) {
109 variable_set('profile_block_author_fields', $edit['profile_block_author_fields']);
111 else if ($op == 'view') {
112 if (user_access('access user profiles')) {
113 if ((arg(0) == 'node') && is_numeric(arg(1)) && (arg(2) == NULL)) {
114 $node = node_load(arg(1));
115 $account = user_load(array('uid' => $node->uid));
117 if ($use_fields = variable_get('profile_block_author_fields', array())) {
118 // Compile a list of fields to show.
120 $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);
121 while ($record = db_fetch_object($result)) {
122 // Ensure that field is displayed only if it is among the defined block fields and, if it is private, the user has appropriate permissions.
123 if (isset($use_fields[$record->name]) && $use_fields[$record->name]) {
130 $fields = _profile_update_user_fields($fields, $account);
131 $output .= theme('profile_block', $account, $fields, true);
134 if (isset($use_fields['user_profile']) && $use_fields['user_profile']) {
135 $output .= '<div>' . l(t('View full user profile'), 'user/' . $account->uid) . '</div>';
140 $block['subject'] = t('About %name', array('%name' => $account->name));
141 $block['content'] = $output;
149 * Implementation of hook_user().
151 function profile_user($type, &$edit, &$user, $category = NULL) {
154 return profile_load_profile($user);
156 return profile_form_profile($edit, $user, $category);
159 return profile_save_profile($edit, $user, $category);
161 return profile_view_profile($user);
163 return profile_form_profile($edit, $user, $category);
165 return profile_validate_profile($edit, $category);
167 return profile_categories();
169 db_query('DELETE FROM {profile_values} WHERE uid = %d', $user->uid);
174 * Menu callback: Generate a form to add/edit a user profile field.
176 function profile_field_form($arg = NULL) {
177 if (arg(3) == 'edit') {
178 if (is_numeric($arg)) {
181 $edit = db_fetch_array(db_query('SELECT * FROM {profile_fields} WHERE fid = %d', $fid));
187 drupal_set_title(t('edit %title', array('%title' => $edit['title'])));
188 $form['fid'] = array('#type' => 'value',
191 $type = $edit['type'];
199 $types = _profile_field_types();
200 if (!isset($types[$arg])) {
205 drupal_set_title(t('add new %type', array('%type' => $types[$type])));
206 $edit = array('name' => 'profile_');
207 $form['type'] = array('#type' => 'value', '#value' => $type);
209 $form['fields'] = array('#type' => 'fieldset',
210 '#title' => t('Field settings'),
212 $form['fields']['category'] = array('#type' => 'textfield',
213 '#title' => t('Category'),
214 '#default_value' => $edit['category'],
215 '#description' => t('The category the new field should be part of. Categories are used to group fields logically. An example category is "Personal information".'),
218 $form['fields']['title'] = array('#type' => 'textfield',
219 '#title' => t('Title'),
220 '#default_value' => $edit['title'],
221 '#description' => t('The title of the new field. The title will be shown to the user. An example title is "Favorite color".'),
224 $form['fields']['name'] = array('#type' => 'textfield',
225 '#title' => t('Form name'),
226 '#default_value' => $edit['name'],
227 '#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.
228 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".'),
231 $form['fields']['explanation'] = array('#type' => 'textarea',
232 '#title' => t('Explanation'),
233 '#default_value' => $edit['explanation'],
234 '#description' => t('An optional explanation to go with the new field. The explanation will be shown to the user.'),
236 if ($type == 'selection') {
237 $form['fields']['options'] = array('#type' => 'textarea',
238 '#title' => t('Selection options'),
239 '#default_value' => $edit['options'],
240 '#description' => t('A list of all options. Put each option on a separate line. Example options are "red", "blue", "green", etc.'),
243 $form['fields']['weight'] = array('#type' => 'weight',
244 '#title' => t('Weight'),
245 '#default_value' => $edit['weight'],
247 '#description' => t('The weights define the order in which the form fields are shown. Lighter fields "float up" towards the top of the category.'),
249 $form['fields']['visibility'] = array('#type' => 'radios',
250 '#title' => t('Visibility'),
251 '#default_value' => isset($edit['visibility']) ? $edit['visibility'] : PROFILE_PUBLIC,
252 '#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.')),
254 if ($type == 'selection' || $type == 'list' || $type == 'textfield') {
255 $form['fields']['page'] = array('#type' => 'textfield',
256 '#title' => t('Page title'),
257 '#default_value' => $edit['page'],
258 '#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.'),
261 else if ($type == 'checkbox') {
262 $form['fields']['page'] = array('#type' => 'textfield',
263 '#title' => t('Page title'),
264 '#default_value' => $edit['page'],
265 '#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.'),
268 $form['fields']['required'] = array('#type' => 'checkbox',
269 '#title' => t('The user must enter a value.'),
270 '#default_value' => $edit['required'],
272 $form['fields']['register'] = array('#type' => 'checkbox',
273 '#title' => t('Visible in user registration form.'),
274 '#default_value' => $edit['register'],
276 $form['submit'] = array('#type' => 'submit',
277 '#value' => t('Save field'),
279 return drupal_get_form('profile_field_form', $form);
283 * Validate profile_field_form submissions.
285 function profile_field_form_validate($form_id, $form_values) {
286 // Validate the 'field name':
287 if (eregi('[^a-z0-9_-]', $form_values['name'])) {
288 form_set_error('name', t('The specified form name contains one or more illegal characters. Spaces or any other special characters expect dash (-) and underscore (_) are not allowed.'));
291 if (in_array($form_values['name'], user_fields())) {
292 form_set_error('name', t('The specified form name is reserved for use by Drupal.'));
294 // Validate the category:
295 if (!$form_values['category']) {
296 form_set_error('category', t('You must enter a category.'));
298 if ($form_values['category'] == 'account') {
299 form_set_error('category', t('The specified category name is reserved for use by Drupal.'));
301 $args1 = array($form_values['title'], $form_values['category']);
302 $args2 = array($form_values['name']);
305 if (isset($form_values['fid'])) {
306 $args1[] = $args2[] = $form_values['fid'];
307 $query_suffix = ' AND fid != %d';
310 if (db_result(db_query("SELECT fid FROM {profile_fields} WHERE title = '%s' AND category = '%s'". $query_suffix, $args1))) {
311 form_set_error('title', t('The specified title is already in use.'));
313 if (db_result(db_query("SELECT fid FROM {profile_fields} WHERE name = '%s'". $query_suffix, $args2))) {
314 form_set_error('name', t('The specified name is already in use.'));
319 * Process profile_field_form submissions.
321 function profile_field_form_submit($form_id, $form_values) {
322 if (!isset($form_values['fid'])) {
323 db_query("INSERT INTO {profile_fields} (title, name, explanation, category, type, weight, required, register, visibility, options, page) VALUES ('%s', '%s', '%s', '%s', '%s', %d, %d, %d, %d, '%s', '%s')", $form_values['title'], $form_values['name'], $form_values['explanation'], $form_values['category'], $form_values['type'], $form_values['weight'], $form_values['required'], $form_values['register'], $form_values['visibility'], $form_values['options'], $form_values['page']);
325 drupal_set_message(t('The field has been created.'));
326 watchdog('profile', t('Profile field %field added under category %category.', array('%field' => theme('placeholder', $form_values['title']), '%category' => theme('placeholder', $form_values['category']))), WATCHDOG_NOTICE, l(t('view'), 'admin/settings/profile'));
329 db_query("UPDATE {profile_fields} SET title = '%s', name = '%s', explanation = '%s', category = '%s', weight = %d, required = %d, register = %d, visibility = %d, options = '%s', page = '%s' WHERE fid = %d", $form_values['title'], $form_values['name'], $form_values['explanation'], $form_values['category'], $form_values['weight'], $form_values['required'], $form_values['register'], $form_values['visibility'], $form_values['options'], $form_values['page'], $form_values['fid']);
331 drupal_set_message(t('The field has been updated.'));
335 return 'admin/settings/profile';
339 * Menu callback; deletes a field from all user profiles.
341 function profile_field_delete($fid) {
342 $field = db_fetch_object(db_query("SELECT title FROM {profile_fields} WHERE fid = %d", $fid));
347 $form['fid'] = array('#type' => 'value', '#value' => $fid);
348 $form['title'] = array('#type' => 'value', '#value' => $field->title);
350 return confirm_form('profile_field_delete', $form, t('Are you sure you want to delete the field %field?', array('%field' => theme('placeholder', $field->title))), 'admin/settings/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-field so that it may only be accessed by administrators.', array('%edit-field' => url('admin/settings/profile/edit/' . $fid), '%hidden-field' => theme('placeholder', t('hidden profile field')))), t('Delete'), t('Cancel'));
354 * Process a field delete form submission.
356 function profile_field_delete_submit($form_id, $form_values) {
357 db_query('DELETE FROM {profile_fields} WHERE fid = %d', $form_values['fid']);
358 db_query('DELETE FROM {profile_values} WHERE fid = %d', $form_values['fid']);
362 drupal_set_message(t('The field %field has been deleted.', array('%field' => theme('placeholder', $form_values['title']))));
363 watchdog('profile', t('Profile field %field deleted.', array('%field' => theme('placeholder', $form_values['title']))), WATCHDOG_NOTICE, l(t('view'), 'admin/settings/profile'));
365 return 'admin/settings/profile';
369 * Menu callback; display a listing of all editable profile fields.
371 function profile_admin_overview() {
373 $result = db_query('SELECT * FROM {profile_fields} ORDER BY category, weight');
375 while ($field = db_fetch_object($result)) {
376 $rows[] = array(check_plain($field->title), $field->name, _profile_field_types($field->type), $field->category, l(t('edit'), "admin/settings/profile/edit/$field->fid"), l(t('delete'), "admin/settings/profile/delete/$field->fid"));
378 if (count($rows) == 0) {
379 $rows[] = array(array('data' => t('No fields defined.'), 'colspan' => '6'));
382 $header = array(t('Title'), t('Name'), t('Type'), t('Category'), array('data' => t('Operations'), 'colspan' => '2'));
384 $output = theme('table', $header, $rows);
385 $output .= '<h2>'. t('Add new field') .'</h2>';
387 foreach (_profile_field_types() as $key => $value) {
388 $output .= '<li>'. l($value, "admin/settings/profile/add/$key") .'</li>';
396 * Menu callback; display a list of user information.
398 function profile_browse() {
401 list(,,$value) = explode('/', $_GET['q'], 3);
403 $field = db_fetch_object(db_query("SELECT DISTINCT(fid), type, title, page, visibility FROM {profile_fields} WHERE name = '%s'", $name));
405 if ($name && $field->fid) {
406 // Only allow browsing of fields that have a page title set.
407 if (empty($field->page)) {
411 // Do not allow browsing of private fields by non-admins.
412 if (!user_access('administer users') && $field->visibility == PROFILE_PRIVATE) {
413 drupal_access_denied();
417 // Compile a list of fields to show.
419 $result = db_query('SELECT name, title, type, weight FROM {profile_fields} WHERE fid != %d AND visibility = %d ORDER BY weight', $field->fid, PROFILE_PUBLIC_LISTINGS);
420 while ($record = db_fetch_object($result)) {
424 // Determine what query to use:
425 $arguments = array($field->fid);
426 switch ($field->type) {
428 $query = 'v.value = 1';
432 $query = "v.value = '%s'";
433 $arguments[] = $value;
436 $query = "v.value LIKE '%%%s%%'";
437 $arguments[] = $value;
444 // Extract the affected users:
445 $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 ORDER BY u.access DESC", 20, 0, NULL, $arguments);
447 $output = '<div id="profile">';
448 while ($account = db_fetch_object($result)) {
449 $account = user_load(array('uid' => $account->uid));
450 $profile = _profile_update_user_fields($fields, $account);
451 $output .= theme('profile_listing', $account, $profile);
453 $output .= theme('pager', NULL, 20);
455 if ($field->type == 'selection' || $field->type == 'list' || $field->type == 'textfield') {
456 $title = strtr($field->page, array('%value' => theme('placeholder', $value)));
459 $title = $field->page;
463 drupal_set_title($title);
466 else if ($name && !$field->fid) {
470 // Compile a list of fields to show.
472 $result = db_query('SELECT name, title, type, weight FROM {profile_fields} WHERE visibility = %d ORDER BY category, weight', PROFILE_PUBLIC_LISTINGS);
473 while ($record = db_fetch_object($result)) {
477 // Extract the affected users:
478 $result = pager_query("SELECT uid, access FROM {users} WHERE uid > 0 AND status != 0 ORDER BY access DESC", 20, 0, NULL);
480 $output = '<div id="profile">';
481 while ($account = db_fetch_object($result)) {
482 $account = user_load(array('uid' => $account->uid));
483 $profile = _profile_update_user_fields($fields, $account);
484 $output .= theme('profile_listing', $account, $profile);
487 $output .= theme('pager', NULL, 20);
489 drupal_set_title(t('user list'));
494 function profile_load_profile(&$user) {
495 $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);
496 while ($field = db_fetch_object($result)) {
497 if (empty($user->{$field->name})) {
498 $user->{$field->name} = _profile_field_serialize($field->type) ? unserialize($field->value) : $field->value;
503 function profile_save_profile(&$edit, &$user, $category) {
504 if ((arg(0) == 'user' && arg(1) == 'register') || (arg(0) == 'admin' && arg(1) == 'user' && arg(2) == 'create')) {
505 $result = db_query('SELECT fid, name, type FROM {profile_fields} WHERE register = 1 AND visibility != %d ORDER BY category, weight', PROFILE_HIDDEN);
508 $result = db_query("SELECT fid, name, type FROM {profile_fields} WHERE LOWER(category) = LOWER('%s') AND visibility != %d", $category, PROFILE_HIDDEN);
509 // Use LOWER('%s') instead of PHP's strtolower() to avoid UTF-8 conversion issues.
511 while ($field = db_fetch_object($result)) {
512 if (_profile_field_serialize($field->type)) {
513 $edit[$field->name] = serialize($edit[$field->name]);
515 db_query("DELETE FROM {profile_values} WHERE fid = %d AND uid = %d", $field->fid, $user->uid);
516 db_query("INSERT INTO {profile_values} (fid, uid, value) VALUES (%d, %d, '%s')", $field->fid, $user->uid, $edit[$field->name]);
517 // Mark field as handled (prevents saving to user->data).
518 $edit[$field->name] = NULL;
522 function profile_view_field($user, $field) {
523 // Only allow browsing of private fields for admins, if browsing is enabled,
524 // and if a user has permission to view profiles. Note that this check is
525 // necessary because a user may always see their own profile.
526 $browse = user_access('access user profiles')
527 && (user_access('administer users') || $field->visibility != PROFILE_PRIVATE)
528 && !empty($field->page);
530 if ($value = $user->{$field->name}) {
531 switch ($field->type) {
533 return check_markup($value);
536 return $browse ? l($value, 'profile/'. $field->name .'/'. $value) : check_plain($value);
538 return $browse ? l($field->title, 'profile/'. $field->name) : check_plain($field->title);
540 return '<a href="'. check_url($value) .'">'. check_plain($value) .'</a>';
542 $format = substr(variable_get('date_format_short', 'm/d/Y - H:i'), 0, 5);
543 // Note: Avoid PHP's date() because it does not handle dates before
544 // 1970 on Windows. This would make the date field useless for e.g.
546 $replace = array('d' => sprintf('%02d', $value['day']),
547 'j' => $value['day'],
548 'm' => sprintf('%02d', $value['month']),
549 'M' => map_month($value['month']),
550 'Y' => $value['year'],
553 return strtr($format, $replace);
555 $values = split("[,\n\r]", $value);
557 foreach ($values as $value) {
558 if ($value = trim($value)) {
559 $fields[] = $browse ? l($value, 'profile/'. $field->name .'/'. $value) : check_plain($value);
562 return implode(', ', $fields);
567 function profile_view_profile($user) {
569 profile_load_profile($user);
571 // Show private fields to administrators and people viewing their own account.
572 if (user_access('administer users') || $GLOBALS['user']->uid == $user->uid) {
573 $result = db_query('SELECT * FROM {profile_fields} WHERE visibility != %d ORDER BY category, weight', PROFILE_HIDDEN);
576 $result = db_query('SELECT * FROM {profile_fields} WHERE visibility != %d AND visibility != %d ORDER BY category, weight', PROFILE_PRIVATE, PROFILE_HIDDEN);
579 while ($field = db_fetch_object($result)) {
580 if ($value = profile_view_field($user, $field)) {
581 $description = ($field->visibility == PROFILE_PRIVATE) ? t('The content of this field is private and only visible to yourself.') : '';
582 $title = ($field->type != 'checkbox') ? check_plain($field->title) : NULL;
583 $item = array('title' => $title,
585 'class' => $field->name,
587 $fields[$field->category][] = $item;
593 function _profile_form_explanation($field) {
594 $output = $field->explanation;
596 if ($field->type == 'list') {
597 $output .= ' '. t('Put each item on a separate line or separate them by commas. No HTML allowed.');
600 if ($field->visibility == PROFILE_PRIVATE) {
601 $output .= ' '. t('The content of this field is kept private and will not be shown publicly.');
607 function profile_form_profile($edit, $user, $category) {
608 if (arg(0) == 'user' && arg(1) == 'register') {
609 $result = db_query('SELECT * FROM {profile_fields} WHERE visibility != %d AND register = 1 ORDER BY category, weight', PROFILE_HIDDEN);
611 elseif (arg(0) == 'admin' && arg(1) == 'user' && arg(2) == 'create') {
612 $result = db_query('SELECT * FROM {profile_fields} WHERE register = 1 ORDER BY category, weight');
614 elseif (user_access('administer users')) {
615 $result = db_query("SELECT * FROM {profile_fields} WHERE LOWER(category) = LOWER('%s') ORDER BY weight", $category);
618 $result = db_query("SELECT * FROM {profile_fields} WHERE visibility != %d AND LOWER(category) = LOWER('%s') ORDER BY weight", PROFILE_HIDDEN, $category);
619 // Use LOWER('%s') instead of PHP's strtolower() to avoid UTF-8 conversion issues.
622 while ($field = db_fetch_object($result)) {
623 $category = $field->category;
624 if (!isset($fields[$category])) {
625 $fields[$category] = array('#type' => 'fieldset', '#title' => $category, '#weight' => $w++);
627 switch ($field->type) {
630 $fields[$category][$field->name] = array('#type' => 'textfield',
631 '#title' => check_plain($field->title),
632 '#default_value' => $edit[$field->name],
634 '#description' => _profile_form_explanation($field),
635 '#required' => $field->required,
639 $fields[$category][$field->name] = array('#type' => 'textarea',
640 '#title' => check_plain($field->title),
641 '#default_value' => $edit[$field->name],
642 '#description' => _profile_form_explanation($field),
643 '#required' => $field->required,
647 $fields[$category][$field->name] = array('#type' => 'textarea',
648 '#title' => check_plain($field->title),
649 '#default_value' => $edit[$field->name],
650 '#description' => _profile_form_explanation($field),
651 '#required' => $field->required,
655 $fields[$category][$field->name] = array('#type' => 'checkbox',
656 '#title' => check_plain($field->title),
657 '#default_value' => $edit[$field->name],
658 '#description' => _profile_form_explanation($field),
659 '#required' => $field->required,
663 $options = $field->required ? array() : array('--');
664 $lines = split("[,\n\r]", $field->options);
665 foreach ($lines as $line) {
666 if ($line = trim($line)) {
667 $options[$line] = $line;
670 $fields[$category][$field->name] = array('#type' => 'select',
671 '#title' => check_plain($field->title),
672 '#default_value' => $edit[$field->name],
673 '#options' => $options,
674 '#description' => _profile_form_explanation($field),
675 '#required' => $field->required,
679 $fields[$category][$field->name] = array('#type' => 'date',
680 '#title' => check_plain($field->title),
681 '#default_value' => $edit[$field->name],
682 '#description' => _profile_form_explanation($field),
683 '#required' => $field->required,
692 * Helper function: update an array of user fields by calling profile_view_field
694 function _profile_update_user_fields($fields, $account) {
695 foreach ($fields as $key => $field) {
696 $fields[$key]->value = profile_view_field($account, $field);
701 function profile_validate_profile($edit, $category) {
702 if (arg(0) == 'user' && arg(1) == 'register') {
703 $result = db_query('SELECT * FROM {profile_fields} WHERE visibility != %d AND register = 1 ORDER BY category, weight', PROFILE_HIDDEN);
705 elseif (arg(0) == 'admin' && arg(1) == 'user' && arg(2) == 'create') {
706 $result = db_query('SELECT * FROM {profile_fields} WHERE register = 1 ORDER BY category, weight');
708 elseif (user_access('administer users')) {
709 $result = db_query("SELECT * FROM {profile_fields} WHERE LOWER(category) = LOWER('%s') ORDER BY weight", $category);
712 $result = db_query("SELECT * FROM {profile_fields} WHERE visibility != %d AND LOWER(category) = LOWER('%s') ORDER BY weight", PROFILE_HIDDEN, $category);
713 // Use LOWER('%s') instead of PHP's strtolower() to avoid UTF-8 conversion issues.
716 while ($field = db_fetch_object($result)) {
717 if ($edit[$field->name]) {
718 if ($field->type == 'url') {
719 if (!valid_url($edit[$field->name], true)) {
720 form_set_error($field->name, t('The value provided for %field is not a valid URL.', array('%field' => theme('placeholder', $field->title))));
724 else if ($field->required && !user_access('administer users')) {
725 form_set_error($field->name, t('The field %field is required.', array('%field' => theme('placeholder', $field->title))));
732 function profile_categories() {
733 $result = db_query("SELECT DISTINCT(category) FROM {profile_fields}");
734 while ($category = db_fetch_object($result)) {
735 $data[] = array('name' => check_plain($category->category), 'title' => $category->category, 'weight' => 3);
740 function theme_profile_block($account, $fields = array()) {
742 $output .= theme('user_picture', $account);
744 foreach ($fields as $field) {
746 if ($field->type == 'checkbox') {
747 $output .= "<p>$field->value</p>\n";
750 $output .= "<p><strong>$field->title</strong><br />$field->value</p>\n";
758 function theme_profile_listing($account, $fields = array()) {
760 $output = "<div class=\"profile\">\n";
761 $output .= theme('user_picture', $account);
762 $output .= ' <div class="name">'. theme('username', $account) ."</div>\n";
764 foreach ($fields as $field) {
766 $output .= " <div class=\"field\">$field->value</div>\n";
770 $output .= "</div>\n";
775 function _profile_field_types($type = NULL) {
776 $types = array('textfield' => t('single-line textfield'),
777 'textarea' => t('multi-line textfield'),
778 'checkbox' => t('checkbox'),
779 'selection' => t('list selection'),
780 'list' => t('freeform list'),
782 'date' => t('date'));
783 return isset($type) ? $types[$type] : $types;
786 function _profile_field_serialize($type = NULL) {
787 return $type == 'date';