From 0d55f5aa35d21b79c5d79f7214c4c9e05b1d2684 Mon Sep 17 00:00:00 2001 From: Frédéric Guillot Date: Sun, 23 Mar 2014 22:10:43 -0400 Subject: Comment edit/remove actions --- assets/css/app.css | 88 ++++++++++++++---- controllers/base.php | 86 +++++++++++++++++- controllers/comment.php | 191 ++++++++++++++++++++++++++++++++++++++++ controllers/task.php | 73 --------------- core/helper.php | 6 ++ locales/fr_FR/translations.php | 10 +++ locales/pl_PL/translations.php | 10 +++ models/acl.php | 3 +- models/comment.php | 115 +++++++++++++++++++++++- templates/comment_forbidden.php | 9 ++ templates/comment_remove.php | 18 ++++ templates/comment_show.php | 36 ++++++++ templates/task_show.php | 21 +++-- tests/CommentTest.php | 94 ++++++++++++++++++++ 14 files changed, 659 insertions(+), 101 deletions(-) create mode 100644 controllers/comment.php create mode 100644 templates/comment_forbidden.php create mode 100644 templates/comment_remove.php create mode 100644 templates/comment_show.php create mode 100644 tests/CommentTest.php diff --git a/assets/css/app.css b/assets/css/app.css index 8bce328d..a2a73be2 100644 --- a/assets/css/app.css +++ b/assets/css/app.css @@ -556,7 +556,8 @@ article .task-score { } #description { - border-left: 5px solid #000; + border: 1px solid #999; + border-radius: 5px; background: #f0f0f0; padding: 10px; } @@ -594,42 +595,85 @@ article .task-score { background: #fff; padding: 10px; border-radius: 5px; + border: 1px dashed #000; overflow: auto; color: #000; } +.markdown blockquote { + font-style: italic; + border-left: 5px solid #ddd; + padding-left: 8px; +} + /* comments */ .comment { - margin: 10px 0; - padding: 10px; - border-left: 5px solid #000; + margin-bottom: 25px; + padding: 0; + border: 1px solid #ddd; + border-radius: 5px; } -.comment:nth-child(odd) { - background-color: #f0f0f0; +.comment-edit { + margin-bottom: 25px; + padding: 0; + border: 2px solid #000; + border-radius: 5px; } -.comment:nth-child(even) { - background-color: #ddd; +.comment:hover { + border: 1px solid #888; } -.comment p:first-child { - font-size: .8em; - margin-bottom: 10px; - border-bottom: 1px dotted #000; +.comment-title { + font-size: 0.9em; + padding: 5px; + margin-bottom: 2px; + border-bottom: 1px solid #ddd; + border-top-left-radius: 5px; + border-top-right-radius: 5px; + background: #f0f0f0; +} + +.comment:nth-child(odd) .comment-title { + background: #f0f0f0; } -.comment p:first-child a { +.comment:nth-child(even) .comment-title { + background: #ddd; +} + +.comment-actions a, +.comment-title a { color: #000; text-decoration: none; } -.comment p:first-child a:hover, -.comment p:first-child a:focus { +.comment-actions a:hover, +.comment-actions a:focus, +.comment-title a:hover, +.comment-title a:focus { text-decoration: underline; } -.comment .username { +.comment-actions { + font-size: 0.8em; + padding: 0; + text-align: right; +} + +.comment-actions li { + display: inline; + padding-left: 5px; + padding-right: 5px; + border-right: 1px dotted #000; +} + +.comment-actions li:last-child { + border: 0; +} + +.comment-username { font-weight: bold; } @@ -638,6 +682,18 @@ article .task-score { width: 500px; } +.comment .markdown { + margin: 10px; +} + +.comment .markdown pre { + background: #fdfdfd; +} + +.comment-edit form { + border: none; +} + /* task colors */ tr td.task-blue, .task-blue { diff --git a/controllers/base.php b/controllers/base.php index 81fb8884..cb76cc05 100644 --- a/controllers/base.php +++ b/controllers/base.php @@ -2,8 +2,20 @@ namespace Controller; +/** + * Base controller + * + * @package controller + * @author Frederic Guillot + */ abstract class Base { + /** + * Constructor + * + * @access public + * @param Core\Registry $registry + */ public function __construct(\Core\Registry $registry) { $this->acl = $registry->acl; @@ -17,6 +29,11 @@ abstract class Base $this->event = $registry->shared('event'); } + /** + * Method executed before each action + * + * @access public + */ public function beforeAction($controller, $action) { // Start the session @@ -50,7 +67,13 @@ abstract class Base $this->action->attachEvents(); } - public function checkProjectPermissions($project_id) + /** + * Check if the current user have access to the given project + * + * @access protected + * @param integer $project_id Project id + */ + protected function checkProjectPermissions($project_id) { if ($this->acl->isRegularUser()) { @@ -60,14 +83,73 @@ abstract class Base } } - public function redirectNoProject() + /** + * Redirection when there is no project in the database + * + * @access protected + */ + protected function redirectNoProject() { $this->session->flash(t('There is no active project, the first step is to create a new project.')); $this->response->redirect('?controller=project&action=create'); } + /** + * Application not found page (404 error) + * + * @access public + */ public function notfound() { $this->response->html($this->template->layout('app_notfound', array('title' => t('Page not found')))); } + + /** + * Display the template show task (common between different actions) + * + * @access protected + * @param array $task Task data + * @param array $comment_form Comment form data + * @param array $description_form Description form data + * @param array $comment_edit_form Comment edit form data + */ + protected function showTask(array $task, array $comment_form = array(), array $description_form = array(), array $comment_edit_form = array()) + { + if (empty($comment_form)) { + $comment_form = array( + 'values' => array('task_id' => $task['id'], 'user_id' => $this->acl->getUserId()), + 'errors' => array() + ); + } + + if (empty($description_form)) { + $description_form = array( + 'values' => array('id' => $task['id']), + 'errors' => array() + ); + } + + if (empty($comment_edit_form)) { + $comment_edit_form = array( + 'values' => array('id' => 0), + 'errors' => array() + ); + } + else { + $hide_comment_form = true; + } + + $this->response->html($this->template->layout('task_show', array( + 'hide_comment_form' => isset($hide_comment_form), + 'comment_edit_form' => $comment_edit_form, + 'comment_form' => $comment_form, + 'description_form' => $description_form, + 'comments' => $this->comment->getAll($task['id']), + 'task' => $task, + 'columns_list' => $this->board->getColumnsList($task['project_id']), + 'colors_list' => $this->task->getColors(), + 'menu' => 'tasks', + 'title' => $task['title'], + ))); + } } diff --git a/controllers/comment.php b/controllers/comment.php new file mode 100644 index 00000000..93dbb5ad --- /dev/null +++ b/controllers/comment.php @@ -0,0 +1,191 @@ +response->html($this->template->layout('comment_forbidden', array( + 'menu' => 'tasks', + 'title' => t('Access Forbidden') + ))); + } + + /** + * Add a comment + * + * @access public + */ + public function save() + { + $task = $this->task->getById($this->request->getIntegerParam('task_id'), true); + $values = $this->request->getValues(); + + if (! $task) $this->notfound(); + $this->checkProjectPermissions($task['project_id']); + + list($valid, $errors) = $this->comment->validateCreation($values); + + if ($valid) { + + if ($this->comment->create($values)) { + $this->session->flash(t('Comment added successfully.')); + } + else { + $this->session->flashError(t('Unable to create your comment.')); + } + + $this->response->redirect('?controller=task&action=show&task_id='.$task['id']); + } + + $this->showTask( + $task, + array('values' => $values, 'errors' => $errors) + ); + } + + /** + * Edit a comment + * + * @access public + */ + public function edit() + { + $task_id = $this->request->getIntegerParam('task_id'); + $comment_id = $this->request->getIntegerParam('comment_id'); + + $task = $this->task->getById($task_id, true); + $comment = $this->comment->getById($comment_id); + + if (! $task || ! $comment) $this->notfound(); + $this->checkProjectPermissions($task['project_id']); + + if ($this->acl->isAdminUser() || $comment['user_id'] == $this->acl->getUserId()) { + + $this->showTask( + $task, + array(), + array(), + array('values' => array('id' => $comment['id']), 'errors' => array()) + ); + } + + $this->forbidden(); + } + + /** + * Update and validate a comment + * + * @access public + */ + public function update() + { + $task_id = $this->request->getIntegerParam('task_id'); + $comment_id = $this->request->getIntegerParam('comment_id'); + + $task = $this->task->getById($task_id, true); + $comment = $this->comment->getById($comment_id); + + $values = $this->request->getValues(); + + if (! $task || ! $comment) $this->notfound(); + $this->checkProjectPermissions($task['project_id']); + + if ($this->acl->isAdminUser() || $comment['user_id'] == $this->acl->getUserId()) { + + list($valid, $errors) = $this->comment->validateModification($values); + + if ($valid) { + + if ($this->comment->update($values)) { + $this->session->flash(t('Comment updated successfully.')); + } + else { + $this->session->flashError(t('Unable to update your comment.')); + } + + $this->response->redirect('?controller=task&action=show&task_id='.$task['id'].'#comment-'.$comment_id); + } + + $this->showTask( + $task, + array(), + array(), + array('values' => $values, 'errors' => $errors) + ); + } + + $this->forbidden(); + } + + /** + * Confirmation dialog before removing a comment + * + * @access public + */ + public function confirm() + { + $project_id = $this->request->getIntegerParam('project_id'); + $comment_id = $this->request->getIntegerParam('comment_id'); + + $this->checkProjectPermissions($project_id); + + $comment = $this->comment->getById($comment_id); + if (! $comment) $this->notfound(); + + if ($this->acl->isAdminUser() || $comment['user_id'] == $this->acl->getUserId()) { + + $this->response->html($this->template->layout('comment_remove', array( + 'comment' => $comment, + 'project_id' => $project_id, + 'menu' => 'tasks', + 'title' => t('Remove a comment') + ))); + } + + $this->forbidden(); + } + + /** + * Remove a comment + * + * @access public + */ + public function remove() + { + $project_id = $this->request->getIntegerParam('project_id'); + $comment_id = $this->request->getIntegerParam('comment_id'); + + $this->checkProjectPermissions($project_id); + + $comment = $this->comment->getById($comment_id); + if (! $comment) $this->notfound(); + + if ($this->acl->isAdminUser() || $comment['user_id'] == $this->acl->getUserId()) { + + if ($this->comment->remove($comment['id'])) { + $this->session->flash(t('Comment removed successfully.')); + } else { + $this->session->flashError(t('Unable to remove this comment.')); + } + + $this->response->redirect('?controller=task&action=show&task_id='.$comment['task_id']); + } + + $this->forbidden(); + } +} diff --git a/controllers/task.php b/controllers/task.php index 0dae014e..7aefb98c 100644 --- a/controllers/task.php +++ b/controllers/task.php @@ -51,46 +51,6 @@ class Task extends Base $this->response->text('FAILED'); } - /** - * Display the template show task, common between different task view - * - * @access public - */ - private function showTask(array $task, array $comment_form = array(), array $description_form = array()) - { - if (empty($comment_form)) { - - $comment_form = array( - 'values' => array( - 'task_id' => $task['id'], - 'user_id' => $this->acl->getUserId() - ), - 'errors' => array() - ); - } - - if (empty($description_form)) { - - $description_form = array( - 'values' => array( - 'id' => $task['id'], - ), - 'errors' => array() - ); - } - - $this->response->html($this->template->layout('task_show', array( - 'comment_form' => $comment_form, - 'description_form' => $description_form, - 'comments' => $this->comment->getAll($task['id']), - 'task' => $task, - 'columns_list' => $this->board->getColumnsList($task['project_id']), - 'colors_list' => $this->task->getColors(), - 'menu' => 'tasks', - 'title' => $task['title'], - ))); - } - /** * Show a task * @@ -106,39 +66,6 @@ class Task extends Base $this->showTask($task); } - /** - * Add a comment - * - * @access public - */ - public function comment() - { - $task = $this->task->getById($this->request->getIntegerParam('task_id'), true); - $values = $this->request->getValues(); - - if (! $task) $this->notfound(); - $this->checkProjectPermissions($task['project_id']); - - list($valid, $errors) = $this->comment->validateCreation($values); - - if ($valid) { - - if ($this->comment->create($values)) { - $this->session->flash(t('Comment added successfully.')); - } - else { - $this->session->flashError(t('Unable to create your comment.')); - } - - $this->response->redirect('?controller=task&action=show&task_id='.$task['id']); - } - - $this->showTask( - $task, - array('values' => $values, 'errors' => $errors) - ); - } - /** * Add a description from the show task page * diff --git a/core/helper.php b/core/helper.php index e4ad26f1..0eeec4cd 100644 --- a/core/helper.php +++ b/core/helper.php @@ -2,6 +2,12 @@ namespace Helper; +function template($name, array $args = array()) +{ + $tpl = new \Core\Template; + return $tpl->load($name, $args); +} + function is_current_user($user_id) { return $_SESSION['user']['id'] == $user_id; diff --git a/locales/fr_FR/translations.php b/locales/fr_FR/translations.php index 4c604e28..9a73228b 100644 --- a/locales/fr_FR/translations.php +++ b/locales/fr_FR/translations.php @@ -254,4 +254,14 @@ return array( 'Move Down' => 'Déplacer vers le bas', 'Duplicate to another project' => 'Dupliquer dans un autre projet', 'Duplicate' => 'Dupliquer', + 'link' => 'lien', + 'Update this comment' => 'Mettre à jour ce commentaire', + 'Comment updated successfully.' => 'Commentaire mis à jour avec succès.', + 'Unable to update your comment.' => 'Impossible de supprimer votre commentaire.', + 'Remove a comment' => 'Supprimer un commentaire', + 'Comment removed successfully.' => 'Commentaire supprimé avec succès.', + 'Unable to remove this comment.' => 'Impossible de supprimer ce commentaire.', + 'Do you really want to remove this comment?' => 'Voulez-vous vraiment supprimer ce commentaire ?', + 'Only administrators or the creator of the comment can access to this page.' => 'Uniquement les administrateurs ou le créateur du commentaire peuvent accéder à cette page.', + 'Details' => 'Détails', ); diff --git a/locales/pl_PL/translations.php b/locales/pl_PL/translations.php index c030bebf..27ffdef7 100644 --- a/locales/pl_PL/translations.php +++ b/locales/pl_PL/translations.php @@ -257,4 +257,14 @@ return array( // 'Move Down' => '', // 'Duplicate to another project' => '', // 'Duplicate' => '', + // 'link' => '', + // 'Update this comment' => '', + // 'Comment updated successfully.' => '', + // 'Unable to update your comment.' => '', + // 'Remove a comment' => '', + // 'Comment removed successfully.' => '', + // 'Unable to remove this comment.' => '', + // 'Do you really want to remove this comment?' => '', + // 'Only administrators or the creator of the comment can access to this page.' => '', + // 'Details' => '', ); diff --git a/models/acl.php b/models/acl.php index fe23bbb4..ea7dd5cb 100644 --- a/models/acl.php +++ b/models/acl.php @@ -18,7 +18,8 @@ class Acl extends Base 'app' => array('index'), 'board' => array('index', 'show', 'assign', 'assigntask', 'save'), 'project' => array('tasks', 'index', 'forbidden'), - 'task' => array('show', 'create', 'save', 'edit', 'update', 'close', 'confirmclose', 'open', 'confirmopen', 'comment', 'description', 'duplicate'), + 'task' => array('show', 'create', 'save', 'edit', 'update', 'close', 'confirmclose', 'open', 'confirmopen', 'description', 'duplicate'), + 'comment' => array('save', 'confirm', 'remove', 'update', 'edit'), 'user' => array('index', 'edit', 'update', 'forbidden', 'logout', 'index'), 'config' => array('index'), ); diff --git a/models/comment.php b/models/comment.php index c476e693..453c2afc 100644 --- a/models/comment.php +++ b/models/comment.php @@ -7,10 +7,28 @@ require_once __DIR__.'/base.php'; use \SimpleValidator\Validator; use \SimpleValidator\Validators; +/** + * Comment model + * + * @package model + * @author Frederic Guillot + */ class Comment extends Base { + /** + * SQL table name + * + * @var string + */ const TABLE = 'comments'; + /** + * Get all comments for a given task + * + * @access public + * @param integer $task_id Task id + * @return array + */ public function getAll($task_id) { return $this->db @@ -18,6 +36,8 @@ class Comment extends Base ->columns( self::TABLE.'.id', self::TABLE.'.date', + self::TABLE.'.task_id', + self::TABLE.'.user_id', self::TABLE.'.comment', User::TABLE.'.username' ) @@ -27,6 +47,37 @@ class Comment extends Base ->findAll(); } + /** + * Get a comment + * + * @access public + * @param integer $comment_id Comment id + * @return array + */ + public function getById($comment_id) + { + return $this->db + ->table(self::TABLE) + ->columns( + self::TABLE.'.id', + self::TABLE.'.task_id', + self::TABLE.'.user_id', + self::TABLE.'.date', + self::TABLE.'.comment', + User::TABLE.'.username' + ) + ->join(User::TABLE, 'id', 'user_id') + ->eq(self::TABLE.'.id', $comment_id) + ->findOne(); + } + + /** + * Get the number of comments for a given task + * + * @access public + * @param integer $task_id Task id + * @return integer + */ public function count($task_id) { return $this->db @@ -35,13 +86,54 @@ class Comment extends Base ->count(); } + /** + * Save a comment in the database + * + * @access public + * @param array $values Form values + * @return boolean + */ public function create(array $values) { $values['date'] = time(); - return (bool) $this->db->table(self::TABLE)->save($values); + return $this->db->table(self::TABLE)->save($values); + } + + /** + * Update a comment in the database + * + * @access public + * @param array $values Form values + * @return boolean + */ + public function update(array $values) + { + return $this->db + ->table(self::TABLE) + ->eq('id', $values['id']) + ->update(array('comment' => $values['comment'])); + } + + /** + * Remove a comment + * + * @access public + * @param integer $comment_id Comment id + * @return boolean + */ + public function remove($comment_id) + { + return $this->db->table(self::TABLE)->eq('id', $comment_id)->remove(); } + /** + * Validate comment creation + * + * @access public + * @param array $values Required parameters to save an action + * @return array $valid, $errors [0] = Success or not, [1] = List of errors + */ public function validateCreation(array $values) { $v = new Validator($values, array( @@ -57,4 +149,25 @@ class Comment extends Base $v->getErrors() ); } + + /** + * Validate comment modification + * + * @access public + * @param array $values Required parameters to save an action + * @return array $valid, $errors [0] = Success or not, [1] = List of errors + */ + public function validateModification(array $values) + { + $v = new Validator($values, array( + new Validators\Required('id', t('This value is required')), + new Validators\Integer('id', t('This value must be an integer')), + new Validators\Required('comment', t('Comment is required')) + )); + + return array( + $v->execute(), + $v->getErrors() + ); + } } diff --git a/templates/comment_forbidden.php b/templates/comment_forbidden.php new file mode 100644 index 00000000..eeea8404 --- /dev/null +++ b/templates/comment_forbidden.php @@ -0,0 +1,9 @@ +
+ + +

