From fac522f4d7cce9a35e605fac2bba0b2d23616ad0 Mon Sep 17 00:00:00 2001 From: Evan Prodromou Date: Thu, 15 May 2008 12:28:44 -0400 Subject: settings and avatars Did considerable work on the settings section. Redesigned the DB to allow avatars. Each avatar image has a size and an URL. There can be multiple avatars per profile, just different sizes. Added accessors in Profile for avatar. Show the avatar in lots of places, where it makes sense. Constants for avatar sizes in common.php. darcs-hash:20080515162844-84dde-fe0630366e247c02ca8ca9d1cc6b963cfce57a26.gz --- actions/password.php | 91 +++++++++++++++++++++++++++++++++++++++ actions/profilesettings.php | 102 ++++++++++++++++++++++++++++++++++++++++++++ actions/settings.php | 52 ---------------------- actions/shownotice.php | 16 ++++++- actions/showstream.php | 15 ++++++- actions/subscribed.php | 9 +++- actions/subscriptions.php | 9 +++- classes/Avatar.php | 24 +++++++++++ classes/Notice.php | 1 - classes/Profile.php | 27 ++++++++++++ classes/User.php | 1 + classes/stoica.ini | 15 ++++++- db/stoica.sql | 12 ++++++ doc/TODO | 16 +++++-- lib/common.php | 13 +++++- lib/settingsaction.php | 38 +++++++++++++++++ lib/stream.php | 18 ++++++-- 17 files changed, 389 insertions(+), 70 deletions(-) create mode 100644 actions/password.php create mode 100644 actions/profilesettings.php delete mode 100644 actions/settings.php create mode 100644 classes/Avatar.php create mode 100644 lib/settingsaction.php diff --git a/actions/password.php b/actions/password.php new file mode 100644 index 000000000..31831d3a9 --- /dev/null +++ b/actions/password.php @@ -0,0 +1,91 @@ +. + */ + +if (!defined('LACONICA')) { exit(1) } + +class PasswordAction extends SettingsAction { + + function handle($args) { + parent::handle($args); + if (!common_logged_in()) { + common_user_error(_t('Not logged in.')); + return; + } + if ($this->arg('METHOD') == 'POST') { + $this->handle_post(); + } else { + $this->show_form(); + } + } + + function show_form($msg=NULL, $success=false) { + common_show_header(_t('Change password')); + $this->settings_menu(); + if ($msg) { + common_element('div', ($success) ? 'success' : 'error', + $msg); + } + common_start_element('form', array('method' => 'POST', + 'id' => 'password', + 'action' => + common_local_url('password'))); + common_password('oldpassword', _t('Old password')); + common_password('newpassword', _t('New password')); + common_password('confirm', _t('Confirm')); + common_element('input', array('name' => 'submit', + 'type' => 'submit', + 'id' => 'submit'), + _t('Login')); + common_element('input', array('name' => 'cancel', + 'type' => 'button', + 'id' => 'cancel'), + _t('Cancel')); + } + + function handle_post() { + + $user = common_current_user(); + assert(!is_null($user)); # should already be checked + + # FIXME: scrub input + + $oldpassword = $this->arg('oldpassword'); + $newpassword = $this->arg('newpassword'); + $confirm = $this->arg('confirm'); + + if (0 != strcmp($newpassword, $confirm)) { + $this->show_form(_t('Passwords don\'t match')); + return; + } + + if (!common_check_user($user->nickname, $oldpassword)) { + $this->show_form(_t('Incorrect old password')); + return; + } + + $user->password = common_munge_password($newpassword, $user->id); + + if (!$user->update()) { + common_server_error(_t('Can\'t save new password.')); + return; + } + + $this->show_form(_t('Password saved'), true); + } +} \ No newline at end of file diff --git a/actions/profilesettings.php b/actions/profilesettings.php new file mode 100644 index 000000000..b87cea7de --- /dev/null +++ b/actions/profilesettings.php @@ -0,0 +1,102 @@ +. + */ + +if (!defined('LACONICA')) { exit(1) } + +class ProfilesettingsAction extends SettingsAction { + + function handle($args) { + parent::handle($args); + if (!common_logged_in()) { + common_user_error(_t('Not logged in.')); + return; + } + if ($this->arg('METHOD') == 'POST') { + $this->handle_post(); + } else { + $this->show_form(); + } + } + + function show_form($msg=NULL, $success=false) { + common_show_header(_t('Profile settings')); + $this->settings_menu(); + if ($msg) { + common_element('div', ($success) ? 'success' : 'error', + $msg); + } + common_start_element('form', array('method' => 'POST', + 'id' => 'profilesettings', + 'action' => + common_local_url('profilesettings'))); + common_input('nickname', _t('Nickname')); + common_input('fullname', _t('Full name')); + common_input('email', _t('Email address')); + common_input('homepage', _t('Homepage')); + common_input('bio', _t('Bio')); + common_input('location', _t('Location')); + common_element('input', array('name' => 'submit', + 'type' => 'submit', + 'id' => 'submit'), + _t('Login')); + common_element('input', array('name' => 'cancel', + 'type' => 'button', + 'id' => 'cancel'), + _t('Cancel')); + common_show_footer(); + } + + function handle_post() { + $nickname = $this->arg('nickname'); + $fullname = $this->arg('fullname'); + $email = $this->arg('email'); + $homepage = $this->arg('homepage'); + $bio = $this->arg('bio'); + $location = $this->arg('location'); + + $user = common_current_user(); + assert(!is_null($user)); # should already be checked + + # FIXME: scrub input + # FIXME: transaction! + + $user->nickname = $this->arg('nickname'); + $user->email = $this->arg('email'); + + if (!$user->update()) { + common_server_error(_t('Couldnt update user.')); + return; + } + + $profile = $user->getProfile(); + + $profile->nickname = $user->nickname; + $profile->fullname = $this->arg('fullname'); + $profile->homepage = $this->arg('homepage'); + $profile->bio = $this->arg('bio'); + $profile->location = $this->arg('location'); + + if (!$profile->update()) { + common_server_error(_t('Couldnt save profile.')); + return; + } + + $this->show_form(_t('Settings saved.'), TRUE); + } +} \ No newline at end of file diff --git a/actions/settings.php b/actions/settings.php deleted file mode 100644 index ea8074efc..000000000 --- a/actions/settings.php +++ /dev/null @@ -1,52 +0,0 @@ -. - */ - -if (!defined('LACONICA')) { exit(1) } - -class SettingsAction extends Action { - - function handle($args) { - parent::handle($args); - if ($this->arg('METHOD') == 'POST') { - $nickname = $this->arg('nickname'); - $fullname = $this->arg('fullname'); - $email = $this->arg('email'); - $homepage = $this->arg('homepage'); - $bio = $this->arg('bio'); - $location = $this->arg('location'); - $oldpass = $this->arg('oldpass'); - $password = $this->arg('password'); - $confirm = $this->arg('confirm'); - - if ($password) { - if ($password != $confirm) { - $this->show_form(_t('Passwords don\'t match.')); - } - } else if ( - - $error = $this->save_settings($nickname, $fullname, $email, $homepage, - $bio, $location, $password); - if (!$error) { - $this->show_form(_t('Settings saved.'), TRUE); - } else { - $this->show_form($error); - } - } else { - $this->show_form(); - } diff --git a/actions/shownotice.php b/actions/shownotice.php index b0128e6e9..2e14963bb 100644 --- a/actions/shownotice.php +++ b/actions/shownotice.php @@ -51,9 +51,21 @@ class ShownoticeAction extends Action { $profile = $notice->getProfile(); # XXX: RDFa common_start_element('div', array('class' => 'notice')); - # FIXME: add the avatar + $avatar = $profile->getAvatar(AVATAR_PROFILE_SIZE); + if ($avatar) { + common_element('img', array('src' => $avatar->url, + 'class' => 'avatar profile', + 'width' => AVATAR_PROFILE_SIZE, + 'height' => AVATAR_PROFILE_SIZE, + 'alt' => + ($profile->fullname) ? $profile->fullname : + $profile->nickname)); + } common_start_element('a', array('href' => $profile->profileurl, - 'class' => 'nickname'), + 'class' => 'nickname', + 'title' => + ($profile->fullname) ? $profile->fullname : + $profile->nickname)), $profile->nickname); # FIXME: URL, image, video, audio common_element('span', array('class' => 'content'), diff --git a/actions/showstream.php b/actions/showstream.php index d476bd297..8272e1038 100644 --- a/actions/showstream.php +++ b/actions/showstream.php @@ -86,6 +86,14 @@ class ShowstreamAction extends StreamAction { function show_profile($profile) { common_start_element('div', 'profile'); + $avatar = $profile->getAvatar(AVATAR_PROFILE_SIZE); + if ($avatar) { + common_element('img', array('src' => $avatar->url, + 'class' => 'avatar profile', + 'width' => AVATAR_PROFILE_SIZE, + 'height' => AVATAR_PROFILE_SIZE, + 'title' => $profile->nickname)); + } common_element('span', 'nickname', $profile->nickname); if ($profile->fullname) { if ($profile->homepage) { @@ -144,8 +152,11 @@ class ShowstreamAction extends StreamAction { $subs->nickname, 'href' => $subs->profileurl, 'class' => 'subscription')); - common_element('img', array('src' => $subs->avatar, - 'class' => 'avatar')); + $avatar = $subs->getAvatar(AVATAR_MINI_SIZE); + common_element('img', array('src' => (($avatar) ? $avatar->url : DEFAULT_MINI_AVATAR), + 'width' => AVATAR_MINI_SIZE, + 'height' => AVATAR_MINI_SIZE, + 'class' => 'avatar mini')); common_end_element('a'); if ($cnt % SUBSCRIPTIONS_PER_ROW == 0) { diff --git a/actions/subscribed.php b/actions/subscribed.php index 924e2b67b..f24470f02 100644 --- a/actions/subscribed.php +++ b/actions/subscribed.php @@ -64,9 +64,14 @@ class SubscribedAction extends Action { $subs->nickname, 'href' => $subs->profileurl, 'class' => 'subscription')); - common_element('img', array('src' => $subs->avatar, - 'class' => 'avatar')); + $avatar = $subs->getAvatar(AVATAR_STREAM_SIZE); + common_element('img', array('src' => (($avatar) ? $avatar->url : DEFAULT_STREAM_AVATAR), + 'width' => AVATAR_STREAM_SIZE, + 'height' => AVATAR_STREAM_SIZE, + 'class' => 'avatar stream')); common_end_element('a'); + + # XXX: subscribe form here if ($idx % SUBSCRIPTIONS_PER_ROW == 0) { common_end_element('div'); diff --git a/actions/subscriptions.php b/actions/subscriptions.php index 653c6d2b5..87b8a4e48 100644 --- a/actions/subscriptions.php +++ b/actions/subscriptions.php @@ -61,9 +61,14 @@ class SubscriptionsAction extends Action { $subs->nickname, 'href' => $subs->profileurl, 'class' => 'subscription')); - common_element('img', array('src' => $subs->avatar, - 'class' => 'avatar')); + $avatar = $subs->getAvatar(AVATAR_STREAM_SIZE); + common_element('img', array('src' => (($avatar) ? $avatar->url : DEFAULT_STREAM_AVATAR), + 'width' => AVATAR_STREAM_SIZE, + 'height' => AVATAR_STREAM_SIZE, + 'class' => 'avatar stream')); common_end_element('a'); + + # XXX: subscribe form here if ($idx % SUBSCRIPTIONS_PER_ROW == 0) { common_end_element('div'); diff --git a/classes/Avatar.php b/classes/Avatar.php new file mode 100644 index 000000000..222725b48 --- /dev/null +++ b/classes/Avatar.php @@ -0,0 +1,24 @@ +profile_id = $this->id; + $avatar->width = $width; + if (is_null($height)) { + $avatar->height = $width; + } else { + $avatar->height = $height; + } + if ($avatar->find(true)) { + return $avatar; + } else { + return NULL; + } + } + + function getOriginalAvatar() { + $avatar = DB_DataObject::factory('avatar'); + $avatar->profile_id = $this->id; + $avatar->original = true; + if ($avatar->find(true)) { + return $avatar; + } else { + return NULL; + } + } } diff --git a/classes/User.php b/classes/User.php index e7d297b3c..2b8610a65 100644 --- a/classes/User.php +++ b/classes/User.php @@ -30,6 +30,7 @@ class User extends DB_DataObject public $__table = 'user'; // table name public $id; // int(4) primary_key not_null + public $nickname; // varchar(64) unique_key public $password; // varchar(255) public $email; // varchar(255) unique_key public $created; // datetime() not_null diff --git a/classes/stoica.ini b/classes/stoica.ini index 52686bcb5..6c951fcb9 100644 --- a/classes/stoica.ini +++ b/classes/stoica.ini @@ -1,9 +1,20 @@ +[avatar] +profile_id = 129 +width = 129 +height = 129 +original = 17 +mediatype = 130 + +[avatar__keys] +profile_id = K +width = K +height = K + [notice] id = 129 profile_id = 129 content = 2 -rendered = 2 url = 2 created = 142 modified = 384 @@ -48,6 +59,7 @@ subscribed = K [user] id = 129 +nickname = 2 password = 2 email = 2 created = 142 @@ -55,4 +67,5 @@ modified = 384 [user__keys] id = K +nickname = U email = U diff --git a/db/stoica.sql b/db/stoica.sql index 28e8f7662..7ee8f6ff8 100644 --- a/db/stoica.sql +++ b/db/stoica.sql @@ -14,6 +14,18 @@ create table profile ( index profile_nickname_idx (nickname) ); +create table avatar ( + profile_id integer not null comment 'foreign key to profile table' references profile (id), + width integer not null comment 'image width', + height integer not null comment 'image height', + original boolean default false comment 'uploaded by user or generated?', + mediatype varchar(32) not null comment 'file type', + url varchar(255) unique key comment 'avatar location', + + constraint primary key (profile_id, width, height), + index avatar_profile_id_idx (profile_id), +); + /* local users */ create table user ( diff --git a/doc/TODO b/doc/TODO index 8b3fe5b75..cd9ac05df 100644 --- a/doc/TODO +++ b/doc/TODO @@ -1,6 +1,10 @@ + login + register -- settings ++ settings +- upload avatar +- default avatar ++ change password ++ settings menu + disallow login if user is logged in + disallow register if user is logged in + common_current_user() @@ -14,6 +18,8 @@ + header menu + footer menu + disallow direct to PHP files +- use only canonical usernames +- use only canonical email addresses - require valid nicknames - common_local_url() - configuration system ($config) @@ -23,8 +29,6 @@ - RDF dump of entire site - FOAF dump for user - delete a notice -- make sure canonical usernames are unique -- upload avatar - licenses - design from Open Source Web Designs - release 0.1 @@ -37,6 +41,9 @@ - tinyurl-ification of URLs - jQuery for as much as possible - themes +- RDFa for stream pages +- RDFa for subscriber pages +- RDFa for subscribed pages - release 0.2 - @ messages - # tags @@ -51,6 +58,9 @@ - forward notices to Jabber - forward notices to other IM - forward notices to mobile phone +- receive notices from Jabber +- receive notices from other IM +- receive notices from mobile phone - machine tags - release 0.4 - include twitter subscriptions diff --git a/lib/common.php b/lib/common.php index 53cd66cbe..f30096796 100644 --- a/lib/common.php +++ b/lib/common.php @@ -19,6 +19,10 @@ if (!defined('LACONICA')) { exit(1) } +define('AVATAR_PROFILE_SIZE', 96); +define('AVATAR_STREAM_SIZE', 48); +define('AVATAR_MINI_SIZE', 24); + # global configuration object // default configuration, overwritten in config.php @@ -108,7 +112,7 @@ function common_head_menu() { common_menu_item(common_local_url('showstream', array('nickname' => $user->nickname)), _t('Profile'), $user->fullname || $user->nickname); - common_menu_item(common_local_url('settings'), + common_menu_item(common_local_url('profilesettings'), _t('Settings')); common_menu_item(common_local_url('logout'), _t('Logout')); @@ -141,6 +145,13 @@ function common_menu_item($url, $text, $title=NULL) { common_element_end('li'); } +function common_input($id, $label) { + common_element('label', array('for' => $id), $label); + common_element('input', array('name' => $id, + 'type' => 'text', + 'id' => $id)); +} + # salted, hashed passwords are stored in the DB function common_munge_password($id, $password) { diff --git a/lib/settingsaction.php b/lib/settingsaction.php new file mode 100644 index 000000000..db07bdef9 --- /dev/null +++ b/lib/settingsaction.php @@ -0,0 +1,38 @@ +. + */ + +if (!defined('LACONICA')) { exit(1) } + +class SettingsAction extends Action { + + function handle($args) { + parent::handle($args); + } + + function settings_menu() { + common_element_start('ul', 'headmenu'); + common_menu_item(common_local_url('editprofile'), + _t('Profile')); + common_menu_item(common_local_url('avatar'), + _t('Avatar')); + common_menu_item(common_local_url('password'), + _t('Password')); + common_element_end('ul'); + } +} diff --git a/lib/stream.php b/lib/stream.php index b659eb8b5..912969380 100644 --- a/lib/stream.php +++ b/lib/stream.php @@ -27,14 +27,24 @@ class StreamAction extends Action { parent::handle($args); } + # XXX: for 'showstream' repeats same avatar over and over function show_notice($notice) { $profile = $notice->getProfile(); # XXX: RDFa common_start_element('div', array('class' => 'notice')); - # FIXME: add the avatar - common_start_element('a', array('href' => $profile->profileurl, - 'class' => 'nickname'), - $profile->nickname); + $avatar = $profile->getAvatar(AVATAR_STREAM_SIZE); + common_start_element('a', array('href' => $profile->profileurl)); + common_element('img', array('src' => ($avatar) ? $avatar->url : DEFAULT_STREAM_AVATAR, + 'class' => 'avatar stream', + 'width' => AVATAR_STREAM_SIZE, + 'height' => AVATAR_STREAM_SIZE, + 'alt' => + ($profile->fullname) ? $profile->fullname : + $profile->nickname)); + common_end_element('a'); + common_element('a', array('href' => $profile->profileurl, + 'class' => 'nickname'), + $profile->nickname); # FIXME: URL, image, video, audio common_element('span', array('class' => 'content'), $notice->content); common_element('span', array('class' => 'date'), -- cgit v1.2.3-54-g00ecf