Premier commit
This commit is contained in:
74
modules/user/AccessChecker.php
Normal file
74
modules/user/AccessChecker.php
Normal file
@@ -0,0 +1,74 @@
|
||||
<?php
|
||||
/**
|
||||
* This file is part of the Piko user module
|
||||
*
|
||||
* @copyright 2020 Sylvain PHILIP.
|
||||
* @license LGPL-3.0; see LICENSE.txt
|
||||
* @link https://github.com/piko-framework/piko-user
|
||||
*/
|
||||
namespace app\modules\user;
|
||||
|
||||
use app\modules\user\models\User;
|
||||
|
||||
/**
|
||||
* Access checker class
|
||||
*
|
||||
* @author Sylvain PHILIP <contact@sphilip.com>
|
||||
*/
|
||||
class AccessChecker
|
||||
{
|
||||
public static $adminRole;
|
||||
|
||||
/**
|
||||
* User roles
|
||||
*
|
||||
* @var null|array
|
||||
*/
|
||||
private static $roles = null;
|
||||
|
||||
/**
|
||||
* User permissions
|
||||
*
|
||||
* @var null|array
|
||||
*/
|
||||
private static $permissions = null;
|
||||
|
||||
/**
|
||||
* Check Permission or role access
|
||||
*
|
||||
* @param int $userId The user Id
|
||||
* @param string $permission The permission or role name
|
||||
* @return bool
|
||||
*
|
||||
* @see \piko\User
|
||||
*/
|
||||
public static function checkAccess($userId, string $permission) : bool
|
||||
{
|
||||
$identity = User::findIdentity($userId);
|
||||
|
||||
if ($identity !== null) {
|
||||
|
||||
if (static::$roles === null) {
|
||||
static::$roles = Rbac::getUserRoles($identity->id);
|
||||
}
|
||||
|
||||
if (in_array(static::$adminRole, static::$roles)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (in_array($permission, static::$roles)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (static::$permissions === null) {
|
||||
static::$permissions = Rbac::getUserPermissions($identity->id);
|
||||
}
|
||||
|
||||
if (in_array($permission, static::$permissions)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
73
modules/user/Module.php
Normal file
73
modules/user/Module.php
Normal file
@@ -0,0 +1,73 @@
|
||||
<?php
|
||||
/**
|
||||
* This file is part of the Piko user module
|
||||
*
|
||||
* @copyright 2020 Sylvain PHILIP.
|
||||
* @license LGPL-3.0; see LICENSE.txt
|
||||
* @link https://github.com/piko-framework/piko-user
|
||||
*
|
||||
* Routes :
|
||||
* /user/default/login : Process login
|
||||
* /user/default/logout : Process logout
|
||||
* /user/default/register : Process user registration
|
||||
* /user/default/edit : User account form
|
||||
* /user/admin/users : Manage users, roles, permissions
|
||||
*/
|
||||
namespace app\modules\user;
|
||||
|
||||
use PDO;
|
||||
use app\modules\user\models\User;
|
||||
use app\modules\user\Rbac;
|
||||
|
||||
|
||||
/**
|
||||
* User module class
|
||||
*
|
||||
* @author Sylvain PHILIP <contact@sphilip.com>
|
||||
*/
|
||||
class Module extends \Piko\Module
|
||||
{
|
||||
/**
|
||||
* The admin role
|
||||
* @var string
|
||||
*/
|
||||
public $adminRole = 'admin';
|
||||
|
||||
/**
|
||||
* Allow user registration
|
||||
*
|
||||
* @var boolean
|
||||
*/
|
||||
public $allowUserRegistration = false;
|
||||
|
||||
/**
|
||||
* Min length of the user password
|
||||
*
|
||||
* @var integer
|
||||
*/
|
||||
public $passwordMinLength = 8;
|
||||
|
||||
public function bootstrap()
|
||||
{
|
||||
$pdo = $this->application->getComponent('PDO');
|
||||
assert($pdo instanceof PDO);
|
||||
|
||||
User::$pdo = $pdo;
|
||||
Rbac::$db = $pdo;
|
||||
AccessChecker::$adminRole = $this->adminRole;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
* @see \piko\Module::init()
|
||||
*/
|
||||
protected function init()
|
||||
{
|
||||
/* @var $i18n \piko\i18n */
|
||||
// $i18n = Piko::get('i18n');
|
||||
// $i18n->addTranslation('user', __DIR__ . '/messages');
|
||||
|
||||
// parent::init();
|
||||
}
|
||||
|
||||
}
|
||||
263
modules/user/Rbac.php
Normal file
263
modules/user/Rbac.php
Normal file
@@ -0,0 +1,263 @@
|
||||
<?php
|
||||
/**
|
||||
* This file is part of the Piko user module
|
||||
*
|
||||
* @copyright 2020 Sylvain PHILIP.
|
||||
* @license LGPL-3.0; see LICENSE.txt
|
||||
* @link https://github.com/piko-framework/piko-user
|
||||
*/
|
||||
namespace app\modules\user;
|
||||
|
||||
use PDO;
|
||||
|
||||
/**
|
||||
* Rbac utility class
|
||||
*
|
||||
* @author Sylvain PHILIP <contact@sphilip.com>
|
||||
*/
|
||||
class Rbac
|
||||
{
|
||||
public static PDO $db;
|
||||
|
||||
/**
|
||||
* Create a role
|
||||
*
|
||||
* @param string $name The role name
|
||||
* @param string $description The role description
|
||||
* @return int The role Id
|
||||
*/
|
||||
public static function createRole($name, $description = '')
|
||||
{
|
||||
$query = 'INSERT INTO `auth_role` (`name`, `description`) VALUES (?, ?)';
|
||||
|
||||
static::$db->beginTransaction();
|
||||
$st = static::$db->prepare($query);
|
||||
$st->execute([$name, $description]);
|
||||
static::$db->commit();
|
||||
|
||||
return static::$db->lastInsertId();
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if the role exists
|
||||
*
|
||||
* @param string $name The role name
|
||||
* @return boolean
|
||||
*/
|
||||
public static function roleExists($name)
|
||||
{
|
||||
$st = static::$db->prepare('SELECT COUNT(`id`) FROM `auth_role` WHERE `name` = :name');
|
||||
$st->execute(['name' => $name]);
|
||||
|
||||
return ((int) $st->fetchColumn() > 0) ? true : false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the role Id
|
||||
*
|
||||
* @param string $name The role name
|
||||
* @return int The role Id (0 if the role is not found)
|
||||
*/
|
||||
public static function getRoleId($name)
|
||||
{
|
||||
$st = static::$db->prepare('SELECT `id` FROM `auth_role` WHERE `name` = :name');
|
||||
$st->execute(['name' => $name]);
|
||||
|
||||
return (int) $st->fetchColumn();
|
||||
}
|
||||
|
||||
/**
|
||||
* Assign a role to an user
|
||||
*
|
||||
* @param int $userId The user Id
|
||||
* @param string $roleName The role name
|
||||
* @throws \RuntimeException If the role doesn't exists
|
||||
*/
|
||||
public static function assignRole($userId, $roleName)
|
||||
{
|
||||
$roleId = static::getRoleId($roleName);
|
||||
|
||||
if (!$roleId) {
|
||||
throw new \RuntimeException("Role $roleName doesn't exists");
|
||||
}
|
||||
|
||||
$query = 'INSERT INTO `auth_assignment` (`role_id`, `user_id`) VALUES (?, ?)';
|
||||
|
||||
static::$db->beginTransaction();
|
||||
$st = static::$db->prepare($query);
|
||||
$st->execute([$roleId, $userId]);
|
||||
static::$db->commit();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get user roles
|
||||
*
|
||||
* @param int $userId The user Id
|
||||
* @return array An array containing user roles
|
||||
*/
|
||||
public static function getUserRoles($userId)
|
||||
{
|
||||
$query = 'SELECT `auth_role`.`name` FROM `auth_role` '
|
||||
. 'INNER JOIN `auth_assignment` ON `auth_assignment`.`role_id` = `auth_role`.`id` '
|
||||
. 'WHERE `auth_assignment`.`user_id` = :user_id '
|
||||
. 'GROUP BY role_id';
|
||||
$st = static::$db->prepare($query);
|
||||
$st->execute(['user_id' => $userId]);
|
||||
|
||||
return $st->fetchAll(\PDO::FETCH_COLUMN);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get user roles ids
|
||||
*
|
||||
* @param int $userId The user Id
|
||||
* @return array An array containing user role ids
|
||||
*/
|
||||
public static function getUserRoleIds($userId)
|
||||
{
|
||||
$query = 'SELECT role_id FROM `auth_assignment` WHERE user_id = :user_id';
|
||||
$sth = static::$db->prepare($query);
|
||||
$sth->execute(['user_id' => $userId]);
|
||||
|
||||
return $sth->fetchAll(\PDO::FETCH_COLUMN);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get user permissions
|
||||
*
|
||||
* @param int $userId The user Id
|
||||
* @return array An array containing user permissions
|
||||
*/
|
||||
public static function getUserPermissions($userId)
|
||||
{
|
||||
$query = 'SELECT p.`name` FROM `auth_permission` AS p '
|
||||
. 'INNER JOIN `auth_role_has_permission` AS ap ON ap.`permission_id` = p.`id` '
|
||||
. 'INNER JOIN `auth_assignment` AS aa ON aa.`role_id` = ap.`role_id` '
|
||||
. 'WHERE aa.`user_id` = :user_id '
|
||||
. 'GROUP BY permission_id';
|
||||
|
||||
$st = static::$db->prepare($query);
|
||||
$st->execute(['user_id' => $userId]);
|
||||
|
||||
return $st->fetchAll(\PDO::FETCH_COLUMN);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get role permissions
|
||||
*
|
||||
* @param string $roleName The role name
|
||||
* @return array An array of permissions as string
|
||||
*/
|
||||
public static function getRolePermissions($roleName): array
|
||||
{
|
||||
$roleId = static::getRoleId($roleName);
|
||||
|
||||
if (!$roleId) {
|
||||
throw new \RuntimeException("Role $roleName doesn't exists");
|
||||
}
|
||||
|
||||
$query = 'SELECT p.`name` FROM `auth_permission` AS p '
|
||||
. 'INNER JOIN `auth_role_has_permission` AS ap ON ap.`permission_id` = p.`id` '
|
||||
. 'WHERE ap.`role_id` = :role_id '
|
||||
. 'GROUP BY permission_id';
|
||||
$st = static::$db->prepare($query);
|
||||
$st->execute(['role_id' => $roleId]);
|
||||
|
||||
return $st->fetchAll(\PDO::FETCH_COLUMN);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get role permission ids
|
||||
*
|
||||
* @param string $roleName The role name
|
||||
* @return array An array of permission ids
|
||||
*/
|
||||
public static function getRolePermissionIds($roleName)
|
||||
{
|
||||
$roleId = static::getRoleId($roleName);
|
||||
|
||||
if (!$roleId) {
|
||||
throw new \RuntimeException("Role $roleName doesn't exists");
|
||||
}
|
||||
|
||||
$query = 'SELECT permission_id FROM `auth_role_has_permission` WHERE role_id = :role_id';
|
||||
$sth = static::$db->prepare($query);
|
||||
$sth->execute(['role_id' => $roleId]);
|
||||
|
||||
return $sth->fetchAll(\PDO::FETCH_COLUMN);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a permission
|
||||
*
|
||||
* @param string $name The permission name
|
||||
* @return int The permission id
|
||||
*/
|
||||
public static function createPermission($name): int
|
||||
{
|
||||
$query = 'INSERT INTO `auth_permission` (`name`) VALUES (?)';
|
||||
|
||||
static::$db->beginTransaction();
|
||||
$st = static::$db->prepare($query);
|
||||
$st->execute([$name]);
|
||||
static::$db->commit();
|
||||
|
||||
return (int) static::$db->lastInsertId();
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if the permission exists
|
||||
*
|
||||
* @param string $name The permission name
|
||||
* @return boolean
|
||||
*/
|
||||
public static function permissionExists($name): bool
|
||||
{
|
||||
$st = static::$db->prepare('SELECT COUNT(`id`) FROM `auth_permission` WHERE `name` = :name');
|
||||
$st->execute(['name' => $name]);
|
||||
|
||||
return ((int) $st->fetchColumn() > 0) ? true : false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the permission Id
|
||||
*
|
||||
* @param string $name The permission name
|
||||
* @return int The permission id (0 if the permission is not found)
|
||||
*/
|
||||
public static function getPermissionId($name): int
|
||||
{
|
||||
$st = static::$db->prepare('SELECT `id` FROM `auth_permission` WHERE `name` = :name');
|
||||
$st->execute(['name' => $name]);
|
||||
|
||||
return (int) $st->fetchColumn();
|
||||
}
|
||||
|
||||
/**
|
||||
* Assign a permission to a role
|
||||
*
|
||||
* @param string $roleName The role name
|
||||
* @param string $permissionName The permission name
|
||||
* @throws \RuntimeException If the role or the permission doesn't exists
|
||||
*/
|
||||
public static function assignPermission($roleName, $permissionName): void
|
||||
{
|
||||
$roleId = static::getRoleId($roleName);
|
||||
$permissionId = static::getPermissionId($permissionName);
|
||||
|
||||
if (!$roleId) {
|
||||
throw new \RuntimeException("Role $roleName doesn't exists");
|
||||
}
|
||||
|
||||
if (!$permissionId) {
|
||||
throw new \RuntimeException("Permission $permissionName doesn't exists");
|
||||
}
|
||||
|
||||
$query = 'INSERT INTO `auth_role_has_permission` (`role_id`, `permission_id`) VALUES (?, ?)';
|
||||
|
||||
static::$db->beginTransaction();
|
||||
$st = static::$db->prepare($query);
|
||||
$st->execute([$roleId, $permissionId]);
|
||||
static::$db->commit();
|
||||
}
|
||||
}
|
||||
248
modules/user/controllers/AdminController.php
Normal file
248
modules/user/controllers/AdminController.php
Normal file
@@ -0,0 +1,248 @@
|
||||
<?php
|
||||
/**
|
||||
* This file is part of the Piko user module
|
||||
*
|
||||
* @copyright 2020 Sylvain PHILIP.
|
||||
* @license LGPL-3.0; see LICENSE.txt
|
||||
* @link https://github.com/piko-framework/piko-user
|
||||
*/
|
||||
namespace app\modules\user\controllers;
|
||||
|
||||
use Piko\HttpException;
|
||||
use function Piko\I18n\__;
|
||||
use Piko\User as PikoUser;
|
||||
use app\modules\user\models\Role;
|
||||
use app\modules\user\models\User;
|
||||
use app\modules\user\models\Permission;
|
||||
|
||||
/**
|
||||
* User admin controller
|
||||
*
|
||||
* @author Sylvain PHILIP <contact@sphilip.com>
|
||||
*/
|
||||
class AdminController extends \Piko\Controller
|
||||
{
|
||||
protected PikoUser $user;
|
||||
protected \PDO $db;
|
||||
|
||||
public function init(): void
|
||||
{
|
||||
$app = $this->module->getApplication();
|
||||
|
||||
$user = $app->getComponent('Piko\User');
|
||||
assert($user instanceof PikoUser);
|
||||
$this->user = $user;
|
||||
|
||||
$db = $app->getComponent('PDO');
|
||||
assert($db instanceof \PDO);
|
||||
$this->db = $db;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
* @see \piko\Controller::runAction()
|
||||
*/
|
||||
public function runAction($id)
|
||||
{
|
||||
assert($this->module instanceof \app\modules\user\Module);
|
||||
|
||||
if (!$this->user->can($this->module->adminRole)) {
|
||||
throw new HttpException('Not authorized.', 403);
|
||||
}
|
||||
|
||||
return parent::runAction($id);
|
||||
}
|
||||
|
||||
/**
|
||||
* Render users view
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function usersAction()
|
||||
{
|
||||
return $this->render('users', [
|
||||
'users' => User::find()
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Render User form and create or update user
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function editAction(int $id = 0)
|
||||
{
|
||||
$user = new User($this->db);
|
||||
|
||||
if ($id) {
|
||||
$user->load($id);
|
||||
}
|
||||
|
||||
$user->scenario = User::SCENARIO_ADMIN;
|
||||
$message = false;
|
||||
|
||||
$post = $this->request->getParsedBody();
|
||||
|
||||
if (!empty($post)) {
|
||||
|
||||
$user->bind($post);
|
||||
|
||||
if ($user->isValid() && $user->save()) {
|
||||
$message['type'] = 'success';
|
||||
$message['content'] = __('user', 'User successfully saved');
|
||||
} else {
|
||||
$message['type'] = 'danger';
|
||||
$message['content'] = __('user', 'Save error!') . implode(' ', $user->errors);
|
||||
}
|
||||
}
|
||||
|
||||
return $this->render('edit', [
|
||||
'user' => $user,
|
||||
'message' => $message,
|
||||
'roles' => Role::find('`name` ASC'),
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete users
|
||||
*/
|
||||
public function deleteAction()
|
||||
{
|
||||
$post = $this->request->getParsedBody();
|
||||
$ids = isset($post['items'])? $post['items'] : [];
|
||||
|
||||
foreach ($ids as $id) {
|
||||
$user = new User($id);
|
||||
$user->delete();
|
||||
}
|
||||
|
||||
$this->redirect($this->getUrl('user/admin/users'));
|
||||
}
|
||||
|
||||
/**
|
||||
* Render roles view
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function rolesAction()
|
||||
{
|
||||
return $this->render('roles', [
|
||||
'roles' => Role::find(),
|
||||
'permissions' => Permission::find('`name` ASC'),
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create/update role (AJAX)
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function editRoleAction(int $id = 0)
|
||||
{
|
||||
$role = new Role($this->db);
|
||||
|
||||
if ($id) {
|
||||
$role->load($id);
|
||||
}
|
||||
|
||||
$role->scenario = Role::SCENARIO_ADMIN;
|
||||
|
||||
$post = $this->request->getParsedBody();
|
||||
|
||||
$response = [
|
||||
'role' => $role
|
||||
];
|
||||
|
||||
if (!empty($post)) {
|
||||
|
||||
$role->bind($post);
|
||||
|
||||
if ($role->isValid() && $role->save()) {
|
||||
$response['status'] = 'success';
|
||||
} else {
|
||||
$response['status'] = 'error';
|
||||
}
|
||||
}
|
||||
|
||||
return $this->jsonResponse($response);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Delete roles
|
||||
*/
|
||||
public function deleteRolesAction()
|
||||
{
|
||||
$post = $this->request->getParsedBody();
|
||||
$ids = isset($post['items'])? $post['items'] : [];
|
||||
|
||||
foreach ($ids as $id) {
|
||||
$item = new Role($id);
|
||||
$item->delete();
|
||||
}
|
||||
|
||||
$this->redirect($this->getUrl('user/admin/roles'));
|
||||
}
|
||||
|
||||
/**
|
||||
* Render permissions view
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function permissionsAction()
|
||||
{
|
||||
return $this->render('permissions', [
|
||||
'permissions' => Permission::find()
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create/update permission (AJAX)
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function editPermissionAction(int $id = 0)
|
||||
{
|
||||
$permission = new Permission($this->db);
|
||||
|
||||
if ($id) {
|
||||
$permission->load($id);
|
||||
}
|
||||
|
||||
$response = [
|
||||
'permission' => $permission
|
||||
];
|
||||
|
||||
$post = $this->request->getParsedBody();
|
||||
|
||||
if (!empty($post)) {
|
||||
|
||||
$permission->bind($post);
|
||||
|
||||
if ($permission->isValid() && $permission->save()) {
|
||||
$response['status'] = 'success';
|
||||
} else {
|
||||
$response['status'] = 'error';
|
||||
$response['error'] = array_pop($permission->getErrors());
|
||||
}
|
||||
}
|
||||
|
||||
return $this->jsonResponse($response);
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete permissions
|
||||
*/
|
||||
public function deletePermissionsAction()
|
||||
{
|
||||
$post = $this->request->getParsedBody();
|
||||
$ids = isset($post['items'])? $post['items'] : [];
|
||||
|
||||
foreach ($ids as $id) {
|
||||
$item = new Permission($id);
|
||||
$item->delete();
|
||||
}
|
||||
|
||||
$this->redirect($this->getUrl('user/admin/permissions'));
|
||||
}
|
||||
}
|
||||
295
modules/user/controllers/DefaultController.php
Normal file
295
modules/user/controllers/DefaultController.php
Normal file
@@ -0,0 +1,295 @@
|
||||
<?php
|
||||
/**
|
||||
* This file is part of the Piko user module
|
||||
*
|
||||
* @copyright 2020 Sylvain PHILIP.
|
||||
* @license LGPL-3.0; see LICENSE.txt
|
||||
* @link https://github.com/piko-framework/piko-user
|
||||
*/
|
||||
namespace app\modules\user\controllers;
|
||||
|
||||
use function Piko\I18n\__;
|
||||
use piko\HttpException;
|
||||
use app\modules\user\models\User;
|
||||
use Piko\User as PikoUser;
|
||||
|
||||
/**
|
||||
* User default controller
|
||||
*
|
||||
* @author Sylvain PHILIP <contact@sphilip.com>
|
||||
*/
|
||||
class DefaultController extends \Piko\Controller
|
||||
{
|
||||
protected PikoUser $user;
|
||||
protected \PDO $db;
|
||||
|
||||
public function init(): void
|
||||
{
|
||||
$app = $this->module->getApplication();
|
||||
|
||||
$user = $app->getComponent('Piko\User');
|
||||
assert($user instanceof PikoUser);
|
||||
$this->user = $user;
|
||||
|
||||
$db = $app->getComponent('PDO');
|
||||
assert($db instanceof \PDO);
|
||||
$this->db = $db;
|
||||
}
|
||||
|
||||
/**
|
||||
* Render and process user registration
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function registerAction()
|
||||
{
|
||||
if (!$this->user->isGuest()) {
|
||||
return $this->redirect('/');
|
||||
}
|
||||
|
||||
$message = false;
|
||||
$post = $this->request->getParsedBody();
|
||||
|
||||
if (!empty($post)) {
|
||||
|
||||
$user = new User($this->db);
|
||||
|
||||
$user->scenario = User::SCENARIO_REGISTER;
|
||||
|
||||
$user->bind($post);
|
||||
|
||||
if ($user->isValid() && $user->save()) {
|
||||
// $user->sendRegistrationConfirmation();
|
||||
$message['type'] = 'success';
|
||||
$message['content'] = __(
|
||||
'user',
|
||||
'Your account was created. Please activate it through the confirmation email that was sent to you.'
|
||||
);
|
||||
} else {
|
||||
$message['type'] = 'danger';
|
||||
$message['content'] = implode(', ', $user->errors);
|
||||
}
|
||||
}
|
||||
|
||||
return $this->render('register', [
|
||||
'message' => $message,
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Validate registration (AJAX)
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function checkRegistrationAction()
|
||||
{
|
||||
$errors = [];
|
||||
$this->layout = false;
|
||||
$post = $this->request->getParsedBody();
|
||||
|
||||
if (!empty($post)) {
|
||||
|
||||
$user = new User($this->db);
|
||||
$user->scenario = 'register';
|
||||
$user->bind($post);
|
||||
$user->isValid();
|
||||
$errors = $user->getErrors();
|
||||
}
|
||||
|
||||
return $this->jsonResponse($errors);
|
||||
}
|
||||
|
||||
/**
|
||||
* Render user activation confirmation
|
||||
*
|
||||
* @throws HttpException
|
||||
* @return string
|
||||
*/
|
||||
public function confirmationAction($token)
|
||||
{
|
||||
$user = User::findByAuthKey($token);
|
||||
|
||||
if (!$user) {
|
||||
throw new HttpException('Not found.', 404);
|
||||
}
|
||||
|
||||
$message = false;
|
||||
|
||||
if (!$user->isActivated()) {
|
||||
|
||||
if ($user->activate()) {
|
||||
$message['type'] = 'success';
|
||||
$message['content'] = __('user', 'Your account has been activated. You can now log in.');
|
||||
} else {
|
||||
$message['type'] = 'danger';
|
||||
$message['content'] = __(
|
||||
'user',
|
||||
'Unable to activate your account. Please contact the site manager.'
|
||||
);
|
||||
}
|
||||
} else {
|
||||
$message['type'] = 'warning';
|
||||
$message['content'] = __('user', 'Your account has already been activated.');
|
||||
}
|
||||
|
||||
return $this->render('login', ['message' => $message]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Render reminder password form and send email to change password
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function reminderAction()
|
||||
{
|
||||
$message = false;
|
||||
$post = $this->request->getParsedBody();
|
||||
|
||||
$reminder = $post['reminder']?? '';
|
||||
|
||||
if (!empty($reminder)) {
|
||||
|
||||
$user = User::findByUsername($reminder);
|
||||
|
||||
if (!$user) {
|
||||
$user = User::findByEmail($reminder);
|
||||
}
|
||||
|
||||
if ($user) {
|
||||
// $user->sendResetPassword();
|
||||
$message['type'] = 'success';
|
||||
$message['content'] = __(
|
||||
'user',
|
||||
'A link has been sent to you by email ({email}). It will allow you to recreate your password.',
|
||||
['email' => $user->email]
|
||||
);
|
||||
} else {
|
||||
$message['type'] = 'danger';
|
||||
$message['content'] = __('user', 'Account not found.');
|
||||
}
|
||||
}
|
||||
|
||||
return $this->render('reminder', [
|
||||
'message' => $message,
|
||||
'reminder' => $reminder,
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Render and process reset password
|
||||
*
|
||||
* @throws HttpException
|
||||
* @return string
|
||||
*/
|
||||
public function resetPasswordAction($token)
|
||||
{
|
||||
$user = User::findByAuthKey($token);
|
||||
|
||||
if (!$user) {
|
||||
throw new HttpException('Not found', 404);
|
||||
}
|
||||
|
||||
$message = false;
|
||||
$post = $this->request->getParsedBody();
|
||||
|
||||
if (!empty($post)) {
|
||||
$user->scenario = 'reset';
|
||||
|
||||
$user->bind($post);
|
||||
|
||||
if ($user->isValid() && $user->save()) {
|
||||
$message['type'] = 'success';
|
||||
$message['content'] = __('user', 'Your password has been successfully updated.');
|
||||
} else {
|
||||
$message['type'] = 'danger';
|
||||
$message['content'] = implode(', ', $user->errors);
|
||||
}
|
||||
}
|
||||
|
||||
return $this->render('reset', [
|
||||
'message' => $message,
|
||||
'user' => $user,
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Render user form and update changes
|
||||
*
|
||||
* @throws HttpException
|
||||
* @return string
|
||||
*/
|
||||
public function editAction()
|
||||
{
|
||||
if ($this->user->isGuest()) {
|
||||
throw new HttpException(__('user', 'You must be logged to access this page.'), 401);
|
||||
}
|
||||
|
||||
$identity = $this->user->getIdentity();
|
||||
|
||||
assert($identity instanceof User);
|
||||
|
||||
$message = false;
|
||||
$post = $this->request->getParsedBody();
|
||||
|
||||
if (!empty($post)) {
|
||||
$identity->bind($post);
|
||||
|
||||
if ($identity->isValid() && $identity->save()) {
|
||||
$message['type'] = 'success';
|
||||
$message['content'] = __('user', 'Changes saved!');
|
||||
} else {
|
||||
$message['type'] = 'danger';
|
||||
$message['content'] = implode(', ', $identity->getErrors());
|
||||
}
|
||||
}
|
||||
|
||||
return $this->render('edit', [
|
||||
'user' => $identity,
|
||||
'message' => $message,
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Render login form and process login
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function loginAction()
|
||||
{
|
||||
$message = false;
|
||||
$post = $this->request->getParsedBody();
|
||||
|
||||
if (!empty($post)) {
|
||||
$identity = User::findByUsername($post['username']);
|
||||
|
||||
if ($identity instanceof User && $identity->validatePassword($post['password'])) {
|
||||
|
||||
$this->user->login($identity);
|
||||
$identity->last_login_at = time();
|
||||
$identity->save();
|
||||
|
||||
return $this->redirect('/');
|
||||
|
||||
} else {
|
||||
$message['type'] = 'danger';
|
||||
$message['content'] = __('user', 'Authentication failure');
|
||||
}
|
||||
}
|
||||
|
||||
assert($this->module instanceof \app\modules\user\Module);
|
||||
|
||||
return $this->render('login', [
|
||||
'message' => $message,
|
||||
'canRegister' => $this->module->allowUserRegistration
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* User logout
|
||||
*/
|
||||
public function logoutAction()
|
||||
{
|
||||
$this->user->logout();
|
||||
$this->redirect('/');
|
||||
}
|
||||
}
|
||||
111
modules/user/messages/fr.php
Normal file
111
modules/user/messages/fr.php
Normal file
@@ -0,0 +1,111 @@
|
||||
<?php
|
||||
|
||||
$confirmationMailBody = <<<MSG
|
||||
Bonjour,
|
||||
|
||||
Merci de vous être inscrit sur {site_name}. Votre compte a été créé et doit être activé avant que vous puissiez l'utiliser.
|
||||
Pour l'activer, cliquez sur le lien ci-dessous ou copiez et collez le dans votre navigateur :
|
||||
|
||||
{link}
|
||||
|
||||
Après activation vous pourrez vous connecter sur {base_url} en utilisant l'identifiant suivant et le mot de passe utilisé à l'enregistrement :
|
||||
Identifiant : {username}
|
||||
MSG;
|
||||
|
||||
$resetPasswordMailBody = <<<MSG
|
||||
Bonjour,
|
||||
|
||||
Une demande de changement de mot passe a été effectuée pour votre compte sur {site_name}.
|
||||
|
||||
Votre identifiant est : {username}.
|
||||
|
||||
Pour changer votre mot de passe , cliquez sur le lien ci-dessous.
|
||||
|
||||
{link}
|
||||
|
||||
Merci.
|
||||
MSG;
|
||||
|
||||
return [
|
||||
'Users' => 'Utilisateurs',
|
||||
'Name' => 'Nom',
|
||||
'Username' => 'Identifiant',
|
||||
'Email' => 'Email',
|
||||
'Password' => 'Mot de passe',
|
||||
'Last login at' => 'Dernière connexion',
|
||||
'Created at' => 'Créé le',
|
||||
'Id' => 'Id',
|
||||
'Roles' => 'Rôles',
|
||||
'Role name' => 'Nom du rôle',
|
||||
'Role name must be filled in.' => 'Le nom du role doit être renseigné.',
|
||||
'Role already exists.' => 'Le rôle existe déjà.',
|
||||
'Description' => 'Description',
|
||||
'Role permissions' => 'Permissions du rôle',
|
||||
'Users management' => 'Gestion des utilisateurs',
|
||||
'Are you sure you want to perform this action?' => 'Êtes-vous certain de vouloir effectuer cette action ?',
|
||||
'Create user' => 'Nouvel utilisateur',
|
||||
'Edit user' => 'Modifier l\'utilisateur',
|
||||
'New role' => 'Nouveau rôle',
|
||||
'Delete' => 'Supprimer',
|
||||
'Close' => 'Fermer',
|
||||
'Cancel' => 'Annuler',
|
||||
'Permissions' => 'Permissions',
|
||||
'New permission' => 'Nouvelle permission',
|
||||
'Permission name' => 'Nom de la permission',
|
||||
'Permission name must be filled in.' => 'Le nom de la permission doit être renseigné.',
|
||||
'Permission already exists.' => 'La permission existe déjà',
|
||||
'User successfully saved' => 'Utilisateur correctement enregistré',
|
||||
'Save' => 'Enregistrer',
|
||||
'Save error!' => 'Erreur lors de l\'enregistrement',
|
||||
'Email must be filled in.' => 'L\'email doit être renseigné.',
|
||||
'{email} is not a valid email address.' => '{email} n\'est pas une adresse email valide.',
|
||||
'Username must be filled in.' => 'Le nom d\'utilisateur doit être renseigné.',
|
||||
'The username should only contain alphanumeric characters.' => 'Le nom d\'utilisateur ne doit contenir que des caractères alphanumériques.',
|
||||
'This email is already used.' => 'Cet email est déjà utilisé.',
|
||||
'This username is already used.' => 'Cet identifant est déjà utilisé.',
|
||||
'Password must be filled in.' => 'Le mot de passe doit être renseigné.',
|
||||
'Password is to short. Minimum {num}: characters.' => 'Mot de passe trop court. Minimum {num} caractères.',
|
||||
'Passwords are not the same.' => 'Les mots de passe ne sont pas identiques.',
|
||||
|
||||
// Register Account
|
||||
'Your account was created. Please activate it through the confirmation email that was sent to you.' => 'Votre compte a été créé. Merci de l\'activer via le mail de confirmation qui vous a été envoyé.',
|
||||
'confirmation_mail_body' => $confirmationMailBody,
|
||||
'Registration confirmation on {site_name}' => 'Confirmation de l\'inscription sur {site_name}',
|
||||
// Account activation
|
||||
'Your account has been activated. You can now log in.' => 'Votre compte a bien été activé. Vous pouvez désormais vous connecter.',
|
||||
'Unable to activate your account. Please contact the site manager.' => 'Impossible d\'activer votre compte. Merci de contacter le responsable du site.',
|
||||
'Your account has already been activated.' => 'Votre compte a déjà été activé.',
|
||||
// Password reset / reminder
|
||||
'A link has been sent to you by email ({email}). It will allow you to recreate your password.' => 'Un lien vous a été envoyé par email ({email}). Il vous permettra de recréer votre mot de passe.',
|
||||
'reset_password_mail_body' => $resetPasswordMailBody,
|
||||
'Password change request on {site_name}' => 'Demande de changement de mot de passe sur {site_name}',
|
||||
'Account not found.' => 'Compte innexistant',
|
||||
'Your password has been successfully updated.' => 'Votre mot de passe a bien été modifié.',
|
||||
'Forget password' => 'Mot de passe oublié',
|
||||
'Your email or your username' => 'Votre email ou votre identifiant',
|
||||
'Send' => 'Envoyer',
|
||||
'Change your account ({account}) password' => 'Réinitialisation du mot de passe pour le compte : {account}',
|
||||
// Edit account
|
||||
'You must be logged to access this page.' => 'Vous devez vous connecter pour accéder à cette page.',
|
||||
'Changes saved!' => 'Modifications enregistrées !',
|
||||
'Edit your account' => 'Modification de votre compte',
|
||||
'Password (leave blank to keep the same)' => 'Mot de passe (laisser vide pour garder le même)',
|
||||
'Last name' => 'Nom',
|
||||
'First name' => 'Prénom',
|
||||
'Company' => 'Entreprise',
|
||||
'Phone number' => 'Téléphone',
|
||||
'Address' => 'Adresse',
|
||||
'Zip code' => 'Code postal',
|
||||
'City' => 'Ville',
|
||||
'Country' => 'Pays',
|
||||
|
||||
// Login
|
||||
'Authentication failure' => 'Échec de l\'authentification.',
|
||||
'Login' => 'Connexion',
|
||||
'No account yet?' => 'Pas encore de compte ?',
|
||||
'Register' => 'Créer un compte',
|
||||
'Forget password?' => 'Mot de passe oublié ?',
|
||||
// register
|
||||
'Confirm your password' => 'Confirmez votre mot de passe'
|
||||
|
||||
];
|
||||
96
modules/user/models/Permission.php
Normal file
96
modules/user/models/Permission.php
Normal file
@@ -0,0 +1,96 @@
|
||||
<?php
|
||||
/**
|
||||
* This file is part of the Piko user module
|
||||
*
|
||||
* @copyright 2020 Sylvain PHILIP.
|
||||
* @license LGPL-3.0; see LICENSE.txt
|
||||
* @link https://github.com/piko-framework/piko-user
|
||||
*/
|
||||
namespace app\modules\user\models;
|
||||
|
||||
use function Piko\I18n\__;
|
||||
|
||||
/**
|
||||
* This is the model class for table "auth_permission".
|
||||
*
|
||||
* @property integer $id
|
||||
* @property string $name;
|
||||
*
|
||||
* @author Sylvain PHILIP <contact@sphilip.com>
|
||||
*/
|
||||
class Permission extends \piko\DbRecord
|
||||
{
|
||||
/**
|
||||
* The table name
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $tableName = 'auth_permission';
|
||||
|
||||
/**
|
||||
* The model errors
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
public $errors = [];
|
||||
|
||||
/**
|
||||
* The table schema
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $schema = [
|
||||
'id' => self::TYPE_INT,
|
||||
'name' => self::TYPE_STRING,
|
||||
];
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
* @see \Piko\ModelTrait::validate()
|
||||
*/
|
||||
protected function validate(): void
|
||||
{
|
||||
if (empty($this->name)) {
|
||||
$this->errors['name'] = __('user', 'Permission name must be filled in.');
|
||||
} else {
|
||||
$st = $this->db->prepare('SELECT COUNT(`id`) FROM `auth_permission` WHERE name = :name');
|
||||
$st->execute(['name' => $this->name]);
|
||||
|
||||
$count = (int) $st->fetchColumn();
|
||||
|
||||
if ($count) {
|
||||
$this->errors['name'] = __('user', 'Permission already exists.');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Find permissions
|
||||
*
|
||||
* @param string $order The order condition
|
||||
* @param number $start The offset start
|
||||
* @param number $limit The offset limit
|
||||
*
|
||||
* @return array An array of permission rows
|
||||
*/
|
||||
public static function find($order = '', $start = 0, $limit = 0)
|
||||
{
|
||||
$db = User::$pdo;
|
||||
$query = 'SELECT `id`, `name` FROM `auth_permission`';
|
||||
$query .= ' ORDER BY ' . (empty($order) ? '`id` DESC' : $order);
|
||||
|
||||
if (!empty($start)) {
|
||||
$query .= ' OFFSET ' . (int) $start;
|
||||
}
|
||||
|
||||
if (!empty($limit)) {
|
||||
$query .= ' LIMIT ' . (int) $limit;
|
||||
}
|
||||
|
||||
$sth = $db->prepare($query);
|
||||
|
||||
$sth->execute();
|
||||
|
||||
return $sth->fetchAll();
|
||||
}
|
||||
}
|
||||
180
modules/user/models/Role.php
Normal file
180
modules/user/models/Role.php
Normal file
@@ -0,0 +1,180 @@
|
||||
<?php
|
||||
/**
|
||||
* This file is part of the Piko user module
|
||||
*
|
||||
* @copyright 2020 Sylvain PHILIP.
|
||||
* @license LGPL-3.0; see LICENSE.txt
|
||||
* @link https://github.com/piko-framework/piko-user
|
||||
*/
|
||||
namespace app\modules\user\models;
|
||||
|
||||
use function Piko\I18n\__;
|
||||
use app\modules\user\Rbac;
|
||||
|
||||
/**
|
||||
* This is the model class for table "auth_role.
|
||||
*
|
||||
* @property integer $id
|
||||
* @property integer $parent_id
|
||||
* @property string $name;
|
||||
* @property string $description;
|
||||
*
|
||||
* @author Sylvain PHILIP <contact@sphilip.com>
|
||||
*/
|
||||
class Role extends \Piko\DbRecord
|
||||
{
|
||||
const SCENARIO_ADMIN = 'admin';
|
||||
|
||||
/**
|
||||
* The table name
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $tableName = 'auth_role';
|
||||
|
||||
/**
|
||||
* The model scenario
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
public $scenario = '';
|
||||
|
||||
/**
|
||||
* The model errors
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
public $errors = [];
|
||||
|
||||
/**
|
||||
* The role permissions
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
public $permissions = [];
|
||||
|
||||
/**
|
||||
* The table schema
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $schema = [
|
||||
'id' => self::TYPE_INT,
|
||||
'name' => self::TYPE_STRING,
|
||||
'description' => self::TYPE_STRING,
|
||||
];
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
* @see \piko\Component::init()
|
||||
*/
|
||||
protected function init()
|
||||
{
|
||||
if (!empty($this->name)) {
|
||||
$this->permissions = Rbac::getRolePermissionIds($this->name);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
* @see \Piko\DbRecord::bind()
|
||||
*/
|
||||
public function bind($data): void
|
||||
{
|
||||
if (isset($data['permissions'])) {
|
||||
$this->permissions = $data['permissions'];
|
||||
unset($data['permissions']);
|
||||
}
|
||||
|
||||
parent::bind($data);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
* @see \Piko\DbRecord::afterSave()
|
||||
*/
|
||||
protected function afterSave(): void
|
||||
{
|
||||
if ($this->scenario === self::SCENARIO_ADMIN) {
|
||||
|
||||
$st = $this->db->prepare('DELETE FROM `auth_role_has_permission` WHERE role_id = :role_id');
|
||||
|
||||
if (!$st->execute(['role_id' => $this->id])) {
|
||||
throw new \RuntimeException(
|
||||
"Error while trying to delete role id {$this->id} in auth_role_has_permission table"
|
||||
);
|
||||
}
|
||||
|
||||
if (!empty($this->permissions)) {
|
||||
|
||||
$values = [];
|
||||
|
||||
foreach ($this->permissions as $id) {
|
||||
$values[] = '(' . (int) $this->id . ',' . (int) $id . ')';
|
||||
}
|
||||
|
||||
$query = 'INSERT INTO `auth_role_has_permission` (role_id, permission_id) VALUES '
|
||||
. implode(', ', $values);
|
||||
|
||||
$this->db->beginTransaction();
|
||||
|
||||
$st = $this->db->prepare($query);
|
||||
$st->execute();
|
||||
$this->db->commit();
|
||||
}
|
||||
}
|
||||
|
||||
parent::afterSave();
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
* @see \Piko\ModelTrait::validate()
|
||||
*/
|
||||
protected function validate(): void
|
||||
{
|
||||
if (empty($this->name)) {
|
||||
$this->errors['name'] = __('user', 'Role name must be filled in.');
|
||||
} else {
|
||||
$st = $this->db->prepare('SELECT COUNT(`id`) FROM `auth_role` WHERE name = :name');
|
||||
$st->execute(['name' => $this->name]);
|
||||
|
||||
$count = (int) $st->fetchColumn();
|
||||
|
||||
if ($count) {
|
||||
$this->errors['name'] = __('user', 'Role already exists.');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get roles
|
||||
*
|
||||
* @param string $order The order condition
|
||||
* @param number $start The offset start
|
||||
* @param number $limit The offset limit
|
||||
*
|
||||
* @return array An array of role rows
|
||||
*/
|
||||
public static function find($order = '', $start = 0, $limit = 0)
|
||||
{
|
||||
$db = User::$pdo;
|
||||
$query = 'SELECT * FROM `auth_role`';
|
||||
|
||||
$query .= ' ORDER BY ' . (empty($order) ? '`id` DESC' : $order);
|
||||
|
||||
if (!empty($start)) {
|
||||
$query .= ' OFFSET ' . (int) $start;
|
||||
}
|
||||
|
||||
if (!empty($limit)) {
|
||||
$query .= ' LIMIT ' . (int) $limit;
|
||||
}
|
||||
|
||||
$sth = $db->prepare($query);
|
||||
|
||||
$sth->execute();
|
||||
|
||||
return $sth->fetchAll();
|
||||
}
|
||||
}
|
||||
551
modules/user/models/User.php
Normal file
551
modules/user/models/User.php
Normal file
@@ -0,0 +1,551 @@
|
||||
<?php
|
||||
/**
|
||||
* This file is part of the Piko user module
|
||||
*
|
||||
* @copyright 2020 Sylvain PHILIP.
|
||||
* @license LGPL-3.0; see LICENSE.txt
|
||||
* @link https://github.com/piko-framework/piko-user
|
||||
*/
|
||||
namespace app\modules\user\models;
|
||||
|
||||
use function Piko\I18n\__;
|
||||
use Piko\Router;
|
||||
use app\modules\user\Rbac;
|
||||
use app\modules\user\Module;
|
||||
use Nette\Mail\Message;
|
||||
use Nette\Mail\SmtpMailer;
|
||||
use Nette\Utils\Random;
|
||||
|
||||
/**
|
||||
* This is the model class for table "user".
|
||||
*
|
||||
* @property integer $id
|
||||
* @property string $name;
|
||||
* @property string $username;
|
||||
* @property string $email;
|
||||
* @property string $password;
|
||||
* @property string $auth_key;
|
||||
* @property integer $confirmed_at;
|
||||
* @property integer $blocked_at;
|
||||
* @property string $registration_ip;
|
||||
* @property integer $created_at;
|
||||
* @property integer $updated_at;
|
||||
* @property integer $last_login_at;
|
||||
* @property string $timezone;
|
||||
* @property string $profil;
|
||||
*
|
||||
* @author Sylvain PHILIP <contact@sphilip.com>
|
||||
*/
|
||||
class User extends \Piko\DbRecord implements \Piko\User\IdentityInterface
|
||||
{
|
||||
const SCENARIO_ADMIN = 'admin';
|
||||
const SCENARIO_REGISTER = 'register';
|
||||
const SCENARIO_RESET = 'reset';
|
||||
|
||||
public static \PDO $pdo;
|
||||
public static Module $module;
|
||||
|
||||
/**
|
||||
* The table name
|
||||
* @var string
|
||||
*/
|
||||
protected $tableName = 'user';
|
||||
|
||||
/**
|
||||
* The model errors
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
public $errors = [];
|
||||
|
||||
/**
|
||||
* The model scenario
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
public $scenario = '';
|
||||
|
||||
/**
|
||||
* The user role ids
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $roleIds = [];
|
||||
|
||||
/**
|
||||
* The confirmation password
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $password2 = '';
|
||||
|
||||
/**
|
||||
* Reset password state
|
||||
*
|
||||
* @var boolean
|
||||
*/
|
||||
protected $resetPassword = false;
|
||||
|
||||
/**
|
||||
* The table schema
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $schema = [
|
||||
'id' => self::TYPE_INT,
|
||||
'name' => self::TYPE_STRING,
|
||||
'username' => self::TYPE_STRING,
|
||||
'email' => self::TYPE_STRING,
|
||||
'password' => self::TYPE_STRING,
|
||||
'auth_key' => self::TYPE_STRING,
|
||||
'confirmed_at' => self::TYPE_INT,
|
||||
'blocked_at' => self::TYPE_INT,
|
||||
'registration_ip' => self::TYPE_STRING,
|
||||
'created_at' => self::TYPE_INT,
|
||||
'updated_at' => self::TYPE_INT,
|
||||
'last_login_at' => self::TYPE_INT,
|
||||
'is_admin' => self::TYPE_INT,
|
||||
'timezone' => self::TYPE_STRING,
|
||||
'profil' => self::TYPE_STRING,
|
||||
];
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
* @see \Piko\DbRecord::beforeSave()
|
||||
*/
|
||||
protected function beforeSave($isNew): bool
|
||||
{
|
||||
if ($isNew) {
|
||||
$this->name = $this->username;
|
||||
$this->password = sha1($this->password);
|
||||
$this->created_at = time();
|
||||
$this->auth_key = sha1(Random::generate(10));
|
||||
} else {
|
||||
$this->updated_at = time();
|
||||
|
||||
if ($this->resetPassword) {
|
||||
$this->password = sha1($this->password);
|
||||
}
|
||||
}
|
||||
|
||||
return parent::beforeSave($isNew);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
* @see \Piko\DbRecord::afterSave()
|
||||
*/
|
||||
protected function afterSave(): void
|
||||
{
|
||||
if ($this->scenario === self::SCENARIO_ADMIN) {
|
||||
|
||||
// Don't allow admin user to remove its admin role
|
||||
/*
|
||||
if ($this->id == Piko::get('user')->getId()) {
|
||||
|
||||
$adminRole = Piko::get('userModule')->adminRole;
|
||||
$adminRoleId = Rbac::getRoleId($adminRole);
|
||||
|
||||
if (!in_array($adminRoleId, $this->roleIds)) {
|
||||
$this->roleIds[] = $adminRoleId;
|
||||
}
|
||||
}
|
||||
*/
|
||||
|
||||
if (!empty($this->roleIds)) {
|
||||
|
||||
$roleIds = Rbac::getUserRoleIds($this->id);
|
||||
|
||||
$idsToRemove = array_diff($roleIds, $this->roleIds);
|
||||
$idsToAdd = array_diff($this->roleIds, $roleIds);
|
||||
|
||||
if (!empty($idsToRemove)) {
|
||||
$query = 'DELETE FROM `auth_assignment` WHERE user_id = :user_id AND role_id IN('
|
||||
. implode(',', $idsToRemove) . ')';
|
||||
$st = $this->db->prepare($query);
|
||||
$st->execute(['user_id' => $this->id]);
|
||||
}
|
||||
|
||||
if (!empty($idsToAdd)) {
|
||||
$values = [];
|
||||
foreach ($idsToAdd as $id) {
|
||||
$values[] = '(' . (int) $this->id . ',' . (int) $id . ')';
|
||||
}
|
||||
|
||||
$query = 'INSERT INTO `auth_assignment` (user_id, role_id) VALUES ' . implode(', ', $values);
|
||||
|
||||
$this->db->beginTransaction();
|
||||
$st = $this->db->prepare($query);
|
||||
$st->execute();
|
||||
$this->db->commit();
|
||||
}
|
||||
} else {
|
||||
|
||||
$st = $this->db->prepare('DELETE FROM `auth_assignment` WHERE user_id = :user_id');
|
||||
$st->execute(['user_id' => $this->id]);
|
||||
}
|
||||
}
|
||||
|
||||
parent::afterSave();
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
* @see \Piko\DbRecord::bind()
|
||||
*/
|
||||
public function bind($data): void
|
||||
{
|
||||
if (isset($data['password']) && empty($data['password'])) {
|
||||
unset($data['password']);
|
||||
}
|
||||
|
||||
if (isset($data['password2'])) {
|
||||
$this->password2 = $data['password2'];
|
||||
unset($data['password2']);
|
||||
}
|
||||
|
||||
if (!empty($data['password']) && !$this->validatePassword($data['password'])) {
|
||||
$this->resetPassword = true;
|
||||
}
|
||||
|
||||
if (!empty($data['profil']) && is_array($data['profil'])) {
|
||||
$data['profil'] = json_encode($data['profil']);
|
||||
}
|
||||
|
||||
if (isset($data['roles']) && $this->scenario == self::SCENARIO_ADMIN) {
|
||||
$this->roleIds = $data['roles'];
|
||||
unset($data['roles']);
|
||||
}
|
||||
|
||||
parent::bind($data);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
* @see \Piko\ModeTrait::validate()
|
||||
*/
|
||||
protected function validate(): void
|
||||
{
|
||||
if (empty($this->email)) {
|
||||
$this->errors['email'] = __('user', 'Email must be filled in.');
|
||||
} elseif (!filter_var($this->email, FILTER_VALIDATE_EMAIL)) {
|
||||
$this->errors['email'] = __(
|
||||
'user',
|
||||
'{email} is not a valid email address.',
|
||||
['email' => $this->data['email']]
|
||||
);
|
||||
}
|
||||
|
||||
if (($this->scenario == self::SCENARIO_REGISTER || $this->scenario == self::SCENARIO_ADMIN)
|
||||
&& empty($this->username)) {
|
||||
$this->errors['username'] = __('user', 'Username must be filled in.') ;
|
||||
}
|
||||
|
||||
// New user
|
||||
if (($this->scenario == self::SCENARIO_REGISTER || $this->scenario == self::SCENARIO_ADMIN)
|
||||
&& empty($this->id)) {
|
||||
|
||||
$st = $this->db->prepare('SELECT id FROM user WHERE email = ?');
|
||||
$st->execute([$this->email]);
|
||||
$id = $st->fetchColumn();
|
||||
|
||||
if ($id) {
|
||||
$this->errors['email'] = __('user', 'This email is already used.');
|
||||
}
|
||||
|
||||
$st = $this->db->prepare('SELECT id FROM user WHERE username = ?');
|
||||
$st->execute([$this->username]);
|
||||
$id = $st->fetchColumn();
|
||||
|
||||
if ($id) {
|
||||
$this->errors['username'] = __('user', 'This username is already used.');
|
||||
}
|
||||
}
|
||||
|
||||
if (($this->scenario == self::SCENARIO_REGISTER || $this->scenario == self::SCENARIO_RESET)
|
||||
&& empty($this->password)) {
|
||||
$this->errors['password'] = __('user', 'Password must be filled in.');
|
||||
|
||||
} elseif (($this->scenario == self::SCENARIO_REGISTER || $this->scenario == self::SCENARIO_RESET) &&
|
||||
strlen($this->password) < static::$module->passwordMinLength) {
|
||||
$this->errors['password'] = __(
|
||||
'user',
|
||||
'Password is to short. Minimum {num}: characters.',
|
||||
['num' => static::$module->passwordMinLength]
|
||||
);
|
||||
}
|
||||
|
||||
if (($this->scenario == self::SCENARIO_REGISTER || $this->scenario == self::SCENARIO_RESET) &&
|
||||
$this->password != $this->password2) {
|
||||
$this->errors['password2'] = __('user', 'Passwords are not the same.');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get user role ids
|
||||
*
|
||||
* @return array An array containg role ids
|
||||
*/
|
||||
public function getRoleIds()
|
||||
{
|
||||
return Rbac::getUserRoleIds($this->id);
|
||||
}
|
||||
|
||||
/**
|
||||
* Activate an user
|
||||
*
|
||||
* @return boolean
|
||||
*/
|
||||
public function activate()
|
||||
{
|
||||
$this->confirmed_at = time();
|
||||
return $this->save();
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if the user is activated
|
||||
* @return boolean
|
||||
*/
|
||||
public function isActivated()
|
||||
{
|
||||
return empty($this->confirmed_at) ? false : true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Send Registration confirmation email
|
||||
*
|
||||
* @return boolean Return false if fail to send email
|
||||
*/
|
||||
public function sendRegistrationConfirmation(Router $router, SmtpMailer $mailer)
|
||||
{
|
||||
$siteName = getenv('SITE_NAME');
|
||||
$baseUrl = $this->getAbsoluteBaseUrl();
|
||||
|
||||
$message = __('user', 'confirmation_mail_body', [
|
||||
'site_name' => $siteName,
|
||||
'link' => $baseUrl . $router->getUrl('user/default/confirmation', ['token' => $this->auth_key]),
|
||||
'base_url' => $baseUrl,
|
||||
'username' => $this->username,
|
||||
]);
|
||||
|
||||
$subject = __('user', 'Registration confirmation on {site_name}', ['site_name' => $siteName]);
|
||||
|
||||
$mail = new Message();
|
||||
$mail->setFrom($siteName . ' <' . getenv('NO_REPLY_EMAIL') . '>')
|
||||
->addTo($this->email)
|
||||
->setSubject($subject)
|
||||
->setBody($message);
|
||||
|
||||
try {
|
||||
$mailer->send($mail);
|
||||
return true;
|
||||
|
||||
} catch (\Exception $e) {
|
||||
$this->errors['sendmail'] = $e->getMessage();
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Send reset password email
|
||||
*
|
||||
* @return boolean Return false if fail to send email
|
||||
*/
|
||||
public function sendResetPassword(Router $router, SmtpMailer $mailer)
|
||||
{
|
||||
$siteName = getenv('SITE_NAME');
|
||||
|
||||
$baseUrl = $this->getAbsoluteBaseUrl();
|
||||
|
||||
$message = __('user', 'reset_password_mail_body', [
|
||||
'site_name' => $siteName,
|
||||
'link' => $baseUrl . $router->getUrl('user/default/reset-password', ['token' => $this->auth_key]),
|
||||
'username' => $this->username,
|
||||
]);
|
||||
|
||||
$subject = __('user', 'Password change request on {site_name}', ['site_name' => $siteName]);
|
||||
|
||||
$mail = new Message();
|
||||
$mail->setFrom($siteName . ' <' . getenv('NO_REPLY_EMAIL') . '>')
|
||||
->addTo($this->email)
|
||||
->setSubject($subject)
|
||||
->setBody($message);
|
||||
|
||||
try {
|
||||
$mailer->send($mail);
|
||||
return true;
|
||||
|
||||
} catch (\Exception $e) {
|
||||
$this->errors['sendmail'] = $e->getMessage();
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get users
|
||||
*
|
||||
* @param array $filters Array of filter conditions (['name' => ''])
|
||||
* @param string $order The order condition
|
||||
* @param number $start The offset start
|
||||
* @param number $limit The offset limit
|
||||
*
|
||||
* @return array An array of user rows
|
||||
*/
|
||||
public static function find($filters = [], $order = '', $start = 0, $limit = 0)
|
||||
{
|
||||
$query = 'SELECT * FROM `user`';
|
||||
$where = [];
|
||||
|
||||
if (!empty($filters['name'])) {
|
||||
$where[] = '`name` LIKE :search';
|
||||
}
|
||||
|
||||
if (!empty($where)) {
|
||||
$query .= ' WHERE ' . implode(' AND ', $where);
|
||||
}
|
||||
|
||||
$query .= ' ORDER BY ' . (empty($order) ? '`id` DESC' : $order);
|
||||
|
||||
if (!empty($start)) {
|
||||
$query .= ' OFFSET ' . (int) $start;
|
||||
}
|
||||
|
||||
if (!empty($limit)) {
|
||||
$query .= ' LIMIT ' . (int) $limit;
|
||||
}
|
||||
|
||||
$sth = static::$pdo->prepare($query);
|
||||
|
||||
$sth->execute($filters);
|
||||
|
||||
return $sth->fetchAll();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Find user by username
|
||||
*
|
||||
* @param string $username
|
||||
* @return User|NULL
|
||||
*/
|
||||
public static function findByUsername($username)
|
||||
{
|
||||
$st = static::$pdo->prepare('SELECT id FROM user WHERE username = ?');
|
||||
$st->bindParam(1, $username, \PDO::PARAM_STR);
|
||||
|
||||
if ($st->execute()) {
|
||||
$id = $st->fetchColumn();
|
||||
|
||||
if ($id) {
|
||||
$user = new static(static::$pdo);
|
||||
$user->load($id);
|
||||
|
||||
return $user;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Find user by email
|
||||
*
|
||||
* @param string $email
|
||||
* @return User|NULL
|
||||
*/
|
||||
public static function findByEmail($email)
|
||||
{
|
||||
|
||||
$st = static::$pdo->prepare('SELECT id FROM user WHERE email = ?');
|
||||
$st->bindParam(1, $email, \PDO::PARAM_STR);
|
||||
|
||||
if ($st->execute()) {
|
||||
$id = $st->fetchColumn();
|
||||
|
||||
if ($id) {
|
||||
$user = new static(static::$pdo);
|
||||
|
||||
return $user->load($id);
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Find user by auth key
|
||||
*
|
||||
* @param string $token
|
||||
* @return User|NULL
|
||||
*/
|
||||
public static function findByAuthKey($token)
|
||||
{
|
||||
$st = static::$pdo->prepare('SELECT id FROM `user` WHERE `auth_key` = ?');
|
||||
|
||||
if ($st->execute([$token])) {
|
||||
$id = $st->fetchColumn();
|
||||
|
||||
if ($id) {
|
||||
$user = new static(static::$pdo);
|
||||
$user->load($id);
|
||||
|
||||
return $user;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Validate password
|
||||
*
|
||||
* @param string $password
|
||||
* @return boolean
|
||||
*/
|
||||
public function validatePassword($password)
|
||||
{
|
||||
return $this->password == sha1($password);
|
||||
}
|
||||
|
||||
/**
|
||||
* Find user by Id
|
||||
*
|
||||
* @param int $id
|
||||
* @return User|NULL
|
||||
*/
|
||||
public static function findIdentity($id)
|
||||
{
|
||||
try {
|
||||
$user = new static(static::$pdo);
|
||||
|
||||
return $user->load($id);
|
||||
} catch (\RuntimeException $e) {
|
||||
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
* @see \piko\IdentityInterface::getId()
|
||||
*/
|
||||
public function getId()
|
||||
{
|
||||
return $this->id;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get absolute base Url
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
protected function getAbsoluteBaseUrl()
|
||||
{
|
||||
$protocol = isset($_SERVER['HTTPS']) && $_SERVER['HTTPS'] === 'on' ? 'https' : 'http';
|
||||
|
||||
return "$protocol://{$_SERVER['HTTP_HOST']}";
|
||||
}
|
||||
}
|
||||
21
modules/user/sql/install-mysql.sql
Normal file
21
modules/user/sql/install-mysql.sql
Normal file
@@ -0,0 +1,21 @@
|
||||
|
||||
CREATE TABLE IF NOT EXISTS `user` (
|
||||
`id` int(11) NOT NULL auto_increment,
|
||||
`name` varchar(50) NOT NULL,
|
||||
`username` varchar(50) NOT NULL,
|
||||
`email` varchar(100) NOT NULL,
|
||||
`password` varchar(50) NOT NULL,
|
||||
`auth_key` varchar(100) NOT NULL DEFAULT '',
|
||||
`confirmed_at` datetime DEFAULT NULL,
|
||||
`blocked_at` datetime DEFAULT NULL,
|
||||
`registration_ip` varchar(40) DEFAULT '' COMMENT 'Stores ip v4 or ip v6',
|
||||
`created_at` datetime DEFAULT NULL,
|
||||
`updated_at` datetime DEFAULT NULL,
|
||||
`last_login_at` datetime DEFAULT NULL,
|
||||
`is_admin` tinyint(1) NOT NULL DEFAULT 0,
|
||||
`timezone` varchar(40) DEFAULT '',
|
||||
`profil` text NOT NULL DEFAULT '{}' COMMENT 'Json encoded profil',
|
||||
PRIMARY KEY (`id`),
|
||||
INDEX `username` (`username`),
|
||||
UNIQUE INDEX `email` (`email`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 DEFAULT COLLATE=utf8mb4_unicode_ci;
|
||||
52
modules/user/sql/install-sqlite.sql
Normal file
52
modules/user/sql/install-sqlite.sql
Normal file
@@ -0,0 +1,52 @@
|
||||
|
||||
CREATE TABLE IF NOT EXISTS "user" (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
name TEXT NOT NULL,
|
||||
username TEXT NOT NULL,
|
||||
email TEXT NOT NULL,
|
||||
password TEXT NOT NULL,
|
||||
auth_key TEXT,
|
||||
confirmed_at INTEGER,
|
||||
blocked_at INTEGER,
|
||||
registration_ip TEXT,
|
||||
created_at INTEGER,
|
||||
updated_at INTEGER,
|
||||
last_login_at INTEGER,
|
||||
timezone TEXT,
|
||||
profil TEXT,
|
||||
UNIQUE(email)
|
||||
);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS auth_role
|
||||
(
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
name VARCHAR(64) NOT NULL,
|
||||
description TEXT,
|
||||
UNIQUE(name)
|
||||
);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS auth_permission
|
||||
(
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
name VARCHAR(64) NOT NULL,
|
||||
UNIQUE(name)
|
||||
);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS auth_role_has_permission
|
||||
(
|
||||
role_id INTEGER NOT NULL,
|
||||
permission_id INTEGER NOT NULL,
|
||||
primary key (role_id, permission_id),
|
||||
foreign key (role_id) references auth_role(id) on delete cascade on update cascade,
|
||||
foreign key (permission_id) references auth_permission(id) on delete cascade on update cascade
|
||||
);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS auth_assignment
|
||||
(
|
||||
role_id INTEGER NOT NULL,
|
||||
user_id INTEGER NOT NULL,
|
||||
primary key (role_id, user_id),
|
||||
foreign key (role_id) references auth_role(id) on delete cascade on update cascade,
|
||||
foreign key (user_id) references "user" (id) on delete cascade on update cascade
|
||||
);
|
||||
|
||||
58
modules/user/views/admin/edit.php
Normal file
58
modules/user/views/admin/edit.php
Normal file
@@ -0,0 +1,58 @@
|
||||
<?php
|
||||
use function Piko\I18n\__;
|
||||
|
||||
assert($this instanceof Piko\View);
|
||||
assert($user instanceof app\modules\user\models\User);
|
||||
|
||||
/* @var $message array */
|
||||
/* @var $roles array */
|
||||
|
||||
$this->title = empty($user->id) ? __('user', 'Create user') : __('user', 'Edit user');
|
||||
$roleIds = $user->getRoleIds();
|
||||
?>
|
||||
<div class="container">
|
||||
|
||||
<?php if (is_array($message)): ?>
|
||||
<div class="alert alert-<?= $message['type'] ?> alert-dismissible" role="alert">
|
||||
<?= $message['content'] ?>
|
||||
<button type="button" class="btn-close" data-bs-dismiss="alert" aria-label="Close"></button>
|
||||
</div>
|
||||
<?php endif ?>
|
||||
|
||||
<form method="post">
|
||||
<div class="mb-3">
|
||||
<label for="name"><?= __('user', 'Name') ?></label>
|
||||
<input type="text" class="form-control" id="name" name="name" value="<?= $user->name ?>">
|
||||
</div>
|
||||
|
||||
<div class="mb-3">
|
||||
<label for="email"><?= __('user', 'Email') ?></label>
|
||||
<input type="text" class="form-control" id="email" name="email" value="<?= $user->email ?>">
|
||||
</div>
|
||||
|
||||
<div class="mb-3">
|
||||
<label for="username"><?= __('user', 'Username') ?></label>
|
||||
<input type="text" class="form-control" id="username" name="username" value="<?= $user->username ?>">
|
||||
</div>
|
||||
|
||||
<div class="mb-3">
|
||||
<label for="password"><?= __('user', 'Password') ?></label>
|
||||
<input type="text" class="form-control" id="password" name="password" value="">
|
||||
</div>
|
||||
|
||||
<div class="mb-3">
|
||||
<label for="roles"><?= __('user', 'Roles') ?></label>
|
||||
<select class="form-select" id="roles" name="roles[]" multiple>
|
||||
<?php foreach ($roles as $role): ?>
|
||||
<option value="<?= $role['id']?>"<?= in_array($role['id'], $roleIds)? ' selected' : '' ?>><?= $role['name']?></option>
|
||||
<?php endforeach ?>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div class="mb-3">
|
||||
<button type="submit" class="btn btn-primary"><?= __('user', 'Save') ?></button>
|
||||
<a href="<?= $this->getUrl('user/admin/users')?>" class="btn btn-default"><?= __('user', 'Close') ?></a>
|
||||
</div>
|
||||
|
||||
</form>
|
||||
</div>
|
||||
17
modules/user/views/admin/nav.php
Normal file
17
modules/user/views/admin/nav.php
Normal file
@@ -0,0 +1,17 @@
|
||||
<?php
|
||||
use function Piko\I18n\__;
|
||||
/* @var $this \piko\View */
|
||||
/* @var $page string */
|
||||
|
||||
?>
|
||||
<ul class="nav nav-tabs my-3">
|
||||
<li class="nav-item">
|
||||
<a class="nav-link<?= $page == 'users' ? ' active' : '' ?>" href="<?= $this->getUrl('user/admin/users') ?>"><?= __('user', 'Users') ?></a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a class="nav-link<?= $page == 'roles' ? ' active' : '' ?>" href="<?= $this->getUrl('user/admin/roles') ?>"><?= __('user', 'Roles') ?></a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a class="nav-link<?= $page == 'permissions' ? ' active' : '' ?>" href="<?= $this->getUrl('user/admin/permissions') ?>"><?= __('user', 'Permissions') ?></a>
|
||||
</li>
|
||||
</ul>
|
||||
111
modules/user/views/admin/permissions.php
Normal file
111
modules/user/views/admin/permissions.php
Normal file
@@ -0,0 +1,111 @@
|
||||
<?php
|
||||
use Piko;
|
||||
use function Piko\I18n\__;
|
||||
assert($this instanceof Piko\View);
|
||||
|
||||
/* @var $permissions array */
|
||||
|
||||
$this->title = __('user', 'Permissions');
|
||||
|
||||
$this->registerCSSFile(Piko::getAlias('@web/js/DataTables/datatables.min.css'));
|
||||
$this->registerJsFile(Piko::getAlias('@web/js/jquery-3.7.1.min.js'));
|
||||
$this->registerJsFile(Piko::getAlias('@web/js/DataTables/datatables.min.js'));
|
||||
|
||||
$script = <<<JS
|
||||
$(function() {
|
||||
|
||||
$('#permissions-table').DataTable({
|
||||
'order': [[1, 'desc']]
|
||||
});
|
||||
|
||||
$('#delete').click(function(e) {
|
||||
if (confirm('Êtes-vous sûr de vouloir effectuer cette action ?')) {
|
||||
$('#admin-form').attr('action', '/user/admin/delete-permissions')
|
||||
$('#admin-form').submit()
|
||||
}
|
||||
});
|
||||
|
||||
$('#btn-new-permission, .edit-permission').on('click', function(e) {
|
||||
e.preventDefault();
|
||||
|
||||
var permissionName = '';
|
||||
var permissionId = $(this).data('id');
|
||||
var action = $(this).attr('href');
|
||||
const modal = new bootstrap.Modal('#editPermissionModal');
|
||||
|
||||
if ($(this).hasClass('edit-permission')) {
|
||||
permissionName = $(this).text();
|
||||
}
|
||||
|
||||
$('#permission-name').val(permissionName);
|
||||
modal.show();
|
||||
|
||||
$('#btn-save-permission').on('click', function() {
|
||||
if ($('#permission-name').val()) {
|
||||
$.ajax({
|
||||
method: 'post',
|
||||
url: action,
|
||||
data: {name: $('#permission-name').val(), id: permissionId}
|
||||
})
|
||||
.done(function(data) {
|
||||
if (data.status == 'success') {
|
||||
location.reload();
|
||||
}
|
||||
if (data.status == 'error') {
|
||||
alert(data.error)
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
JS;
|
||||
$this->registerJs($script);
|
||||
|
||||
?>
|
||||
|
||||
<?= $this->render('nav', ['page' => 'permissions']) ?>
|
||||
|
||||
<form action="" method="post" id="admin-form">
|
||||
|
||||
<div class="btn-group mb-4" role="group">
|
||||
<a href="<?= $this->getUrl('user/admin/edit-permission') ?>" class="btn btn-primary btn-sm" id="btn-new-permission"><?= __('user', 'New permission') ?></a>
|
||||
<button type="button" class="btn btn-danger btn-sm" id="delete"><?= __('user', 'Delete') ?></button>
|
||||
</div>
|
||||
|
||||
<table class="table table-striped" id="permissions-table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th><?= __('user', 'Name') ?></th>
|
||||
<th><?= __('user', 'Id') ?></th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<?php foreach($permissions as $permission): ?>
|
||||
<tr>
|
||||
<td>
|
||||
<input type="checkbox" name="items[]" value="<?= $permission['id'] ?>">
|
||||
<a href="<?= $this->getUrl('user/admin/edit-permission', ['id' => $permission['id']])?>"
|
||||
class="edit-permission" data-id="<?= $permission['id'] ?>"><?= $permission['name'] ?></a>
|
||||
</td>
|
||||
<td><?= $permission['id'] ?></td>
|
||||
</tr>
|
||||
<?php endforeach ?>
|
||||
</tbody>
|
||||
</table>
|
||||
</form>
|
||||
|
||||
|
||||
<div class="modal fade" id="editPermissionModal" tabindex="-1" role="dialog" aria-labelledby="editPermissionModal" aria-hidden="true">
|
||||
<div class="modal-dialog modal-dialog-centered" role="document">
|
||||
<div class="modal-content">
|
||||
<div class="modal-body">
|
||||
<input type="text" id="permission-name" class="form-control" placeholder="<?= __('user', 'Permission name') ?>">
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal"><?= __('user', 'Cancel') ?></button>
|
||||
<button type="button" class="btn btn-primary" id="btn-save-permission"><?= __('user', 'Save') ?></button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
152
modules/user/views/admin/roles.php
Normal file
152
modules/user/views/admin/roles.php
Normal file
@@ -0,0 +1,152 @@
|
||||
<?php
|
||||
use Piko;
|
||||
use function Piko\I18n\__;
|
||||
assert($this instanceof Piko\View);
|
||||
|
||||
/* @var $roles array */
|
||||
/* @var $permissions array */
|
||||
|
||||
$this->title = __('user', 'Roles');
|
||||
|
||||
$this->registerCSSFile(Piko::getAlias('@web/js/DataTables/datatables.min.css'));
|
||||
$this->registerJsFile(Piko::getAlias('@web/js/jquery-3.7.1.min.js'));
|
||||
$this->registerJsFile(Piko::getAlias('@web/js/DataTables/datatables.min.js'));
|
||||
|
||||
$confirmDeleteMsg = __('user', 'Are you sure you want to perform this action?');
|
||||
|
||||
$script = <<<JS
|
||||
$(function() {
|
||||
|
||||
$('#roles-table').DataTable({
|
||||
'order': [[1, 'desc']]
|
||||
});
|
||||
|
||||
$('#delete').click(function(e) {
|
||||
if (confirm('{$confirmDeleteMsg}')) {
|
||||
$('#admin-form').attr('action', '/user/admin/delete-roles')
|
||||
$('#admin-form').submit()
|
||||
}
|
||||
});
|
||||
|
||||
$('#btn-new, .edit-role').on('click', function(e) {
|
||||
e.preventDefault();
|
||||
|
||||
var data = {
|
||||
id: $(this).data('id'),
|
||||
parent_id: $(this).data('parent_id'),
|
||||
name: '',
|
||||
description: $(this).data('description')
|
||||
};
|
||||
|
||||
var action = $(this).attr('href');
|
||||
|
||||
const modal = new bootstrap.Modal('#editRoleModal');
|
||||
|
||||
if ($(this).hasClass('edit-role')) {
|
||||
data.name = $(this).text();
|
||||
}
|
||||
|
||||
$('#role-name').val(data.name);
|
||||
$('#role-description').val(data.description);
|
||||
|
||||
$.ajax({
|
||||
method: 'get',
|
||||
url: action,
|
||||
})
|
||||
.done(function(data) {
|
||||
if (data.role.permissions) {
|
||||
$('#permissions').val(data.role.permissions)
|
||||
}
|
||||
});
|
||||
|
||||
modal.show();
|
||||
|
||||
$('#btn-save').on('click', function() {
|
||||
if ($('#role-name').val()) {
|
||||
data.name = $('#role-name').val();
|
||||
data.description = $('#role-description').val();
|
||||
data.parent_id = $('#role-parent-id').val();
|
||||
data.permissions = $('#permissions').val();
|
||||
$.ajax({
|
||||
method: 'post',
|
||||
url: action,
|
||||
data: data
|
||||
})
|
||||
.done(function(data) {
|
||||
if (data.status == 'success') {
|
||||
location.reload();
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
JS;
|
||||
$this->registerJs($script);
|
||||
|
||||
?>
|
||||
|
||||
<?= $this->render('nav', ['page' => 'roles']) ?>
|
||||
|
||||
<form action="" method="post" id="admin-form">
|
||||
|
||||
<div class="btn-group mb-4" role="group">
|
||||
<a href="<?= $this->getUrl('user/admin/edit-role') ?>" class="btn btn-primary btn-sm" id="btn-new"><?= __('user', 'New role') ?></a>
|
||||
<button type="button" class="btn btn-danger btn-sm" id="delete"><?= __('user', 'Delete') ?></button>
|
||||
</div>
|
||||
|
||||
<table class="table table-striped" id="roles-table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th><?= __('user', 'Name') ?></th>
|
||||
<th><?= __('user', 'Description') ?></th>
|
||||
<th><?= __('user', 'Id') ?></th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<?php foreach($roles as $role): ?>
|
||||
<tr>
|
||||
<td>
|
||||
<input type="checkbox" name="items[]" value="<?= $role['id'] ?>">
|
||||
<a href="<?= $this->getUrl('user/admin/edit-role', ['id' => $role['id']])?>"
|
||||
class="edit-role"
|
||||
data-description="<?= $role['description'] ?>"
|
||||
data-id="<?= $role['id'] ?>"><?= $role['name'] ?></a>
|
||||
</td>
|
||||
<td><?= $role['description'] ?></td>
|
||||
<td><?= $role['id'] ?></td>
|
||||
</tr>
|
||||
<?php endforeach ?>
|
||||
</tbody>
|
||||
</table>
|
||||
</form>
|
||||
|
||||
<div class="modal fade" id="editRoleModal" tabindex="-1" role="dialog" aria-labelledby="editRoleModal" aria-hidden="true">
|
||||
<div class="modal-dialog modal-dialog-centered" role="document">
|
||||
<div class="modal-content">
|
||||
<div class="modal-body">
|
||||
<div class="form-group">
|
||||
<label for="role-name"><?= __('user', 'Role name') ?></label>
|
||||
<input type="text" id="role-name" class="form-control">
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="role-description"><?= __('user', 'Description') ?></label>
|
||||
<textarea rows="3" class="form-control" id="role-description"></textarea>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="permissions"><?= __('user', 'Role permissions') ?></label>
|
||||
<select class="form-select" id="permissions" multiple>
|
||||
<?php foreach ($permissions as $perm): ?>
|
||||
<option value="<?= $perm['id']?>"><?= $perm['name']?></option>
|
||||
<?php endforeach ?>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal"><?= __('user', 'Cancel') ?></button>
|
||||
<button type="button" class="btn btn-primary" id="btn-save"><?= __('user', 'Save') ?></button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
71
modules/user/views/admin/users.php
Normal file
71
modules/user/views/admin/users.php
Normal file
@@ -0,0 +1,71 @@
|
||||
<?php
|
||||
use function Piko\I18n\__;
|
||||
|
||||
assert($this instanceof Piko\View);
|
||||
|
||||
/* @var $users array */
|
||||
|
||||
$this->title = __('user', 'Users management');
|
||||
|
||||
$this->registerCSSFile(Piko::getAlias('@web/js/DataTables/datatables.min.css'));
|
||||
$this->registerJsFile(Piko::getAlias('@web/js/jquery-3.7.1.min.js'));
|
||||
$this->registerJsFile(Piko::getAlias('@web/js/DataTables/datatables.min.js'));
|
||||
|
||||
|
||||
$confirmDeleteMsg = __('user', 'Are you sure you want to perform this action?');
|
||||
$script = <<<JS
|
||||
$(document).ready(function() {
|
||||
|
||||
$('#users-table').DataTable({
|
||||
'order': [[3, 'desc']]
|
||||
});
|
||||
|
||||
$('#delete').click(function(e) {
|
||||
if (confirm('{$confirmDeleteMsg}')) {
|
||||
$('#admin-form').attr('action', '/user/admin/delete')
|
||||
$('#admin-form').submit()
|
||||
}
|
||||
});
|
||||
});
|
||||
JS;
|
||||
$this->registerJs($script);
|
||||
|
||||
?>
|
||||
|
||||
<?= $this->render('nav', ['page' => 'users']) ?>
|
||||
|
||||
<form action="" method="post" id="admin-form">
|
||||
|
||||
<div class="btn-group mb-4" role="group">
|
||||
<a href="<?= $this->getUrl('user/admin/edit') ?>" class="btn btn-primary btn-sm"><?= __('user', 'Create user') ?></a>
|
||||
<button type="button" class="btn btn-danger btn-sm" id="delete"><?= __('user', 'Delete') ?></button>
|
||||
</div>
|
||||
|
||||
<table id="users-table" class="table table-striped">
|
||||
<thead>
|
||||
<tr>
|
||||
<th><?= __('user', 'Name') ?></th>
|
||||
<th><?= __('user', 'Username') ?></th>
|
||||
<th><?= __('user', 'Email') ?></th>
|
||||
<th><?= __('user', 'Last login at') ?></th>
|
||||
<th><?= __('user', 'Created at') ?></th>
|
||||
<th><?= __('user', 'Id') ?></th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<?php foreach($users as $user): ?>
|
||||
<tr>
|
||||
<td>
|
||||
<input type="checkbox" name="items[]" value="<?= $user['id'] ?>">
|
||||
<a href="<?= $this->getUrl('user/admin/edit', ['id' => $user['id']])?>"><?= $user['name'] ?></a>
|
||||
</td>
|
||||
<td><?= $user['username']?></td>
|
||||
<td><?= $user['email']?></td>
|
||||
<td><?= empty($user['last_login_at']) ? '' : date('Y-m-d H:i', $user['last_login_at']) ?></td>
|
||||
<td><?= date('Y-m-d H:i', $user['created_at']) ?></td>
|
||||
<td><?= $user['id'] ?></td>
|
||||
</tr>
|
||||
<?php endforeach ?>
|
||||
</tbody>
|
||||
</table>
|
||||
</form>
|
||||
93
modules/user/views/default/edit.php
Normal file
93
modules/user/views/default/edit.php
Normal file
@@ -0,0 +1,93 @@
|
||||
<?php
|
||||
use function Piko\I18n\__;
|
||||
|
||||
assert($this instanceof Piko\View);
|
||||
|
||||
/* @var $message array */
|
||||
/* @var $user piko\user\models\User */
|
||||
|
||||
$this->title = __('user', 'Edit your account');
|
||||
|
||||
if (is_array($message)) {
|
||||
$this->params['message'] = $message;
|
||||
}
|
||||
|
||||
if (!empty($user->profil)) {
|
||||
$user->profil = json_decode($user->profil);
|
||||
}
|
||||
|
||||
?>
|
||||
|
||||
<div class="container mt-5">
|
||||
<h1><?= $this->title ?></h1>
|
||||
|
||||
<form method="post" novalidate>
|
||||
|
||||
<div class="form-group">
|
||||
<label for="username"><?= __('user', 'Username') ?> : <strong><?= $user->username ?></strong></label>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label for="email"><?= __('user', 'Email') ?></label>
|
||||
<input type="text" class="form-control" id="email" name="email" value="<?= $user->email ?>">
|
||||
<?php if (!empty($user->errors['email'])): ?>
|
||||
<div class="alert alert-danger" role="alert"><?= $user->errors['email'] ?></div>
|
||||
<?php endif ?>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label for="password"><?= __('user', 'Password (leave blank to keep the same)') ?></label>
|
||||
<input type="password" class="form-control" id="password" name="password" value="" autocomplete="off">
|
||||
</div>
|
||||
|
||||
<div class="form-row">
|
||||
<div class="col-md-6 mb-3">
|
||||
<label for="lastname"><?= __('user', 'Last name') ?></label>
|
||||
<input type="text" class="form-control" id="lastname" name="profil[lastname]" value="<?= isset($user->profil->lastname) ? $user->profil->lastname : '' ?>">
|
||||
</div>
|
||||
|
||||
<div class="col-md-6 mb-3">
|
||||
<label for="firstname"><?= __('user', 'First name') ?></label>
|
||||
<input type="text" class="form-control" id="firstname" name="profil[firstname]" value="<?= isset($user->profil->firstname) ? $user->profil->firstname : '' ?>">
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-row">
|
||||
<div class="col-md-6 mb-3">
|
||||
<label for="company"><?= __('user', 'Company') ?></label>
|
||||
<input type="text" class="form-control" id="company" name="profil[company]" value="<?= isset($user->profil->company) ? $user->profil->company : ''?>">
|
||||
</div>
|
||||
|
||||
<div class="col-md-6 mb-3">
|
||||
<label for="telephone"><?= __('user', 'Phone number') ?></label>
|
||||
<input type="text" class="form-control" id="telephone" name="profil[telephone]" value="<?= isset($user->profil->telephone) ? $user->profil->telephone : ''?>">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-group mb-3">
|
||||
<label for="address"><?= __('user', 'Address') ?></label>
|
||||
<input type="text" class="form-control" id="address" name="profil[address]" value="<?= isset($user->profil->address) ? $user->profil->address : ''?>">
|
||||
</div>
|
||||
|
||||
<div class="form-row">
|
||||
<div class="col-md-4 mb-3">
|
||||
<label for="zipcode"><?= __('user', 'Zip code') ?></label>
|
||||
<input type="text" class="form-control" id="zipcode" name="profil[zipcode]" value="<?= isset($user->profil->zipcode) ? $user->profil->zipcode : ''?>">
|
||||
</div>
|
||||
|
||||
<div class="col-md-4 mb-3">
|
||||
<label for="city"><?= __('user', 'City') ?></label>
|
||||
<input type="text" class="form-control" id="city" name="profil[city]" value="<?= isset($user->profil->city) ? $user->profil->city : ''?>">
|
||||
</div>
|
||||
|
||||
<div class="col-md-4 mb-3">
|
||||
<label for="country"><?= __('user', 'Country') ?></label>
|
||||
<input type="text" class="form-control" id="country" name="profil[country]" value="<?= isset($user->profil->country) ? $user->profil->country : ''?>">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<button type="submit" class="btn btn-primary"><?= __('user', 'Save') ?></button>
|
||||
<a href="<?= Piko::getAlias('@web/')?>" class="btn btn-default"><?= __('user', 'Cancel') ?></a>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
|
||||
77
modules/user/views/default/login.php
Normal file
77
modules/user/views/default/login.php
Normal file
@@ -0,0 +1,77 @@
|
||||
<?php
|
||||
use function Piko\I18n\__;
|
||||
|
||||
assert($this instanceof Piko\View);
|
||||
|
||||
/**
|
||||
* @var $message boolean | array
|
||||
* @var $canRegister boolean
|
||||
*/
|
||||
|
||||
$this->title = __('user', 'Login');
|
||||
$this->params['breadcrumbs'][] = $this->title;
|
||||
|
||||
if (is_array($message)) {
|
||||
$this->params['message'] = $message;
|
||||
}
|
||||
|
||||
?>
|
||||
|
||||
<main class="form-signin w-100 m-auto">
|
||||
|
||||
<form action="<?= $this->getUrl('user/default/login') ?>" id="login-form" method="post">
|
||||
<h1 class="h3 mb-3 fw-normal"><?= $this->title ?></h1>
|
||||
|
||||
<div class="form-floating">
|
||||
<input type="text" class="form-control" id="username" name="username" autofocus="autofocus" aria-required="true"
|
||||
aria-invalid="true">
|
||||
<label for="username"><?= __('user', 'Username') ?></label>
|
||||
</div>
|
||||
<div class="form-floating">
|
||||
<input type="password" class="form-control" id="loginform-password" name="password" value="" aria-required="true">
|
||||
<label for="loginform-password"><?= __('user', 'Password') ?></label>
|
||||
</div>
|
||||
|
||||
<button class="btn btn-primary w-100 py-2" type="submit"><?= __('user', 'Login') ?></button>
|
||||
<p class="mt-5 mb-3 text-body-secondary">© 2017–2023</p>
|
||||
|
||||
|
||||
<!--
|
||||
<div class="form-group row">
|
||||
<label class="col-sm-3 col-form-label" for="loginform-username"><?= __('user', 'Username') ?></label>
|
||||
<div class="col-sm-9">
|
||||
<input type="text" id="username" class="form-control" name="username" autofocus="autofocus" aria-required="true"
|
||||
aria-invalid="true">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-group row">
|
||||
<label class="col-sm-3 col-form-label" for="loginform-password"><?= __('user', 'Password') ?></label>
|
||||
<div class="col-sm-9">
|
||||
<input type="password" id="loginform-password" class="form-control" name="password" value="" aria-required="true">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
|
||||
<div class="offset-sm-3 col-sm-9">
|
||||
<button type="submit" class="btn btn-primary" name="login-button"><?= __('user', 'Login') ?></button>
|
||||
</div>
|
||||
</div>
|
||||
-->
|
||||
</form>
|
||||
|
||||
<?php if ($canRegister): ?>
|
||||
|
||||
<div class="p-3 border bg-light text-dark">
|
||||
<p><?= __('user', 'No account yet?') ?></p>
|
||||
<p><a href="<?= $this->getUrl('user/default/register')?>" class="btn btn-primary"><?= __('user', 'Register') ?></a></p>
|
||||
<hr>
|
||||
<p><a href="<?= $this->getUrl('user/default/reminder')?>"><?= __('user', 'Forget password?') ?></a></p>
|
||||
</div>
|
||||
|
||||
<?php endif ?>
|
||||
</main>
|
||||
|
||||
|
||||
|
||||
82
modules/user/views/default/register.php
Normal file
82
modules/user/views/default/register.php
Normal file
@@ -0,0 +1,82 @@
|
||||
<?php
|
||||
use piko\Piko;
|
||||
/* @var $this \piko\View */
|
||||
/* @var $router \piko\Router */
|
||||
/* @var $message array */
|
||||
|
||||
$router = Piko::get('router');
|
||||
|
||||
$this->title = Piko::t('user', 'Register');
|
||||
|
||||
if (is_array($message)) {
|
||||
$this->params['message'] = $message;
|
||||
return;
|
||||
}
|
||||
|
||||
$js = <<<SCRIPT
|
||||
jQuery(document).ready(function($) {
|
||||
|
||||
function validateField(e) {
|
||||
var that = this;
|
||||
|
||||
$.post('{$router->getUrl('user/default/check-registration')}', $('#register-form').serialize(), function(errors) {
|
||||
if (errors[that.name]) {
|
||||
$(that).addClass('is-invalid')
|
||||
$(that).removeClass('is-valid')
|
||||
$(that).next('.invalid-feedback').text(errors[that.name])
|
||||
} else {
|
||||
$(that).removeClass('is-invalid')
|
||||
$(that).addClass('is-valid')
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
$('#username').focusout(validateField);
|
||||
$('#email').focusout(validateField);
|
||||
$('#password').focusout(validateField);
|
||||
$('#password2').focusout(validateField);
|
||||
|
||||
});
|
||||
SCRIPT;
|
||||
|
||||
$this->registerJs($js);
|
||||
|
||||
|
||||
?>
|
||||
|
||||
<div class="container mt-5">
|
||||
|
||||
<h1><?= $this->title ?></h1>
|
||||
|
||||
<form method="post" id="register-form" novalidate>
|
||||
<div class="form-group">
|
||||
<label for="username"><?= Piko::t('user', 'Username') ?></label>
|
||||
<input type="text" class="form-control" id="username" name="username" value="">
|
||||
<div class="invalid-feedback"></div>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label for="email"><?= Piko::t('user', 'Email') ?></label>
|
||||
<input type="text" class="form-control" id="email" name="email" value="">
|
||||
<div class="invalid-feedback"></div>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label for="password"><?= Piko::t('user', 'Password') ?></label>
|
||||
<input type="password" class="form-control" id="password" name="password" value="" autocomplete="off">
|
||||
<div class="invalid-feedback"></div>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label for="password2"><?= Piko::t('user', 'Confirm your password') ?></label>
|
||||
<input type="password" class="form-control" id="password2" name="password2" value="" autocomplete="off">
|
||||
<div class="invalid-feedback"></div>
|
||||
</div>
|
||||
|
||||
<button type="submit" class="btn btn-primary"><?= Piko::t('user', 'Register') ?></button>
|
||||
</form>
|
||||
|
||||
</div>
|
||||
|
||||
|
||||
|
||||
31
modules/user/views/default/reminder.php
Normal file
31
modules/user/views/default/reminder.php
Normal file
@@ -0,0 +1,31 @@
|
||||
<?php
|
||||
use piko\Piko;
|
||||
/* @var $this \piko\View */
|
||||
/* @var $message array */
|
||||
/* @var $reminder string */
|
||||
|
||||
$this->title = Piko::t('user', 'Forget password');
|
||||
|
||||
if (is_array($message)) {
|
||||
$this->params['message'] = $message;
|
||||
}
|
||||
|
||||
?>
|
||||
|
||||
<div class="container" style="margin-top: 100px">
|
||||
|
||||
<h1><?= $this->title ?></h1>
|
||||
|
||||
<form method="post" id="reminder-form" novalidate>
|
||||
<div class="form-group">
|
||||
<label for="reminder"><?= Piko::t('user', 'Your email or your username') ?></label>
|
||||
<input type="text" class="form-control" id="reminder" name="reminder" value="<?= $reminder ?>" autocomplete="off">
|
||||
</div>
|
||||
<button type="submit" class="btn btn-primary"><?= Piko::t('user', 'Send') ?></button>
|
||||
</form>
|
||||
|
||||
</div>
|
||||
|
||||
|
||||
|
||||
|
||||
75
modules/user/views/default/reset.php
Normal file
75
modules/user/views/default/reset.php
Normal file
@@ -0,0 +1,75 @@
|
||||
<?php
|
||||
use piko\Piko;
|
||||
|
||||
/* @var $this \piko\View */
|
||||
/* @var $user piko\user\models\User */
|
||||
/* @var $message array */
|
||||
/* @var $router \piko\Router */
|
||||
|
||||
$router = Piko::get('router');
|
||||
|
||||
$this->title = Piko::t('user', 'Change your account ({account}) password',['account' => $user->username]);
|
||||
|
||||
if (is_array($message)) {
|
||||
$this->params['message'] = $message;
|
||||
|
||||
echo '<div class="container text-center"><a class="btn btn-primary" href="'. $router->getUrl('user/default/login').'">'
|
||||
. Piko::t('user', 'Login') . '</a></div>';
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
$js = <<<SCRIPT
|
||||
jQuery(document).ready(function($) {
|
||||
|
||||
function validateField(e) {
|
||||
var that = this;
|
||||
|
||||
$.post('{$router->getUrl('user/default/check-registration')}', $('#register-form').serialize(), function(errors) {
|
||||
if (errors[that.name]) {
|
||||
$(that).addClass('is-invalid')
|
||||
$(that).removeClass('is-valid')
|
||||
$(that).next('.invalid-feedback').text(errors[that.name])
|
||||
} else {
|
||||
$(that).removeClass('is-invalid')
|
||||
$(that).addClass('is-valid')
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
$('#password').focusout(validateField);
|
||||
$('#password2').focusout(validateField);
|
||||
|
||||
});
|
||||
SCRIPT;
|
||||
|
||||
$this->registerJs($js);
|
||||
|
||||
|
||||
?>
|
||||
|
||||
<div class="container" style="margin-top: 100px">
|
||||
|
||||
<h1 class="h4"><?= $this->title ?></h1>
|
||||
|
||||
<form method="post" id="register-form" novalidate>
|
||||
|
||||
<div class="form-group">
|
||||
<label for="password"><?= Piko::t('user', 'Password') ?></label>
|
||||
<input type="password" class="form-control" id="password" name="password" value="" autocomplete="off">
|
||||
<div class="invalid-feedback"></div>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label for="password2"><?= Piko::t('user', 'Confirm your password') ?></label>
|
||||
<input type="password" class="form-control" id="password2" name="password2" value="" autocomplete="off">
|
||||
<div class="invalid-feedback"></div>
|
||||
</div>
|
||||
|
||||
<button type="submit" class="btn btn-primary"><?= Piko::t('user', 'Send') ?></button>
|
||||
</form>
|
||||
|
||||
</div>
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user