Merge branch '4-settings-page'
This commit is contained in:
commit
25e9168f96
8 changed files with 436 additions and 15 deletions
|
|
@ -110,17 +110,37 @@
|
||||||
&-user-menu {
|
&-user-menu {
|
||||||
&:extend(.nav);
|
&:extend(.nav);
|
||||||
.pull-right();
|
.pull-right();
|
||||||
margin-top: 18px;
|
margin-top: 10px;
|
||||||
|
|
||||||
> li {
|
&-login {
|
||||||
display: inline-block;
|
padding-top: .3em;
|
||||||
|
padding-bottom: .3em;
|
||||||
|
}
|
||||||
|
|
||||||
.icon {
|
&-dropdown {
|
||||||
margin-right: .2em;
|
&:extend(.dropdown);
|
||||||
|
|
||||||
|
&-button {
|
||||||
|
display: inline-block;
|
||||||
|
padding: .3em .8em;
|
||||||
|
background: @usermenu-button-bg;
|
||||||
|
border-radius: @border-radius-base;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
background: @usermenu-button-hover-bg;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
&:not(:first-child) {
|
&-list {
|
||||||
padding-left: 1em;
|
&:extend(.dropdown-menu all);
|
||||||
|
&:extend(.dropdown-menu-right all);
|
||||||
|
|
||||||
|
text-shadow: none;
|
||||||
|
.box-shadow(@dropdown-shadow);
|
||||||
|
}
|
||||||
|
|
||||||
|
&.open &-list {
|
||||||
|
display: inline-block;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -74,6 +74,10 @@
|
||||||
@navbar-brand-color: @brand-color;
|
@navbar-brand-color: @brand-color;
|
||||||
@navbar-brand-hover-color: darken(@brand-color, 15%);
|
@navbar-brand-hover-color: darken(@brand-color, 15%);
|
||||||
|
|
||||||
|
// User menu
|
||||||
|
@usermenu-button-bg: @navbar-background;
|
||||||
|
@usermenu-button-hover-bg: darken(@usermenu-button-bg, 8%);
|
||||||
|
|
||||||
// Footer
|
// Footer
|
||||||
@footer-height: 80px;
|
@footer-height: 80px;
|
||||||
@footer-border-color: @border-color;
|
@footer-border-color: @border-color;
|
||||||
|
|
@ -116,6 +120,12 @@
|
||||||
@block-shadow-2: 0 3px 3px 0 rgba(0, 0, 0, 0.14), 0 1px 7px 0 rgba(0, 0, 0, 0.12);
|
@block-shadow-2: 0 3px 3px 0 rgba(0, 0, 0, 0.14), 0 1px 7px 0 rgba(0, 0, 0, 0.12);
|
||||||
@block-shadow-3: 0 3px 3px 0 rgba(0, 0, 0, 0.16);
|
@block-shadow-3: 0 3px 3px 0 rgba(0, 0, 0, 0.16);
|
||||||
|
|
||||||
|
// ----------------------------------
|
||||||
|
// Dropdown
|
||||||
|
// ----------------------------------
|
||||||
|
|
||||||
|
@dropdown-shadow: 0 2px 4px rgba(0,0,0,0.1);
|
||||||
|
|
||||||
// ----------------------------------
|
// ----------------------------------
|
||||||
// Blankslate
|
// Blankslate
|
||||||
// ----------------------------------
|
// ----------------------------------
|
||||||
|
|
|
||||||
|
|
@ -111,10 +111,10 @@ $di->setShared('router', function() {
|
||||||
'action' => 'oauth'
|
'action' => 'oauth'
|
||||||
))->setName('oauth');
|
))->setName('oauth');
|
||||||
|
|
||||||
$router->add('/user', array(
|
$router->add('/settings', array(
|
||||||
'controller' => 'user',
|
'controller' => 'user',
|
||||||
'action' => 'profile',
|
'action' => 'settings',
|
||||||
))->setName('profile');
|
))->setName('user-settings');
|
||||||
|
|
||||||
return $router;
|
return $router;
|
||||||
});
|
});
|
||||||
|
|
|
||||||
34
app/controllers/UserController.php
Normal file
34
app/controllers/UserController.php
Normal file
|
|
@ -0,0 +1,34 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
use Form\UserSettings as UserSettingsForm;
|
||||||
|
|
||||||
|
class UserController extends ControllerBase
|
||||||
|
{
|
||||||
|
public function settingsAction()
|
||||||
|
{
|
||||||
|
$user = $this->_getAuth()->getUser();
|
||||||
|
|
||||||
|
$form = new UserSettingsForm($user);
|
||||||
|
|
||||||
|
if ($this->request->isPost()) {
|
||||||
|
$data = $this->request->getPost();
|
||||||
|
|
||||||
|
if ($form->isValid($data)) {
|
||||||
|
|
||||||
|
$new_pw = $form->getValue('passwordNew');
|
||||||
|
if (strlen($new_pw) > 0) {
|
||||||
|
$hash = password_hash($new_pw, PASSWORD_BCRYPT);
|
||||||
|
$user->setPassword($hash);
|
||||||
|
}
|
||||||
|
$user->save();
|
||||||
|
$form->initialize();
|
||||||
|
|
||||||
|
$this->flash->message('success', 'Settings saved!');
|
||||||
|
} else {
|
||||||
|
$this->flash->message('error', 'Could not save settings');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->view->form = $form;
|
||||||
|
}
|
||||||
|
}
|
||||||
235
app/forms/UserSettings.php
Normal file
235
app/forms/UserSettings.php
Normal file
|
|
@ -0,0 +1,235 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Form;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Models
|
||||||
|
*/
|
||||||
|
use Model\Data\User as UserModel;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Phalcon Form
|
||||||
|
*/
|
||||||
|
use Phalcon\Forms\Form as FormBase,
|
||||||
|
Phalcon\Forms\Element as FormElement;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Element types
|
||||||
|
*/
|
||||||
|
use Phalcon\Forms\Element\Text,
|
||||||
|
Phalcon\Forms\Element\Password,
|
||||||
|
Phalcon\Forms\Element\Submit;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Validators
|
||||||
|
*/
|
||||||
|
use Phalcon\Validation\Validator\Callback as CallbackValidator,
|
||||||
|
Phalcon\Validation\Validator\Uniqueness as UniquenessValidator,
|
||||||
|
\Validation\Validator\Alpha as AlphaValidator,
|
||||||
|
Phalcon\Validation\Validator\Alnum as AlnumValidator,
|
||||||
|
Phalcon\Validation\Validator\PresenceOf as PresenceOfValidator,
|
||||||
|
Phalcon\Validation\Validator\Email as EmailValidator,
|
||||||
|
Phalcon\Validation\Validator\Confirmation as ConfirmationValidator,
|
||||||
|
Phalcon\Validation\Validator\StringLength as StringLengthValidator,
|
||||||
|
Phalcon\Validation\Validator\Identical as IdenticalValidator;
|
||||||
|
|
||||||
|
|
||||||
|
class UserSettings extends FormBase
|
||||||
|
{
|
||||||
|
public function initialize()
|
||||||
|
{
|
||||||
|
$this->setValidation(new \Phalcon\Validation());
|
||||||
|
|
||||||
|
// Id
|
||||||
|
$id = new Text('id', array(
|
||||||
|
'class' => 'form-control',
|
||||||
|
'readonly' => '',
|
||||||
|
));
|
||||||
|
$id->addValidator(new IdenticalValidator([
|
||||||
|
'accepted' => $this->getEntity()->getId(),
|
||||||
|
]));
|
||||||
|
|
||||||
|
$id->setLabel('ID');
|
||||||
|
$this->add($id);
|
||||||
|
|
||||||
|
// Username
|
||||||
|
$username = new Text('username', array(
|
||||||
|
'class' => 'form-control',
|
||||||
|
'placeholder' => 'Username',
|
||||||
|
));
|
||||||
|
|
||||||
|
$username->setLabel('Username');
|
||||||
|
|
||||||
|
$username->addValidator(new AlnumValidator());
|
||||||
|
|
||||||
|
$validator = new UniquenessValidator(array(
|
||||||
|
'model' => new UserModel(),
|
||||||
|
'message' => 'The username already exists.',
|
||||||
|
'attribute' => 'username',
|
||||||
|
'except' => [ $this->getEntity()->getUsername() ]
|
||||||
|
));
|
||||||
|
|
||||||
|
$username->addValidator($validator);
|
||||||
|
|
||||||
|
$this->add($username);
|
||||||
|
|
||||||
|
// Name
|
||||||
|
$name = new Text('name', array(
|
||||||
|
'class' => 'form-control',
|
||||||
|
'placeholder' => 'Name',
|
||||||
|
));
|
||||||
|
|
||||||
|
$name->setLabel('Name');
|
||||||
|
$name->addValidator(new AlphaValidator([
|
||||||
|
'allowSpace' => true,
|
||||||
|
'allowEmpty' => true,
|
||||||
|
]));
|
||||||
|
|
||||||
|
$this->add($name);
|
||||||
|
|
||||||
|
// Email
|
||||||
|
$email = new Text('email', array(
|
||||||
|
'class' => 'form-control',
|
||||||
|
'placeholder' => 'Email',
|
||||||
|
'readonly' => '',
|
||||||
|
));
|
||||||
|
|
||||||
|
$email->addValidator(new IdenticalValidator([
|
||||||
|
'accepted' => $this->getEntity()->getEmail(),
|
||||||
|
]));
|
||||||
|
|
||||||
|
$email->setLabel('Email');
|
||||||
|
|
||||||
|
$this->add($email);
|
||||||
|
|
||||||
|
// Passwords
|
||||||
|
$this->_passwords();
|
||||||
|
|
||||||
|
// Submit
|
||||||
|
$submit = new Submit('Save', array('class' => 'button button-default'));
|
||||||
|
$this->add($submit);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Password section
|
||||||
|
*/
|
||||||
|
protected function _passwords()
|
||||||
|
{
|
||||||
|
$current_pw = $this->getEntity()->getPassword();
|
||||||
|
|
||||||
|
// Current
|
||||||
|
if (strlen($current_pw) > 0) {
|
||||||
|
$current = new Password('passwordCurrent', array(
|
||||||
|
'class' => 'form-control',
|
||||||
|
));
|
||||||
|
$current->setLabel('Current password');
|
||||||
|
$this->add($current);
|
||||||
|
}
|
||||||
|
|
||||||
|
// New
|
||||||
|
$new = new Password('passwordNew', array(
|
||||||
|
'class' => 'form-control',
|
||||||
|
));
|
||||||
|
$new->setLabel('New password');
|
||||||
|
$this->add($new);
|
||||||
|
|
||||||
|
// Confirm
|
||||||
|
$confirm = new Password('passwordConfirm', array(
|
||||||
|
'class' => 'form-control',
|
||||||
|
));
|
||||||
|
$confirm->setLabel('Confirm');
|
||||||
|
$this->add($confirm);
|
||||||
|
|
||||||
|
// Validation
|
||||||
|
$validation = $this->getValidation();
|
||||||
|
|
||||||
|
if (strlen($current_pw) > 0) {
|
||||||
|
$validation->add('passwordCurrent', new CallbackValidator([
|
||||||
|
'callback' => function($data) {
|
||||||
|
$new_pw = $data['passwordNew'];
|
||||||
|
if (strlen($new_pw) > 0) {
|
||||||
|
$value = $data['passwordCurrent'];
|
||||||
|
$hash = $this->getEntity()->getPassword();
|
||||||
|
|
||||||
|
// Only fail if there is a password and they did not match.
|
||||||
|
if (strlen($hash) > 0 && password_verify($value, $hash) === false) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
},
|
||||||
|
'message' => 'Password is not valid.'
|
||||||
|
]));
|
||||||
|
}
|
||||||
|
|
||||||
|
$validation->add('passwordNew', new StringLengthValidator([
|
||||||
|
'allowEmpty' => true,
|
||||||
|
'min' => 8,
|
||||||
|
'messageMinimum' => 'Password must be atleast 8 characters long',
|
||||||
|
]));
|
||||||
|
|
||||||
|
$validation->add('passwordConfirm', new ConfirmationValidator([
|
||||||
|
'message' => 'Passwords does not match',
|
||||||
|
'with' => 'passwordNew',
|
||||||
|
]));
|
||||||
|
}
|
||||||
|
|
||||||
|
public function renderDecorated($name, $opt = [])
|
||||||
|
{
|
||||||
|
$options = [
|
||||||
|
'label-class' => 'control-label',
|
||||||
|
'class' => 'col-sm-10',
|
||||||
|
'message' => ''
|
||||||
|
];
|
||||||
|
|
||||||
|
$ele = $this->get($name);
|
||||||
|
|
||||||
|
if (isset($opt['label-length'])) {
|
||||||
|
$length = (int) $opt['label-length'];
|
||||||
|
} else {
|
||||||
|
$length = 2;
|
||||||
|
}
|
||||||
|
$options['label-class'] .= ' col-sm-' . $length;
|
||||||
|
|
||||||
|
if (isset($opt['length'])) {
|
||||||
|
|
||||||
|
$len = $opt['length'];
|
||||||
|
|
||||||
|
if ($len === 'full') {
|
||||||
|
$options['class'] = '';
|
||||||
|
} else {
|
||||||
|
$options['class'] = 'col-sm-' . $opt['length'];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($ele->hasMessages()) {
|
||||||
|
$options['class'] .= ' has-error';
|
||||||
|
$options['message'] = $ele->getMessages()->current();
|
||||||
|
}
|
||||||
|
|
||||||
|
return $this->_render($ele, $options);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function _render(FormElement $ele, $opt)
|
||||||
|
{
|
||||||
|
$xhtml = '';
|
||||||
|
|
||||||
|
if (strlen($ele->getLabel()) > 0) {
|
||||||
|
|
||||||
|
$xhtml .= sprintf(
|
||||||
|
'<label class="%s" for="%s">%s</label>',
|
||||||
|
$opt['label-class'], $ele->getName(), $ele->getLabel());
|
||||||
|
}
|
||||||
|
|
||||||
|
$xhtml .= '<div class="' . $opt['class'] . '">'
|
||||||
|
. $ele->render();
|
||||||
|
|
||||||
|
if (strlen($opt['message']) > 0) {
|
||||||
|
$xhtml .= '<span class="help-block">' . $opt['message'] . '</span>';
|
||||||
|
}
|
||||||
|
|
||||||
|
$xhtml .= '</div>';
|
||||||
|
|
||||||
|
return $xhtml;
|
||||||
|
}
|
||||||
|
}
|
||||||
66
app/library/Validation/Validator/Alpha.php
Normal file
66
app/library/Validation/Validator/Alpha.php
Normal file
|
|
@ -0,0 +1,66 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Validation\Validator;
|
||||||
|
|
||||||
|
use Phalcon\Validation\Message;
|
||||||
|
use Phalcon\Validation\Validator as BaseValidator;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The same as the default Alpha validator shipped with phalcon.
|
||||||
|
* But this validator supports the option "allowSpace" that also
|
||||||
|
* allow whitespaces.
|
||||||
|
*
|
||||||
|
* @package Validation\Validator
|
||||||
|
*/
|
||||||
|
class Alpha extends BaseValidator
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Executes the validation
|
||||||
|
*
|
||||||
|
* @param mixed $validation
|
||||||
|
* @param string $attribute
|
||||||
|
* @return bool
|
||||||
|
*/
|
||||||
|
public function validate(\Phalcon\Validation $validation, $attribute)
|
||||||
|
{
|
||||||
|
$allowSpace = $this->getOption('allowSpace', false);
|
||||||
|
|
||||||
|
$charlist = '[:alpha:]';
|
||||||
|
if ($allowSpace) {
|
||||||
|
$charlist .= '[:space:]';
|
||||||
|
}
|
||||||
|
|
||||||
|
$value = $validation->getValue($attribute);
|
||||||
|
|
||||||
|
if (preg_match("/[^{$charlist}]/imu", $value)) {
|
||||||
|
|
||||||
|
$label = $this->getOption('label');
|
||||||
|
if (empty($label)) {
|
||||||
|
$label = $validation->getLabel($attribute);
|
||||||
|
}
|
||||||
|
|
||||||
|
$message = $this->getOption('message');
|
||||||
|
if (empty($message)) {
|
||||||
|
$message = $validation->getDefaultMessage('Alpha');
|
||||||
|
}
|
||||||
|
|
||||||
|
//var_dump($message);exit;
|
||||||
|
|
||||||
|
$replace = [ ":field" => $label ];
|
||||||
|
|
||||||
|
$code = $this->getOption("code");
|
||||||
|
if (is_array($code)) {
|
||||||
|
$code = $code[$attribute];
|
||||||
|
}
|
||||||
|
|
||||||
|
$message = str_replace(array_keys($replace), $replace, $message);
|
||||||
|
|
||||||
|
$msg = new Message($message, $attribute, "Alpha", $code);
|
||||||
|
$validation->appendMessage($msg);
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -5,14 +5,25 @@
|
||||||
<i class="icon ion-navicon-round"></i>
|
<i class="icon ion-navicon-round"></i>
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
<ul class="navigation-user-menu">
|
<div class="navigation-user-menu">
|
||||||
{% if auth.hasIdentity() %}
|
{% if auth.hasIdentity() %}
|
||||||
<li>{{ icon('android-person') }} Signed in as <strong>{{ auth.getUser().username }}</strong></li>
|
<div class="navigation-user-menu-dropdown">
|
||||||
<li>{{ link_to(['for': 'logout'], '<i class="icon ion-log-out"></i> Log out') }}</li>
|
<a id="user-dropdown-button" class="navigation-user-menu-dropdown-button"
|
||||||
|
data-toggle="dropdown" role="button" aria-haspopup="true" aria-expanded="false">
|
||||||
|
|
||||||
|
{{ icon('android-person') }} <strong>{{ auth.getUser().username }}</strong>
|
||||||
|
</a>
|
||||||
|
|
||||||
|
<ul aria-labelledby="user-dropdown" class="navigation-user-menu-dropdown-list">
|
||||||
|
<li>{{ link_to(['for': 'user-settings'], '<i class="icon ion-gear-a"></i> Settings') }}</li>
|
||||||
|
<li role="separator" class="divider"></li>
|
||||||
|
<li>{{ link_to(['for': 'logout'], '<i class="icon ion-log-out"></i> Log out') }}</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
{% else %}
|
{% else %}
|
||||||
<li>{{ link_to(['for': 'login'], '<i class="icon ion-log-in"></i> Login', 'class': 'login-button') }}</li>
|
<div class="navigation-user-menu-login">{{ link_to(['for': 'login'], '<i class="icon ion-log-in"></i> Login', 'class': 'login-button') }}</div>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</ul>
|
</div>
|
||||||
|
|
||||||
<nav class="navigation-menu collapse" id="main-menu">
|
<nav class="navigation-menu collapse" id="main-menu">
|
||||||
{{ menu.render(0) }}
|
{{ menu.render(0) }}
|
||||||
|
|
|
||||||
45
app/views/user/settings.volt
Normal file
45
app/views/user/settings.volt
Normal file
|
|
@ -0,0 +1,45 @@
|
||||||
|
|
||||||
|
<div class="section">
|
||||||
|
|
||||||
|
<form class="form-horizontal" method="post" action="">
|
||||||
|
|
||||||
|
<div class="form-group">
|
||||||
|
{{ form.renderDecorated('username', [ 'length': 7 ]) }}
|
||||||
|
{{ form.renderDecorated('id', [ 'length': 2, 'label-length' : 1 ]) }}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="form-group">
|
||||||
|
{{ form.renderDecorated('name') }}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="form-group">
|
||||||
|
{{ form.renderDecorated('email') }}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="form-group">
|
||||||
|
<div class="col-sm-offset-2 col-sm-10">
|
||||||
|
<h4>Password</h4>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{% if form.has('passwordCurrent') %}
|
||||||
|
<div class="form-group">
|
||||||
|
{{ form.renderDecorated('passwordCurrent') }}
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
<div class="form-group">
|
||||||
|
{{ form.renderDecorated('passwordNew') }}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="form-group">
|
||||||
|
{{ form.renderDecorated('passwordConfirm') }}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="form-group">
|
||||||
|
<div class="col-sm-offset-2 col-sm-10">
|
||||||
|
{{ form.render('Save') }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
Reference in a new issue