diff options
-rw-r--r-- | assets/css/app.css | 19 | ||||
-rw-r--r-- | controllers/task.php | 4 | ||||
-rw-r--r-- | locales/fr_FR/translations.php | 8 | ||||
-rw-r--r-- | locales/pl_PL/translations.php | 8 | ||||
-rw-r--r-- | models/base.php | 18 | ||||
-rw-r--r-- | models/comment.php | 8 | ||||
-rw-r--r-- | models/schema.php | 5 | ||||
-rw-r--r-- | models/task.php | 33 | ||||
-rw-r--r-- | templates/board_index.php | 13 | ||||
-rw-r--r-- | templates/board_public.php | 14 | ||||
-rw-r--r-- | templates/task_edit.php | 3 | ||||
-rw-r--r-- | templates/task_new.php | 3 | ||||
-rw-r--r-- | templates/task_show.php | 5 | ||||
-rw-r--r-- | tests/TaskTest.php | 27 | ||||
-rw-r--r-- | vendor/SimpleValidator/Validators/Date.php | 33 |
15 files changed, 195 insertions, 6 deletions
diff --git a/assets/css/app.css b/assets/css/app.css index c99a1eac..93321c2e 100644 --- a/assets/css/app.css +++ b/assets/css/app.css @@ -210,6 +210,10 @@ textarea.form-error { margin-right: 15px; } +input.form-date { + width: 150px; +} + /* alerts */ .alert { padding: 8px 35px 8px 14px; @@ -462,6 +466,21 @@ nav .active a { font-style: italic; } +.task-date { + font-weight: bold; + color: #D90000; +} + +.task-comment-counter { + position: absolute; + bottom: 0; + right: 5px; +} + +.task-footer { + margin-top: 10px; +} + .task { border: 1px solid #000; padding: 5px; diff --git a/controllers/task.php b/controllers/task.php index 89e1ee6c..fba4d4f5 100644 --- a/controllers/task.php +++ b/controllers/task.php @@ -216,6 +216,10 @@ class Task extends Base if (! $task) $this->notfound(); $this->checkProjectPermissions($task['project_id']); + if (! empty($task['date_due'])) { + $task['date_due'] = date(t('m/d/Y'), $task['date_due']); + } + $this->response->html($this->template->layout('task_edit', array( 'errors' => array(), 'values' => $task, diff --git a/locales/fr_FR/translations.php b/locales/fr_FR/translations.php index b69c0445..29214886 100644 --- a/locales/fr_FR/translations.php +++ b/locales/fr_FR/translations.php @@ -151,7 +151,7 @@ return array( 'Task opened successfully.' => 'Tâche ouverte avec succès.', 'Unable to close this task.' => 'Impossible de fermer cette tâche.', 'Task closed successfully.' => 'Tâche fermé avec succès.', - 'Unable to update your task.' => 'Impossible de fermer cette tâche.', + 'Unable to update your task.' => 'Impossible de modifier cette tâche.', 'Task updated successfully.' => 'Tâche mise à jour avec succès.', 'Unable to create your task.' => 'Impossible de créer cette tâche.', 'Task created successfully.' => 'Tâche créée avec succès.', @@ -212,4 +212,10 @@ return array( 'Unable to create your comment.' => 'Impossible de sauvegarder votre commentaire.', 'The description is required' => 'La description est obligatoire', 'Edit this task' => 'Modifier cette tâche', + 'Due Date' => 'Date d\'échéance', + 'm/d/Y' => 'd/m/Y', // Date format parsed with php + 'month/day/year' => 'jour/mois/année', // Help shown to the user + 'Invalid date' => 'Date invalide', + 'Must be done before %B %e, %G' => 'Doit être fait avant le %e %B %G', + '%B %e, %G' => '%e %B %G', ); diff --git a/locales/pl_PL/translations.php b/locales/pl_PL/translations.php index b088e30e..17b27c8e 100644 --- a/locales/pl_PL/translations.php +++ b/locales/pl_PL/translations.php @@ -214,5 +214,11 @@ return array( 'Comment added successfully.' => 'Komentarz dodany', 'Unable to create your comment.' => 'Nie udało się dodać komentarza', 'The description is required' => 'Opis jest wymagany', - 'Edit this task' => 'Edytuj zadanie' + 'Edit this task' => 'Edytuj zadanie', + // 'Due Date' => '', + // 'm/d/Y' => 'd/m/Y', // Date format parsed with php + // 'month/day/year' => 'jour/mois/année', // Help shown to the user + // 'Invalid date' => '', + // 'Must be done before %B %e, %G' => '', + // '%B %e, %G' => '%e %B %G', ); diff --git a/models/base.php b/models/base.php index 2ecf4280..95c5b07f 100644 --- a/models/base.php +++ b/models/base.php @@ -12,13 +12,14 @@ require __DIR__.'/../vendor/SimpleValidator/Validators/Integer.php'; require __DIR__.'/../vendor/SimpleValidator/Validators/Equals.php'; require __DIR__.'/../vendor/SimpleValidator/Validators/AlphaNumeric.php'; require __DIR__.'/../vendor/SimpleValidator/Validators/GreaterThan.php'; +require __DIR__.'/../vendor/SimpleValidator/Validators/Date.php'; require __DIR__.'/../vendor/PicoDb/Database.php'; require __DIR__.'/schema.php'; abstract class Base { const APP_VERSION = 'master'; - const DB_VERSION = 8; + const DB_VERSION = 9; private static $dbInstance = null; protected $db; @@ -59,4 +60,19 @@ abstract class Base return hash('crc32b', $token); } + + public function getTimestampFromDate($value, $format) + { + $date = \DateTime::createFromFormat($format, $value); + + if ($date !== false) { + $errors = \DateTime::getLastErrors(); + if ($errors['error_count'] === 0 && $errors['warning_count'] === 0) { + $timestamp = $date->getTimestamp(); + return $timestamp > 0 ? $timestamp : 0; + } + } + + return 0; + } } diff --git a/models/comment.php b/models/comment.php index e0944249..9e5cf8fd 100644 --- a/models/comment.php +++ b/models/comment.php @@ -25,6 +25,14 @@ class Comment extends Base ->findAll(); } + public function count($task_id) + { + return $this->db + ->table(self::TABLE) + ->eq(self::TABLE.'.task_id', $task_id) + ->count(); + } + public function create(array $values) { $values['date'] = time(); diff --git a/models/schema.php b/models/schema.php index 2c3ba7e8..14604e60 100644 --- a/models/schema.php +++ b/models/schema.php @@ -2,6 +2,11 @@ namespace Schema; +function version_9($pdo) +{ + $pdo->exec("ALTER TABLE tasks ADD COLUMN date_due INTEGER"); +} + function version_8($pdo) { $pdo->exec( diff --git a/models/task.php b/models/task.php index 77c620c8..017c7806 100644 --- a/models/task.php +++ b/models/task.php @@ -34,6 +34,7 @@ class Task extends Base self::TABLE.'.description', self::TABLE.'.date_creation', self::TABLE.'.date_completed', + self::TABLE.'.date_due', self::TABLE.'.color_id', self::TABLE.'.project_id', self::TABLE.'.column_id', @@ -66,6 +67,7 @@ class Task extends Base self::TABLE.'.description', self::TABLE.'.date_creation', self::TABLE.'.date_completed', + self::TABLE.'.date_due', self::TABLE.'.color_id', self::TABLE.'.project_id', self::TABLE.'.column_id', @@ -95,15 +97,23 @@ class Task extends Base public function getAllByColumnId($project_id, $column_id, $status = array(1)) { - return $this->db + $tasks = $this->db ->table(self::TABLE) - ->columns('tasks.id', 'title', 'color_id', 'project_id', 'owner_id', 'column_id', 'position', 'score', 'users.username') + ->columns('tasks.id', 'title', 'color_id', 'project_id', 'owner_id', 'column_id', 'position', 'score', 'date_due', 'users.username') ->join('users', 'id', 'owner_id') ->eq('project_id', $project_id) ->eq('column_id', $column_id) ->in('is_active', $status) ->asc('position') ->findAll(); + + $commentModel = new Comment; + + foreach ($tasks as &$task) { + $task['nb_comments'] = $commentModel->count($task['id']); + } + + return $tasks; } public function countByColumnId($project_id, $column_id, $status = array(1)) @@ -122,10 +132,17 @@ class Task extends Base unset($values['another_task']); + if (! empty($values['date_due'])) { + $values['date_due'] = $this->getTimestampFromDate($values['date_due'], t('m/d/Y')) ?: null; + } + $values['date_creation'] = time(); $values['position'] = $this->countByColumnId($values['project_id'], $values['column_id']); - $this->db->table(self::TABLE)->save($values); + if (! $this->db->table(self::TABLE)->save($values)) { + $this->db->cancelTransaction(); + return false; + } $task_id = $this->db->getConnection()->getLastId(); @@ -136,9 +153,14 @@ class Task extends Base public function update(array $values) { + if (! empty($values['date_due'])) { + $values['date_due'] = $this->getTimestampFromDate($values['date_due'], t('m/d/Y')) ?: null; + } + return $this->db->table(self::TABLE)->eq('id', $values['id'])->update($values); } + // Mark a task closed public function close($task_id) { return $this->db->table(self::TABLE) @@ -149,6 +171,7 @@ class Task extends Base )); } + // Mark a task open public function open($task_id) { return $this->db->table(self::TABLE) @@ -159,11 +182,13 @@ class Task extends Base )); } + // Remove a task public function remove($task_id) { return $this->db->table(self::TABLE)->eq('id', $task_id)->remove(); } + // Move a task to another column or to another position public function move($task_id, $column_id, $position) { return (bool) $this->db @@ -184,6 +209,7 @@ class Task extends Base new Validators\Integer('score', t('This value must be an integer')), new Validators\Required('title', t('The title is required')), new Validators\MaxLength('title', t('The maximum length is %d characters', 200), 200), + new Validators\Date('date_due', t('Invalid date'), t('m/d/Y')), )); return array( @@ -220,6 +246,7 @@ class Task extends Base new Validators\Integer('score', t('This value must be an integer')), new Validators\Required('title', t('The title is required')), new Validators\MaxLength('title', t('The maximum length is %d characters', 200), 200), + new Validators\Date('date_due', t('Invalid date'), t('m/d/Y')), )); return array( diff --git a/templates/board_index.php b/templates/board_index.php index 20862a4b..e6054189 100644 --- a/templates/board_index.php +++ b/templates/board_index.php @@ -74,6 +74,19 @@ <?= Helper\escape($task['title']) ?> </div> + <div class="task-footer"> + <?php if (! empty($task['date_due'])): ?> + <div class="task-date"> + <?= dt('%B %e, %G', $task['date_due']) ?> + </div> + <?php endif ?> + + <?php if (! empty($task['nb_comments'])): ?> + <div class="task-comment-counter"> + <?= $task['nb_comments'] ?> + </div> + <?php endif ?> + </div> </div> </div> <?php endforeach ?> diff --git a/templates/board_public.php b/templates/board_public.php index 339c8035..4c8f7c92 100644 --- a/templates/board_public.php +++ b/templates/board_public.php @@ -40,6 +40,20 @@ <?= Helper\escape($task['title']) ?> </div> + <div class="task-footer"> + <?php if (! empty($task['date_due'])): ?> + <div class="task-date"> + <?= dt('%B %e, %G', $task['date_due']) ?> + </div> + <?php endif ?> + + <?php if (! empty($task['nb_comments'])): ?> + <div class="task-comment-counter"> + <?= $task['nb_comments'] ?> + </div> + <?php endif ?> + </div> + </div> </div> <?php endforeach ?> diff --git a/templates/task_edit.php b/templates/task_edit.php index e400e790..eca4537a 100644 --- a/templates/task_edit.php +++ b/templates/task_edit.php @@ -23,6 +23,9 @@ <?= Helper\form_label(t('Story Points'), 'score') ?> <?= Helper\form_number('score', $values, $errors) ?><br/> + <?= Helper\form_label(t('Due Date'), 'date_due') ?> + <?= Helper\form_text('date_due', $values, $errors, array('placeholder="'.t('month/day/year').'"'), 'form-date') ?><br/> + <?= Helper\form_label(t('Description'), 'description') ?> <?= Helper\form_textarea('description', $values, $errors) ?><br/> <div class="form-help"><a href="http://en.wikipedia.org/wiki/Markdown#Example" target="_blank"><?= t('Write your text in Markdown') ?></a></div> diff --git a/templates/task_new.php b/templates/task_new.php index dd2ba3b4..30e97a85 100644 --- a/templates/task_new.php +++ b/templates/task_new.php @@ -23,6 +23,9 @@ <?= Helper\form_label(t('Story Points'), 'score') ?> <?= Helper\form_number('score', $values, $errors) ?><br/> + <?= Helper\form_label(t('Due Date'), 'date_due') ?> + <?= Helper\form_text('date_due', $values, $errors, array('placeholder="'.t('month/day/year').'"'), 'form-date') ?><br/> + <?= Helper\form_label(t('Description'), 'description') ?> <?= Helper\form_textarea('description', $values, $errors) ?><br/> diff --git a/templates/task_show.php b/templates/task_show.php index 1c5cffaa..59e00b1b 100644 --- a/templates/task_show.php +++ b/templates/task_show.php @@ -20,6 +20,11 @@ <?= dt('Completed on %B %e, %G at %k:%M %p', $task['date_completed']) ?> </li> <?php endif ?> + <?php if ($task['date_due']): ?> + <li> + <strong><?= dt('Must be done before %B %e, %G', $task['date_due']) ?></strong> + </li> + <?php endif ?> <li> <strong> <?php if ($task['username']): ?> diff --git a/tests/TaskTest.php b/tests/TaskTest.php new file mode 100644 index 00000000..415faede --- /dev/null +++ b/tests/TaskTest.php @@ -0,0 +1,27 @@ +<?php + +require_once __DIR__.'/../models/base.php'; +require_once __DIR__.'/../models/task.php'; + +use Model\Task; + +class TaskTest extends PHPUnit_Framework_TestCase +{ + public function setUp() + { + defined('DB_FILENAME') or define('DB_FILENAME', ':memory:'); + } + + public function testDateFormat() + { + $t = new Task; + + $this->assertEquals('2014-03-05', date('Y-m-d', $t->getTimestampFromDate('05/03/2014', 'd/m/Y'))); + $this->assertEquals('2014-03-05', date('Y-m-d', $t->getTimestampFromDate('03/05/2014', 'm/d/Y'))); + $this->assertEquals('2014-03-05', date('Y-m-d', $t->getTimestampFromDate('3/5/2014', 'm/d/Y'))); + $this->assertEquals('2014-03-05', date('Y-m-d', $t->getTimestampFromDate('5/3/2014', 'd/m/Y'))); + $this->assertEquals('2014-03-05', date('Y-m-d', $t->getTimestampFromDate('5/3/14', 'd/m/y'))); + $this->assertEquals(0, $t->getTimestampFromDate('5/3/14', 'd/m/Y')); + $this->assertEquals(0, $t->getTimestampFromDate('5-3-2014', 'd/m/Y')); + } +} diff --git a/vendor/SimpleValidator/Validators/Date.php b/vendor/SimpleValidator/Validators/Date.php new file mode 100644 index 00000000..36b59fbe --- /dev/null +++ b/vendor/SimpleValidator/Validators/Date.php @@ -0,0 +1,33 @@ +<?php + +namespace SimpleValidator\Validators; + +use SimpleValidator\Base; + +class Date extends Base +{ + private $format; + + public function __construct($field, $error_message, $format) + { + parent::__construct($field, $error_message); + $this->format = $format; + } + + public function execute(array $data) + { + if (isset($data[$this->field]) && $data[$this->field] !== '') { + + $date = \DateTime::createFromFormat($this->format, $data[$this->field]); + + if ($date !== false) { + $errors = \DateTime::getLastErrors(); + return $errors['error_count'] === 0 && $errors['warning_count'] === 0; + } + + return false; + } + + return true; + } +} |