Add email address field for projects

This commit is contained in:
Frederic Guillot 2017-02-04 16:03:29 -05:00
parent 8eac121888
commit d5c4c18ea0
36 changed files with 198 additions and 9 deletions

View File

@ -3,6 +3,7 @@ Version 1.0.39 (unreleased)
Improvements:
* Add email address field for projects
* Improve forget password behaviour (notify the user that an email has been sent or not)
* Do not display current project in board selector
* Do not set default task assignee for team projects

View File

@ -32,6 +32,13 @@ class ProjectProcedure extends BaseProcedure
return $this->formatProject($project);
}
public function getProjectByEmail($email)
{
$project = $this->formatProject($this->projectModel->getByEmail($email));
ProjectAuthorization::getInstance($this->container)->check($this->getClassName(), 'getProjectByEmail', $project['id']);
return $this->formatProject($project);
}
public function getAllProjects()
{
return $this->formatProjects($this->projectModel->getAll());

View File

@ -12,6 +12,11 @@ use Kanboard\Core\Base;
*/
class AppHelper extends Base
{
public function isAjax()
{
return $this->request->isAjax();
}
/**
* Render Javascript component
*

View File

@ -1311,4 +1311,6 @@ return array(
// 'Your profile must have a valid email address.' => '',
// 'Unfortunately, we are unable to reset your password. Did you entered a valid username? Do you have an email address in your profile?' => '',
// 'TRL - Turkish Lira' => '',
// 'The project email is optional and could be used by several plugins.' => '',
// 'The email project must be unique across all projects' => '',
);

View File

@ -1311,4 +1311,6 @@ return array(
// 'Your profile must have a valid email address.' => '',
// 'Unfortunately, we are unable to reset your password. Did you entered a valid username? Do you have an email address in your profile?' => '',
// 'TRL - Turkish Lira' => '',
// 'The project email is optional and could be used by several plugins.' => '',
// 'The email project must be unique across all projects' => '',
);

View File

@ -1311,4 +1311,6 @@ return array(
// 'Your profile must have a valid email address.' => '',
// 'Unfortunately, we are unable to reset your password. Did you entered a valid username? Do you have an email address in your profile?' => '',
// 'TRL - Turkish Lira' => '',
// 'The project email is optional and could be used by several plugins.' => '',
// 'The email project must be unique across all projects' => '',
);

View File

@ -1311,4 +1311,6 @@ return array(
// 'Your profile must have a valid email address.' => '',
// 'Unfortunately, we are unable to reset your password. Did you entered a valid username? Do you have an email address in your profile?' => '',
// 'TRL - Turkish Lira' => '',
// 'The project email is optional and could be used by several plugins.' => '',
// 'The email project must be unique across all projects' => '',
);

View File

@ -1311,4 +1311,6 @@ return array(
// 'Your profile must have a valid email address.' => '',
// 'Unfortunately, we are unable to reset your password. Did you entered a valid username? Do you have an email address in your profile?' => '',
// 'TRL - Turkish Lira' => '',
// 'The project email is optional and could be used by several plugins.' => '',
// 'The email project must be unique across all projects' => '',
);

View File

@ -1311,4 +1311,6 @@ return array(
// 'Your profile must have a valid email address.' => '',
// 'Unfortunately, we are unable to reset your password. Did you entered a valid username? Do you have an email address in your profile?' => '',
// 'TRL - Turkish Lira' => '',
// 'The project email is optional and could be used by several plugins.' => '',
// 'The email project must be unique across all projects' => '',
);

View File

@ -1311,4 +1311,6 @@ return array(
// 'Your profile must have a valid email address.' => '',
// 'Unfortunately, we are unable to reset your password. Did you entered a valid username? Do you have an email address in your profile?' => '',
// 'TRL - Turkish Lira' => '',
// 'The project email is optional and could be used by several plugins.' => '',
// 'The email project must be unique across all projects' => '',
);

View File

@ -961,7 +961,7 @@ return array(
'Do you really want to close all tasks of this column?' => 'Voulez-vous vraiment fermer toutes les tâches de cette colonne ?',
'%d task(s) in the column "%s" and the swimlane "%s" will be closed.' => '%d tâche(s) dans la colonne « %s » et la swimlane « %s » seront fermées.',
'Close all tasks of this column' => 'Fermer toutes les tâches de cette colonne',
'No plugin has registered a project notification method. You can still configure individual notifications in your user profile.' => 'Aucun plugin n\'a enregistré une méthode de notification de projet. Vous pouvez toujours configurer les notifications individuelles dans votre profil d\'utilisateur.',
'No plugin has registered a project notification method. You can still configure individual notifications in your user profile.' => 'Aucun plugin n\'a enregistré une méthode de notification de projet. Vous pouvez toujours configurer les notifications individuelles dans votre profil utilisateur.',
'My dashboard' => 'Mon tableau de bord',
'My profile' => 'Mon profil',
'Project owner: ' => 'Responsable du projet : ',
@ -1311,4 +1311,6 @@ return array(
'Your profile must have a valid email address.' => 'Votre profil doit avoir une adresse e-mail valide.',
'Unfortunately, we are unable to reset your password. Did you entered a valid username? Do you have an email address in your profile?' => 'Malheureusement, nous ne pouvons pas réinitialiser votre mot de passe. Avez-vous saisi un nom d\'utilisateur valide ? Avez-vous une adresse e-mail dans votre profil ?',
'TRL - Turkish Lira' => 'TRL - Livre turque',
'The project email is optional and could be used by several plugins.' => 'L\'adresse email d\'un projet est optionnel et pourrait être utilisé par plusieurs extensions.',
'The email project must be unique across all projects' => 'L\'adresse email d\'un projet doit être unique pour tous les projets',
);

View File

@ -1311,4 +1311,6 @@ return array(
// 'Your profile must have a valid email address.' => '',
// 'Unfortunately, we are unable to reset your password. Did you entered a valid username? Do you have an email address in your profile?' => '',
// 'TRL - Turkish Lira' => '',
// 'The project email is optional and could be used by several plugins.' => '',
// 'The email project must be unique across all projects' => '',
);

View File

@ -1311,4 +1311,6 @@ return array(
// 'Your profile must have a valid email address.' => '',
// 'Unfortunately, we are unable to reset your password. Did you entered a valid username? Do you have an email address in your profile?' => '',
// 'TRL - Turkish Lira' => '',
// 'The project email is optional and could be used by several plugins.' => '',
// 'The email project must be unique across all projects' => '',
);

View File

@ -1311,4 +1311,6 @@ return array(
// 'Your profile must have a valid email address.' => '',
// 'Unfortunately, we are unable to reset your password. Did you entered a valid username? Do you have an email address in your profile?' => '',
// 'TRL - Turkish Lira' => '',
// 'The project email is optional and could be used by several plugins.' => '',
// 'The email project must be unique across all projects' => '',
);

View File

@ -1311,4 +1311,6 @@ return array(
// 'Your profile must have a valid email address.' => '',
// 'Unfortunately, we are unable to reset your password. Did you entered a valid username? Do you have an email address in your profile?' => '',
// 'TRL - Turkish Lira' => '',
// 'The project email is optional and could be used by several plugins.' => '',
// 'The email project must be unique across all projects' => '',
);

View File

@ -1311,4 +1311,6 @@ return array(
// 'Your profile must have a valid email address.' => '',
// 'Unfortunately, we are unable to reset your password. Did you entered a valid username? Do you have an email address in your profile?' => '',
// 'TRL - Turkish Lira' => '',
// 'The project email is optional and could be used by several plugins.' => '',
// 'The email project must be unique across all projects' => '',
);

View File

@ -1311,4 +1311,6 @@ return array(
// 'Your profile must have a valid email address.' => '',
// 'Unfortunately, we are unable to reset your password. Did you entered a valid username? Do you have an email address in your profile?' => '',
// 'TRL - Turkish Lira' => '',
// 'The project email is optional and could be used by several plugins.' => '',
// 'The email project must be unique across all projects' => '',
);

View File

@ -1311,4 +1311,6 @@ return array(
// 'Your profile must have a valid email address.' => '',
// 'Unfortunately, we are unable to reset your password. Did you entered a valid username? Do you have an email address in your profile?' => '',
// 'TRL - Turkish Lira' => '',
// 'The project email is optional and could be used by several plugins.' => '',
// 'The email project must be unique across all projects' => '',
);

View File

@ -1311,4 +1311,6 @@ return array(
// 'Your profile must have a valid email address.' => '',
// 'Unfortunately, we are unable to reset your password. Did you entered a valid username? Do you have an email address in your profile?' => '',
// 'TRL - Turkish Lira' => '',
// 'The project email is optional and could be used by several plugins.' => '',
// 'The email project must be unique across all projects' => '',
);

View File

@ -1311,4 +1311,6 @@ return array(
// 'Your profile must have a valid email address.' => '',
// 'Unfortunately, we are unable to reset your password. Did you entered a valid username? Do you have an email address in your profile?' => '',
// 'TRL - Turkish Lira' => '',
// 'The project email is optional and could be used by several plugins.' => '',
// 'The email project must be unique across all projects' => '',
);

View File

@ -1311,4 +1311,6 @@ return array(
// 'Your profile must have a valid email address.' => '',
// 'Unfortunately, we are unable to reset your password. Did you entered a valid username? Do you have an email address in your profile?' => '',
// 'TRL - Turkish Lira' => '',
// 'The project email is optional and could be used by several plugins.' => '',
// 'The email project must be unique across all projects' => '',
);

View File

@ -1311,4 +1311,6 @@ return array(
'Your profile must have a valid email address.' => 'O seu perfil deve ter um endereço de email válido.',
'Unfortunately, we are unable to reset your password. Did you entered a valid username? Do you have an email address in your profile?' => 'Infelizmente, não conseguimos redefinir a sua password. Colocou um utilizador válido? Tem um endereço de email definido no seu perfil?',
'TRL - Turkish Lira' => 'TRL - Lira Turca',
// 'The project email is optional and could be used by several plugins.' => '',
// 'The email project must be unique across all projects' => '',
);

View File

@ -1311,4 +1311,6 @@ return array(
// 'Your profile must have a valid email address.' => '',
// 'Unfortunately, we are unable to reset your password. Did you entered a valid username? Do you have an email address in your profile?' => '',
// 'TRL - Turkish Lira' => '',
// 'The project email is optional and could be used by several plugins.' => '',
// 'The email project must be unique across all projects' => '',
);

View File

@ -1311,4 +1311,6 @@ return array(
// 'Your profile must have a valid email address.' => '',
// 'Unfortunately, we are unable to reset your password. Did you entered a valid username? Do you have an email address in your profile?' => '',
// 'TRL - Turkish Lira' => '',
// 'The project email is optional and could be used by several plugins.' => '',
// 'The email project must be unique across all projects' => '',
);

View File

@ -1311,4 +1311,6 @@ return array(
// 'Your profile must have a valid email address.' => '',
// 'Unfortunately, we are unable to reset your password. Did you entered a valid username? Do you have an email address in your profile?' => '',
// 'TRL - Turkish Lira' => '',
// 'The project email is optional and could be used by several plugins.' => '',
// 'The email project must be unique across all projects' => '',
);

View File

@ -1311,4 +1311,6 @@ return array(
// 'Your profile must have a valid email address.' => '',
// 'Unfortunately, we are unable to reset your password. Did you entered a valid username? Do you have an email address in your profile?' => '',
// 'TRL - Turkish Lira' => '',
// 'The project email is optional and could be used by several plugins.' => '',
// 'The email project must be unique across all projects' => '',
);

View File

@ -1311,4 +1311,6 @@ return array(
// 'Your profile must have a valid email address.' => '',
// 'Unfortunately, we are unable to reset your password. Did you entered a valid username? Do you have an email address in your profile?' => '',
// 'TRL - Turkish Lira' => '',
// 'The project email is optional and could be used by several plugins.' => '',
// 'The email project must be unique across all projects' => '',
);

View File

@ -1311,4 +1311,6 @@ return array(
// 'Your profile must have a valid email address.' => '',
// 'Unfortunately, we are unable to reset your password. Did you entered a valid username? Do you have an email address in your profile?' => '',
// 'TRL - Turkish Lira' => '',
// 'The project email is optional and could be used by several plugins.' => '',
// 'The email project must be unique across all projects' => '',
);

View File

@ -105,6 +105,22 @@ class ProjectModel extends Base
return $this->db->table(self::TABLE)->eq('identifier', strtoupper($identifier))->findOne();
}
/**
* Get a project by the email address
*
* @access public
* @param string $email
* @return array|boolean
*/
public function getByEmail($email)
{
if (empty($email)) {
return false;
}
return $this->db->table(self::TABLE)->eq('email', $email)->findOne();
}
/**
* Fetch project data by using the token
*

View File

@ -6,7 +6,12 @@ use PDO;
use Kanboard\Core\Security\Token;
use Kanboard\Core\Security\Role;
const VERSION = 120;
const VERSION = 121;
function version_121(PDO $pdo)
{
$pdo->exec('ALTER TABLE projects ADD COLUMN email VARCHAR(255)');
}
function version_120(PDO $pdo)
{

View File

@ -6,7 +6,12 @@ use PDO;
use Kanboard\Core\Security\Token;
use Kanboard\Core\Security\Role;
const VERSION = 99;
const VERSION = 100;
function version_100(PDO $pdo)
{
$pdo->exec('ALTER TABLE "projects" ADD COLUMN email VARCHAR(255)');
}
function version_99(PDO $pdo)
{

View File

@ -6,7 +6,12 @@ use Kanboard\Core\Security\Token;
use Kanboard\Core\Security\Role;
use PDO;
const VERSION = 110;
const VERSION = 111;
function version_111(PDO $pdo)
{
$pdo->exec('ALTER TABLE "projects" ADD COLUMN email TEXT');
}
function version_110(PDO $pdo)
{

View File

@ -1,6 +1,12 @@
<div class="page-header">
<h2><?= $this->text->e($project['name']) ?> &gt; <?= t('Edit project') ?></h2>
</div>
<?php if ($this->app->isAjax()): ?>
<div class="page-header">
<h2><?= $this->text->e($project['name']) ?> &gt; <?= t('Edit project') ?></h2>
</div>
<?php else: ?>
<div class="page-header">
<h2><?= t('Edit project') ?></h2>
</div>
<?php endif ?>
<form method="post" action="<?= $this->url->href('ProjectEditController', 'update', array('project_id' => $project['id'], 'redirect' => 'edit')) ?>" autocomplete="off">
<?= $this->form->csrf() ?>
<?= $this->form->hidden('id', $values) ?>
@ -11,12 +17,16 @@
<?= $this->form->label(t('Name'), 'name') ?>
<?= $this->form->text('name', $values, $errors, array('required', 'maxlength="50"', 'autofocus', 'tabindex="1"')) ?>
<?= $this->form->label(t('Email'), 'email') ?>
<?= $this->form->email('email', $values, $errors, array('maxlength="255"', 'tabindex="2"')) ?>
<p class="form-help"><?= t('The project email is optional and could be used by several plugins.') ?></p>
<?= $this->form->label(t('Identifier'), 'identifier') ?>
<?= $this->form->text('identifier', $values, $errors, array('maxlength="50"', 'tabindex="2"')) ?>
<?= $this->form->text('identifier', $values, $errors, array('maxlength="50"', 'tabindex="3"')) ?>
<p class="form-help"><?= t('The project identifier is optional and must be alphanumeric, example: MYPROJECT.') ?></p>
<?= $this->form->label(t('Description'), 'description') ?>
<?= $this->form->textEditor('description', $values, $errors, array('tabindex' => 3)) ?>
<?= $this->form->textEditor('description', $values, $errors, array('tabindex' => 4)) ?>
</fieldset>
<fieldset>

View File

@ -34,6 +34,8 @@ class ProjectValidator extends BaseValidator
new Validators\MaxLength('end_date', t('The maximum length is %d characters', 10), 10),
new Validators\AlphaNumeric('identifier', t('This value must be alphanumeric')) ,
new Validators\Unique('identifier', t('The identifier must be unique'), $this->db->getConnection(), ProjectModel::TABLE),
new Validators\Email('email', t('Email address invalid')) ,
new Validators\Unique('email', t('The email project must be unique across all projects'), $this->db->getConnection(), ProjectModel::TABLE),
);
}

View File

@ -182,6 +182,56 @@ Response example:
}
```
## getProjectByEmail
- Purpose: **Get project information**
- Parameters:
- **email** (string, required)
- Result on success: **project properties**
- Result on failure: **null**
Request example:
```json
{
"jsonrpc": "2.0",
"method": "getProjectByEmail",
"id": 1620253806,
"params": {
"email": "my_project@my_domain.tld"
}
}
```
Response example:
```json
{
"jsonrpc": "2.0",
"id": 1620253806,
"result": {
"id": "1",
"name": "Test",
"is_active": "1",
"token": "",
"last_modified": "1436119135",
"is_public": "0",
"is_private": "0",
"is_everybody_allowed": "0",
"default_swimlane": "Default swimlane",
"show_default_swimlane": "1",
"description": "test",
"identifier": "",
"email": "my_project@my_domain.tld",
"url": {
"board": "http:\/\/127.0.0.1:8000\/?controller=board&action=show&project_id=1",
"calendar": "http:\/\/127.0.0.1:8000\/?controller=calendar&action=show&project_id=1",
"list": "http:\/\/127.0.0.1:8000\/?controller=listing&action=show&project_id=1"
}
}
}
```
## getAllProjects
- Purpose: **Get all available projects**

View File

@ -298,6 +298,39 @@ class ProjectModelTest extends Base
$this->assertFalse($project);
}
public function testEmail()
{
$projectModel = new ProjectModel($this->container);
// Creation
$this->assertEquals(1, $projectModel->create(array('name' => 'UnitTest1', 'email' => 'test1@localhost')));
$this->assertEquals(2, $projectModel->create(array('name' => 'UnitTest2')));
$project = $projectModel->getById(1);
$this->assertNotEmpty($project);
$this->assertEquals('test1@localhost', $project['email']);
$project = $projectModel->getById(2);
$this->assertNotEmpty($project);
$this->assertEquals('', $project['email']);
// Update
$this->assertTrue($projectModel->update(array('id' => '1', 'email' => 'test1@here')));
$project = $projectModel->getById(1);
$this->assertNotEmpty($project);
$this->assertEquals('test1@here', $project['email']);
$project = $projectModel->getByEmail('test1@here');
$this->assertEquals(1, $project['id']);
$project = $projectModel->getByEmail('test1@localhost');
$this->assertEmpty($project);
$project = $projectModel->getByEmail('');
$this->assertFalse($project);
}
public function testThatProjectCreatorAreAlsoOwner()
{
$projectModel = new ProjectModel($this->container);