From 784c9718eba0b4844a7b8bba587e295cf3e15032 Mon Sep 17 00:00:00 2001 From: Thomas Keller Date: Sun, 5 Dec 2010 01:22:32 +0100 Subject: [PATCH] Implemented an extended user profile based on a patch from Jethro Carr (issue 510). Changes with respect to the original patch: - use Gconf instead of separate table / data scheme - better form validation for URLs and emails - no htmlentity-encoded contents in the database (pluf automatically safe-encodes stuff before it writes out contents into templates) - add visual separators in the form views to have a distinct view of basic (important) data and other data which are only displayed in the public profile - give a hint about the maximum display size of 60x60 px^2 and use max-width and max-height in the templates to avoid nasty distortions by the browser - use target=_blank and rel=nofollow on the twitter and website links in the profile - some whitespace / formatting / code style fixes --- src/IDF/Form/Admin/UserUpdate.php | 114 +++++++++++++++++- src/IDF/Form/UserAccount.php | 108 ++++++++++++++++- src/IDF/Gconf.php | 6 +- src/IDF/IssueComment.php | 21 ++-- src/IDF/Review/Comment.php | 25 ++-- src/IDF/UserData.php | 59 +++++++++ src/IDF/Views.php | 5 +- src/IDF/Views/Admin.php | 8 +- src/IDF/Views/Issue.php | 3 +- src/IDF/Views/Review.php | 13 +- src/IDF/Views/User.php | 34 ++++-- src/IDF/relations.php | 2 + .../templates/idf/gadmin/users/update.html | 47 +++++++- src/IDF/templates/idf/issues/view.html | 11 +- src/IDF/templates/idf/review/view.html | 21 ++-- src/IDF/templates/idf/user/myaccount.html | 42 ++++++- src/IDF/templates/idf/user/public.html | 32 ++++- www/media/idf/css/style.css | 5 + 18 files changed, 490 insertions(+), 66 deletions(-) create mode 100644 src/IDF/UserData.php diff --git a/src/IDF/Form/Admin/UserUpdate.php b/src/IDF/Form/Admin/UserUpdate.php index 6859859..d9111ae 100644 --- a/src/IDF/Form/Admin/UserUpdate.php +++ b/src/IDF/Form/Admin/UserUpdate.php @@ -24,13 +24,15 @@ /** * Update user's details. */ -class IDF_Form_Admin_UserUpdate extends Pluf_Form +class IDF_Form_Admin_UserUpdate extends Pluf_Form { public $user = null; public function initFields($extra=array()) { $this->user = $extra['user']; + $user_data = IDF_UserData::factory($this->user); + $this->fields['first_name'] = new Pluf_Form_Field_Varchar( array('required' => false, 'label' => __('First name'), @@ -66,7 +68,7 @@ class IDF_Form_Admin_UserUpdate extends Pluf_Form 'initial' => $this->user->language, 'widget' => 'Pluf_Form_Widget_SelectInput', 'widget_attrs' => array( - 'choices' => + 'choices' => Pluf_L10n::getInstalledLanguages() ), )); @@ -93,6 +95,66 @@ class IDF_Form_Admin_UserUpdate extends Pluf_Form ), )); + $this->fields['description'] = new Pluf_Form_Field_Varchar( + array('required' => false, + 'label' => __('Description'), + 'initial' => $user_data->description, + 'widget_attrs' => array('rows' => 3, + 'cols' => 40), + 'widget' => 'Pluf_Form_Widget_TextareaInput', + )); + + $this->fields['twitter'] = new Pluf_Form_Field_Varchar( + array('required' => false, + 'label' => __('Twitter username'), + 'initial' => $user_data->twitter, + 'widget_attrs' => array( + 'maxlength' => 50, + 'size' => 15, + ), + )); + + $this->fields['public_email'] = new Pluf_Form_Field_Email( + array('required' => false, + 'label' => __('Public email address'), + 'initial' => $user_data->public_email, + 'widget_attrs' => array( + 'maxlength' => 50, + 'size' => 15, + ), + )); + + $this->fields['website'] = new Pluf_Form_Field_Url( + array('required' => false, + 'label' => __('Website URL'), + 'initial' => $user_data->website, + 'widget_attrs' => array( + 'maxlength' => 50, + 'size' => 15, + ), + )); + + $this->fields['custom_avatar'] = new Pluf_Form_Field_File( + array('required' => false, + 'label' => __('Upload custom avatar'), + 'initial' => '', + 'max_size' => Pluf::f('max_upload_size', 2097152), + 'move_function_params' => array('upload_path' => Pluf::f('upload_path').'/avatars', + 'upload_path_create' => true, + 'upload_overwrite' => true, + 'file_name' => 'user_'.$this->user->id.'_%s'), + 'help_text' => __('An image file with a width and height not larger than 60 pixels (bigger images are scaled down).'), + )); + + $this->fields['remove_custom_avatar'] = new Pluf_Form_Field_Boolean( + array('required' => false, + 'label' => __('Remove custom avatar'), + 'initial' => false, + 'widget' => 'Pluf_Form_Widget_CheckboxInput', + 'widget_attrs' => array(), + 'help_text' => __('Tick this to delete the custom avatar.'), + )); + if ($extra['request']->user->administrator) { $this->fields['staff'] = new Pluf_Form_Field_Boolean( array('required' => false, @@ -136,8 +198,37 @@ class IDF_Form_Admin_UserUpdate extends Pluf_Form $update_pass = true; } $this->user->setFromFormData($this->cleaned_data); + if ($commit) { $this->user->update(); + + // FIXME: go the extra mile and check the input lengths for + // all fields here! + // FIXME: this is all doubled in UserAccount! + + $user_data = IDF_UserData::factory($this->user); + + // Add or remove avatar - we need to do this here because every + // single setter directly leads to a save in the database + if ($user_data->avatar != '' && + ($this->cleaned_data['remove_custom_avatar'] == 1 || + $this->cleaned_data['custom_avatar'] != '')) { + $avatar_path = Pluf::f('upload_path').'/avatars/'.basename($user_data->avatar); + if (basename($avatar_path) != '' && is_file($avatar_path)) { + unlink($avatar_path); + } + $user_data->avatar = ''; + } + + if ($this->cleaned_data['custom_avatar'] != '') { + $user_data->avatar = $this->cleaned_data['custom_avatar']; + } + + $user_data->description = $this->cleaned_data['description']; + $user_data->twitter = $this->cleaned_data['twitter']; + $user_data->public_email = $this->cleaned_data['public_email']; + $user_data->website = $this->cleaned_data['website']; + if ($update_pass) { /** * [signal] @@ -170,7 +261,7 @@ class IDF_Form_Admin_UserUpdate extends Pluf_Form { $last_name = trim($this->cleaned_data['last_name']); if ($last_name == mb_strtoupper($last_name)) { - return mb_convert_case(mb_strtolower($last_name), + return mb_convert_case(mb_strtolower($last_name), MB_CASE_TITLE, 'UTF-8'); } return $last_name; @@ -183,7 +274,7 @@ class IDF_Form_Admin_UserUpdate extends Pluf_Form throw new Pluf_Form_Invalid(__('--- is not a valid first name.')); } if ($first_name == mb_strtoupper($first_name)) { - $first_name = mb_convert_case(mb_strtolower($first_name), + $first_name = mb_convert_case(mb_strtolower($first_name), MB_CASE_TITLE, 'UTF-8'); } return $first_name; @@ -201,12 +292,23 @@ class IDF_Form_Admin_UserUpdate extends Pluf_Form return $email; } + function clean_custom_avatar() + { + // Just png, jpeg/jpg or gif + if (!preg_match('/\.(png|jpg|jpeg|gif)$/i', $this->cleaned_data['custom_avatar']) && + $this->cleaned_data['custom_avatar'] != '') { + @unlink(Pluf::f('upload_path').'/avatars/'.$this->cleaned_data['custom_avatar']); + throw new Pluf_Form_Invalid(__('For security reason, you cannot upload a file with this extension.')); + } + return $this->cleaned_data['custom_avatar']; + } + /** - * Check to see if the 2 passwords are the same. + * Check to see if the two passwords are the same. */ public function clean() { - if (!isset($this->errors['password']) + if (!isset($this->errors['password']) && !isset($this->errors['password2'])) { $password1 = $this->cleaned_data['password']; $password2 = $this->cleaned_data['password2']; diff --git a/src/IDF/Form/UserAccount.php b/src/IDF/Form/UserAccount.php index 5af3185..7b6a5cb 100644 --- a/src/IDF/Form/UserAccount.php +++ b/src/IDF/Form/UserAccount.php @@ -33,6 +33,8 @@ class IDF_Form_UserAccount extends Pluf_Form public function initFields($extra=array()) { $this->user = $extra['user']; + $user_data = IDF_UserData::factory($this->user); + $this->fields['first_name'] = new Pluf_Form_Field_Varchar( array('required' => false, 'label' => __('First name'), @@ -92,6 +94,66 @@ class IDF_Form_UserAccount extends Pluf_Form ), )); + $this->fields['description'] = new Pluf_Form_Field_Varchar( + array('required' => false, + 'label' => __('Description'), + 'initial' => $user_data->description, + 'widget_attrs' => array('rows' => 3, + 'cols' => 40), + 'widget' => 'Pluf_Form_Widget_TextareaInput', + )); + + $this->fields['twitter'] = new Pluf_Form_Field_Varchar( + array('required' => false, + 'label' => __('Twitter username'), + 'initial' => $user_data->twitter, + 'widget_attrs' => array( + 'maxlength' => 50, + 'size' => 15, + ), + )); + + $this->fields['public_email'] = new Pluf_Form_Field_Email( + array('required' => false, + 'label' => __('Public email address'), + 'initial' => $user_data->public_email, + 'widget_attrs' => array( + 'maxlength' => 50, + 'size' => 15, + ), + )); + + $this->fields['website'] = new Pluf_Form_Field_Url( + array('required' => false, + 'label' => __('Website URL'), + 'initial' => $user_data->website, + 'widget_attrs' => array( + 'maxlength' => 50, + 'size' => 15, + ), + )); + + $this->fields['custom_avatar'] = new Pluf_Form_Field_File( + array('required' => false, + 'label' => __('Upload custom avatar'), + 'initial' => '', + 'max_size' => Pluf::f('max_upload_size', 2097152), + 'move_function_params' => array('upload_path' => Pluf::f('upload_path').'/avatars', + 'upload_path_create' => true, + 'upload_overwrite' => true, + 'file_name' => 'user_'.$this->user->id.'_%s'), + 'help_text' => __('An image file with a width and height not larger than 60 pixels (bigger images are scaled down).'), + )); + + $this->fields['remove_custom_avatar'] = new Pluf_Form_Field_Boolean( + array('required' => false, + 'label' => __('Remove custom avatar'), + 'initial' => false, + 'widget' => 'Pluf_Form_Widget_CheckboxInput', + 'widget_attrs' => array(), + 'help_text' => __('Tick this to delete the custom avatar.'), + )); + $this->fields['public_key'] = new Pluf_Form_Field_Varchar( array('required' => false, 'label' => __('Add a public key'), @@ -138,7 +200,7 @@ class IDF_Form_UserAccount extends Pluf_Form 'email' => $new_email, 'user'=> $this->user, ) - ); + ); $tmpl = new Pluf_Template('idf/user/changeemail-email.txt'); $text_email = $tmpl->render($context); $email = new Pluf_Mail(Pluf::f('from_email'), $new_email, @@ -157,8 +219,37 @@ class IDF_Form_UserAccount extends Pluf_Form $key->create(); } } + if ($commit) { $this->user->update(); + + // FIXME: go the extra mile and check the input lengths for + // all fields here! + // FIXME: this is all doubled in admin/UserUpdate! + + $user_data = IDF_UserData::factory($this->user); + + // Add or remove avatar - we need to do this here because every + // single setter directly leads to a save in the database + if ($user_data->avatar != '' && + ($this->cleaned_data['remove_custom_avatar'] == 1 || + $this->cleaned_data['custom_avatar'] != '')) { + $avatar_path = Pluf::f('upload_path').'/avatars/'.basename($user_data->avatar); + if (basename($avatar_path) != '' && is_file($avatar_path)) { + unlink($avatar_path); + } + $user_data->avatar = ''; + } + + if ($this->cleaned_data['custom_avatar'] != '') { + $user_data->avatar = $this->cleaned_data['custom_avatar']; + } + + $user_data->description = $this->cleaned_data['description']; + $user_data->twitter = $this->cleaned_data['twitter']; + $user_data->public_email = $this->cleaned_data['public_email']; + $user_data->website = $this->cleaned_data['website']; + if ($update_pass) { /** * [signal] @@ -266,6 +357,19 @@ class IDF_Form_UserAccount extends Pluf_Form return $key; } + + function clean_custom_avatar() + { + // Just png, jpeg/jpg or gif + if (!preg_match('/\.(png|jpg|jpeg|gif)$/i', $this->cleaned_data['custom_avatar']) && + $this->cleaned_data['custom_avatar'] != '') { + @unlink(Pluf::f('upload_path').'/avatars/'.$this->cleaned_data['custom_avatar']); + throw new Pluf_Form_Invalid(__('For security reason, you cannot upload a file with this extension.')); + } + return $this->cleaned_data['custom_avatar']; + } + + function clean_last_name() { $last_name = trim($this->cleaned_data['last_name']); @@ -322,4 +426,6 @@ class IDF_Form_UserAccount extends Pluf_Form return $this->cleaned_data; } + + } diff --git a/src/IDF/Gconf.php b/src/IDF/Gconf.php index 4bbec62..ad09ef8 100644 --- a/src/IDF/Gconf.php +++ b/src/IDF/Gconf.php @@ -46,7 +46,7 @@ class IDF_Gconf extends Pluf_Model array( 'type' => 'Pluf_DB_Field_Sequence', //It is automatically added. - 'blank' => true, + 'blank' => true, ), 'model_class' => array( @@ -108,7 +108,7 @@ class IDF_Gconf extends Pluf_Model */ function setVal($key, $value) { - if (!is_null($this->getVal($key, null)) + if (!is_null($this->getVal($key, null)) and $value == $this->getVal($key)) { return; } @@ -121,7 +121,7 @@ class IDF_Gconf extends Pluf_Model $this->datacache[$key] = $value; return; } - } + } // we insert $conf = new IDF_Gconf(); $conf->model_class = $this->_mod->_model; diff --git a/src/IDF/IssueComment.php b/src/IDF/IssueComment.php index 110f3d8..084ecf3 100644 --- a/src/IDF/IssueComment.php +++ b/src/IDF/IssueComment.php @@ -41,9 +41,9 @@ class IDF_IssueComment extends Pluf_Model 'id' => array( 'type' => 'Pluf_DB_Field_Sequence', - 'blank' => true, + 'blank' => true, ), - 'issue' => + 'issue' => array( 'type' => 'Pluf_DB_Field_Foreignkey', 'model' => 'IDF_Issue', @@ -57,7 +57,7 @@ class IDF_IssueComment extends Pluf_Model 'blank' => false, 'verbose' => __('comment'), ), - 'submitter' => + 'submitter' => array( 'type' => 'Pluf_DB_Field_Foreignkey', 'model' => 'Pluf_User', @@ -79,7 +79,7 @@ class IDF_IssueComment extends Pluf_Model 'verbose' => __('creation date'), ), ); - $this->_a['idx'] = array( + $this->_a['idx'] = array( 'creation_dtime_idx' => array( 'col' => 'creation_dtime', @@ -119,7 +119,7 @@ class IDF_IssueComment extends Pluf_Model $sql = new Pluf_SQL('issue=%s', array($this->issue)); $co = Pluf::factory('IDF_IssueComment')->getList(array('filter'=>$sql->gen())); if ($co->count() > 1) { - IDF_Timeline::insert($this, $this->get_issue()->get_project(), + IDF_Timeline::insert($this, $this->get_issue()->get_project(), $this->get_submitter()); } } @@ -129,7 +129,7 @@ class IDF_IssueComment extends Pluf_Model public function timelineFragment($request) { $issue = $this->get_issue(); - $url = Pluf_HTTP_URL_urlForView('IDF_Views_Issue::view', + $url = Pluf_HTTP_URL_urlForView('IDF_Views_Issue::view', array($request->project->shortname, $issue->id)); $url .= '#ic'.$this->id; @@ -168,7 +168,7 @@ class IDF_IssueComment extends Pluf_Model } $out .= ''; $out .= "\n".' -
'.sprintf(__('Comment on issue %d, by %s'), $url, $ic, $issue->id, $user).'
'; +
'.sprintf(__('Comment on issue %d, by %s'), $url, $ic, $issue->id, $user).'
'; return Pluf_Template::markSafe($out); } @@ -176,7 +176,7 @@ class IDF_IssueComment extends Pluf_Model { $issue = $this->get_issue(); $url = Pluf::f('url_base') - .Pluf_HTTP_URL_urlForView('IDF_Views_Issue::view', + .Pluf_HTTP_URL_urlForView('IDF_Views_Issue::view', array($request->project->shortname, $issue->id)); $title = sprintf(__('%s: Comment on issue %d - %s'), @@ -196,4 +196,9 @@ class IDF_IssueComment extends Pluf_Model $tmpl = new Pluf_Template('idf/issues/feedfragment.xml'); return $tmpl->render($context); } + + public function get_submitter_data() + { + return IDF_UserData::factory($this->get_submitter()); + } } diff --git a/src/IDF/Review/Comment.php b/src/IDF/Review/Comment.php index 0407cda..b3becda 100644 --- a/src/IDF/Review/Comment.php +++ b/src/IDF/Review/Comment.php @@ -25,12 +25,12 @@ * A comment set on a review. * * A comment is associated to a patch as a review can have many - * patches associated to it. + * patches associated to it. * * A comment is also tracking the changes in the review in the same * way the issue comment is tracking the changes in the issue. * - * + * */ class IDF_Review_Comment extends Pluf_Model { @@ -45,9 +45,9 @@ class IDF_Review_Comment extends Pluf_Model 'id' => array( 'type' => 'Pluf_DB_Field_Sequence', - 'blank' => true, + 'blank' => true, ), - 'patch' => + 'patch' => array( 'type' => 'Pluf_DB_Field_Foreignkey', 'model' => 'IDF_Review_Patch', @@ -61,7 +61,7 @@ class IDF_Review_Comment extends Pluf_Model 'blank' => true, // if only commented on lines 'verbose' => __('comment'), ), - 'submitter' => + 'submitter' => array( 'type' => 'Pluf_DB_Field_Foreignkey', 'model' => 'Pluf_User', @@ -118,8 +118,8 @@ class IDF_Review_Comment extends Pluf_Model function postSave($create=false) { if ($create) { - IDF_Timeline::insert($this, - $this->get_patch()->get_review()->get_project(), + IDF_Timeline::insert($this, + $this->get_patch()->get_review()->get_project(), $this->get_submitter()); } } @@ -127,7 +127,7 @@ class IDF_Review_Comment extends Pluf_Model public function timelineFragment($request) { $review = $this->get_patch()->get_review(); - $url = Pluf_HTTP_URL_urlForView('IDF_Views_Review::view', + $url = Pluf_HTTP_URL_urlForView('IDF_Views_Review::view', array($request->project->shortname, $review->id)); $out = ''. @@ -138,14 +138,14 @@ class IDF_Review_Comment extends Pluf_Model $ic = (in_array($review->status, $request->project->getTagIdsByStatus('closed'))) ? 'issue-c' : 'issue-o'; $out .= sprintf(__('Review %3$d, %4$s'), $url, $ic, $review->id, Pluf_esc($review->summary)).''; $out .= "\n".' -
'.sprintf(__('Update of review %d, by %s'), $url, $ic, $review->id, $user).'
'; +
'.sprintf(__('Update of review %d, by %s'), $url, $ic, $review->id, $user).'
'; return Pluf_Template::markSafe($out); } public function feedFragment($request) { $review = $this->get_patch()->get_review(); - $url = Pluf_HTTP_URL_urlForView('IDF_Views_Review::view', + $url = Pluf_HTTP_URL_urlForView('IDF_Views_Review::view', array($request->project->shortname, $review->id)); $title = sprintf(__('%s: Updated review %d - %s'), @@ -221,4 +221,9 @@ class IDF_Review_Comment extends Pluf_Model } Pluf_Translation::loadSetLocale($current_locale); } + + public function get_submitter_data() + { + return IDF_UserData::factory($this->get_submitter()); + } } diff --git a/src/IDF/UserData.php b/src/IDF/UserData.php new file mode 100644 index 0000000..b28099e --- /dev/null +++ b/src/IDF/UserData.php @@ -0,0 +1,59 @@ +setVal($key, $value); + } + + function __get($key) + { + if (in_array($key, self::$protectedVars)) + return parent::__get($key); + return $this->getVal($key, null); + } + + public static function factory($user) + { + $conf = new IDF_UserData(); + $conf->setModel((object) array('_model'=>'IDF_UserData', 'id' => $user->id)); + $conf->initCache(); + return $conf; + } +} diff --git a/src/IDF/Views.php b/src/IDF/Views.php index 03ae86a..235eacc 100644 --- a/src/IDF/Views.php +++ b/src/IDF/Views.php @@ -86,7 +86,10 @@ class IDF_Views $title = __('Create Your Account'); $params = array('request'=>$request); if ($request->method == 'POST') { - $form = new IDF_Form_Register($request->POST, $params); + $form = new IDF_Form_Register(array_merge( + (array)$request->POST, + (array)$request->FILES + ), $params); if ($form->isValid()) { $user = $form->save(); // It is sending the confirmation email $url = Pluf_HTTP_URL_urlForView('IDF_Views::registerInputKey'); diff --git a/src/IDF/Views/Admin.php b/src/IDF/Views/Admin.php index 2b1db96..6c4d8d0 100644 --- a/src/IDF/Views/Admin.php +++ b/src/IDF/Views/Admin.php @@ -268,7 +268,9 @@ class IDF_Views_Admin } if ($request->method == 'POST') { - $form = new IDF_Form_Admin_UserUpdate($request->POST, $params); + $form = new IDF_Form_Admin_UserUpdate(array_merge($request->POST, + $request->FILES), + $params); if ($form->isValid()) { $form->save(); $request->user->setMessage(__('The user has been updated.')); @@ -299,7 +301,9 @@ class IDF_Views_Admin 'request' => $request, ); if ($request->method == 'POST') { - $form = new IDF_Form_Admin_UserCreate($request->POST, $params); + $form = new IDF_Form_Admin_UserCreate(array_merge($request->POST, + $request->FILES), + $params); if ($form->isValid()) { $cuser = $form->save(); $request->user->setMessage(sprintf(__('The user %s has been created.'), (string) $cuser)); diff --git a/src/IDF/Views/Issue.php b/src/IDF/Views/Issue.php index e0147fe..7dbafee 100644 --- a/src/IDF/Views/Issue.php +++ b/src/IDF/Views/Issue.php @@ -279,6 +279,7 @@ class IDF_Views_Issue $form = new IDF_Form_IssueUpdate(null, $params); } } + $arrays = self::autoCompleteArrays($prj); return Pluf_Shortcuts_RenderToResponse('idf/issues/view.html', array_merge( @@ -290,7 +291,7 @@ class IDF_Views_Issue 'page_title' => $title, 'closed' => $closed, 'preview' => $preview, - 'interested' =>$interested->count(), + 'interested' => $interested->count(), ), $arrays), $request); diff --git a/src/IDF/Views/Review.php b/src/IDF/Views/Review.php index 79671b8..1afb0fd 100644 --- a/src/IDF/Views/Review.php +++ b/src/IDF/Views/Review.php @@ -89,10 +89,10 @@ class IDF_Views_Review )); if ($form->isValid()) { $review = $form->save(); - $urlr = Pluf_HTTP_URL_urlForView('IDF_Views_Review::view', + $urlr = Pluf_HTTP_URL_urlForView('IDF_Views_Review::view', array($prj->shortname, $review->id)); $request->user->setMessage(sprintf(__('The code review %d has been created.'), $urlr, $review->id)); - $url = Pluf_HTTP_URL_urlForView('IDF_Views_Review::index', + $url = Pluf_HTTP_URL_urlForView('IDF_Views_Review::index', array($prj->shortname)); return new Pluf_HTTP_Response_Redirect($url); } @@ -155,10 +155,10 @@ class IDF_Views_Review if ($form->isValid()) { $review_comment = $form->save(); $review = $patch->get_review(); - $urlr = Pluf_HTTP_URL_urlForView('IDF_Views_Review::view', + $urlr = Pluf_HTTP_URL_urlForView('IDF_Views_Review::view', array($prj->shortname, $review->id)); $request->user->setMessage(sprintf(__('Your code review %d has been published.'), $urlr, $review->id)); - $url = Pluf_HTTP_URL_urlForView('IDF_Views_Review::index', + $url = Pluf_HTTP_URL_urlForView('IDF_Views_Review::index', array($prj->shortname)); $review_comment->notify($request->conf); return new Pluf_HTTP_Response_Redirect($url); @@ -181,7 +181,7 @@ class IDF_Views_Review foreach ($cts as $ct) { $reviewers[] = $ct->get_comment()->get_submitter(); } - if (count($def['chunks'])) { + if (count($def['chunks'])) { $orig_file = ($fileinfo) ? $scm->getFile($fileinfo) : ''; $files[$filename] = array( $diff->fileCompare($orig_file, $def, $filename), @@ -192,6 +192,7 @@ class IDF_Views_Review $files[$filename] = array('', $form->f->{md5($filename)}, $cts); } } + $reviewers = Pluf_Model_RemoveDuplicates($reviewers); return Pluf_Shortcuts_RenderToResponse('idf/review/view.html', array_merge( @@ -219,7 +220,7 @@ class IDF_Views_Review */ function IDF_Views_Review_SummaryAndLabels($field, $review, $extra='') { - $edit = Pluf_HTTP_URL_urlForView('IDF_Views_Review::view', + $edit = Pluf_HTTP_URL_urlForView('IDF_Views_Review::view', array($review->shortname, $review->id)); $tags = array(); foreach ($review->get_tags_list() as $tag) { diff --git a/src/IDF/Views/User.php b/src/IDF/Views/User.php index 50f1579..6086ae2 100644 --- a/src/IDF/Views/User.php +++ b/src/IDF/Views/User.php @@ -33,13 +33,13 @@ Pluf::loadFunction('Pluf_Shortcuts_RenderToResponse'); class IDF_Views_User { /** - * Dashboard of a user. + * Dashboard of a user. * * Shows all the open issues assigned to the user. * * TODO: This views is a SQL horror. What needs to be done to cut * by many the number of SQL queries: - * - Add a table to cache the open/closed status ids for all the + * - Add a table to cache the open/closed status ids for all the * projects. * - Left join the issues with the project to get the shortname. * @@ -110,7 +110,10 @@ class IDF_Views_User $ext_pass = substr(sha1($request->user->password.Pluf::f('secret_key')), 0, 8); $params = array('user' => $request->user); if ($request->method == 'POST') { - $form = new IDF_Form_UserAccount($request->POST, $params); + $form = new IDF_Form_UserAccount(array_merge( + (array)$request->POST, + (array)$request->FILES + ), $params); if ($form->isValid()) { $user = $form->save(); $url = Pluf_HTTP_URL_urlForView('IDF_Views_User::myAccount'); @@ -121,10 +124,11 @@ class IDF_Views_User } else { $data = $request->user->getData(); unset($data['password']); - $form = new IDF_Form_UserAccount($data, $params); + $form = new IDF_Form_UserAccount(null, $params); } $keys = $request->user->get_idf_key_list(); - return Pluf_Shortcuts_RenderToResponse('idf/user/myaccount.html', + + return Pluf_Shortcuts_RenderToResponse('idf/user/myaccount.html', array('page_title' => __('Your Account'), 'api_key' => $api_key, 'ext_pass' => $ext_pass, @@ -170,11 +174,11 @@ class IDF_Views_User } else { $form = new IDF_Form_UserChangeEmail(); } - return Pluf_Shortcuts_RenderToResponse('idf/user/changeemail.html', + return Pluf_Shortcuts_RenderToResponse('idf/user/changeemail.html', array('page_title' => __('Confirm The Email Change'), 'form' => $form), $request); - + } /** @@ -208,13 +212,17 @@ class IDF_Views_User public function view($request, $match) { $sql = new Pluf_SQL('login=%s', array($match[1])); - $users = Pluf::factory('Pluf_User')->getList(array('filter'=>$sql->gen())); + $users = Pluf::factory('Pluf_User')->getList(array('filter'=>$sql->gen())); if (count($users) != 1 or !$users[0]->active) { throw new Pluf_HTTP_Error404(); } - return Pluf_Shortcuts_RenderToResponse('idf/user/public.html', - array('page_title' => (string) $users[0], - 'member' => $users[0], + + $user = $users[0]; + $user_data = IDF_UserData::factory($user); + return Pluf_Shortcuts_RenderToResponse('idf/user/public.html', + array('page_title' => (string) $user, + 'member' => $user, + 'user_data' => $user_data, ), $request); } @@ -230,11 +238,11 @@ class IDF_Views_User function IDF_Views_IssueSummaryAndLabels($field, $issue, $extra='') { $project = $issue->get_project(); - $edit = Pluf_HTTP_URL_urlForView('IDF_Views_Issue::view', + $edit = Pluf_HTTP_URL_urlForView('IDF_Views_Issue::view', array($project->shortname, $issue->id)); $tags = array(); foreach ($issue->get_tags_list() as $tag) { - $url = Pluf_HTTP_URL_urlForView('IDF_Views_Issue::listLabel', + $url = Pluf_HTTP_URL_urlForView('IDF_Views_Issue::listLabel', array($project->shortname, $tag->id, 'open')); $tags[] = sprintf('%s', $url, Pluf_esc((string) $tag)); } diff --git a/src/IDF/relations.php b/src/IDF/relations.php index 6848e1a..ffc2376 100644 --- a/src/IDF/relations.php +++ b/src/IDF/relations.php @@ -43,6 +43,8 @@ $m['IDF_Conf'] = array('relate_to' => array('IDF_Project')); $m['IDF_Commit'] = array('relate_to' => array('IDF_Project', 'Pluf_User')); $m['IDF_Scm_Cache_Git'] = array('relate_to' => array('IDF_Project')); +$m['IDF_UserData'] = array('relate_to' => array('Pluf_User')); + Pluf_Signal::connect('Pluf_Template_Compiler::construct_template_tags_modifiers', array('IDF_Middleware', 'updateTemplateTagsModifiers')); diff --git a/src/IDF/templates/idf/gadmin/users/update.html b/src/IDF/templates/idf/gadmin/users/update.html index 2c431e2..d4a081d 100644 --- a/src/IDF/templates/idf/gadmin/users/update.html +++ b/src/IDF/templates/idf/gadmin/users/update.html @@ -9,7 +9,7 @@ {/if} {/if} -
+ {aurl 'url', 'IDF_Views_User::view', array($cuser.login)} @@ -51,7 +51,48 @@ -{if $user.administrator} + + + + + + + + + + + + + + + + + + + + + + + + + + +{if $user.administrator} + diff --git a/src/IDF/templates/idf/issues/view.html b/src/IDF/templates/idf/issues/view.html index 7c0b16b..24e57d8 100644 --- a/src/IDF/templates/idf/issues/view.html +++ b/src/IDF/templates/idf/issues/view.html @@ -3,8 +3,15 @@ {block body} {assign $i = 0} {assign $nc = $comments.count()} -{foreach $comments as $c}{ashowuser 'submitter', $c.get_submitter(), $request}{assign $who = $c.get_submitter()} -
 +{foreach $comments as $c}{ashowuser 'submitter', $c.get_submitter(), $request} +{assign $submitter = $c.get_submitter()} +{assign $submitter_data = $c.get_submitter_data()} +
+{if $submitter_data.avatar != ''} + +{else} + +{/if} {if $i == 0}

{blocktrans}Reported by {$submitter}, {$c.creation_dtime|date}{/blocktrans}

{else} diff --git a/src/IDF/templates/idf/review/view.html b/src/IDF/templates/idf/review/view.html index ff9bc23..462a275 100644 --- a/src/IDF/templates/idf/review/view.html +++ b/src/IDF/templates/idf/review/view.html @@ -79,9 +79,9 @@ to propose more contributions.
{trans 'Login:'}{if $form.f.password2.errors}{$form.f.password2.fieldErrors}{/if} {$form.f.password2|unsafe}
{trans "Public Profile"}
{$form.f.description.labelTag}:{if $form.f.description.errors}{$form.f.description.fieldErrors}{/if} +{$form.f.description|unsafe} +
{$form.f.twitter.labelTag}:{if $form.f.twitter.errors}{$form.f.twitter.fieldErrors}{/if} +{$form.f.twitter|unsafe} +
{$form.f.public_email.labelTag}:{if $form.f.public_email.errors}{$form.f.public_email.fieldErrors}{/if} +{$form.f.public_email|unsafe} +
{$form.f.website.labelTag}:{if $form.f.website.errors}{$form.f.website.fieldErrors}{/if} +{$form.f.website|unsafe} +
{$form.f.custom_avatar.labelTag}:{if $form.f.custom_avatar.errors}{$form.f.custom_avatar.fieldErrors}{/if} +{$form.f.custom_avatar|unsafe}
+{$form.f.custom_avatar.help_text} +
{if $form.f.remove_custom_avatar.errors}{$form.f.remove_custom_avatar.fieldErrors}{/if} +{$form.f.remove_custom_avatar|unsafe} +{$form.f.remove_custom_avatar.labelTag}
+{$form.f.remove_custom_avatar.help_text}
{trans "Administrative"}
{if $form.f.staff.errors}{$form.f.staff.fieldErrors}{/if} {$form.f.staff|unsafe} @@ -69,7 +110,7 @@
  - + | {trans 'Cancel'}
{assign $fcomments = $def[2]} -{assign $nc = $fcomments.count()} +{assign $nc = $fcomments.count()} {assign $i = 1} -{foreach $fcomments as $c} +{foreach $fcomments as $c}
{assign $who = $c.get_comment().get_submitter()}{aurl 'whourl', 'IDF_Views_User::view', array($who.login)} {aurl 'url', 'IDF_Views_Review::view', array($project.shortname, $review.id)} {assign $id = $c.id} @@ -101,13 +101,18 @@ to propose more contributions. {/if} {/foreach} -{assign $i = 1} -{assign $nc = $comments.count()} +{assign $i = 1} +{assign $nc = $comments.count()} {if $nc}

{trans 'General Comments'}

{/if} -{foreach $comments as $c}{ashowuser 'submitter', $c.get_submitter(), $request}{assign $who = $c.get_submitter()} -
 +{foreach $comments as $c}{ashowuser 'submitter', $c.get_submitter(), $request}{assign $submitter = $c.get_submitter()}{assign $submitter_data = $c.get_submitter_data()} +
+{if $submitter_data.avatar != ''} +  +{else} +  +{/if} {aurl 'url', 'IDF_Views_Review::view', array($project.shortname, $review.id)} {assign $id = $c.id} {assign $url = $url~'#ic'~$c.id} @@ -118,11 +123,11 @@ to propose more contributions. {if strlen($c.content) > 0}
{issuetext $c.content, $request}
{/if} {if $c.changedReview()} -
+
{foreach $c.changes as $w => $v} {if $w == 'su'}{trans 'Summary:'}{/if}{if $w == 'st'}{trans 'Status:'}{/if} {$v}
{/foreach} -
+
{/if}
{assign $i = $i + 1}{if $i == $nc+1 and $user.isAnonymous()} {/if} - + {aurl 'url', 'IDF_Views_User::view', array($user.login)} @@ -53,6 +53,46 @@ {$form.f.password2|unsafe} + + + + + + + + + + + + + + + + + + + + + + + + + +
{trans 'Login:'}
{trans "Public Profile"}
{$form.f.description.labelTag}:{if $form.f.description.errors}{$form.f.description.fieldErrors}{/if} +{$form.f.description|unsafe} +
{$form.f.twitter.labelTag}:{if $form.f.twitter.errors}{$form.f.twitter.fieldErrors}{/if} +{$form.f.twitter|unsafe} +
{$form.f.public_email.labelTag}:{if $form.f.public_email.errors}{$form.f.public_email.fieldErrors}{/if} +{$form.f.public_email|unsafe} +
{$form.f.website.labelTag}:{if $form.f.website.errors}{$form.f.website.fieldErrors}{/if} +{$form.f.website|unsafe} +
{$form.f.custom_avatar.labelTag}:{if $form.f.custom_avatar.errors}{$form.f.custom_avatar.fieldErrors}{/if} +{$form.f.custom_avatar|unsafe}
+{$form.f.custom_avatar.help_text} +
{if $form.f.remove_custom_avatar.errors}{$form.f.remove_custom_avatar.fieldErrors}{/if} +{$form.f.remove_custom_avatar|unsafe} +{$form.f.remove_custom_avatar.labelTag}
+{$form.f.remove_custom_avatar.help_text}
{trans "Key Management"}
{$form.f.public_key.labelTag}: {if $form.f.public_key.errors}{$form.f.public_key.fieldErrors}{/if} diff --git a/src/IDF/templates/idf/user/public.html b/src/IDF/templates/idf/user/public.html index 17d6b25..2373a23 100644 --- a/src/IDF/templates/idf/user/public.html +++ b/src/IDF/templates/idf/user/public.html @@ -2,9 +2,39 @@ {block body} - + +{if $user_data.description != ''} + + + + +{/if} +{if $user_data.twitter != ''} + + + + +{/if} +{if $user_data.public_email != ''} + + + + +{/if} +{if $user_data.website != ''} + + + + +{/if} + diff --git a/www/media/idf/css/style.css b/www/media/idf/css/style.css index fe1f8ab..273f539 100644 --- a/www/media/idf/css/style.css +++ b/www/media/idf/css/style.css @@ -104,6 +104,11 @@ table.form th { font-weight: normal; } +table.form td.separator { + font-weight: bold; + text-align: center; +} + .px-message-error { padding-left: 37px; background: url("../img/dialog-error.png");
{if $user_data.avatar != ''} +  +{else} +  +{/if} + {$member}
{trans 'Description:'}{$user_data.description}
{trans 'Twitter:'}{$user_data.twitter}
{trans 'Public Email:'}{$user_data.public_email}
{trans 'Website:'}{$user_data.website}
{trans 'Last time seen:'} {$member.last_login|dateago}