diff --git a/database/updates.inc b/database/updates.inc index 6bc5a5741666b859f5180458c5faa8e6196fc260..799e57f4df48c68a9323dcb8fe03cd2c61127b19 100644 --- a/database/updates.inc +++ b/database/updates.inc @@ -789,17 +789,42 @@ function update_79() { function update_80() { + // Add a 'created' field to the users table: + $ret[] = update_sql('ALTER TABLE {users} ADD created INT(11) NOT NULL'); + $ret[] = update_sql('ALTER TABLE {users} CHANGE timestamp changed INT(11) NOT NULL'); + // Add some indices to speed up the update process: + $ret[] = update_sql('ALTER TABLE {comments} ADD index (timestamp)'); + $ret[] = update_sql('ALTER TABLE {node} ADD index (created)'); - if ($GLOBALS["db_type"] == "mysql") { - // Add a 'created' field to the users table: - $ret[] = update_sql('ALTER TABLE {users} ADD created INT(11) NOT NULL'); - $ret[] = update_sql('ALTER TABLE {users} CHANGE timestamp changed INT(11) NOT NULL'); + // Assign everyone a created timestamp to begin with: + $ret[] = update_sql("UPDATE {users} SET created = changed WHERE created = ''"); + // Print a status message:" + print '<p>Note: this might take a while ...</p>'; + // Try updating the user records using the comment table: + $result = db_query('SELECT DISTINCT(u.uid) FROM {comments} c LEFT JOIN {users} u ON c.uid = u.uid WHERE c.timestamp < u.created'); + while ($account = db_fetch_object($result)) { + // Retrieve the proper timestamp: + $timestamp = db_result(db_query('SELECT MIN(timestamp) FROM {comments} WHERE uid = %d', $account->uid)); + + // Update this user record as well as older records with an older timestamp: + db_query('UPDATE {users} SET created = %d WHERE created > %d AND uid <= %d', $timestamp, $timestamp, $account->uid); + } - // Add profile module related tables: - $ret[] = update_sql("CREATE TABLE {profile_fields} ( + // Try updating the user records using the node table: + $result = db_query('SELECT DISTINCT(u.uid) FROM {node} n LEFT JOIN {users} u ON n.uid = u.uid WHERE n.created < u.created'); + while ($account = db_fetch_object($result)) { + // Retrieve the proper timestamp: + $timestamp = db_result(db_query('SELECT MIN(created) FROM {node} WHERE uid = %d', $account->uid)); + + // Update this user record as well as older records with an older timestamp: + db_query('UPDATE {users} SET created = %d WHERE created > %d AND uid <= %d', $timestamp, $timestamp, $account->uid); + } + + // Add profile module related tables: + $ret[] = update_sql("CREATE TABLE {profile_fields} ( fid int(10) NOT NULL auto_increment, title varchar(255) default NULL, name varchar(128) default NULL, @@ -814,149 +839,80 @@ function update_80() { PRIMARY KEY (fid) );"); - $ret[] = update_sql("CREATE TABLE {profile_values} ( + $ret[] = update_sql("CREATE TABLE {profile_values} ( fid int(11) unsigned default '0', uid int(11) unsigned default '0', value text, KEY uid (uid), KEY fid (fid) );"); - // Add some indices to speed up the update process: - $ret[] = update_sql('ALTER TABLE {comments} ADD index (timestamp)'); - $ret[] = update_sql('ALTER TABLE {node} ADD index (created)'); - // Assign everyone a created timestamp to begin with: - $ret[] = update_sql("UPDATE {users} SET created = changed WHERE created = ''"); - - } - else {// pgsql - - $ret[] = update_sql('ALTER TABLE {users} RENAME timestamp TO changed'); - $ret[] = update_sql('ALTER TABLE {users} ADD created integer'); - $ret[] = update_sql('UPDATE {users} SET created = 0'); - $ret[] = update_sql('ALTER TABLE {users} ALTER COLUMN created SET NOT NULL'); - $ret[] = update_sql('ALTER TABLE {users} ALTER COLUMN created SET DEFAULT 0'); - // Add profile module related tables: - $ret[] = update_sql("CREATE TABLE {profile_fields} ( - fid serial, - title varchar(255) default NULL, - name varchar(128) default NULL, - explanation TEXT default NULL, - category varchar(255) default NULL, - type varchar(128) default NULL, - weight smallint DEFAULT '0' NOT NULL, - overview smallint DEFAULT '0' NOT NULL, - options text, - UNIQUE (name), - PRIMARY KEY (fid) - );"); - $ret[] = update_sql("CREATE INDEX profile_fields_category ON {profile_fields} (category);"); - $ret[] = update_sql("CREATE TABLE {profile_values} ( - fid integer default '0', - uid integer default '0', - value text - );"); - $ret[] = update_sql("CREATE INDEX profile_values_uid ON profile_values (uid);"); - $ret[] = update_sql("CREATE INDEX profile_values_fid ON profile_values (fid);"); - - // Add some indices to speed up the update process: - $ret[] = update_sql('CREATE INDEX comments_timestamp ON {comments} (timestamp)'); - $ret[] = update_sql('CREATE INDEX node_created on {node} (created)'); - // Assign everyone a created timestamp to begin with: - $ret[] = update_sql("UPDATE {users} SET created = changed WHERE created = 0"); - } - - - // Print a status message:" - print '<p>Note: this might take a while ...</p>'; - - // Try updating the user records using the comment table: - $result = db_query('SELECT DISTINCT(u.uid) FROM {comments} c LEFT JOIN {users} u ON c.uid = u.uid WHERE c.timestamp < u.created'); + // Migrate the old profile data to the new scheme: + $fields = array( + array("Name", "realname", "textfield", NULL, 0), + array("Address", "address", "textfield", NULL, 0), + array("City", "city", "textfield", NULL, 0), + array("State, province or region", "state", "textfield", NULL, 0), + array("Zip or postal code", "zip", "textfield", NULL, 0), + array("Country", "country", "textfield", NULL, 1), + array("Gender", "gender", "selection", "male\nfemale", 1), + array("Job title", "job", "textfield", NULL, 0), + array("ICQ messenger ID", "icq", "textfield", NULL, 0), + array("MSN messenger ID", "msn", "textfield", NULL, 0), + array("Yahoo messenger ID", "yahoo", "textfield", NULL, 0), + array("AIM messenger ID", "aim", "textfield", NULL, 0), + array("URL of homepage", "homepage", "url", NULL, 1), + array("Biography", "biography", "textarea", NULL, 0), + array("Interests", "interests", "textarea", NULL, 0), + array("Public key", "publickey", "textarea", NULL, 0), + array("Birthday", "birthday", "date", NULL, 0) + ); + + // Remove existing data (debug mode): + db_query('DELETE FROM {profile_fields}'); + db_query('DELETE FROM {profile_values}'); + + foreach ($fields as $field) { + db_query("INSERT INTO {profile_fields} (title, name, type, category, options, overview) VALUES ('%s', '%s', '%s', 'Personal information', '%s', %d)", $field[0], $field[1], $field[2], $field[3], $field[4]); + } + db_query("ALTER TABLE {users} ADD picture varchar(255) NOT NULL DEFAULT ''"); + + $result = db_query("SELECT uid FROM {users}"); while ($account = db_fetch_object($result)) { - // Retrieve the proper timestamp: - $timestamp = db_result(db_query('SELECT MIN(timestamp) FROM {comments} WHERE uid = %d', $account->uid)); - // Try updating the user records using the node table: - $result = db_query('SELECT DISTINCT(u.uid) FROM {node} n LEFT JOIN {users} u ON n.uid = u.uid WHERE n.created < u.created'); - while ($account = db_fetch_object($result)) { - // Retrieve the proper timestamp: - $timestamp = db_result(db_query('SELECT MIN(created) FROM {node} WHERE uid = %d', $account->uid)); - - // Update this user record as well as older records with an older timestamp: - db_query('UPDATE {users} SET created = %d WHERE created > %d AND uid <= %d', $timestamp, $timestamp, $account->uid); - } - - // Update this user record as well as older records with an older timestamp: - db_query('UPDATE {users} SET created = %d WHERE created > %d AND uid <= %d', $timestamp, $timestamp, $account->uid); - } - - - // Migrate the old profile data to the new scheme: - $fields = array( - array("Name", "realname", "textfield", NULL, 0), - array("Address", "address", "textfield", NULL, 0), - array("City", "city", "textfield", NULL, 0), - array("State, province or region", "state", "textfield", NULL, 0), - array("Zip or postal code", "zip", "textfield", NULL, 0), - array("Country", "country", "textfield", NULL, 1), - array("Gender", "gender", "selection", "male\nfemale", 1), - array("Job title", "job", "textfield", NULL, 0), - array("ICQ messenger ID", "icq", "textfield", NULL, 0), - array("MSN messenger ID", "msn", "textfield", NULL, 0), - array("Yahoo messenger ID", "yahoo", "textfield", NULL, 0), - array("AIM messenger ID", "aim", "textfield", NULL, 0), - array("URL of homepage", "homepage", "url", NULL, 1), - array("Biography", "biography", "textarea", NULL, 0), - array("Interests", "interests", "textarea", NULL, 0), - array("Public key", "publickey", "textarea", NULL, 0) - ); - - // Remove existing data (debug mode): - db_query('DELETE FROM {profile_fields}'); - db_query('DELETE FROM {profile_values}'); + // Load the user record: + $account = user_load(array('uid' => $account->uid)); + $edit = array(); + // Modify the user record: foreach ($fields as $field) { - db_query("INSERT INTO {profile_fields} (title, name, type, category, options, overview) VALUES ('%s', '%s', '%s', 'Personal information', '%s', %d)", $field[0], $field[1], $field[2], $field[3], $field[4]); + $old = "profile_". $field[1]; + $new = $field[1]; + if ($account->$old) { + $edit[$new] = $account->$old; + } + unset($account->$old); } - if ($GLOBALS["db_type"] == "mysql") { - db_query("ALTER TABLE {users} ADD picture varchar(255) NOT NULL DEFAULT ''"); + // Birthday format change: + if ($edit['birthday']) { + $edit['birthday'] = array('day' => $edit['birthday'], 'month' => $account->profile_birthmonth, 'year' => $account->profile_birthyear); + unset($account->profile_birthmonth); + unset($account->profile_birthyear); } - else { - db_query("ALTER TABLE {users} ADD picture varchar(255)"); - db_query("UPDATE {users} SET picture = ''"); - db_query("ALTER TABLE {users} ALTER COLUMN picture SET NOT NULL"); - db_query("ALTER TABLE {users} ALTER COLUMN picture SET DEFAULT ''"); - } - - $result = db_query("SELECT uid FROM {users} WHERE uid > 0"); - while ($account = db_fetch_object($result)) { - // Load the user record: - $account = user_load(array('uid' => $account->uid)); - $edit = array(); - - // Modify the user record: - foreach ($fields as $field) { - $old = "profile_". $field[1]; - $new = $field[1]; - if ($account->$old) { - $edit[$new] = $account->$old; - } - unset($account->$old); - } - - // Gender specific changes: - if ($edit['gender'] == 'f') $edit['gender'] = 'female'; - if ($edit['gender'] == 'm') $edit['gender'] = 'male'; - // Avatar specific changes: - if ($account->profile_avatar) { - $edit['picture'] = $account->profile_avatar; - } - unset($account->profile_avatar); + // Gender specific changes: + if ($edit['gender'] == 'f') $edit['gender'] = 'female'; + if ($edit['gender'] == 'm') $edit['gender'] = 'male'; - // Save the update record: - user_save($account, $edit, 'Personal information'); + // Avatar specific changes: + if ($account->profile_avatar) { + $edit['picture'] = $account->profile_avatar; } + unset($account->profile_avatar); + + // Save the update record: + user_save($account, $edit); + } return $ret; } diff --git a/modules/profile.module b/modules/profile.module index 1ad8288c7e477b8e66a5d503e4ea19cd61900e0b..802daca6cbd1af5ab1026105d23e408eff83b1a1 100644 --- a/modules/profile.module +++ b/modules/profile.module @@ -1,8 +1,6 @@ <?php // $Id$ -// TODO: add a 'date' field so we can migrate the birthday information. - /** * Flags to define the visibility of a profile field. */ @@ -56,12 +54,18 @@ function profile_menu() { */ function profile_browse() { - $name = strip_tags(arg(1)); - $value = strip_tags(arg(2)); + $name = arg(1); + $value = arg(2); - $field = db_fetch_object(db_query("SELECT DISTINCT(fid), type, title, page FROM {profile_fields} WHERE name = '%s'", $name)); + $field = db_fetch_object(db_query("SELECT DISTINCT(fid), type, title, page, visibility FROM {profile_fields} WHERE name = '%s'", $name)); if ($name && $field->fid) { + // Do not allow browsing of private fields by non-admins + if (!user_access('administer users') && $field->visibility == PROFILE_PRIVATE) { + drupal_access_denied(); + return; + } + // Compile a list of fields to show $fields = array(); $result = db_query('SELECT name, title, type FROM {profile_fields} WHERE fid != %d AND visibility = %d', $field->fid, PROFILE_PUBLIC_LISTINGS); @@ -80,6 +84,9 @@ function profile_browse() { case 'list': $query = "v.value LIKE '%%". check_query($value) ."%%'"; break; + default: + drupal_not_found(); + return; } // Extract the affected users: @@ -127,17 +134,20 @@ function profile_browse() { } function profile_load_profile(&$user) { - $result = db_query('SELECT f.name, v.value FROM {profile_fields} f INNER JOIN {profile_values} v ON f.fid = v.fid WHERE uid = %d', $user->uid); + $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); while ($field = db_fetch_object($result)) { if (empty($user->{$field->name})) { - $user->{$field->name} = $field->value; + $user->{$field->name} = _profile_field_serialize($field->type) ? unserialize($field->value) : $field->value; } } } function profile_save_profile(&$edit, &$user, $category) { - $result = db_query("SELECT fid, name FROM {profile_fields} WHERE LOWER(category) = '%s'", strtolower($category)); + $result = db_query("SELECT fid, name, type FROM {profile_fields} WHERE LOWER(category) = '%s'", strtolower($category)); while ($field = db_fetch_object($result)) { + if (_profile_field_serialize($field->type)) { + $edit[$field->name] = serialize($edit[$field->name]); + } 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]); unset($edit[$field->name], $user->{$field->name}); @@ -145,23 +155,38 @@ function profile_save_profile(&$edit, &$user, $category) { } function profile_view_field($user, $field) { + // Only allow browsing of private fields for admins + $browse = user_access('administer users') || $field->visibility != PROFILE_PRIVATE; + if ($value = $user->{$field->name}) { switch ($field->type) { case 'textfield': + return drupal_specialchars($value); case 'textarea': return check_output($value); case 'selection': - return l($value, "profile/$field->name/". drupal_specialchars($value, ENT_QUOTES)); + return $browse ? l(drupal_specialchars($value), "profile/$field->name/". check_url($value)) : drupal_specialchars($value); case 'checkbox': - return l($field->title, "profile/$field->name"); + return $browse ? l(drupal_specialchars($field->title), "profile/$field->name") : drupal_specialchars($field->title); case 'url': - return '<a href="'. check_url(strip_tags($value)) .'">'. strip_tags($value) .'</a>'; + return '<a href="'. check_url($value) .'">'. drupal_specialchars($value) .'</a>'; + case 'date': + list($format) = explode(' - ', variable_get('date_format_short', 'm/d/Y - H:i'), 2); + // Note: we avoid PHP's date() because it does not handle dates before + // 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']), + 'M' => _profile_map_month($value['month']), + 'Y' => $value['year']); + return strtr($format, $replace); case 'list': $values = split("[,\n\r]", $value); $fields = array(); foreach ($values as $value) { - if ($value = trim(strip_tags($value))) { - $fields[] = l($value, "profile/$field->name/". drupal_specialchars($value, ENT_QUOTES)); + if ($value = trim($value)) { + $fields[] = $browse ? l(drupal_specialchars($value), "profile/$field->name/". check_url($value)) : drupal_specialchars($value); } } return implode(', ', $fields); @@ -173,15 +198,19 @@ function profile_view_profile($user) { profile_load_profile($user); - $result = db_query('SELECT * FROM {profile_fields} WHERE visibility != %d ORDER BY category, weight', PROFILE_PRIVATE); + // Show private fields to administrators and people viewing their own account. + if (user_access('administer users') || $GLOBALS['user']->uid == $user->uid) { + $result = db_query('SELECT * FROM {profile_fields} ORDER BY category, weight'); + } + else { + $result = db_query('SELECT * FROM {profile_fields} WHERE visibility != %d ORDER BY category, weight', PROFILE_PRIVATE); + } + while ($field = db_fetch_object($result)) { if ($value = profile_view_field($user, $field)) { - if ($field->type == 'checkbox') { - $fields[$field->category] .= "<p>$value</p>"; - } - else { - $fields[$field->category] .= form_item($field->title, check_output($value)); - } + $description = ($field->visibility == PROFILE_PRIVATE) ? t('The content of this field is private and only visible to yourself.') : ''; + $title = ($field->type != 'checkbox') ? $field->title : ''; + $fields[$field->category] .= form_item($title, $value, $description); } } @@ -232,6 +261,9 @@ function profile_form_profile($edit, $user, $category) { $output .= form_select($field->title, $field->name, $edit[$field->name], $options, _profile_form_explanation($field), 0, 0, $field->required); break; + case 'date': + $output .= _profile_date_field($field, $edit); + break; } } @@ -240,6 +272,54 @@ function profile_form_profile($edit, $user, $category) { } } +/** + * Helper function: output a date selector + */ +function _profile_date_field($field, $edit) { + // Default to current date + if (!isset($edit[$field->name])) { + $edit[$field->name] = array('day' => format_date(time(), 'custom', 'j'), + 'month' => format_date(time(), 'custom', 'n'), + 'year' => format_date(time(), 'custom', 'Y')); + } + + // Determine the order of day, month, year in the site's chosen date format. + $format = variable_get('date_format_short', 'm/d/Y'); + $sort = array(); + $sort['day'] = max(strpos($format, 'd'), strpos($format, 'j')); + $sort['month'] = max(strpos($format, 'm'), strpos($format, 'M')); + $sort['year'] = strpos($format, 'Y'); + asort($sort); + $order = array_keys($sort); + + // Output multi-selector for date + $output = '<div class="container-inline">'; + foreach ($order as $type) { + switch ($type) { + case 'day': + $options = drupal_map_assoc(range(1, 31)); + break; + case 'month': + $options = drupal_map_assoc(range(1, 12), '_profile_map_month'); + break; + case 'year': + $options = drupal_map_assoc(range(1900, 2050)); + break; + } + $output .= form_select('', $field->name .']['. $type, $edit[$field->name][$type], $options, '', 0, 0); + } + $output .= '</div>'; + + return form_item($field->title, $output, _profile_form_explanation($field), NULL, $field->required); +} + +/** + * Helper function for usage with drupal_map_assoc + */ +function _profile_map_month($month) { + return format_date(gmmktime(0, 0, 0, $month, 2, 1970), 'custom', 'M', 0); +} + function profile_validate_profile($edit, $category) { $result = db_query("SELECT * FROM {profile_fields} WHERE LOWER(category) = '%s' ORDER BY weight", strtolower($category)); @@ -316,9 +396,6 @@ function profile_validate_form($edit) { * Menu callback; adds a new field to all user profiles. */ function profile_admin_add($type) { - $type = _profile_field_types($type); - - if ($_POST['op']) { $data = $_POST['edit']; @@ -326,17 +403,17 @@ function profile_admin_add($type) { profile_validate_form($data); if (db_result(db_query("SELECT fid FROM {profile_fields} WHERE title = '%s'", $data['title']))) { - form_set_error('title', t('the specified title is already in use.')); + 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'", $data['name']))) { - form_set_error('name', t('the specified name is already in use.')); + form_set_error('name', t('The specified name is already in use.')); } if (!form_get_errors()) { db_query("INSERT INTO {profile_fields} (title, name, explanation, category, type, weight, required, visibility, options, page) VALUES ('%s', '%s', '%s', '%s', '%s', %d, %d, %d, '%s', '%s')", $data['title'], $data['name'], $data['explanation'], $data['category'], $type, $data['weight'], $data['required'], $data['visibility'], $data['options'], $data['page']); - drupal_set_message(t('the field has been created.')); + drupal_set_message(t('The field has been created.')); drupal_goto('admin/user/configure/profile'); } } @@ -344,7 +421,7 @@ function profile_admin_add($type) { $data = array('name' => 'profile_'); } - print theme('page', _profile_field_form($type, $data), t('Add new %type', array('%type' => $type))); + print theme('page', _profile_field_form($type, $data), t('Add new %type', array('%type' => _profile_field_types($type)))); } /** @@ -361,7 +438,7 @@ function profile_admin_edit($fid) { if (!form_get_errors()) { db_query("UPDATE {profile_fields} SET title = '%s', name = '%s', explanation = '%s', category = '%s', weight = %d, required = %d, visibility = %d, options = '%s', page = '%s' WHERE fid = %d", $data['title'], $data['name'], $data['explanation'], $data['category'], $data['weight'], $data['required'], $data['visibility'], $data['options'], $data['page'], $fid); - drupal_set_message(t('the field has been updated.')); + drupal_set_message(t('The field has been updated.')); drupal_goto('admin/user/configure/profile'); } } @@ -377,7 +454,7 @@ function profile_admin_edit($fid) { */ function profile_admin_delete($fid) { db_query('DELETE FROM {profile_fields} WHERE fid = %d', $fid); - drupal_set_message(t('the field has been deleted.')); + drupal_set_message(t('The field has been deleted.')); drupal_goto('admin/user/configure/profile'); } @@ -413,17 +490,21 @@ function _profile_field_form($type, $edit = array()) { function profile_admin_overview() { $result = db_query('SELECT * FROM {profile_fields} ORDER BY category, weight'); + $rows = array(); while ($field = db_fetch_object($result)) { - $rows[] = array($field->title, $field->name, $field->type, $field->category, l(t('edit'), "admin/user/configure/profile/edit/$field->fid"), l(t('delete'), "admin/user/configure/profile/delete/$field->fid")); + $rows[] = array($field->title, $field->name, _profile_field_types($field->type), $field->category, l(t('edit'), "admin/user/configure/profile/edit/$field->fid"), l(t('delete'), "admin/user/configure/profile/delete/$field->fid")); + } + 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('Create new field') .'</h2>'; + $output .= '<h2>'. t('Add new field') .'</h2>'; $output .= '<ul>'; foreach (_profile_field_types() as $key => $value) { - $output .= '<li>'. l(t('Add new %type', array('%type' => $value)), "admin/user/configure/profile/add/$key") .'</li>'; + $output .= '<li>'. l($value, "admin/user/configure/profile/add/$key") .'</li>'; } $output .= '</ul>'; @@ -448,8 +529,18 @@ function theme_profile_profile($user, $fields = array()) { } function _profile_field_types($type = NULL) { - $types = array('textfield', 'textarea', 'checkbox', 'selection', 'list', 'url'); + $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')); return isset($type) ? $types[$type] : $types; } +function _profile_field_serialize($type = NULL) { + return $type == 'date'; +} + ?> diff --git a/modules/profile/profile.module b/modules/profile/profile.module index 1ad8288c7e477b8e66a5d503e4ea19cd61900e0b..802daca6cbd1af5ab1026105d23e408eff83b1a1 100644 --- a/modules/profile/profile.module +++ b/modules/profile/profile.module @@ -1,8 +1,6 @@ <?php // $Id$ -// TODO: add a 'date' field so we can migrate the birthday information. - /** * Flags to define the visibility of a profile field. */ @@ -56,12 +54,18 @@ function profile_menu() { */ function profile_browse() { - $name = strip_tags(arg(1)); - $value = strip_tags(arg(2)); + $name = arg(1); + $value = arg(2); - $field = db_fetch_object(db_query("SELECT DISTINCT(fid), type, title, page FROM {profile_fields} WHERE name = '%s'", $name)); + $field = db_fetch_object(db_query("SELECT DISTINCT(fid), type, title, page, visibility FROM {profile_fields} WHERE name = '%s'", $name)); if ($name && $field->fid) { + // Do not allow browsing of private fields by non-admins + if (!user_access('administer users') && $field->visibility == PROFILE_PRIVATE) { + drupal_access_denied(); + return; + } + // Compile a list of fields to show $fields = array(); $result = db_query('SELECT name, title, type FROM {profile_fields} WHERE fid != %d AND visibility = %d', $field->fid, PROFILE_PUBLIC_LISTINGS); @@ -80,6 +84,9 @@ function profile_browse() { case 'list': $query = "v.value LIKE '%%". check_query($value) ."%%'"; break; + default: + drupal_not_found(); + return; } // Extract the affected users: @@ -127,17 +134,20 @@ function profile_browse() { } function profile_load_profile(&$user) { - $result = db_query('SELECT f.name, v.value FROM {profile_fields} f INNER JOIN {profile_values} v ON f.fid = v.fid WHERE uid = %d', $user->uid); + $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); while ($field = db_fetch_object($result)) { if (empty($user->{$field->name})) { - $user->{$field->name} = $field->value; + $user->{$field->name} = _profile_field_serialize($field->type) ? unserialize($field->value) : $field->value; } } } function profile_save_profile(&$edit, &$user, $category) { - $result = db_query("SELECT fid, name FROM {profile_fields} WHERE LOWER(category) = '%s'", strtolower($category)); + $result = db_query("SELECT fid, name, type FROM {profile_fields} WHERE LOWER(category) = '%s'", strtolower($category)); while ($field = db_fetch_object($result)) { + if (_profile_field_serialize($field->type)) { + $edit[$field->name] = serialize($edit[$field->name]); + } 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]); unset($edit[$field->name], $user->{$field->name}); @@ -145,23 +155,38 @@ function profile_save_profile(&$edit, &$user, $category) { } function profile_view_field($user, $field) { + // Only allow browsing of private fields for admins + $browse = user_access('administer users') || $field->visibility != PROFILE_PRIVATE; + if ($value = $user->{$field->name}) { switch ($field->type) { case 'textfield': + return drupal_specialchars($value); case 'textarea': return check_output($value); case 'selection': - return l($value, "profile/$field->name/". drupal_specialchars($value, ENT_QUOTES)); + return $browse ? l(drupal_specialchars($value), "profile/$field->name/". check_url($value)) : drupal_specialchars($value); case 'checkbox': - return l($field->title, "profile/$field->name"); + return $browse ? l(drupal_specialchars($field->title), "profile/$field->name") : drupal_specialchars($field->title); case 'url': - return '<a href="'. check_url(strip_tags($value)) .'">'. strip_tags($value) .'</a>'; + return '<a href="'. check_url($value) .'">'. drupal_specialchars($value) .'</a>'; + case 'date': + list($format) = explode(' - ', variable_get('date_format_short', 'm/d/Y - H:i'), 2); + // Note: we avoid PHP's date() because it does not handle dates before + // 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']), + 'M' => _profile_map_month($value['month']), + 'Y' => $value['year']); + return strtr($format, $replace); case 'list': $values = split("[,\n\r]", $value); $fields = array(); foreach ($values as $value) { - if ($value = trim(strip_tags($value))) { - $fields[] = l($value, "profile/$field->name/". drupal_specialchars($value, ENT_QUOTES)); + if ($value = trim($value)) { + $fields[] = $browse ? l(drupal_specialchars($value), "profile/$field->name/". check_url($value)) : drupal_specialchars($value); } } return implode(', ', $fields); @@ -173,15 +198,19 @@ function profile_view_profile($user) { profile_load_profile($user); - $result = db_query('SELECT * FROM {profile_fields} WHERE visibility != %d ORDER BY category, weight', PROFILE_PRIVATE); + // Show private fields to administrators and people viewing their own account. + if (user_access('administer users') || $GLOBALS['user']->uid == $user->uid) { + $result = db_query('SELECT * FROM {profile_fields} ORDER BY category, weight'); + } + else { + $result = db_query('SELECT * FROM {profile_fields} WHERE visibility != %d ORDER BY category, weight', PROFILE_PRIVATE); + } + while ($field = db_fetch_object($result)) { if ($value = profile_view_field($user, $field)) { - if ($field->type == 'checkbox') { - $fields[$field->category] .= "<p>$value</p>"; - } - else { - $fields[$field->category] .= form_item($field->title, check_output($value)); - } + $description = ($field->visibility == PROFILE_PRIVATE) ? t('The content of this field is private and only visible to yourself.') : ''; + $title = ($field->type != 'checkbox') ? $field->title : ''; + $fields[$field->category] .= form_item($title, $value, $description); } } @@ -232,6 +261,9 @@ function profile_form_profile($edit, $user, $category) { $output .= form_select($field->title, $field->name, $edit[$field->name], $options, _profile_form_explanation($field), 0, 0, $field->required); break; + case 'date': + $output .= _profile_date_field($field, $edit); + break; } } @@ -240,6 +272,54 @@ function profile_form_profile($edit, $user, $category) { } } +/** + * Helper function: output a date selector + */ +function _profile_date_field($field, $edit) { + // Default to current date + if (!isset($edit[$field->name])) { + $edit[$field->name] = array('day' => format_date(time(), 'custom', 'j'), + 'month' => format_date(time(), 'custom', 'n'), + 'year' => format_date(time(), 'custom', 'Y')); + } + + // Determine the order of day, month, year in the site's chosen date format. + $format = variable_get('date_format_short', 'm/d/Y'); + $sort = array(); + $sort['day'] = max(strpos($format, 'd'), strpos($format, 'j')); + $sort['month'] = max(strpos($format, 'm'), strpos($format, 'M')); + $sort['year'] = strpos($format, 'Y'); + asort($sort); + $order = array_keys($sort); + + // Output multi-selector for date + $output = '<div class="container-inline">'; + foreach ($order as $type) { + switch ($type) { + case 'day': + $options = drupal_map_assoc(range(1, 31)); + break; + case 'month': + $options = drupal_map_assoc(range(1, 12), '_profile_map_month'); + break; + case 'year': + $options = drupal_map_assoc(range(1900, 2050)); + break; + } + $output .= form_select('', $field->name .']['. $type, $edit[$field->name][$type], $options, '', 0, 0); + } + $output .= '</div>'; + + return form_item($field->title, $output, _profile_form_explanation($field), NULL, $field->required); +} + +/** + * Helper function for usage with drupal_map_assoc + */ +function _profile_map_month($month) { + return format_date(gmmktime(0, 0, 0, $month, 2, 1970), 'custom', 'M', 0); +} + function profile_validate_profile($edit, $category) { $result = db_query("SELECT * FROM {profile_fields} WHERE LOWER(category) = '%s' ORDER BY weight", strtolower($category)); @@ -316,9 +396,6 @@ function profile_validate_form($edit) { * Menu callback; adds a new field to all user profiles. */ function profile_admin_add($type) { - $type = _profile_field_types($type); - - if ($_POST['op']) { $data = $_POST['edit']; @@ -326,17 +403,17 @@ function profile_admin_add($type) { profile_validate_form($data); if (db_result(db_query("SELECT fid FROM {profile_fields} WHERE title = '%s'", $data['title']))) { - form_set_error('title', t('the specified title is already in use.')); + 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'", $data['name']))) { - form_set_error('name', t('the specified name is already in use.')); + form_set_error('name', t('The specified name is already in use.')); } if (!form_get_errors()) { db_query("INSERT INTO {profile_fields} (title, name, explanation, category, type, weight, required, visibility, options, page) VALUES ('%s', '%s', '%s', '%s', '%s', %d, %d, %d, '%s', '%s')", $data['title'], $data['name'], $data['explanation'], $data['category'], $type, $data['weight'], $data['required'], $data['visibility'], $data['options'], $data['page']); - drupal_set_message(t('the field has been created.')); + drupal_set_message(t('The field has been created.')); drupal_goto('admin/user/configure/profile'); } } @@ -344,7 +421,7 @@ function profile_admin_add($type) { $data = array('name' => 'profile_'); } - print theme('page', _profile_field_form($type, $data), t('Add new %type', array('%type' => $type))); + print theme('page', _profile_field_form($type, $data), t('Add new %type', array('%type' => _profile_field_types($type)))); } /** @@ -361,7 +438,7 @@ function profile_admin_edit($fid) { if (!form_get_errors()) { db_query("UPDATE {profile_fields} SET title = '%s', name = '%s', explanation = '%s', category = '%s', weight = %d, required = %d, visibility = %d, options = '%s', page = '%s' WHERE fid = %d", $data['title'], $data['name'], $data['explanation'], $data['category'], $data['weight'], $data['required'], $data['visibility'], $data['options'], $data['page'], $fid); - drupal_set_message(t('the field has been updated.')); + drupal_set_message(t('The field has been updated.')); drupal_goto('admin/user/configure/profile'); } } @@ -377,7 +454,7 @@ function profile_admin_edit($fid) { */ function profile_admin_delete($fid) { db_query('DELETE FROM {profile_fields} WHERE fid = %d', $fid); - drupal_set_message(t('the field has been deleted.')); + drupal_set_message(t('The field has been deleted.')); drupal_goto('admin/user/configure/profile'); } @@ -413,17 +490,21 @@ function _profile_field_form($type, $edit = array()) { function profile_admin_overview() { $result = db_query('SELECT * FROM {profile_fields} ORDER BY category, weight'); + $rows = array(); while ($field = db_fetch_object($result)) { - $rows[] = array($field->title, $field->name, $field->type, $field->category, l(t('edit'), "admin/user/configure/profile/edit/$field->fid"), l(t('delete'), "admin/user/configure/profile/delete/$field->fid")); + $rows[] = array($field->title, $field->name, _profile_field_types($field->type), $field->category, l(t('edit'), "admin/user/configure/profile/edit/$field->fid"), l(t('delete'), "admin/user/configure/profile/delete/$field->fid")); + } + 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('Create new field') .'</h2>'; + $output .= '<h2>'. t('Add new field') .'</h2>'; $output .= '<ul>'; foreach (_profile_field_types() as $key => $value) { - $output .= '<li>'. l(t('Add new %type', array('%type' => $value)), "admin/user/configure/profile/add/$key") .'</li>'; + $output .= '<li>'. l($value, "admin/user/configure/profile/add/$key") .'</li>'; } $output .= '</ul>'; @@ -448,8 +529,18 @@ function theme_profile_profile($user, $fields = array()) { } function _profile_field_types($type = NULL) { - $types = array('textfield', 'textarea', 'checkbox', 'selection', 'list', 'url'); + $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')); return isset($type) ? $types[$type] : $types; } +function _profile_field_serialize($type = NULL) { + return $type == 'date'; +} + ?>