diff --git a/src/IDF/Form/Register.php b/src/IDF/Form/Register.php index 042c1cb..5c28e14 100644 --- a/src/IDF/Form/Register.php +++ b/src/IDF/Form/Register.php @@ -107,5 +107,36 @@ class IDF_Form_Register extends Pluf_Form if (!$this->isValid()) { throw new Exception(__('Cannot save the model from an invalid form.')); } + $user = new Pluf_User(); + $user->first_name = '---'; // with both this set and + // active==false we can find later + // on, all the unconfirmed accounts + // that could be purged. + $user->last_name = $this->cleaned_data['login']; + $user->login = $this->cleaned_data['login']; + $user->email = $this->cleaned_data['email']; + $user->active = false; + $user->create(); + $from_email = Pluf::f('from_email'); + Pluf::loadFunction('Pluf_HTTP_URL_urlForView'); + $cr = new Pluf_Crypt(md5(Pluf::f('secret_key'))); + $encrypted = trim($cr->encrypt($user->email.':'.$user->id), '~'); + $key = substr(md5(Pluf::f('secret_key').$encrypted), 0, 2).$encrypted; + $url = Pluf::f('url_base').Pluf_HTTP_URL_urlForView('IDF_Views::registerConfirmation', array($key), array(), false); + $urlik = Pluf::f('url_base').Pluf_HTTP_URL_urlForView('IDF_Views::registerInputKey', array(), array(), false); + $context = new Pluf_Template_Context( + array('key' => $key, + 'url' => $url, + 'urlik' => $urlik, + 'user'=> $user, + ) + ); + $tmpl = new Pluf_Template('register/confirmation-email.txt'); + $text_email = $tmpl->render($context); + $email = new Pluf_Mail($from_email, $user->email, + __('Confirm the creation of your account.')); + $email->addTextMessage($text_email); + $email->sendMail(); + return $user; } } diff --git a/src/IDF/Form/RegisterConfirmation.php b/src/IDF/Form/RegisterConfirmation.php new file mode 100644 index 0000000..ce253d6 --- /dev/null +++ b/src/IDF/Form/RegisterConfirmation.php @@ -0,0 +1,147 @@ +_user = $extra['user']; + + $this->fields['key'] = new Pluf_Form_Field_Varchar( + array('required' => true, + 'label' => __('Your confirmation key'), + 'initial' => $extra['key'], + 'widget' => 'Pluf_Form_Widget_HiddenInput', + 'widget_attrs' => array( + 'readonly' => 'readonly', + ), + + )); + $this->fields['first_name'] = new Pluf_Form_Field_Varchar( + array('required' => false, + 'label' => __('First name'), + 'initial' => '', + 'widget_attrs' => array( + 'maxlength' => 50, + 'size' => 15, + ), + )); + $this->fields['last_name'] = new Pluf_Form_Field_Varchar( + array('required' => true, + 'label' => __('Last name'), + 'initial' => '', + 'widget_attrs' => array( + 'maxlength' => 50, + 'size' => 15, + ), + )); + $this->fields['password'] = new Pluf_Form_Field_Varchar( + array('required' => true, + 'label' => __('Your password'), + 'initial' => '', + 'widget' => 'Pluf_Form_Widget_PasswordInput', + 'help_text' => __('Your password must be hard for other people to find it, but easy for you to remember.'), + 'widget_attrs' => array( + 'maxlength' => 50, + 'size' => 15, + ), + )); + $this->fields['password2'] = new Pluf_Form_Field_Varchar( + array('required' => true, + 'label' => __('Confirm your password'), + 'initial' => '', + 'widget' => 'Pluf_Form_Widget_PasswordInput', + 'widget_attrs' => array( + 'maxlength' => 50, + 'size' => 15, + ), + )); + + + + } + + /** + * Just a simple control. + */ + public function clean_key() + { + $this->cleaned_data['key'] = trim($this->cleaned_data['key']); + $error = __('We are sorry but this confirmation key is not valid. Maybe you should directly copy/paste it from your confirmation email.'); + if (false === ($email_id=IDF_Form_RegisterInputKey::checkKeyHash($this->cleaned_data['key']))) { + throw new Pluf_Form_Invalid($error); + } + $guser = new Pluf_User(); + $sql = new Pluf_SQL('email=%s AND id=%s', $email_id); + $users = $guser->getList(array('filter' => $sql->gen())); + if ($users->count() != 1) { + throw new Pluf_Form_Invalid($error); + } + if ($users[0]->active) { + throw new Pluf_Form_Invalid(__('This account has already been confirmed. Maybe should you try to recover your password using the help link.')); + } + $this->_user_id = $email_id[1]; + return $this->cleaned_data['key']; + } + + /** + * Check the passwords. + */ + public function clean() + { + if ($this->cleaned_data['password'] != $this->cleaned_data['password2']) { + throw new Pluf_Form_Invalid(__('The two passwords must be the same.')); + } + return $this->cleaned_data; + } + + /** + * Save the model in the database. + * + * @param bool Commit in the database or not. If not, the object + * is returned but not saved in the database. + * @return Object Model with data set from the form. + */ + function save($commit=true) + { + if (!$this->isValid()) { + throw new Exception(__('Cannot save an invalid form.')); + } + $this->_user->setFromFormData($this->cleaned_data); + $this->_user->active = true; + $this->_user->administrator = false; + $this->_user->staff = false; + if ($commit) { + $this->_user->update(); + } + return $this->_user; + } +} diff --git a/src/IDF/Form/RegisterInputKey.php b/src/IDF/Form/RegisterInputKey.php new file mode 100644 index 0000000..658291b --- /dev/null +++ b/src/IDF/Form/RegisterInputKey.php @@ -0,0 +1,96 @@ +fields['key'] = new Pluf_Form_Field_Varchar( + array('required' => true, + 'label' => __('Your confirmation key'), + 'initial' => '', + 'widget_attrs' => array( + 'size' => 50, + ), + )); + } + + /** + * Validate the key. + */ + public function clean_key() + { + $this->cleaned_data['key'] = trim($this->cleaned_data['key']); + $error = __('We are sorry but this confirmation key is not valid. Maybe you should directly copy/paste it from your confirmation email.'); + if (false === ($email_id=self::checkKeyHash($this->cleaned_data['key']))) { + throw new Pluf_Form_Invalid($error); + } + $guser = new Pluf_User(); + $sql = new Pluf_SQL('email=%s AND id=%s', $email_id); + if ($guser->getCount(array('filter' => $sql->gen())) != 1) { + throw new Pluf_Form_Invalid($error); + } + return $this->cleaned_data['key']; + } + + /** + * Save the model in the database. + * + * @param bool Commit in the database or not. If not, the object + * is returned but not saved in the database. + * @return string Url to redirect to the form. + */ + function save($commit=true) + { + if (!$this->isValid()) { + throw new Exception(__('Cannot save an invalid form.')); + } + return Pluf_HTTP_URL_urlForView('IDF_Views::registerConfirmation', + array($this->cleaned_data['key'])); + } + + /** + * Return false or an array with the email and id. + * + * This is a static function to be reused by other forms. + * + * @param string Confirmation key + * @return mixed Either false or array(email, id) + */ + public static function checkKeyHash($key) + { + $hash = substr($key, 0, 2); + $encrypted = substr($key, 2); + if ($hash != substr(md5(Pluf::f('secret_key').$encrypted), 0, 2)) { + return false; + } + $cr = new Pluf_Crypt(md5(Pluf::f('secret_key'))); + return split(':', $cr->decrypt($encrypted), 2); + } +} diff --git a/src/IDF/Views.php b/src/IDF/Views.php index ab201ac..ab50a8a 100644 --- a/src/IDF/Views.php +++ b/src/IDF/Views.php @@ -82,17 +82,85 @@ class IDF_Views if ($request->method == 'POST') { $form = new IDF_Form_Register($request->POST); if ($form->isValid()) { - $user = $form->save(); - $url = Pluf_HTTP_URL_urlForView('IDF_Views::registerConfirmation'); + $user = $form->save(); // It is sending the confirmation email + $url = Pluf_HTTP_URL_urlForView('IDF_Views::registerInputKey'); return new Pluf_HTTP_Response_Redirect($url); } } else { $init = (isset($request->GET['login'])) ? array('initial' => array('login' => $request->GET['login'])) : array(); $form = new IDF_Form_Register(null, $init); } - return Pluf_Shortcuts_RenderToResponse('register.html', + return Pluf_Shortcuts_RenderToResponse('register/index.html', array('page_title' => $title, 'form' => $form), $request); } + + /** + * Input the registration confirmation key. + * + * Very simple view just to redirect to the register confirmation + * views to input the password. + */ + function registerInputKey($request, $match) + { + $title = __('Confirm Your Account Creation'); + if ($request->method == 'POST') { + $form = new IDF_Form_RegisterInputKey($request->POST); + if ($form->isValid()) { + $url = $form->save(); + return new Pluf_HTTP_Response_Redirect($url); + } + } else { + $form = new IDF_Form_RegisterInputKey(); + } + return Pluf_Shortcuts_RenderToResponse('register/inputkey.html', + array('page_title' => $title, + 'form' => $form), + $request); + } + + /** + * Registration confirmation. + * + * Input first/last name, password and sign in the user. + * + * Maybe in the future send the user to its personal page for + * customization. + */ + function registerConfirmation($request, $match) + { + $title = __('Confirm Your Account Creation'); + $key = $match[1]; + // first "check", full check is done in the form. + $email_id = IDF_Form_RegisterInputKey::checkKeyHash($key); + if (false == $email_id) { + $url = Pluf_HTTP_URL_urlForView('IDF_Views::registerInputKey'); + return new Pluf_HTTP_Response_Redirect($url); + } + $user = new Pluf_User($email_id[1]); + $extra = array('key' => $key, + 'user' => $user); + if ($request->method == 'POST') { + $form = new IDF_Form_RegisterConfirmation($request->POST, $extra); + if ($form->isValid()) { + $user = $form->save(); + $request->user = $user; + $request->session->clear(); + $request->session->setData('login_time', gmdate('Y-m-d H:i:s')); + $user->last_login = gmdate('Y-m-d H:i:s'); + $user->update(); + $request->user->setMessage(__('Welcome! You can now participate in the life of your project of choice.')); + $url = Pluf_HTTP_URL_urlForView('IDF_Views::index'); + return new Pluf_HTTP_Response_Redirect($url); + } + } else { + $form = new IDF_Form_RegisterConfirmation(null, $extra); + } + return Pluf_Shortcuts_RenderToResponse('register/confirmation.html', + array('page_title' => $title, + 'new_user' => $user, + 'form' => $form), + $request); + } } \ No newline at end of file diff --git a/src/IDF/conf/views.php b/src/IDF/conf/views.php index ef8c433..ff1dccb 100644 --- a/src/IDF/conf/views.php +++ b/src/IDF/conf/views.php @@ -42,6 +42,18 @@ $ctl[] = array('regex' => '#^/register/$#', 'model' => 'IDF_Views', 'method' => 'register'); +$ctl[] = array('regex' => '#^/register/k/(.*)/$#', + 'base' => $base, + 'priority' => 4, + 'model' => 'IDF_Views', + 'method' => 'registerConfirmation'); + +$ctl[] = array('regex' => '#^/register/ik/$#', + 'base' => $base, + 'priority' => 4, + 'model' => 'IDF_Views', + 'method' => 'registerInputKey'); + $ctl[] = array('regex' => '#^/logout/$#', 'base' => $base, 'priority' => 4, diff --git a/src/IDF/templates/register/confirmation-email.txt b/src/IDF/templates/register/confirmation-email.txt new file mode 100644 index 0000000..4db056f --- /dev/null +++ b/src/IDF/templates/register/confirmation-email.txt @@ -0,0 +1,27 @@ +{blocktrans}Hello, + +You have requested the creation of an account to +participate in the life of a software project. + +To confirm the account please follow this link: + +{$url|safe} + +Alternatively, go on this page: + +{$urlik|safe} + +and provide the following confirmation key: + +{$key} + +If you are not interested any longer in getting +part in the life of the software project or if +you can't remember having requested the creation +of an accout, please excuse us and simply ignore +this email. + +Yours faithfully, +The development team. +{/blocktrans} + diff --git a/src/IDF/templates/register/confirmation.html b/src/IDF/templates/register/confirmation.html new file mode 100644 index 0000000..a31778f --- /dev/null +++ b/src/IDF/templates/register/confirmation.html @@ -0,0 +1,65 @@ +{extends "base-simple.html"} +{block body} +{if $form.errors} +
+

