[opw-web] Evolve user profile handling
- From: Owen Taylor <otaylor src gnome org>
- To: commits-list gnome org
- Cc:
- Subject: [opw-web] Evolve user profile handling
- Date: Mon, 10 Mar 2014 04:32:43 +0000 (UTC)
commit 84c47deebac05fb3747a436f08ec84cc00f1a2d3
Author: Owen W. Taylor <otaylor fishsoup net>
Date: Sun Mar 9 17:58:20 2014 -0400
Evolve user profile handling
* Show more information on the user profile screen
* Show different information to different users for the user profile
* Add user profile editing
* Add concept of website / profile urls to the database
* Improve user creation routines
* Fix various bugs
classes/class_user.php | 100 ++++++++----
lang/en-gb.php | 8 +
modules/mod_login.php | 10 +-
modules/mod_user_profile.php | 159 ++++++++++++++++----
schema.sql | 5 +
skins/easterngreen/css/main.css | 5 +
skins/easterngreen/html/tpl_user_profile.html | 35 ++++-
.../easterngreen/html/tpl_user_profile_editor.html | 43 ++++++
.../html/tpl_user_profile_identity.html | 23 +++
9 files changed, 317 insertions(+), 71 deletions(-)
---
diff --git a/classes/class_user.php b/classes/class_user.php
index a39b318..10c9ae2 100644
--- a/classes/class_user.php
+++ b/classes/class_user.php
@@ -216,6 +216,7 @@ class user
{
$hybridauth = $this->hybridauth();
if ($hybridauth->authenticate("Google", array (
+ 'scope' => 'https://www.googleapis.com/auth/userinfo.profile
https://www.googleapis.com/auth/userinfo.email',
'hauth_return_to' => Hybrid_Auth::getCurrentUrl() . '&p=Google'
))) {
return $this->finish_login('Google');
@@ -229,6 +230,7 @@ class user
{
$hybridauth = $this->hybridauth();
if ($hybridauth->authenticate("Facebook", array (
+ 'scope' => 'email, basic_info',
'hauth_return_to' => Hybrid_Auth::getCurrentUrl() . '&p=Facebook'
))) {
return $this->finish_login('Facebook');
@@ -265,7 +267,7 @@ class user
if ($values == false) {
// Get current session data
- $sql = "SELECT is_admin, fullname, email FROM {$db->prefix}profiles " .
+ $sql = "SELECT * FROM {$db->prefix}profiles " .
"WHERE username = ?";
$row = $db->query($sql, $username, true);
if ($row === false) {
@@ -275,7 +277,9 @@ class user
$values['is_admin'] = $row['is_admin'] != 0;
$values['fullname'] = $row['fullname'];
+ $values['websiteUrl'] = $row['websiteUrl'];
$values['email'] = $row['email'];
+ $values['emailVerified'] = $row['emailVerified'] != 0;
$cache->put($key, $values, 'users');
}
@@ -286,8 +290,21 @@ class user
function create_user($provider, $identifier, $profile)
{
- global $db;
+ global $db, $cache;
+
+ // The OpenID provider sets displayName to a reversed lastname - firstname,
+ // if unset. Detect this to avoid thinking we got a real DisplayName from
+ // the provider.
+ $badDisplayName = trim( $profile->lastName . " " . $profile->firstName );
+ if ($profile->displayName != '' && $profile->displayName != $badDisplayName)
+ $fullname = trim($profile->displayName);
+ else if ($profile->firstName != '' && $profile->lastName != '')
+ $fullname = trim($profile->firstName) . " " . trim($profile->lastName);
+ else
+ $fullname = '';
+
+ // Derive the username first from the email; if not present from the display name
$suffix = "";
$email = $profile->email;
if ($email != "") {
@@ -296,16 +313,28 @@ class user
$username = $email;
else
$username = substr($email, 0, $pos);
- } else if ($profile->firstName != '' && $profile->lastName != '') {
- $username = trim($profile->firstName) . "." . trim($profile->lastName);
- $suffix = 1;
+ } else if ($fullname != '') {
+ $username = $fullname;
} else {
$username = 'user';
+ $suffix = 1;
+ }
+
+ // Sanitize resulting username
+ $username = preg_replace('/[^A-Za-z0-9_.-]+/', '.', $username);
+ $username = preg_replace('/^\./', '', $username);
+ $username = preg_replace('/\.$/', '', $username);
+
+ // If we sanitized everything away, go back to the extreme fallback
+ if ($username == '') {
+ $username = 'user';
+ $suffix = 1;
}
+ // Now we need to make sure that everything is unique
while ($this->lookup_user($username . $suffix)) {
if ($suffix === "")
- $suffix = 1;
+ $suffix = 2;
else
$suffix++;
}
@@ -320,45 +349,40 @@ class user
$emailVerified = 0;
}
- // The OpenID provider sets displayName to a reversed lastname - firstname,
- // if unset. Detect this to avoid thinking we got a real DisplayName from
- // the provider.
- $badDisplayName = trim( $this->user->profile->lastName . " " . $this->user->profile->firstName );
-
- if ($profile->displayName != '' && $profile->displayName != $badDisplayName)
- $fullname = $profile->displayName;
- else if ($profile->firstName != '' && $profile->lastName != '')
- $fullname = trim($profile->firstName) . " " . trim($profile->lastName);
+ # The profileUrl for the provider should be something verified
+ if ($provider == 'OpenID')
+ $profileUrl = $identifier;
else
- $fullname = '';
+ $profileUrl = $profile->profileURL;
$params = array('username' => $username,
'fullname' => $fullname,
'email' => $email,
'emailVerified' => $emailVerified,
+ 'profileUrl' => $profileUrl,
'is_admin' => 0,
'provider' => $provider,
'identifier' => $identifier);
$sql = "INSERT INTO {$db->prefix}profiles " .
- "(username, fullname, email, emailVerified, is_admin) " .
- "VALUES (:username, :fullname, :email, :emailVerified, :is_admin)";
+ "(username, fullname, email, emailVerified, websiteUrl, is_admin) " .
+ "VALUES (:username, :fullname, :email, :emailVerified, :profileUrl, :is_admin)";
$db->query($sql, $params);
$sql = "INSERT INTO {$db->prefix}identities " .
- "(provider, identifier, username) " .
- "VALUES (:provider, :identifier, :username)";
+ "(provider, identifier, username, fullname, email, emailVerified, profileUrl) " .
+ "VALUES (:provider, :identifier, :username, :fullname, :email, :emailVerified,
:profileUrl)";
$db->query($sql, $params);
$cache->purge('users');
+
+ return $username;
}
- function finish_login($provider)
+ function finish_login($provider, &$user_created)
{
$hybridauth = $this->hybridauth();
- Hybrid_Logger::info("finish_login - $provider");
-
if ($provider == "OpenID" && $hybridauth->isConnectedWith("OpenID")) {
$adapter = $hybridauth->getAdapter("OpenID");
$profile = $adapter->getUserProfile();
@@ -372,16 +396,14 @@ class user
return false;
}
- Hybrid_Logger::info("finish_login - connected");
+ $identifier = $profile->identifier;
- $identifier = $adapter->getUserProfile()->identifier;
-
- Hybrid_Logger::info("finish_login - looking up identity");
$username = $this->lookup_identity($provider, $identifier);
+ $user_created = false;
if ($username == null) {
- Hybrid_Logger::info("finish_login - create user");
$username = $this->create_user($provider, $identifier, $profile);
+ $user_created = true;
}
$info = $this->lookup_user($username);
@@ -440,6 +462,12 @@ class user
function get_role($program_id, &$role, &$organization) {
global $cache, $db;
+ $organization = null;
+ $role = 'g';
+
+ if ($this->username === null)
+ return;
+
$key = "role_{$this->username}_{$program_id}";
$role_data = $cache->get($key, 'roles');
@@ -462,11 +490,17 @@ class user
$role = $role_data['role'];
$organization = $role_data['organization_id'];
}
- else
- {
- $role = 'g';
- $organization = null;
- }
+ }
+
+ // See if the current user is a mentor in any program (allows seeing user profile details)
+ function isMentorAnyProgram() {
+ global $db;
+
+ $sql = "SELECT COUNT(*) as count FROM {$db->prefix}roles " .
+ "WHERE username = ? and role = 'm'";
+ $row = $db->query($sql, $this->username, true);
+
+ return $row['count'] > 0;
}
// Restricts a screen to a specific condition only
diff --git a/lang/en-gb.php b/lang/en-gb.php
index 4200c49..49978ed 100644
--- a/lang/en-gb.php
+++ b/lang/en-gb.php
@@ -194,12 +194,20 @@ $lang_data = array(
'user_profile' => 'User profile',
'full_name' => 'Full name',
'email' => 'Email address',
+ 'website' => 'Website',
+ 'verified' => '(verified)',
+ 'unverified' => '(unverified)',
'previous_page' => 'Previous page',
'user_avatar' => 'User avatar',
'user_404' => 'The requested user could not be found',
'contact_user' => 'Contact user',
'full_profile' => 'View full profile',
'site_admin' => 'Site admin',
+ 'log_in_identity' => 'Log-in Identity',
+ 'profile_url' => 'Profile page',
+ 'visible_to_mentors' => '(visible to mentors)',
+ 'visible_to_public' => '(visible to public)',
+ 'edit_user_profile' => 'Edit user profile',
/* Module: user_bans */
'ban_user' => 'Ban a user',
diff --git a/modules/mod_login.php b/modules/mod_login.php
index 2cc300a..0ec4d01 100644
--- a/modules/mod_login.php
+++ b/modules/mod_login.php
@@ -20,8 +20,14 @@ if ($user->is_logged_in)
}
// If we're on the end of successful authentication request
-if ($provider != '' && $user->finish_login($provider)) {
- $core->redirect($dest_url);
+if ($provider != '' && $user->finish_login($provider, $user_created)) {
+ if ($user_created) {
+ // The first time a user logs in, take them to their profile
+ $username_url = urlencode($user->username);
+ $core->redirect("?q=user_profile&u={$username_url}");
+ } else {
+ $core->redirect($dest_url);
+ }
}
// Login data was submitted
diff --git a/modules/mod_user_profile.php b/modules/mod_user_profile.php
index 4b5b8fd..2f0f9e2 100644
--- a/modules/mod_user_profile.php
+++ b/modules/mod_user_profile.php
@@ -8,6 +8,7 @@
if (!defined('IN_PANDORA')) exit;
// Get the username and return URL
+$action = $core->variable('a', 'view');
$return_encoded = $core->variable('r', '');
$username_encoded = $core->variable('u', '');
@@ -18,44 +19,148 @@ $username = urldecode($username_encoded);
if (empty($username)) {
$user->restrict(!is_null($user->username));
$username = $user->username;
+ $username_encoded = urlencode($username);
}
+$is_self = ($username == $user->username);
+$can_edit = $is_self || $user->is_admin;
+$can_view_details = $is_self || $user->is_admin || $user->isMentorAnyProgram();
+
// Get the user data
$username_data = $user->lookup_user($username);
-// Set the template data
-if (!is_null($username_data))
-{
- $is_admin = false;
- $avatar_url = "?q=user_avatar&u={$username_encoded}";
- $full_name = $username_data['fullname'];
- $user_email = $username_data['email'];
- $is_admin = $username_data['is_admin'];
+$skin->assign(array(
+ 'visible_to_public' => $is_self ? $lang->get('visible_to_public') : '',
+ 'visible_to_mentors' => $is_self ? $lang->get('visible_to_mentors') : ''
+));
+
+if ($action == 'view') {
+ // Set the template data
+ if (!is_null($username_data))
+ {
+ // And the identity data
+ if ($can_view_details) {
+ $sql = "SELECT * from {$db->prefix}identities " .
+ "WHERE username = ?";
+ $identity_data = $db->query($sql, $username);
+ } else {
+ $identity_data = array();
+ }
+
+ $identities_list = '';
+ foreach ($identity_data as $row)
+ {
+ // Assign data for each project
+ $skin->assign(array(
+ 'identity_provider' => $row['provider'],
+ 'identity_full_name' => htmlspecialchars($row['fullname']),
+ 'identity_email' => htmlspecialchars($row['email']),
+ 'identity_email_verified' => $row['emailVerified'] != 0 ?
+ $lang->get('{{verified}}') : $lang->get('{{unverified}}'),
+ 'identity_profile_url' => htmlspecialchars($row['profileUrl'])
+ ));
+
+ $identities_list .= $skin->output('tpl_user_profile_identity');
+ }
+
+ $is_admin = false;
+ $avatar_url = "?q=user_avatar&u={$username_encoded}";
+ $edit_url = "?q=user_profile&a=editor&u={$username_encoded}";
+ $full_name = $username_data['fullname'];
+ $user_email = $can_view_details ? $username_data['email'] : '';
+ $user_email_verified = $can_view_details ? $username_data['emailVerified'] : false;
+ $website_url = $username_data['websiteUrl'];
+ $is_admin = $username_data['is_admin'];
+
+ // Assign profile variables
+ $skin->assign(array(
+ 'user_username' => htmlspecialchars($username),
+ 'user_fullname' => htmlspecialchars($full_name),
+ 'user_email' => htmlspecialchars($user_email),
+ 'user_email_verified' => $user_email_verified ? $lang->get('{{verified}}') :
$lang->get('{{unverified}}'),
+ 'website_url' => htmlspecialchars($website_url),
+ 'avatar_url' => $avatar_url,
+ 'return_url' => $return_url,
+ 'edit_url' => $edit_url,
+ 'identities_list' => $identities_list,
+ 'profile_visibility' => $skin->visibility(true),
+ 'notice_visibility' => $skin->visibility(false),
+ 'edit_visibility' => $skin->visibility($can_edit),
+ 'details_visibility' => $skin->visibility($can_view_details),
+ 'contact_visibility' => $skin->visibility(!$is_self && $can_view_details),
+ 'badge_visibility' => $skin->visibility($is_admin),
+ 'return_visibility' => $skin->visibility(empty($return_url), true),
+ ));
+ }
+ else
+ {
+ // No profile found, show notice
+ $skin->assign(array(
+ 'profile_visibility' => $skin->visibility(false),
+ 'notice_visibility' => $skin->visibility(true),
+ ));
+ }
+ // Output the page
+ $module_title = $lang->get('user_profile');
+ $module_data = $skin->output('tpl_user_profile');
+
+} else if ($action == 'editor') {
+ $user->restrict($can_edit);
+
+ if (isset($_POST['user_save'])) {
+ $fullname = $core->variable('fullname', '', false, true);
+ $email = $core->variable('email', '', false, true);
+ $website = $core->variable('website', '', false, true);
+
+ if ($email == $username_data['email']) {
+ $email_verified = $username_data['emailVerified'];
+ } else {
+ // Check if the user is changing the email back to a verified login email
+ $sql = "SELECT COUNT(*) as count from {$db->prefix}identities " .
+ "WHERE username = :username " .
+ "AND email = :email " .
+ "AND emailVerified = 1";
+ $row = $db->query($sql,
+ array('username' => $username,
+ 'email' => $email),
+ true);
+ $email_verified = $row['count'] > 0;
+ }
+
+ $sql = "UPDATE {$db->prefix}profiles " .
+ "SET fullname = :fullname, " .
+ "email = :email, " .
+ "emailVerified = :email_verified, " .
+ "websiteUrl = :website_url " .
+ "WHERE username = :username";
+ $db->query($sql,
+ array('username' => $username,
+ 'fullname' => $fullname,
+ 'email' => $email,
+ 'email_verified' => $email_verified ? 1 : 0,
+ 'website_url' => $website));
+
+ $core->redirect("?q=user_profile&u={$username_encoded}");
+ }
// Assign profile variables
$skin->assign(array(
- 'user_username' => htmlspecialchars($username),
- 'user_fullname' => htmlspecialchars($full_name),
- 'user_email' => htmlspecialchars($user_email),
- 'avatar_url' => $avatar_url,
- 'return_url' => $return_url,
- 'profile_visibility' => $skin->visibility(empty($user_email), true),
- 'notice_visibility' => $skin->visibility(empty($user_email)),
- 'badge_visibility' => $skin->visibility($is_admin),
- 'return_visibility' => $skin->visibility(empty($return_url), true),
- ));
+ 'user_fullname' => htmlspecialchars($username_data['fullname']),
+ 'user_email' => htmlspecialchars($username_data['email']),
+ 'website_url' => htmlspecialchars($username_data['websiteUrl']),
+ 'cancel_url' => "?q=user_profile&u={$username_encoded}",
+ 'error_visibility' => $skin->visibility(false),
+ 'error_message' => ''
+ ));
+
+ // Output the page
+ $module_title = $lang->get('edit_user_profile');
+ $module_data = $skin->output('tpl_user_profile_editor');
}
else
{
- // No profile found, show notice
- $skin->assign(array(
- 'profile_visibility' => $skin->visibility(false),
- 'notice_visibility' => $skin->visibility(true),
- ));
+ // Unknown action
+ $core->redirect($core->path());
}
-// Output the page
-$module_title = $lang->get('user_profile');
-$module_data = $skin->output('tpl_user_profile');
-
?>
\ No newline at end of file
diff --git a/schema.sql b/schema.sql
index 3688929..77c8a05 100644
--- a/schema.sql
+++ b/schema.sql
@@ -89,6 +89,10 @@ CREATE TABLE `opw_identities` (
`provider` varchar(20) NOT NULL,
`identifier` varchar(255) NOT NULL,
`username` varchar(255) NOT NULL,
+ `fullname` varchar(255) NOT NULL,
+ `email` varchar(255) NOT NULL,
+ `emailVerified` tinyint(1) DEFAULT 0,
+ `profileUrl` varchar(255) NOT NULL,
PRIMARY KEY (`provider`, `identifier`)
) ENGINE=MyISAM DEFAULT CHARSET=utf8;
@@ -97,6 +101,7 @@ CREATE TABLE `opw_profiles` (
`fullname` varchar(255) NOT NULL,
`email` varchar(255) NOT NULL,
`emailVerified` tinyint(1) DEFAULT 0,
+ `websiteUrl` varchar(255) NOT NULL DEFAULT '',
`is_admin` tinyint(1) DEFAULT 0,
PRIMARY KEY (`username`)
) ENGINE=MyISAM DEFAULT CHARSET=utf8;
diff --git a/skins/easterngreen/css/main.css b/skins/easterngreen/css/main.css
index 06065d4..34ba1b2 100644
--- a/skins/easterngreen/css/main.css
+++ b/skins/easterngreen/css/main.css
@@ -168,6 +168,11 @@ h3 {
padding: 30px 30px 20px 30px;
}
+.visibility-note {
+ font-size: 90%;
+ color: #888;
+}
+
/*-----------------Links and images-----------------*/
.learn-more {
diff --git a/skins/easterngreen/html/tpl_user_profile.html b/skins/easterngreen/html/tpl_user_profile.html
index 61f882c..20a657f 100644
--- a/skins/easterngreen/html/tpl_user_profile.html
+++ b/skins/easterngreen/html/tpl_user_profile.html
@@ -14,7 +14,9 @@
<table>
<tr>
<td style="width:165px">
+ <!--
<img class="thumbnail" src="[[avatar_url]]" alt="{{user_avatar}}" />
+ -->
<div class="align-center top-spacer [[badge_visibility]]">
<span class="label label-admin">
@@ -30,16 +32,25 @@
<tr>
<td style="width:140px">{{username}}</td>
<td>[[user_username]]</td>
+ <td class="visibility-note"></td>
</tr>
<tr>
<td>{{full_name}}</td>
<td>[[user_fullname]]</td>
+ <td class="visibility-note">[[visible_to_public]]</td>
</tr>
<tr>
+ <td>{{website}}</td>
+ <td><a href="[[website_url]]">[[website_url]]</a></td>
+ <td class="visibility-note">[[visible_to_public]]</td>
+ </tr>
+
+ <tr class="[[details_visibility]]"> <!-- avoid blank row -->
<td>{{email}}</td>
- <td>[[user_email]]</td>
+ <td>[[user_email]] [[user_email_verified]]</td>
+ <td class="visibility-note">[[visible_to_mentors]]</td>
</tr>
</tbody>
</table>
@@ -47,16 +58,22 @@
</tr>
</table>
+ <h4 class="[[details_visibility]]">{{log_in_identity}} <span
class="visibility-note">[[visible_to_mentors]]</span></h4>
+ <table>
+ <tbody>
+ [[identities_list]]
+ </tbody>
+ </table>
+
<div class="form-actions">
- <a href="mailto:[[user_email]]" class="btn btn-primary">
- <i class="icon-envelope icon-white"></i>
- {{contact_user}}
+ <a href="[[edit_url]]" class="btn btn-primary [[edit_visibility]]">
+ <i class="icon-pencil icon-white"></i>
+ {{edit}}
</a>
- <a href="https://identity.kde.org/index.php?r=people/view&uid=[[user_username]]" class="btn"
- target="_blank">
- <i class="icon-user icon-black"></i>
- {{full_profile}}
+ <a href="mailto:[[user_email]]" class="btn btn-primary [[contact_visibility]]">
+ <i class="icon-envelope icon-white"></i>
+ {{contact_user}}
</a>
</div>
-</div>
\ No newline at end of file
+</div>
diff --git a/skins/easterngreen/html/tpl_user_profile_editor.html
b/skins/easterngreen/html/tpl_user_profile_editor.html
new file mode 100644
index 0000000..dd886ff
--- /dev/null
+++ b/skins/easterngreen/html/tpl_user_profile_editor.html
@@ -0,0 +1,43 @@
+<h1>{{edit_user_profile}}</h1>
+<hr class="hr-head" />
+
+<div class="alert alert-error [[error_visibility]]">
+ <a class="close" data-dismiss="alert">×</a>
+ [[error_message]]
+</div>
+
+<div class="control-group">
+ <label class="control-label">{{full_name}}</label>
+ <div class="controls">
+ <input type="text" name="fullname" maxlength="100" class="input-xxlarge" value="[[user_fullname]]" />
+ <span class="visibility-note">[[visible_to_public]]</span>
+ </div>
+</div>
+
+<div class="control-group">
+ <label class="control-label">{{website}}</label>
+ <div class="controls">
+ <input type="text" name="website" maxlength="100" class="input-xxlarge" value="[[website_url]]" />
+ <span class="visibility-note">[[visible_to_public]]</span>
+ </div>
+</div>
+
+<div class="control-group">
+ <label class="control-label">{{email}}</label>
+ <div class="controls">
+ <input type="text" name="email" maxlength="100" class="input-xxlarge" value="[[user_email]]" />
+ <span class="visibility-note">[[visible_to_mentors]]</span>
+ </div>
+</div>
+
+<div class="form-actions">
+ <button type="submit" name="user_save" class="btn btn-primary">
+ <i class="icon-ok-sign icon-white"></i>
+ {{save}}
+ </button>
+
+ <a href="[[cancel_url]]" class="btn">
+ <i class="icon-remove icon-black"></i>
+ {{cancel}}
+ </a>
+</div>
diff --git a/skins/easterngreen/html/tpl_user_profile_identity.html
b/skins/easterngreen/html/tpl_user_profile_identity.html
new file mode 100644
index 0000000..7e10dea
--- /dev/null
+++ b/skins/easterngreen/html/tpl_user_profile_identity.html
@@ -0,0 +1,23 @@
+<tr>
+ <td style="width:165px">
+ [[identity_provider]]
+ </td>
+ <td style="width:140px">{{full_name}}</td>
+ <td>
+ [[identity_full_name]]
+ </td>
+</tr>
+<tr>
+ <td></td>
+ <td>{{email}}</td>
+ <td>
+ [[identity_email]] [[identity_email_verified]]
+ </td>
+</tr>
+<tr>
+ <td></td>
+ <td>{{profile_url}}</td>
+ <td>
+ <a href="[[identity_profile_url]]">[[identity_profile_url]]</a> {{verified}}
+ </td>
+</tr>
[
Date Prev][
Date Next] [
Thread Prev][
Thread Next]
[
Thread Index]
[
Date Index]
[
Author Index]