summaryrefslogtreecommitdiff
path: root/app
diff options
context:
space:
mode:
authorFrederic Guillot <fred@kanboard.net>2017-01-22 22:38:00 -0500
committerFrederic Guillot <fred@kanboard.net>2017-01-22 22:38:00 -0500
commit10d96bfd668f445249190c52bedb2eb0e7e9410d (patch)
tree951bcf637b9371529a6552a2efd22e7a84707d9e /app
parent2f43d365a0aaeff638eb4731bcff6907b491633a (diff)
Add user invitations
Diffstat (limited to 'app')
-rw-r--r--app/Controller/UserCreationController.php2
-rw-r--r--app/Controller/UserInviteController.php107
-rw-r--r--app/Core/Base.php1
-rw-r--r--app/Helper/AppHelper.php6
-rw-r--r--app/Model/InviteModel.php73
-rw-r--r--app/Schema/Mysql.php18
-rw-r--r--app/Schema/Postgres.php18
-rw-r--r--app/Schema/Sqlite.php18
-rw-r--r--app/ServiceProvider/AuthenticationProvider.php1
-rw-r--r--app/ServiceProvider/ClassProvider.php1
-rw-r--r--app/Template/layout.php1
-rw-r--r--app/Template/user_invite/email.php12
-rw-r--r--app/Template/user_invite/show.php15
-rw-r--r--app/Template/user_invite/signup.php46
-rw-r--r--app/Template/user_list/show.php3
-rw-r--r--app/Validator/UserValidator.php2
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')),
);