diff --git a/_darcs/inventory b/_darcs/inventory index da63f8216a..22b47336e1 100644 --- a/_darcs/inventory +++ b/_darcs/inventory @@ -124,4 +124,6 @@ csarven@controlyourself.ca**20081210021607] [Jcrop CSS updates to original/preview views csarven@controlyourself.ca**20081210025922] [Actually crop your avatar when hitting 'crop' button on profile -Zach Copley **20081212043018] \ No newline at end of file +Zach Copley **20081212043018] +[Some fixups of patches not already migrated to trunk to bring inline with PEAR coding stds +Zach Copley **20081225144601] \ No newline at end of file diff --git a/_darcs/patches/20081225144601-7b5ce-4846f3d036c36037836d15ed672c10ba33f9f84c.gz b/_darcs/patches/20081225144601-7b5ce-4846f3d036c36037836d15ed672c10ba33f9f84c.gz new file mode 100644 index 0000000000..6a5d9a9db0 Binary files /dev/null and b/_darcs/patches/20081225144601-7b5ce-4846f3d036c36037836d15ed672c10ba33f9f84c.gz differ diff --git a/_darcs/pristine/actions/profilesettings.php b/_darcs/pristine/actions/profilesettings.php index caec2f93d7..d861919b92 100644 --- a/_darcs/pristine/actions/profilesettings.php +++ b/_darcs/pristine/actions/profilesettings.php @@ -54,13 +54,17 @@ class ProfilesettingsAction extends SettingsAction return; } - if ($this->arg('save')) { - $this->save_profile(); - } else if ($this->arg('upload')) { - $this->upload_avatar(); - } else if ($this->arg('changepass')) { - $this->change_password(); - } + if ($this->arg('save')) { + $this->save_profile(); + } else if ($this->arg('upload')) { + $this->upload_avatar(); + } else if ($this->arg('crop')) { + $this->crop_avatar(); + } else if ($this->arg('changepass')) { + $this->change_password(); + } else { + $this->show_form(_('Unexpected form submission.')); + } } @@ -70,31 +74,30 @@ class ProfilesettingsAction extends SettingsAction $user = common_current_user(); $profile = $user->getProfile(); - common_element_start('form', array('method' => 'POST', - 'id' => 'profilesettings', - 'action' => - common_local_url('profilesettings'))); - common_hidden('token', common_session_token()); - - # too much common patterns here... abstractable? - - common_input('nickname', _('Nickname'), - ($this->arg('nickname')) ? $this->arg('nickname') : $profile->nickname, - _('1-64 lowercase letters or numbers, no punctuation or spaces')); - common_input('fullname', _('Full name'), - ($this->arg('fullname')) ? $this->arg('fullname') : $profile->fullname); - common_input('homepage', _('Homepage'), - ($this->arg('homepage')) ? $this->arg('homepage') : $profile->homepage, - _('URL of your homepage, blog, or profile on another site')); - common_textarea('bio', _('Bio'), - ($this->arg('bio')) ? $this->arg('bio') : $profile->bio, - _('Describe yourself and your interests in 140 chars')); - common_input('location', _('Location'), - ($this->arg('location')) ? $this->arg('location') : $profile->location, - _('Where you are, like "City, State (or Region), Country"')); - common_input('tags', _('Tags'), - ($this->arg('tags')) ? $this->arg('tags') : implode(' ', $user->getSelfTags()), - _('Tags for yourself (letters, numbers, -, ., and _), comma- or space- separated')); + common_element_start('form', array('method' => 'POST', + 'id' => 'profilesettings', + 'action' => common_local_url('profilesettings'))); + common_hidden('token', common_session_token()); + + # too much common patterns here... abstractable? + + common_input('nickname', _('Nickname'), + ($this->arg('nickname')) ? $this->arg('nickname') : $profile->nickname, + _('1-64 lowercase letters or numbers, no punctuation or spaces')); + common_input('fullname', _('Full name'), + ($this->arg('fullname')) ? $this->arg('fullname') : $profile->fullname); + common_input('homepage', _('Homepage'), + ($this->arg('homepage')) ? $this->arg('homepage') : $profile->homepage, + _('URL of your homepage, blog, or profile on another site')); + common_textarea('bio', _('Bio'), + ($this->arg('bio')) ? $this->arg('bio') : $profile->bio, + _('Describe yourself and your interests in 140 chars')); + common_input('location', _('Location'), + ($this->arg('location')) ? $this->arg('location') : $profile->location, + _('Where you are, like "City, State (or Region), Country"')); + common_input('tags', _('Tags'), + ($this->arg('tags')) ? $this->arg('tags') : implode(' ', $user->getSelfTags()), + _('Tags for yourself (letters, numbers, -, ., and _), comma- or space- separated')); $language = common_language(); common_dropdown('language', _('Language'), get_nice_language_list(), _('Preferred language'), true, $language); @@ -112,7 +115,6 @@ class ProfilesettingsAction extends SettingsAction common_element_end('form'); - } function show_avatar_form() @@ -137,24 +139,40 @@ class ProfilesettingsAction extends SettingsAction common_local_url('profilesettings'))); common_hidden('token', common_session_token()); - if ($original) { - common_element('img', array('src' => $original->url, - 'class' => 'avatar original', - 'width' => $original->width, - 'height' => $original->height, - 'alt' => $user->nickname)); - } + if ($original) { + common_element_start('div', array('id'=>'avatar_original', 'class'=>'avatar_view')); + common_element('h3', null, _("Original:")); + common_element_start('div', array('id'=>'avatar_original_view')); + common_element('img', array('src' => $original->url, + 'class' => 'avatar original', + 'width' => $original->width, + 'height' => $original->height, + 'alt' => $user->nickname)); + common_element_end('div'); + common_element_end('div'); + } $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' => $user->nickname)); - } + if ($avatar) { + common_element_start('div', array('id'=>'avatar_preview', 'class'=>'avatar_view')); + common_element('h3', null, _("Preview:")); + common_element_start('div', array('id'=>'avatar_preview_view')); + common_element('img', array('src' => $original->url,//$avatar->url, + 'class' => 'avatar profile', + 'width' => AVATAR_PROFILE_SIZE, + 'height' => AVATAR_PROFILE_SIZE, + 'alt' => $user->nickname)); + common_element_end('div'); + common_element_end('div'); + foreach(array('avatar_crop_x', 'avatar_crop_y', 'avatar_crop_w', 'avatar_crop_h') as $crop_info) { + common_element('input', array('name' => $crop_info, + 'type' => 'hidden', + 'id' => $crop_info)); + } + common_submit('crop', _('Crop')); + } common_element('input', array('name' => 'MAX_FILE_SIZE', 'type' => 'hidden', @@ -163,7 +181,6 @@ class ProfilesettingsAction extends SettingsAction common_element_start('p'); - common_element('input', array('name' => 'avatarfile', 'type' => 'file', 'id' => 'avatarfile')); @@ -390,8 +407,25 @@ class ProfilesettingsAction extends SettingsAction $this->show_form(_('Failed updating avatar.')); } - @unlink($_FILES['avatarfile']['tmp_name']); - } + @unlink($_FILES['avatarfile']['tmp_name']); + } + + function crop_avatar() { + + $user = common_current_user(); + $profile = $user->getProfile(); + + $x = $this->arg('avatar_crop_x'); + $y = $this->arg('avatar_crop_y'); + $w = $this->arg('avatar_crop_w'); + $h = $this->arg('avatar_crop_h'); + + if ($profile->crop_avatars($x, $y, $w, $h)) { + $this->show_form(_('Avatar updated.'), true); + } else { + $this->show_form(_('Failed updating avatar.')); + } + } function nickname_exists($nickname) { diff --git a/_darcs/pristine/classes/Avatar.php b/_darcs/pristine/classes/Avatar.php index 4fdb99d35e..9ae920647a 100644 --- a/_darcs/pristine/classes/Avatar.php +++ b/_darcs/pristine/classes/Avatar.php @@ -79,20 +79,63 @@ class Avatar extends Memcached_DataObject } } - function to_image() { - $filepath = common_avatar_path($this->filename); - if ($this->mediatype == 'image/gif') { - return imagecreatefromgif($filepath); - } else if ($this->mediatype == 'image/jpeg') { - return imagecreatefromjpeg($filepath); - } else if ($this->mediatype == 'image/png') { - return imagecreatefrompng($filepath); - } else { - return NULL; - } - } - - function &pkeyGet($kv) { - return Memcached_DataObject::pkeyGet('Avatar', $kv); - } + function scale_and_crop($size, $x, $y, $w, $h) + { + + $image_s = imagecreatetruecolor($size, $size); + $image_a = $this->to_image(); + + # Retain alpha channel info if possible for .pngs + $background = imagecolorallocate($image_s, 0, 0, 0); + ImageColorTransparent($image_s, $background); + imagealphablending($image_s, false); + + imagecopyresized($image_s, $image_a, 0, 0, $x, $y, $size, $size, $w, $h); + + $ext = ($this->mediattype == 'image/jpeg') ? ".jpeg" : ".png"; + + $filename = common_avatar_filename($this->profile_id, $ext, $size, common_timestamp()); + + if ($this->mediatype == 'image/jpeg') { + imagejpeg($image_s, common_avatar_path($filename)); + } else { + imagepng($image_s, common_avatar_path($filename)); + } + + $cropped = DB_DataObject::factory('avatar'); + $cropped->profile_id = $this->profile_id; + $cropped->width = $size; + $cropped->height = $size; + $cropped->original = false; + $cropped->mediatype = ($this->mediattype == 'image/jpeg') ? 'image/jpeg' : 'image/png'; + $cropped->filename = $filename; + $cropped->url = common_avatar_url($filename); + $cropped->created = DB_DataObject_Cast::dateTime(); # current time + + if ($cropped->insert()) { + return $cropped; + } else { + return NULL; + } + } + + function to_image() + { + $filepath = common_avatar_path($this->filename); + if ($this->mediatype == 'image/gif') { + return imagecreatefromgif($filepath); + } else if ($this->mediatype == 'image/jpeg') { + return imagecreatefromjpeg($filepath); + } else if ($this->mediatype == 'image/png') { + return imagecreatefrompng($filepath); + } else { + return NULL; + } + } + + function &pkeyGet($kv) + { + return Memcached_DataObject::pkeyGet('Avatar', $kv); + } + } diff --git a/_darcs/pristine/classes/Profile.php b/_darcs/pristine/classes/Profile.php index 3b6ac1d7f2..31bdf71d59 100644 --- a/_darcs/pristine/classes/Profile.php +++ b/_darcs/pristine/classes/Profile.php @@ -121,15 +121,39 @@ class Profile extends Memcached_DataObject return $avatar; } - function delete_avatars() { - $avatar = new Avatar(); - $avatar->profile_id = $this->id; - $avatar->find(); - while ($avatar->fetch()) { - $avatar->delete(); - } - return true; - } + function crop_avatars($x, $y, $w, $h) + { + + $avatar = $this->getOriginalAvatar(); + $this->delete_avatars(false); # don't delete original + + foreach (array(AVATAR_PROFILE_SIZE, AVATAR_STREAM_SIZE, AVATAR_MINI_SIZE) as $size) { + # We don't do a scaled one if original is our scaled size + if (!($avatar->width == $size && $avatar->height == $size)) { + $s = $avatar->scale_and_crop($size, $x, $y, $w, $h); + if (!$s) { + return NULL; + } + } + } + return true; + } + + function delete_avatars($original=true) + { + $avatar = new Avatar(); + $avatar->profile_id = $this->id; + $avatar->find(); + while ($avatar->fetch()) { + if ($avatar->original) { + if ($original == false) { + continue; + } + } + $avatar->delete(); + } + return true; + } function getBestName() { diff --git a/_darcs/pristine/lib/settingsaction.php b/_darcs/pristine/lib/settingsaction.php index 62de39d459..03bac3a93b 100644 --- a/_darcs/pristine/lib/settingsaction.php +++ b/_darcs/pristine/lib/settingsaction.php @@ -60,12 +60,25 @@ class SettingsAction extends Action } } - function form_header($title, $msg=NULL, $success=false) { - common_show_header($title, - NULL, - array($msg, $success), - array($this, 'show_top')); - } + function form_header($title, $msg=NULL, $success=false) + { + common_show_header($title, + array($this, 'show_header'), + array($msg, $success), + array($this, 'show_top')); + } + + function show_header() + { + common_element('link', array('rel' => 'stylesheet', + 'type' => 'text/css', + 'href' => common_path('js/jcrop/jquery.Jcrop.css?version='.LACONICA_VERSION), + 'media' => 'screen, projection, tv')); + common_element('script', array('type' => 'text/javascript', + 'src' => common_path('js/jcrop/jquery.Jcrop.pack.js'))); + common_element('script', array('type' => 'text/javascript', + 'src' => common_path('js/jcrop/jquery.Jcrop.go.js'))); + } function show_top($arr) { diff --git a/_darcs/tentative_inventory b/_darcs/tentative_inventory deleted file mode 100644 index da63f8216a..0000000000 --- a/_darcs/tentative_inventory +++ /dev/null @@ -1,127 +0,0 @@ -Starting with tag: -[TAG 0.6.4.1 -Evan Prodromou **20081220204906] -[identica badge by Kent Brewster. For more information see: -Sarven Capadisli **20081218003302 - http://kentbrewster.com/identica-badge/ - - Copy and paste the following wherever you want the badge to show up: - - - - Substitute your own ID in the user parameter. - -] -[more information in subscription notices -Evan Prodromou **20081212171135] -[first step of phpcs-cleanup of index.php -Evan Prodromou **20081221002332] -[reformatting for phpcs in lib/util.php -Evan Prodromou **20081221003016] -[some modifications to assuage phpcs -Evan Prodromou **20081221003955] -[reformat lib/daemon.php for phpcs -Evan Prodromou **20081221004607] -[reformat for phpcs -Evan Prodromou **20081221005837] -[reformat lib/jabber.php for phpcs, including doc comments -Evan Prodromou **20081222173249] -[reformat lib/language.php for PEAR Coding Standards -Evan Prodromou **20081222193029] -[bring mailbox.php into line with PEAR Coding Standards (mostly) -Evan Prodromou **20081222195041] -[bring messaging section (inbox, outbox, mailbox) into PEAR Code Standards compliance -Evan Prodromou **20081222201304 - - Actually refactored the method names on these classes to come into - complete compliance with the code standards. Untested; maybe there are - some bad method names now. - -] -[reformat and document lib/mail.php for phpcs conformance -Evan Prodromou **20081223173330] -[bring lib/noticelist.php into line with PEAR code standards -Evan Prodromou **20081223190851] -[incorrect label on notice list file comment -Evan Prodromou **20081223191430] -[replace all tabs with four spaces -Evan Prodromou **20081223191907 - - The PEAR coding standards decree: no tabs, but indent by four spaces. - I've done a global search-and-replace on all tabs, replacing them by - four spaces. This is a huge change, but it will go a long way to - getting us towards phpcs-compliance. And that means better code - readability, and that means more participation. - -] -[replace NULL with null -Evan Prodromou **20081223192129 - - Another global search-and-replace update. Here, I've replaced the PHP - keyword 'NULL' with its lowercase version. This is another PEAR code - standards change. - -] -[change function headers to K&R style -Evan Prodromou **20081223193323 - - Another huge change, for PEAR code standards compliance. Function - headers have to be in K&R style (opening brace on its own line), - instead of having the opening brace on the same line as the function - and parameters. So, a little perl magic found all the function - definitions and move the opening brace to the next line (properly - indented... usually). - -] -[TRUE => true, FALSE => false -Evan Prodromou **20081223194428 - - More PEAR coding standards global changes. Here, I've changed all - instances of TRUE to true and FALSE to false. - -] -[move opening brace of class declaration to next line -Evan Prodromou **20081223194923 - - Another gigantor PEAR coding standards patch. Here, I've moved the - opening curly bracket on a class statement to the following line. - -] -[Twitter-bridge: fix for Twitter's new strict policy of rejecting HTTP POSTs with invalid "expect" headers -Zach Copley **20081225152207] -[whitespace changes in actions/register.php after global search-and-replace -Evan Prodromou **20081223195722] -[Laconica-specific extensions for Twitter API -Evan Prodromou **20081230202019] -[add laconica-specific methods to htaccess.sample -Evan Prodromou **20081230202513] -[add laconica methods to unauthed ones -Evan Prodromou **20081230203747] -[implement api/laconica/version method -Evan Prodromou **20081230205939] -[add some breaks so that switch statement works -Evan Prodromou **20081230210114] -[implement the api/laconica/config method -Evan Prodromou **20081230211444] -[better serialization of arrays and booleans in config output -Evan Prodromou **20081230211957] -[wrapper element for config.xml -Evan Prodromou **20081230212202] -[trac750 Automatically update linked Facebook users' statuses -Zach Copley **20090105010407] -[trac750 configurable sync flags for Facebook app (noticesync, replysync) -Zach Copley **20090105040212] -[trac750 added some output to the facebook_update.php script -Zach Copley **20090105045603] -[Jcrop v2 (POST cropping to be completed) -csarven@controlyourself.ca**20081210021607] -[Jcrop CSS updates to original/preview views -csarven@controlyourself.ca**20081210025922] -[Actually crop your avatar when hitting 'crop' button on profile -Zach Copley **20081212043018] \ No newline at end of file diff --git a/_darcs/tentative_pristine b/_darcs/tentative_pristine index e69de29bb2..04a3f84cd7 100644 --- a/_darcs/tentative_pristine +++ b/_darcs/tentative_pristine @@ -0,0 +1,287 @@ +hunk ./actions/profilesettings.php 57 +- if ($this->arg('save')) { +- $this->save_profile(); +- } else if ($this->arg('upload')) { +- $this->upload_avatar(); +- } else if ($this->arg('changepass')) { +- $this->change_password(); +- } ++ if ($this->arg('save')) { ++ $this->save_profile(); ++ } else if ($this->arg('upload')) { ++ $this->upload_avatar(); ++ } else if ($this->arg('crop')) { ++ $this->crop_avatar(); ++ } else if ($this->arg('changepass')) { ++ $this->change_password(); ++ } else { ++ $this->show_form(_('Unexpected form submission.')); ++ } +hunk ./actions/profilesettings.php 77 +- common_element_start('form', array('method' => 'POST', +- 'id' => 'profilesettings', +- 'action' => +- common_local_url('profilesettings'))); +- common_hidden('token', common_session_token()); +- +- # too much common patterns here... abstractable? +- +- common_input('nickname', _('Nickname'), +- ($this->arg('nickname')) ? $this->arg('nickname') : $profile->nickname, +- _('1-64 lowercase letters or numbers, no punctuation or spaces')); +- common_input('fullname', _('Full name'), +- ($this->arg('fullname')) ? $this->arg('fullname') : $profile->fullname); +- common_input('homepage', _('Homepage'), +- ($this->arg('homepage')) ? $this->arg('homepage') : $profile->homepage, +- _('URL of your homepage, blog, or profile on another site')); +- common_textarea('bio', _('Bio'), +- ($this->arg('bio')) ? $this->arg('bio') : $profile->bio, +- _('Describe yourself and your interests in 140 chars')); +- common_input('location', _('Location'), +- ($this->arg('location')) ? $this->arg('location') : $profile->location, +- _('Where you are, like "City, State (or Region), Country"')); +- common_input('tags', _('Tags'), +- ($this->arg('tags')) ? $this->arg('tags') : implode(' ', $user->getSelfTags()), +- _('Tags for yourself (letters, numbers, -, ., and _), comma- or space- separated')); ++ common_element_start('form', array('method' => 'POST', ++ 'id' => 'profilesettings', ++ 'action' => common_local_url('profilesettings'))); ++ common_hidden('token', common_session_token()); ++ ++ # too much common patterns here... abstractable? ++ ++ common_input('nickname', _('Nickname'), ++ ($this->arg('nickname')) ? $this->arg('nickname') : $profile->nickname, ++ _('1-64 lowercase letters or numbers, no punctuation or spaces')); ++ common_input('fullname', _('Full name'), ++ ($this->arg('fullname')) ? $this->arg('fullname') : $profile->fullname); ++ common_input('homepage', _('Homepage'), ++ ($this->arg('homepage')) ? $this->arg('homepage') : $profile->homepage, ++ _('URL of your homepage, blog, or profile on another site')); ++ common_textarea('bio', _('Bio'), ++ ($this->arg('bio')) ? $this->arg('bio') : $profile->bio, ++ _('Describe yourself and your interests in 140 chars')); ++ common_input('location', _('Location'), ++ ($this->arg('location')) ? $this->arg('location') : $profile->location, ++ _('Where you are, like "City, State (or Region), Country"')); ++ common_input('tags', _('Tags'), ++ ($this->arg('tags')) ? $this->arg('tags') : implode(' ', $user->getSelfTags()), ++ _('Tags for yourself (letters, numbers, -, ., and _), comma- or space- separated')); +hunk ./actions/profilesettings.php 118 +- +hunk ./actions/profilesettings.php 142 +- if ($original) { +- common_element('img', array('src' => $original->url, +- 'class' => 'avatar original', +- 'width' => $original->width, +- 'height' => $original->height, +- 'alt' => $user->nickname)); +- } ++ if ($original) { ++ common_element_start('div', array('id'=>'avatar_original', 'class'=>'avatar_view')); ++ common_element('h3', null, _("Original:")); ++ common_element_start('div', array('id'=>'avatar_original_view')); ++ common_element('img', array('src' => $original->url, ++ 'class' => 'avatar original', ++ 'width' => $original->width, ++ 'height' => $original->height, ++ 'alt' => $user->nickname)); ++ common_element_end('div'); ++ common_element_end('div'); ++ } +hunk ./actions/profilesettings.php 157 +- if ($avatar) { +- common_element('img', array('src' => $avatar->url, +- 'class' => 'avatar profile', +- 'width' => AVATAR_PROFILE_SIZE, +- 'height' => AVATAR_PROFILE_SIZE, +- 'alt' => $user->nickname)); +- } ++ if ($avatar) { ++ common_element_start('div', array('id'=>'avatar_preview', 'class'=>'avatar_view')); ++ common_element('h3', null, _("Preview:")); ++ common_element_start('div', array('id'=>'avatar_preview_view')); ++ common_element('img', array('src' => $original->url,//$avatar->url, ++ 'class' => 'avatar profile', ++ 'width' => AVATAR_PROFILE_SIZE, ++ 'height' => AVATAR_PROFILE_SIZE, ++ 'alt' => $user->nickname)); ++ common_element_end('div'); ++ common_element_end('div'); +hunk ./actions/profilesettings.php 169 ++ foreach(array('avatar_crop_x', 'avatar_crop_y', 'avatar_crop_w', 'avatar_crop_h') as $crop_info) { ++ common_element('input', array('name' => $crop_info, ++ 'type' => 'hidden', ++ 'id' => $crop_info)); ++ } ++ common_submit('crop', _('Crop')); ++ } +hunk ./actions/profilesettings.php 184 +- +hunk ./actions/profilesettings.php 410 +- @unlink($_FILES['avatarfile']['tmp_name']); +- } ++ @unlink($_FILES['avatarfile']['tmp_name']); ++ } ++ ++ function crop_avatar() { ++ ++ $user = common_current_user(); ++ $profile = $user->getProfile(); ++ ++ $x = $this->arg('avatar_crop_x'); ++ $y = $this->arg('avatar_crop_y'); ++ $w = $this->arg('avatar_crop_w'); ++ $h = $this->arg('avatar_crop_h'); ++ ++ if ($profile->crop_avatars($x, $y, $w, $h)) { ++ $this->show_form(_('Avatar updated.'), true); ++ } else { ++ $this->show_form(_('Failed updating avatar.')); ++ } ++ } +hunk ./classes/Avatar.php 82 +- function to_image() { +- $filepath = common_avatar_path($this->filename); +- if ($this->mediatype == 'image/gif') { +- return imagecreatefromgif($filepath); +- } else if ($this->mediatype == 'image/jpeg') { +- return imagecreatefromjpeg($filepath); +- } else if ($this->mediatype == 'image/png') { +- return imagecreatefrompng($filepath); +- } else { +- return NULL; +- } +- } +- +- function &pkeyGet($kv) { +- return Memcached_DataObject::pkeyGet('Avatar', $kv); +- } ++ function scale_and_crop($size, $x, $y, $w, $h) ++ { ++ ++ $image_s = imagecreatetruecolor($size, $size); ++ $image_a = $this->to_image(); ++ ++ # Retain alpha channel info if possible for .pngs ++ $background = imagecolorallocate($image_s, 0, 0, 0); ++ ImageColorTransparent($image_s, $background); ++ imagealphablending($image_s, false); ++ ++ imagecopyresized($image_s, $image_a, 0, 0, $x, $y, $size, $size, $w, $h); ++ ++ $ext = ($this->mediattype == 'image/jpeg') ? ".jpeg" : ".png"; ++ ++ $filename = common_avatar_filename($this->profile_id, $ext, $size, common_timestamp()); ++ ++ if ($this->mediatype == 'image/jpeg') { ++ imagejpeg($image_s, common_avatar_path($filename)); ++ } else { ++ imagepng($image_s, common_avatar_path($filename)); ++ } ++ ++ $cropped = DB_DataObject::factory('avatar'); ++ $cropped->profile_id = $this->profile_id; ++ $cropped->width = $size; ++ $cropped->height = $size; ++ $cropped->original = false; ++ $cropped->mediatype = ($this->mediattype == 'image/jpeg') ? 'image/jpeg' : 'image/png'; ++ $cropped->filename = $filename; ++ $cropped->url = common_avatar_url($filename); ++ $cropped->created = DB_DataObject_Cast::dateTime(); # current time ++ ++ if ($cropped->insert()) { ++ return $cropped; ++ } else { ++ return NULL; ++ } ++ } ++ ++ function to_image() ++ { ++ $filepath = common_avatar_path($this->filename); ++ if ($this->mediatype == 'image/gif') { ++ return imagecreatefromgif($filepath); ++ } else if ($this->mediatype == 'image/jpeg') { ++ return imagecreatefromjpeg($filepath); ++ } else if ($this->mediatype == 'image/png') { ++ return imagecreatefrompng($filepath); ++ } else { ++ return NULL; ++ } ++ } ++ ++ function &pkeyGet($kv) ++ { ++ return Memcached_DataObject::pkeyGet('Avatar', $kv); ++ } ++ +hunk ./classes/Profile.php 124 +- function delete_avatars() { +- $avatar = new Avatar(); +- $avatar->profile_id = $this->id; +- $avatar->find(); +- while ($avatar->fetch()) { +- $avatar->delete(); +- } +- return true; +- } ++ function crop_avatars($x, $y, $w, $h) ++ { ++ ++ $avatar = $this->getOriginalAvatar(); ++ $this->delete_avatars(false); # don't delete original ++ ++ foreach (array(AVATAR_PROFILE_SIZE, AVATAR_STREAM_SIZE, AVATAR_MINI_SIZE) as $size) { ++ # We don't do a scaled one if original is our scaled size ++ if (!($avatar->width == $size && $avatar->height == $size)) { ++ $s = $avatar->scale_and_crop($size, $x, $y, $w, $h); ++ if (!$s) { ++ return NULL; ++ } ++ } ++ } ++ return true; ++ } ++ ++ function delete_avatars($original=true) ++ { ++ $avatar = new Avatar(); ++ $avatar->profile_id = $this->id; ++ $avatar->find(); ++ while ($avatar->fetch()) { ++ if ($avatar->original) { ++ if ($original == false) { ++ continue; ++ } ++ } ++ $avatar->delete(); ++ } ++ return true; ++ } +hunk ./lib/settingsaction.php 63 +- function form_header($title, $msg=NULL, $success=false) { +- common_show_header($title, +- NULL, +- array($msg, $success), +- array($this, 'show_top')); +- } ++ function form_header($title, $msg=NULL, $success=false) ++ { ++ common_show_header($title, ++ array($this, 'show_header'), ++ array($msg, $success), ++ array($this, 'show_top')); ++ } ++ ++ function show_header() ++ { ++ common_element('link', array('rel' => 'stylesheet', ++ 'type' => 'text/css', ++ 'href' => common_path('js/jcrop/jquery.Jcrop.css?version='.LACONICA_VERSION), ++ 'media' => 'screen, projection, tv')); ++ common_element('script', array('type' => 'text/javascript', ++ 'src' => common_path('js/jcrop/jquery.Jcrop.pack.js'))); ++ common_element('script', array('type' => 'text/javascript', ++ 'src' => common_path('js/jcrop/jquery.Jcrop.go.js'))); ++ } diff --git a/actions/profilesettings.php b/actions/profilesettings.php index caec2f93d7..d861919b92 100644 --- a/actions/profilesettings.php +++ b/actions/profilesettings.php @@ -54,13 +54,17 @@ class ProfilesettingsAction extends SettingsAction return; } - if ($this->arg('save')) { - $this->save_profile(); - } else if ($this->arg('upload')) { - $this->upload_avatar(); - } else if ($this->arg('changepass')) { - $this->change_password(); - } + if ($this->arg('save')) { + $this->save_profile(); + } else if ($this->arg('upload')) { + $this->upload_avatar(); + } else if ($this->arg('crop')) { + $this->crop_avatar(); + } else if ($this->arg('changepass')) { + $this->change_password(); + } else { + $this->show_form(_('Unexpected form submission.')); + } } @@ -70,31 +74,30 @@ class ProfilesettingsAction extends SettingsAction $user = common_current_user(); $profile = $user->getProfile(); - common_element_start('form', array('method' => 'POST', - 'id' => 'profilesettings', - 'action' => - common_local_url('profilesettings'))); - common_hidden('token', common_session_token()); - - # too much common patterns here... abstractable? - - common_input('nickname', _('Nickname'), - ($this->arg('nickname')) ? $this->arg('nickname') : $profile->nickname, - _('1-64 lowercase letters or numbers, no punctuation or spaces')); - common_input('fullname', _('Full name'), - ($this->arg('fullname')) ? $this->arg('fullname') : $profile->fullname); - common_input('homepage', _('Homepage'), - ($this->arg('homepage')) ? $this->arg('homepage') : $profile->homepage, - _('URL of your homepage, blog, or profile on another site')); - common_textarea('bio', _('Bio'), - ($this->arg('bio')) ? $this->arg('bio') : $profile->bio, - _('Describe yourself and your interests in 140 chars')); - common_input('location', _('Location'), - ($this->arg('location')) ? $this->arg('location') : $profile->location, - _('Where you are, like "City, State (or Region), Country"')); - common_input('tags', _('Tags'), - ($this->arg('tags')) ? $this->arg('tags') : implode(' ', $user->getSelfTags()), - _('Tags for yourself (letters, numbers, -, ., and _), comma- or space- separated')); + common_element_start('form', array('method' => 'POST', + 'id' => 'profilesettings', + 'action' => common_local_url('profilesettings'))); + common_hidden('token', common_session_token()); + + # too much common patterns here... abstractable? + + common_input('nickname', _('Nickname'), + ($this->arg('nickname')) ? $this->arg('nickname') : $profile->nickname, + _('1-64 lowercase letters or numbers, no punctuation or spaces')); + common_input('fullname', _('Full name'), + ($this->arg('fullname')) ? $this->arg('fullname') : $profile->fullname); + common_input('homepage', _('Homepage'), + ($this->arg('homepage')) ? $this->arg('homepage') : $profile->homepage, + _('URL of your homepage, blog, or profile on another site')); + common_textarea('bio', _('Bio'), + ($this->arg('bio')) ? $this->arg('bio') : $profile->bio, + _('Describe yourself and your interests in 140 chars')); + common_input('location', _('Location'), + ($this->arg('location')) ? $this->arg('location') : $profile->location, + _('Where you are, like "City, State (or Region), Country"')); + common_input('tags', _('Tags'), + ($this->arg('tags')) ? $this->arg('tags') : implode(' ', $user->getSelfTags()), + _('Tags for yourself (letters, numbers, -, ., and _), comma- or space- separated')); $language = common_language(); common_dropdown('language', _('Language'), get_nice_language_list(), _('Preferred language'), true, $language); @@ -112,7 +115,6 @@ class ProfilesettingsAction extends SettingsAction common_element_end('form'); - } function show_avatar_form() @@ -137,24 +139,40 @@ class ProfilesettingsAction extends SettingsAction common_local_url('profilesettings'))); common_hidden('token', common_session_token()); - if ($original) { - common_element('img', array('src' => $original->url, - 'class' => 'avatar original', - 'width' => $original->width, - 'height' => $original->height, - 'alt' => $user->nickname)); - } + if ($original) { + common_element_start('div', array('id'=>'avatar_original', 'class'=>'avatar_view')); + common_element('h3', null, _("Original:")); + common_element_start('div', array('id'=>'avatar_original_view')); + common_element('img', array('src' => $original->url, + 'class' => 'avatar original', + 'width' => $original->width, + 'height' => $original->height, + 'alt' => $user->nickname)); + common_element_end('div'); + common_element_end('div'); + } $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' => $user->nickname)); - } + if ($avatar) { + common_element_start('div', array('id'=>'avatar_preview', 'class'=>'avatar_view')); + common_element('h3', null, _("Preview:")); + common_element_start('div', array('id'=>'avatar_preview_view')); + common_element('img', array('src' => $original->url,//$avatar->url, + 'class' => 'avatar profile', + 'width' => AVATAR_PROFILE_SIZE, + 'height' => AVATAR_PROFILE_SIZE, + 'alt' => $user->nickname)); + common_element_end('div'); + common_element_end('div'); + foreach(array('avatar_crop_x', 'avatar_crop_y', 'avatar_crop_w', 'avatar_crop_h') as $crop_info) { + common_element('input', array('name' => $crop_info, + 'type' => 'hidden', + 'id' => $crop_info)); + } + common_submit('crop', _('Crop')); + } common_element('input', array('name' => 'MAX_FILE_SIZE', 'type' => 'hidden', @@ -163,7 +181,6 @@ class ProfilesettingsAction extends SettingsAction common_element_start('p'); - common_element('input', array('name' => 'avatarfile', 'type' => 'file', 'id' => 'avatarfile')); @@ -390,8 +407,25 @@ class ProfilesettingsAction extends SettingsAction $this->show_form(_('Failed updating avatar.')); } - @unlink($_FILES['avatarfile']['tmp_name']); - } + @unlink($_FILES['avatarfile']['tmp_name']); + } + + function crop_avatar() { + + $user = common_current_user(); + $profile = $user->getProfile(); + + $x = $this->arg('avatar_crop_x'); + $y = $this->arg('avatar_crop_y'); + $w = $this->arg('avatar_crop_w'); + $h = $this->arg('avatar_crop_h'); + + if ($profile->crop_avatars($x, $y, $w, $h)) { + $this->show_form(_('Avatar updated.'), true); + } else { + $this->show_form(_('Failed updating avatar.')); + } + } function nickname_exists($nickname) { diff --git a/classes/Avatar.php b/classes/Avatar.php index 4fdb99d35e..9ae920647a 100644 --- a/classes/Avatar.php +++ b/classes/Avatar.php @@ -79,20 +79,63 @@ class Avatar extends Memcached_DataObject } } - function to_image() { - $filepath = common_avatar_path($this->filename); - if ($this->mediatype == 'image/gif') { - return imagecreatefromgif($filepath); - } else if ($this->mediatype == 'image/jpeg') { - return imagecreatefromjpeg($filepath); - } else if ($this->mediatype == 'image/png') { - return imagecreatefrompng($filepath); - } else { - return NULL; - } - } - - function &pkeyGet($kv) { - return Memcached_DataObject::pkeyGet('Avatar', $kv); - } + function scale_and_crop($size, $x, $y, $w, $h) + { + + $image_s = imagecreatetruecolor($size, $size); + $image_a = $this->to_image(); + + # Retain alpha channel info if possible for .pngs + $background = imagecolorallocate($image_s, 0, 0, 0); + ImageColorTransparent($image_s, $background); + imagealphablending($image_s, false); + + imagecopyresized($image_s, $image_a, 0, 0, $x, $y, $size, $size, $w, $h); + + $ext = ($this->mediattype == 'image/jpeg') ? ".jpeg" : ".png"; + + $filename = common_avatar_filename($this->profile_id, $ext, $size, common_timestamp()); + + if ($this->mediatype == 'image/jpeg') { + imagejpeg($image_s, common_avatar_path($filename)); + } else { + imagepng($image_s, common_avatar_path($filename)); + } + + $cropped = DB_DataObject::factory('avatar'); + $cropped->profile_id = $this->profile_id; + $cropped->width = $size; + $cropped->height = $size; + $cropped->original = false; + $cropped->mediatype = ($this->mediattype == 'image/jpeg') ? 'image/jpeg' : 'image/png'; + $cropped->filename = $filename; + $cropped->url = common_avatar_url($filename); + $cropped->created = DB_DataObject_Cast::dateTime(); # current time + + if ($cropped->insert()) { + return $cropped; + } else { + return NULL; + } + } + + function to_image() + { + $filepath = common_avatar_path($this->filename); + if ($this->mediatype == 'image/gif') { + return imagecreatefromgif($filepath); + } else if ($this->mediatype == 'image/jpeg') { + return imagecreatefromjpeg($filepath); + } else if ($this->mediatype == 'image/png') { + return imagecreatefrompng($filepath); + } else { + return NULL; + } + } + + function &pkeyGet($kv) + { + return Memcached_DataObject::pkeyGet('Avatar', $kv); + } + } diff --git a/classes/Profile.php b/classes/Profile.php index 3b6ac1d7f2..31bdf71d59 100644 --- a/classes/Profile.php +++ b/classes/Profile.php @@ -121,15 +121,39 @@ class Profile extends Memcached_DataObject return $avatar; } - function delete_avatars() { - $avatar = new Avatar(); - $avatar->profile_id = $this->id; - $avatar->find(); - while ($avatar->fetch()) { - $avatar->delete(); - } - return true; - } + function crop_avatars($x, $y, $w, $h) + { + + $avatar = $this->getOriginalAvatar(); + $this->delete_avatars(false); # don't delete original + + foreach (array(AVATAR_PROFILE_SIZE, AVATAR_STREAM_SIZE, AVATAR_MINI_SIZE) as $size) { + # We don't do a scaled one if original is our scaled size + if (!($avatar->width == $size && $avatar->height == $size)) { + $s = $avatar->scale_and_crop($size, $x, $y, $w, $h); + if (!$s) { + return NULL; + } + } + } + return true; + } + + function delete_avatars($original=true) + { + $avatar = new Avatar(); + $avatar->profile_id = $this->id; + $avatar->find(); + while ($avatar->fetch()) { + if ($avatar->original) { + if ($original == false) { + continue; + } + } + $avatar->delete(); + } + return true; + } function getBestName() { diff --git a/lib/settingsaction.php b/lib/settingsaction.php index 62de39d459..03bac3a93b 100644 --- a/lib/settingsaction.php +++ b/lib/settingsaction.php @@ -60,12 +60,25 @@ class SettingsAction extends Action } } - function form_header($title, $msg=NULL, $success=false) { - common_show_header($title, - NULL, - array($msg, $success), - array($this, 'show_top')); - } + function form_header($title, $msg=NULL, $success=false) + { + common_show_header($title, + array($this, 'show_header'), + array($msg, $success), + array($this, 'show_top')); + } + + function show_header() + { + common_element('link', array('rel' => 'stylesheet', + 'type' => 'text/css', + 'href' => common_path('js/jcrop/jquery.Jcrop.css?version='.LACONICA_VERSION), + 'media' => 'screen, projection, tv')); + common_element('script', array('type' => 'text/javascript', + 'src' => common_path('js/jcrop/jquery.Jcrop.pack.js'))); + common_element('script', array('type' => 'text/javascript', + 'src' => common_path('js/jcrop/jquery.Jcrop.go.js'))); + } function show_top($arr) {