diff options
Diffstat (limited to 'app')
-rw-r--r-- | app/Controller/UserCreationController.php | 2 | ||||
-rw-r--r-- | app/Controller/UserInviteController.php | 107 | ||||
-rw-r--r-- | app/Core/Base.php | 1 | ||||
-rw-r--r-- | app/Helper/AppHelper.php | 6 | ||||
-rw-r--r-- | app/Model/InviteModel.php | 73 | ||||
-rw-r--r-- | app/Schema/Mysql.php | 18 | ||||
-rw-r--r-- | app/Schema/Postgres.php | 18 | ||||
-rw-r--r-- | app/Schema/Sqlite.php | 18 | ||||
-rw-r--r-- | app/ServiceProvider/AuthenticationProvider.php | 1 | ||||
-rw-r--r-- | app/ServiceProvider/ClassProvider.php | 1 | ||||
-rw-r--r-- | app/Template/layout.php | 1 | ||||
-rw-r--r-- | app/Template/user_invite/email.php | 12 | ||||
-rw-r--r-- | app/Template/user_invite/show.php | 15 | ||||
-rw-r--r-- | app/Template/user_invite/signup.php | 46 | ||||
-rw-r--r-- | app/Template/user_list/show.php | 3 | ||||
-rw-r--r-- | app/Validator/UserValidator.php | 2 |
16 files changed, 307 insertions, 17 deletions
diff --git a/app/Controller/UserCreationController.php b/app/Controller/UserCreationController.php index f0fd34d2..27f1687b 100644 --- a/app/Controller/UserCreationController.php +++ b/app/Controller/UserCreationController.php @@ -54,7 +54,7 @@ class UserCreationController extends BaseController * * @param array $values */ - private function createUser(array $values) + protected function createUser(array $values) { $project_id = empty($values['project_id']) ? 0 : $values['project_id']; unset($values['project_id']); diff --git a/app/Controller/UserInviteController.php b/app/Controller/UserInviteController.php new file mode 100644 index 00000000..8c77940c --- /dev/null +++ b/app/Controller/UserInviteController.php @@ -0,0 +1,107 @@ +<?php + +namespace Kanboard\Controller; + +use Kanboard\Core\Controller\PageNotFoundException; +use Kanboard\Core\Security\Role; +use Kanboard\Notification\MailNotification; + +/** + * Class UserInviteController + * + * @package Kanboard\Controller + * @author Frederic Guillot + */ +class UserInviteController extends BaseController +{ + public function show(array $values = array(), array $errors = array()) + { + $this->response->html($this->template->render('user_invite/show', array( + 'projects' => $this->projectModel->getList(), + 'errors' => $errors, + 'values' => $values, + ))); + } + + public function save() + { + $values = $this->request->getValues(); + + if (! empty($values['emails']) && isset($values['project_id'])) { + $emails = explode("\r\n", trim($values['emails'])); + $nb = $this->inviteModel->createInvites($emails, $values['project_id']); + $this->flash->success($nb > 1 ? t('%d invitations were sent.', $nb) : t('%d invitation was sent.', $nb)); + } + + $this->response->redirect($this->helper->url->to('UserListController', 'show')); + } + + public function signup(array $values = array(), array $errors = array()) + { + $invite = $this->getInvite(); + + $this->response->html($this->helper->layout->app('user_invite/signup', array( + 'no_layout' => true, + 'not_editable' => true, + 'token' => $invite['token'], + 'errors' => $errors, + 'values' => $values + array('email' => $invite['email']), + 'timezones' => $this->timezoneModel->getTimezones(true), + 'languages' => $this->languageModel->getLanguages(true), + ))); + } + + public function register() + { + $invite = $this->getInvite(); + + $values = $this->request->getValues(); + list($valid, $errors) = $this->userValidator->validateCreation($values); + + if ($valid) { + $this->createUser($invite, $values); + } else { + $this->signup($values, $errors); + } + } + + protected function getInvite() + { + $token = $this->request->getStringParam('token'); + + if (empty($token)) { + throw PageNotFoundException::getInstance()->withoutLayout(); + } + + $invite = $this->inviteModel->getByToken($token); + + if (empty($invite)) { + throw PageNotFoundException::getInstance()->withoutLayout(); + } + + return $invite; + } + + protected function createUser(array $invite, array $values) + { + $user_id = $this->userModel->create($values); + + if ($user_id !== false) { + if ($invite['project_id'] != 0) { + $this->projectUserRoleModel->addUser($invite['project_id'], $user_id, Role::PROJECT_MEMBER); + } + + if (! empty($values['notifications_enabled'])) { + $this->userNotificationTypeModel->saveSelectedTypes($user_id, array(MailNotification::TYPE)); + } + + $this->inviteModel->remove($invite['email']); + + $this->flash->success(t('User created successfully.')); + $this->response->redirect($this->helper->url->to('AuthController', 'login')); + } else { + $this->flash->failure(t('Unable to create this user.')); + $this->response->redirect($this->helper->url->to('UserInviteController', 'signup')); + } + } +} diff --git a/app/Core/Base.php b/app/Core/Base.php index d3c264d1..17ed5b33 100644 --- a/app/Core/Base.php +++ b/app/Core/Base.php @@ -94,6 +94,7 @@ use Pimple\Container; * @property \Kanboard\Model\ProjectFileModel $projectFileModel * @property \Kanboard\Model\GroupModel $groupModel * @property \Kanboard\Model\GroupMemberModel $groupMemberModel + * @property \Kanboard\Model\InviteModel $inviteModel * @property \Kanboard\Model\LanguageModel $languageModel * @property \Kanboard\Model\LastLoginModel $lastLoginModel * @property \Kanboard\Model\LinkModel $linkModel diff --git a/app/Helper/AppHelper.php b/app/Helper/AppHelper.php index 62062244..3b48d7d3 100644 --- a/app/Helper/AppHelper.php +++ b/app/Helper/AppHelper.php @@ -29,12 +29,12 @@ class AppHelper extends Base * * @access public * @param string $param - * @param mixed $default_value + * @param mixed $default * @return mixed */ - public function config($param, $default_value = '') + public function config($param, $default = '') { - return $this->configModel->get($param, $default_value); + return $this->configModel->get($param, $default); } /** diff --git a/app/Model/InviteModel.php b/app/Model/InviteModel.php new file mode 100644 index 00000000..13d75f69 --- /dev/null +++ b/app/Model/InviteModel.php @@ -0,0 +1,73 @@ +<?php + +namespace Kanboard\Model; + +use Kanboard\Core\Base; +use Kanboard\Core\Security\Token; + +/** + * Class InviteModel + * + * @package Kanboard\Model + * @author Frederic Guillot + */ +class InviteModel extends Base +{ + const TABLE = 'invites'; + + public function createInvites(array $emails, $projectId) + { + $emails = array_unique($emails); + $nb = 0; + + foreach ($emails as $email) { + $email = trim($email); + + if (! empty($email) && $this->createInvite($email, $projectId)) { + $nb++; + } + } + + return $nb; + } + + protected function createInvite($email, $projectId) + { + $values = array( + 'email' => $email, + 'project_id' => $projectId, + 'token' => Token::getToken(), + ); + + if ($this->db->table(self::TABLE)->insert($values)) { + $this->sendInvite($values); + return true; + } + + return false; + } + + protected function sendInvite(array $values) + { + $this->emailClient->send( + $values['email'], + $values['email'], + e('Kanboard Invitation'), + $this->template->render('user_invite/email', array('token' => $values['token'])) + ); + } + + public function getByToken($token) + { + return $this->db->table(self::TABLE) + ->eq('token', $token) + ->findOne(); + } + + public function remove($email) + { + return $this->db->table(self::TABLE) + ->eq('email', $email) + ->remove(); + } +} diff --git a/app/Schema/Mysql.php b/app/Schema/Mysql.php index 6c1c9da6..fe376018 100644 --- a/app/Schema/Mysql.php +++ b/app/Schema/Mysql.php @@ -6,14 +6,24 @@ use PDO; use Kanboard\Core\Security\Token; use Kanboard\Core\Security\Role; -const VERSION = 119; +const VERSION = 120; + +function version_120(PDO $pdo) +{ + $pdo->exec(" + CREATE TABLE invites ( + email VARCHAR(255) NOT NULL, + project_id INTEGER NOT NULL, + token VARCHAR(255) NOT NULL, + PRIMARY KEY(email, token) + ) ENGINE=InnoDB CHARSET=utf8 + "); +} function version_119(PDO $pdo) { $pdo->exec('ALTER TABLE `comments` ADD COLUMN `date_modification` BIGINT(20)'); - $pdo->exec('UPDATE `comments` - SET `date_modification` = `date_creation` - WHERE `date_modification` IS NULL'); + $pdo->exec('UPDATE `comments` SET `date_modification` = `date_creation` WHERE `date_modification` IS NULL'); } function version_118(PDO $pdo) diff --git a/app/Schema/Postgres.php b/app/Schema/Postgres.php index e7f89f82..fd7ecb57 100644 --- a/app/Schema/Postgres.php +++ b/app/Schema/Postgres.php @@ -6,14 +6,24 @@ use PDO; use Kanboard\Core\Security\Token; use Kanboard\Core\Security\Role; -const VERSION = 98; +const VERSION = 99; + +function version_99(PDO $pdo) +{ + $pdo->exec(" + CREATE TABLE invites ( + email VARCHAR(255) NOT NULL, + project_id INTEGER NOT NULL, + token VARCHAR(255) NOT NULL, + PRIMARY KEY(email, token) + ) + "); +} function version_98(PDO $pdo) { $pdo->exec('ALTER TABLE "comments" ADD COLUMN date_modification BIGINT'); - $pdo->exec('UPDATE "comments" - SET date_modification = date_creation - WHERE date_modification IS NULL'); + $pdo->exec('UPDATE "comments" SET date_modification = date_creation WHERE date_modification IS NULL'); } function version_97(PDO $pdo) diff --git a/app/Schema/Sqlite.php b/app/Schema/Sqlite.php index d2a7899e..a726a572 100644 --- a/app/Schema/Sqlite.php +++ b/app/Schema/Sqlite.php @@ -6,14 +6,24 @@ use Kanboard\Core\Security\Token; use Kanboard\Core\Security\Role; use PDO; -const VERSION = 109; +const VERSION = 110; + +function version_110(PDO $pdo) +{ + $pdo->exec(" + CREATE TABLE invites ( + email TEXT NOT NULL, + project_id INTEGER NOT NULL, + token TEXT NOT NULL, + PRIMARY KEY(email, token) + ) + "); +} function version_109(PDO $pdo) { $pdo->exec('ALTER TABLE comments ADD COLUMN date_modification INTEGER'); - $pdo->exec('UPDATE comments - SET date_modification = date_creation - WHERE date_modification IS NULL;'); + $pdo->exec('UPDATE comments SET date_modification = date_creation WHERE date_modification IS NULL;'); } function version_108(PDO $pdo) diff --git a/app/ServiceProvider/AuthenticationProvider.php b/app/ServiceProvider/AuthenticationProvider.php index c2978467..d953705d 100644 --- a/app/ServiceProvider/AuthenticationProvider.php +++ b/app/ServiceProvider/AuthenticationProvider.php @@ -136,6 +136,7 @@ class AuthenticationProvider implements ServiceProviderInterface $acl->add('ICalendarController', '*', Role::APP_PUBLIC); $acl->add('FeedController', '*', Role::APP_PUBLIC); $acl->add('AvatarFileController', array('show', 'image'), Role::APP_PUBLIC); + $acl->add('UserInviteController', array('signup', 'register'), Role::APP_PUBLIC); $acl->add('ConfigController', '*', Role::APP_ADMIN); $acl->add('TagController', '*', Role::APP_ADMIN); diff --git a/app/ServiceProvider/ClassProvider.php b/app/ServiceProvider/ClassProvider.php index 8d471b79..50ce531f 100644 --- a/app/ServiceProvider/ClassProvider.php +++ b/app/ServiceProvider/ClassProvider.php @@ -42,6 +42,7 @@ class ClassProvider implements ServiceProviderInterface 'CustomFilterModel', 'GroupModel', 'GroupMemberModel', + 'InviteModel', 'LanguageModel', 'LastLoginModel', 'LinkModel', diff --git a/app/Template/layout.php b/app/Template/layout.php index 8c85ffc6..241b99df 100644 --- a/app/Template/layout.php +++ b/app/Template/layout.php @@ -53,6 +53,7 @@ > <?php if (isset($no_layout) && $no_layout): ?> + <?= $this->app->flashMessage() ?> <?= $content_for_layout ?> <?php else: ?> <?= $this->hook->render('template:layout:top') ?> diff --git a/app/Template/user_invite/email.php b/app/Template/user_invite/email.php new file mode 100644 index 00000000..674e4a84 --- /dev/null +++ b/app/Template/user_invite/email.php @@ -0,0 +1,12 @@ +<p> + <?= t('You have been invited to register on Kanboard.') ?> +</p> + +<p> + <?= $this->url->absoluteLink(t('Click here to join your team'), 'UserInviteController', 'signup', array('token' => $token)) ?> +</p> + +<?php if ($this->app->config('application_url')): ?> + <hr> + <a href="<?= $this->app->config('application_url') ?>">Kanboard</a> +<?php endif ?> diff --git a/app/Template/user_invite/show.php b/app/Template/user_invite/show.php new file mode 100644 index 00000000..9d822248 --- /dev/null +++ b/app/Template/user_invite/show.php @@ -0,0 +1,15 @@ +<div class="page-header"> + <h2><?= t('Invite people') ?></h2> +</div> +<form method="post" action="<?= $this->url->href('UserInviteController', 'save') ?>" autocomplete="off"> + <?= $this->form->csrf() ?> + + <?= $this->form->label(t('Emails'), 'emails') ?> + <?= $this->form->textarea('emails', $values, $errors, array('required', 'autofocus')) ?> + <p class="form-help"><?= t('Enter one email address by line.') ?></p> + + <?= $this->form->label(t('Add these people to this project'), 'project_id') ?> + <?= $this->form->select('project_id', $projects, $values, $errors) ?> + + <?= $this->modal->submitButtons() ?> +</form> diff --git a/app/Template/user_invite/signup.php b/app/Template/user_invite/signup.php new file mode 100644 index 00000000..51edbab7 --- /dev/null +++ b/app/Template/user_invite/signup.php @@ -0,0 +1,46 @@ +<div class="form-login"> + <div class="page-header"> + <h2><?= t('Sign-up') ?></h2> + </div> + <form method="post" action="<?= $this->url->href('UserInviteController', 'register', array('token' => $token)) ?>" autocomplete="off"> + <?= $this->form->csrf() ?> + + <fieldset> + <legend><?= t('Profile') ?></legend> + + <?= $this->form->label(t('Username'), 'username') ?> + <?= $this->form->text('username', $values, $errors, array('autofocus', 'required', 'maxlength="50"')) ?> + + <?= $this->form->label(t('Name'), 'name') ?> + <?= $this->form->text('name', $values, $errors) ?> + + <?= $this->form->label(t('Email'), 'email') ?> + <?= $this->form->email('email', $values, $errors, array('required')) ?> + </fieldset> + + <fieldset> + <legend><?= t('Credentials') ?></legend> + <?= $this->form->label(t('Password'), 'password') ?> + <?= $this->form->password('password', $values, $errors, array('required')) ?> + + <?= $this->form->label(t('Confirmation'), 'confirmation') ?> + <?= $this->form->password('confirmation', $values, $errors, array('required')) ?> + </fieldset> + + <fieldset> + <legend><?= t('Preferences') ?></legend> + + <?= $this->form->label(t('Timezone'), 'timezone') ?> + <?= $this->form->select('timezone', $timezones, $values, $errors) ?> + + <?= $this->form->label(t('Language'), 'language') ?> + <?= $this->form->select('language', $languages, $values, $errors) ?> + + <?= $this->form->checkbox('notifications_enabled', t('Enable email notifications'), 1, isset($values['notifications_enabled']) && $values['notifications_enabled'] == 1 ? true : false) ?> + </fieldset> + + <div class="form-actions"> + <button class="btn btn-blue"><?= t('Sign-up') ?></button> + </div> + </form> +</div>
\ No newline at end of file diff --git a/app/Template/user_list/show.php b/app/Template/user_list/show.php index 667945ca..e83895ea 100644 --- a/app/Template/user_list/show.php +++ b/app/Template/user_list/show.php @@ -6,6 +6,9 @@ <?= $this->modal->medium('plus', t('New user'), 'UserCreationController', 'show') ?> </li> <li> + <?= $this->modal->medium('paper-plane', t('Invite people'), 'UserInviteController', 'show') ?> + </li> + <li> <?= $this->modal->medium('upload', t('Import'), 'UserImportController', 'show') ?> </li> <li> diff --git a/app/Validator/UserValidator.php b/app/Validator/UserValidator.php index 9911de50..fe402c47 100644 --- a/app/Validator/UserValidator.php +++ b/app/Validator/UserValidator.php @@ -25,7 +25,7 @@ class UserValidator extends BaseValidator return array( new Validators\MaxLength('role', t('The maximum length is %d characters', 25), 25), new Validators\MaxLength('username', t('The maximum length is %d characters', 50), 50), - new Validators\Unique('username', t('The username must be unique'), $this->db->getConnection(), UserModel::TABLE, 'id'), + new Validators\Unique('username', t('This username is already taken'), $this->db->getConnection(), UserModel::TABLE, 'id'), new Validators\Email('email', t('Email address invalid')), new Validators\Integer('is_ldap_user', t('This value must be an integer')), ); |