diff --git a/actions/password.php b/actions/password.php new file mode 100644 index 0000000000..31831d3a93 --- /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 0000000000..b87cea7de2 --- /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/shownotice.php b/actions/shownotice.php index b0128e6e96..2e14963bb5 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 d476bd297b..8272e10384 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 924e2b67b3..f24470f029 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 653c6d2b5e..87b8a4e485 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 0000000000..222725b484 --- /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 e7d297b3c6..2b8610a652 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 52686bcb56..6c951fcb9e 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 28e8f7662c..7ee8f6ff8b 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 8b3fe5b75f..cd9ac05df0 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 53cd66cbec..f30096796b 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/actions/settings.php b/lib/settingsaction.php similarity index 54% rename from actions/settings.php rename to lib/settingsaction.php index ea8074efce..db07bdef92 100644 --- a/actions/settings.php +++ b/lib/settingsaction.php @@ -16,37 +16,23 @@ * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see . */ - + 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(); - } + } + + 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 b659eb8b5b..9129693804 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'),