+ +

+
\ No newline at end of file diff --git a/templates/comment_remove.php b/templates/comment_remove.php new file mode 100644 index 00000000..ad1b8e4a --- /dev/null +++ b/templates/comment_remove.php @@ -0,0 +1,18 @@ +
+ + +
+

+ +

+ + $comment)) ?> + +
+ + +
+
+
\ No newline at end of file diff --git a/templates/comment_show.php b/templates/comment_show.php new file mode 100644 index 00000000..24bf9070 --- /dev/null +++ b/templates/comment_show.php @@ -0,0 +1,36 @@ +
+

+ @ +

+ + + + + +
+ + +
+ +
+ + + +
+
+ +
+ +
+ +
\ No newline at end of file diff --git a/templates/task_show.php b/templates/task_show.php index 986b240d..409e5205 100644 --- a/templates/task_show.php +++ b/templates/task_show.php @@ -8,7 +8,7 @@
-

+

@@ -60,7 +60,7 @@
-

+

@@ -78,19 +78,23 @@ -

+

    -
    -

    @

    - -
    + $comment, + 'task' => $task, + 'display_edit_form' => $comment['id'] == $comment_edit_form['values']['id'], + 'values' => $comment_edit_form['values'] + array('comment' => $comment['comment']), + 'errors' => $comment_edit_form['errors'] + )) ?>