{trans 'Oups, please check the form for errors.'}

+{if $form.get_top_errors} +{$form.render_top_errors|unsafe} +{/if} +{if $form.f.key.errors}{$form.f.key.fieldErrors}{/if} +
+{/if} + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
{trans 'Login:'}{$new_user.login}
{trans 'Email:'}{$new_user.email}
{$form.f.first_name.labelTag}:{if $form.f.first_name.errors}{$form.f.first_name.fieldErrors}{/if} +{$form.f.first_name|unsafe} +
{$form.f.last_name.labelTag}:{if $form.f.last_name.errors}{$form.f.last_name.fieldErrors}{/if} +{$form.f.last_name|unsafe} +
{$form.f.password.labelTag}:{if $form.f.password.errors}{$form.f.password.fieldErrors}{/if} +{$form.f.password|unsafe}
+{$form.f.password.help_text} +
{$form.f.password2.labelTag}:{if $form.f.password2.errors}{$form.f.password2.fieldErrors}{/if} +{$form.f.password2|unsafe} +
  | {trans 'Cancel'} +
{$form.f.key|unsafe} +
+{/block} +{block context} +
+

{trans 'This is the last step, but just be sure to have the cookies enabled to log in afterwards.'}

+
+{/block} +{block javascript} +{include 'issues/js-autocomplete.html'}{/block} + diff --git a/src/IDF/templates/register.html b/src/IDF/templates/register/index.html similarity index 89% rename from src/IDF/templates/register.html rename to src/IDF/templates/register/index.html index ca3c3c6..66b92f9 100644 --- a/src/IDF/templates/register.html +++ b/src/IDF/templates/register/index.html @@ -30,7 +30,7 @@ {if $form.f.terms.errors}{$form.f.terms.fieldErrors}{/if} {$form.f.terms|unsafe} {$form.f.terms.labelTag}
-{blocktrans}Read the terms and conditions (basically "Please be nice, we respect you."){/blocktrans} +{blocktrans}Read the terms and conditions – basically "Please be nice, we respect you".{/blocktrans} @@ -49,9 +49,9 @@ {aurl 'url', 'IDF_Views::faq'} {blocktrans}With your account, you will able to participate in the life of all the projects hosted here. Participating in a software project must be fun, so if you have troubles, you can let us know about your issues at anytime!{/blocktrans} - -{/block} -{block javascript}{include 'issues/js-autocomplete.html'}{/block} +{include 'issues/js-autocomplete.html'}{/block} diff --git a/src/IDF/templates/register/inputkey.html b/src/IDF/templates/register/inputkey.html new file mode 100644 index 0000000..24ee832 --- /dev/null +++ b/src/IDF/templates/register/inputkey.html @@ -0,0 +1,40 @@ +{extends "base-simple.html"} +{block body} +{if $form.errors} +
+

{trans 'Oups, we found an error in the form.'}

+{if $form.get_top_errors} +{$form.render_top_errors|unsafe} +{/if} +
+{/if} + +
+ + + + + + + + + +
 {$form.f.key.labelTag}:
+{if $form.f.key.errors}{$form.f.key.fieldErrors}{/if} +{$form.f.key|unsafe} +
  | {trans 'Cancel'} +
+
+{/block} +{block context} +
+

{trans 'Instructions'}

+

{trans 'Use your email software to read your emails and open your confirmation email. Either click directly on the confirmation link or copy/paste the confirmation key in the box and submit the form.'}

+

{trans 'Just after providing the confirmation key, you will be able to set your password and start using this website fully.'}

+
+{/block} +{block javascript} +{include 'issues/js-autocomplete.html'}{/block} + diff --git a/www/media/idf/css/style.css b/www/media/idf/css/style.css index 1d71d56..e919868 100644 --- a/www/media/idf/css/style.css +++ b/www/media/idf/css/style.css @@ -174,6 +174,10 @@ div.issue-submit-info { margin-bottom: 1em; } +div.issue-submit-info h2 { + margin-top: 0; +} + span.label { color: #204a87; padding-left: 0.5em;