-
+ + @@ -101,5 +105,6 @@
+
diff --git a/tests/CommentTest.php b/tests/CommentTest.php new file mode 100644 index 00000000..c23c4e51 --- /dev/null +++ b/tests/CommentTest.php @@ -0,0 +1,94 @@ +db, $this->event); + $t = new Task($this->db, $this->event); + $p = new Project($this->db, $this->event); + + $this->assertEquals(1, $p->create(array('name' => 'test1'))); + $this->assertEquals(1, $t->create(array('title' => 'test', 'project_id' => 1, 'column_id' => 3, 'owner_id' => 1))); + $this->assertTrue($c->create(array('task_id' => 1, 'comment' => 'bla bla', 'user_id' => 1))); + + $comment = $c->getById(1); + + $this->assertNotEmpty($comment); + $this->assertEquals('bla bla', $comment['comment']); + $this->assertEquals(1, $comment['task_id']); + $this->assertEquals(1, $comment['user_id']); + $this->assertEquals('admin', $comment['username']); + $this->assertNotEmpty($comment['date']); + } + + public function testGetAll() + { + $c = new Comment($this->db, $this->event); + $t = new Task($this->db, $this->event); + $p = new Project($this->db, $this->event); + + $this->assertEquals(1, $p->create(array('name' => 'test1'))); + $this->assertEquals(1, $t->create(array('title' => 'test', 'project_id' => 1, 'column_id' => 3, 'owner_id' => 1))); + $this->assertTrue($c->create(array('task_id' => 1, 'comment' => 'c1', 'user_id' => 1))); + $this->assertTrue($c->create(array('task_id' => 1, 'comment' => 'c2', 'user_id' => 1))); + $this->assertTrue($c->create(array('task_id' => 1, 'comment' => 'c3', 'user_id' => 1))); + + $comments = $c->getAll(1); + + $this->assertNotEmpty($comments); + $this->assertEquals(3, count($comments)); + $this->assertEquals(1, $comments[0]['id']); + $this->assertEquals(2, $comments[1]['id']); + $this->assertEquals(3, $comments[2]['id']); + } + + public function testUpdate() + { + $c = new Comment($this->db, $this->event); + $t = new Task($this->db, $this->event); + $p = new Project($this->db, $this->event); + + $this->assertEquals(1, $p->create(array('name' => 'test1'))); + $this->assertEquals(1, $t->create(array('title' => 'test', 'project_id' => 1, 'column_id' => 3, 'owner_id' => 1))); + $this->assertTrue($c->create(array('task_id' => 1, 'comment' => 'c1', 'user_id' => 1))); + $this->assertTrue($c->update(array('id' => 1, 'comment' => 'bla'))); + + $comment = $c->getById(1); + $this->assertNotEmpty($comment); + $this->assertEquals('bla', $comment['comment']); + + $this->assertFalse($c->update(array('id' => 4, 'comment' => 'bla'))); + } + + public function testValidateCreation() + { + $c = new Comment($this->db, $this->event); + + $this->assertTrue($c->validateCreation(['user_id' => 1, 'task_id' => 1, 'comment' => 'bla'])[0]); + $this->assertFalse($c->validateCreation(['user_id' => 1, 'task_id' => 1, 'comment' => ''])[0]); + $this->assertFalse($c->validateCreation(['user_id' => 1, 'task_id' => 'a', 'comment' => 'bla'])[0]); + $this->assertFalse($c->validateCreation(['user_id' => 'b', 'task_id' => 1, 'comment' => 'bla'])[0]); + $this->assertFalse($c->validateCreation(['user_id' => 1, 'comment' => 'bla'])[0]); + $this->assertFalse($c->validateCreation(['task_id' => 1, 'comment' => 'bla'])[0]); + $this->assertFalse($c->validateCreation(['comment' => 'bla'])[0]); + $this->assertFalse($c->validateCreation([])[0]); + } + + public function testValidateModification() + { + $c = new Comment($this->db, $this->event); + + $this->assertTrue($c->validateModification(['id' => 1, 'comment' => 'bla'])[0]); + $this->assertFalse($c->validateModification(['id' => 1, 'comment' => ''])[0]); + $this->assertFalse($c->validateModification(['comment' => 'bla'])[0]); + $this->assertFalse($c->validateModification(['id' => 'b', 'comment' => 'bla'])[0]); + $this->assertFalse($c->validateModification([])[0]); + } +} -- cgit v1.2.3