From a750b8ab2a0cb715da6fd9025a7ec8375db68a4d Mon Sep 17 00:00:00 2001 From: Frédéric Guillot Date: Wed, 21 May 2014 22:33:57 -0400 Subject: Add categories for projects and tasks --- actions/task_assign_color_category.php | 85 +++++++++++++++ actions/task_assign_color_user.php | 4 +- assets/css/app.css | 30 +++++- assets/js/board.js | 35 +++--- common.php | 7 +- controllers/action.php | 2 + controllers/base.php | 9 ++ controllers/board.php | 14 ++- controllers/category.php | 191 +++++++++++++++++++++++++++++++++ controllers/project.php | 4 +- controllers/task.php | 6 ++ core/helper.php | 4 +- docs/automatic-actions.markdown | 12 ++- locales/es_ES/translations.php | 21 ++++ locales/fr_FR/translations.php | 21 ++++ locales/pl_PL/translations.php | 21 ++++ locales/pt_BR/translations.php | 21 ++++ models/action.php | 6 ++ models/category.php | 152 ++++++++++++++++++++++++++ models/project.php | 6 +- models/task.php | 16 ++- schemas/mysql.php | 16 +++ schemas/sqlite.php | 15 +++ templates/action_index.php | 2 + templates/action_params.php | 3 + templates/board_index.php | 6 +- templates/board_public.php | 11 ++ templates/board_show.php | 13 +++ templates/category_edit.php | 24 +++++ templates/category_index.php | 48 +++++++++ templates/category_remove.php | 16 +++ templates/project_index.php | 3 + templates/project_search.php | 4 + templates/project_tasks.php | 4 + templates/task_edit.php | 3 + templates/task_new.php | 3 + templates/task_show.php | 5 + tests/Base.php | 2 +- tests/TaskTest.php | 14 ++- 39 files changed, 815 insertions(+), 44 deletions(-) create mode 100644 actions/task_assign_color_category.php create mode 100644 controllers/category.php create mode 100644 models/category.php create mode 100644 templates/category_edit.php create mode 100644 templates/category_index.php create mode 100644 templates/category_remove.php diff --git a/actions/task_assign_color_category.php b/actions/task_assign_color_category.php new file mode 100644 index 00000000..6fba5223 --- /dev/null +++ b/actions/task_assign_color_category.php @@ -0,0 +1,85 @@ +task = $task; + } + + /** + * Get the required parameter for the action (defined by the user) + * + * @access public + * @return array + */ + public function getActionRequiredParameters() + { + return array( + 'color_id' => t('Color'), + 'category_id' => t('Category'), + ); + } + + /** + * Get the required parameter for the event + * + * @access public + * @return string[] + */ + public function getEventRequiredParameters() + { + return array( + 'task_id', + 'category_id', + ); + } + + /** + * Execute the action + * + * @access public + * @param array $data Event data dictionary + * @return bool True if the action was executed or false when not executed + */ + public function doAction(array $data) + { + if ($data['category_id'] == $this->getParam('category_id')) { + + $this->task->update(array( + 'id' => $data['task_id'], + 'color_id' => $this->getParam('color_id'), + )); + + return true; + } + + return false; + } +} diff --git a/actions/task_assign_color_user.php b/actions/task_assign_color_user.php index d3ce1fbe..d5784270 100644 --- a/actions/task_assign_color_user.php +++ b/actions/task_assign_color_user.php @@ -42,7 +42,6 @@ class TaskAssignColorUser extends Base public function getActionRequiredParameters() { return array( - 'column_id' => t('Column'), 'color_id' => t('Color'), 'user_id' => t('Assignee'), ); @@ -59,7 +58,6 @@ class TaskAssignColorUser extends Base return array( 'task_id', 'owner_id', - 'column_id', ); } @@ -72,7 +70,7 @@ class TaskAssignColorUser extends Base */ public function doAction(array $data) { - if ($data['column_id'] == $this->getParam('column_id') && $data['owner_id'] == $this->getParam('user_id')) { + if ($data['owner_id'] == $this->getParam('user_id')) { $this->task->update(array( 'id' => $data['task_id'], diff --git a/assets/css/app.css b/assets/css/app.css index 67e4e6df..45ec7444 100644 --- a/assets/css/app.css +++ b/assets/css/app.css @@ -484,23 +484,26 @@ nav .active a { } .project-menu li { + padding-left: 5px; display: inline; - padding-left: 10px; - padding-right: 10px; border-right: 1px dotted #ccc; } .project-menu li:last-child { border: none; - padding-right: 0; } .project-menu ul { padding-bottom: 5px; } +.project-menu a { + padding-right: 5px; +} + .project-menu { text-align: right; + font-size: 0.8em; } .public-board { @@ -540,7 +543,26 @@ a.filter-on { color: #444; } +.task-category-container { + text-align: right; + padding-bottom: 2px; +} + +.task-category { + font-weight: bold; + font-size: 0.8em; + color: #000; + border: 1px solid #555; + border-radius: 4px; + padding: 2px; + padding-right: 5px; + padding-left: 5px; +} + .task-date { + position: absolute; + bottom: 0; + left: 5px; font-weight: bold; color: #D90000; } @@ -552,7 +574,7 @@ a.filter-on { } .task-footer { - margin-top: 10px; + height: 18px; } .task { diff --git a/assets/js/board.js b/assets/js/board.js index 84aca8b3..af54d0c8 100644 --- a/assets/js/board.js +++ b/assets/js/board.js @@ -76,7 +76,7 @@ $("#board").remove(); $("#main").append(data); board_load_events(); - filter_apply(filter_get_user_id(), filter_has_due_date()); + filter_apply(); } }); } @@ -96,32 +96,25 @@ $("#main").append(data); board_unload_events(); board_load_events(); - filter_apply(filter_get_user_id(), filter_has_due_date()); + filter_apply(); } } }); } } - // Get the selected user id - function filter_get_user_id() - { - return $("#form-user_id").val(); - } - - // Return true if the filter is activated - function filter_has_due_date() - { - return $("#filter-due-date").hasClass("filter-on"); - } - // Apply user or date filter (change tasks opacity) - function filter_apply(selectedUserId, filterDueDate) + function filter_apply() { + var selectedUserId = $("#form-user_id").val(); + var selectedCategoryId = $("#form-category_id").val(); + var filterDueDate = $("#filter-due-date").hasClass("filter-on"); + $("[data-task-id]").each(function(index, item) { var ownerId = item.getAttribute("data-owner-id"); var dueDate = item.getAttribute("data-due-date"); + var categoryId = item.getAttribute("data-category-id"); if (ownerId != selectedUserId && selectedUserId != -1) { item.style.opacity = "0.2"; @@ -133,19 +126,23 @@ if (filterDueDate && (dueDate == "" || dueDate == "0")) { item.style.opacity = "0.2"; } + + if (categoryId != selectedCategoryId && selectedCategoryId != -1) { + item.style.opacity = "0.2"; + } }); } // Load filter events function filter_load_events() { - $("#form-user_id").change(function() { - filter_apply(filter_get_user_id(), filter_has_due_date()); - }); + $("#form-user_id").change(filter_apply); + + $("#form-category_id").change(filter_apply); $("#filter-due-date").click(function(e) { $(this).toggleClass("filter-on"); - filter_apply(filter_get_user_id(), filter_has_due_date()); + filter_apply(); e.preventDefault(); }); } diff --git a/common.php b/common.php index 75b92df4..70b18a51 100644 --- a/common.php +++ b/common.php @@ -6,7 +6,7 @@ require __DIR__.'/core/translator.php'; $registry = new Core\Registry; -$registry->db_version = 15; +$registry->db_version = 16; $registry->db = function() use ($registry) { require __DIR__.'/vendor/PicoDb/Database.php'; @@ -110,6 +110,11 @@ $registry->google = function() use ($registry) { return new \Model\Google($registry->shared('db'), $registry->shared('event')); }; +$registry->category = function() use ($registry) { + require_once __DIR__.'/models/category.php'; + return new \Model\Category($registry->shared('db'), $registry->shared('event')); +}; + if (file_exists('config.php')) require 'config.php'; // Board refresh frequency in seconds for the public board view diff --git a/controllers/action.php b/controllers/action.php index b4006940..5c5d5726 100644 --- a/controllers/action.php +++ b/controllers/action.php @@ -38,6 +38,7 @@ class Action extends Base 'users_list' => $this->project->getUsersList($project['id'], false), 'projects_list' => $this->project->getList(false), 'colors_list' => $this->task->getColors(), + 'categories_list' => $this->category->getList($project['id'], false), 'menu' => 'projects', 'title' => t('Automatic actions') ))); @@ -68,6 +69,7 @@ class Action extends Base 'users_list' => $this->project->getUsersList($project['id'], false), 'projects_list' => $this->project->getList(false), 'colors_list' => $this->task->getColors(), + 'categories_list' => $this->category->getList($project['id'], false), 'project' => $project, 'menu' => 'projects', 'title' => t('Automatic actions') diff --git a/controllers/base.php b/controllers/base.php index 07c5db63..49376dce 100644 --- a/controllers/base.php +++ b/controllers/base.php @@ -130,6 +130,14 @@ abstract class Base */ protected $google; + /** + * Category model + * + * @accesss protected + * @var \Model\Category + */ + protected $category; + /** * Event instance * @@ -157,6 +165,7 @@ abstract class Base $this->rememberMe = $registry->rememberMe; $this->lastLogin = $registry->lastLogin; $this->google = $registry->google; + $this->category = $registry->category; $this->event = $registry->shared('event'); } diff --git a/controllers/board.php b/controllers/board.php index 02669e3a..f7128210 100644 --- a/controllers/board.php +++ b/controllers/board.php @@ -128,6 +128,7 @@ class Board extends Base $this->response->html($this->template->layout('board_public', array( 'project' => $project, 'columns' => $this->board->get($project['id']), + 'categories' => $this->category->getList($project['id'], false), 'title' => $project['name'], 'no_layout' => true, 'auto_refresh' => true, @@ -195,6 +196,7 @@ class Board extends Base 'current_project_id' => $project_id, 'current_project_name' => $projects[$project_id], 'board' => $this->board->get($project_id), + 'categories' => $this->category->getList($project_id, true, true), 'menu' => 'boards', 'title' => $projects[$project_id] ))); @@ -369,7 +371,11 @@ class Board extends Base } $this->response->html( - $this->template->load('board_show', array('current_project_id' => $project_id, 'board' => $this->board->get($project_id))), + $this->template->load('board_show', array( + 'current_project_id' => $project_id, + 'board' => $this->board->get($project_id), + 'categories' => $this->category->getList($project_id, false), + )), 201 ); } @@ -390,7 +396,11 @@ class Board extends Base if ($this->project->isModifiedSince($project_id, $timestamp)) { $this->response->html( - $this->template->load('board_show', array('current_project_id' => $project_id, 'board' => $this->board->get($project_id))) + $this->template->load('board_show', array( + 'current_project_id' => $project_id, + 'board' => $this->board->get($project_id), + 'categories' => $this->category->getList($project_id, false), + )) ); } else { diff --git a/controllers/category.php b/controllers/category.php new file mode 100644 index 00000000..23de2735 --- /dev/null +++ b/controllers/category.php @@ -0,0 +1,191 @@ +request->getIntegerParam('project_id'); + $project = $this->project->getById($project_id); + + if (! $project) { + $this->session->flashError(t('Project not found.')); + $this->response->redirect('?controller=project'); + } + + return $project; + } + + /** + * Get the category (common method between actions) + * + * @access private + * @return array + */ + private function getCategory($project_id) + { + $category = $this->category->getById($this->request->getIntegerParam('category_id')); + + if (! $category) { + $this->session->flashError(t('Category not found.')); + $this->response->redirect('?controller=category&action=index&project_id='.$project_id); + } + + return $category; + } + + /** + * List of categories for a given project + * + * @access public + */ + public function index() + { + $project = $this->getProject(); + + $this->response->html($this->template->layout('category_index', array( + 'categories' => $this->category->getList($project['id'], false), + 'values' => array('project_id' => $project['id']), + 'errors' => array(), + 'project' => $project, + 'menu' => 'projects', + 'title' => t('Categories') + ))); + } + + /** + * Validate and save a new project + * + * @access public + */ + public function save() + { + $project = $this->getProject(); + + $values = $this->request->getValues(); + list($valid, $errors) = $this->category->validateCreation($values); + + if ($valid) { + + if ($this->category->create($values)) { + $this->session->flash(t('Your category have been created successfully.')); + $this->response->redirect('?controller=category&action=index&project_id='.$project['id']); + } + else { + $this->session->flashError(t('Unable to create your category.')); + } + } + + $this->response->html($this->template->layout('category_index', array( + 'categories' => $this->category->getList($project['id'], false), + 'values' => $values, + 'errors' => $errors, + 'project' => $project, + 'menu' => 'projects', + 'title' => t('Categories') + ))); + } + + /** + * Edit a category (display the form) + * + * @access public + */ + public function edit() + { + $project = $this->getProject(); + $category = $this->getCategory($project['id']); + + $this->response->html($this->template->layout('category_edit', array( + 'values' => $category, + 'errors' => array(), + 'project' => $project, + 'menu' => 'projects', + 'title' => t('Categories') + ))); + } + + /** + * Edit a category (validate the form and update the database) + * + * @access public + */ + public function update() + { + $project = $this->getProject(); + + $values = $this->request->getValues(); + list($valid, $errors) = $this->category->validateModification($values); + + if ($valid) { + + if ($this->category->update($values)) { + $this->session->flash(t('Your category have been updated successfully.')); + $this->response->redirect('?controller=category&action=index&project_id='.$project['id']); + } + else { + $this->session->flashError(t('Unable to update your category.')); + } + } + + $this->response->html($this->template->layout('category_edit', array( + 'values' => $values, + 'errors' => $errors, + 'project' => $project, + 'menu' => 'projects', + 'title' => t('Categories') + ))); + } + + /** + * Confirmation dialog before removing a category + * + * @access public + */ + public function confirm() + { + $project = $this->getProject(); + $category = $this->getCategory($project['id']); + + $this->response->html($this->template->layout('category_remove', array( + 'project' => $project, + 'category' => $category, + 'menu' => 'projects', + 'title' => t('Remove a category') + ))); + } + + /** + * Remove a category + * + * @access public + */ + public function remove() + { + $project = $this->getProject(); + $category = $this->getCategory($project['id']); + + if ($this->category->remove($category['id'])) { + $this->session->flash(t('Category removed successfully.')); + } else { + $this->session->flashError(t('Unable to remove this category.')); + } + + $this->response->redirect('?controller=category&action=index&project_id='.$project['id']); + } +} diff --git a/controllers/project.php b/controllers/project.php index a9ce8634..1b9fd110 100644 --- a/controllers/project.php +++ b/controllers/project.php @@ -52,7 +52,7 @@ class Project extends Base array('column' => 'project_id', 'operator' => 'eq', 'value' => $project_id), 'or' => array( array('column' => 'title', 'operator' => 'like', 'value' => '%'.$search.'%'), - array('column' => 'description', 'operator' => 'like', 'value' => '%'.$search.'%'), + //array('column' => 'description', 'operator' => 'like', 'value' => '%'.$search.'%'), ) ); @@ -72,6 +72,7 @@ class Project extends Base 'menu' => 'projects', 'project' => $project, 'columns' => $this->board->getColumnsList($project_id), + 'categories' => $this->category->getList($project['id'], false), 'title' => $project['name'].($nb_tasks > 0 ? ' ('.$nb_tasks.')' : '') ))); } @@ -105,6 +106,7 @@ class Project extends Base 'menu' => 'projects', 'project' => $project, 'columns' => $this->board->getColumnsList($project_id), + 'categories' => $this->category->getList($project['id'], false), 'tasks' => $tasks, 'nb_tasks' => $nb_tasks, 'title' => $project['name'].' ('.$nb_tasks.')' diff --git a/controllers/task.php b/controllers/task.php index f5738a55..bdedb9dd 100644 --- a/controllers/task.php +++ b/controllers/task.php @@ -34,6 +34,7 @@ class Task extends Base 'project_id' => $this->request->getIntegerParam('project_id', $defaultProject['id']), 'owner_id' => $this->request->getIntegerParam('owner_id'), 'column_id' => $this->request->getIntegerParam('column_id'), + 'category_id' => $this->request->getIntegerParam('category_id'), ); if ($values['column_id'] == 0) { @@ -121,6 +122,7 @@ class Task extends Base 'columns_list' => $this->board->getColumnsList($project_id), 'users_list' => $this->project->getUsersList($project_id), 'colors_list' => $this->task->getColors(), + 'categories_list' => $this->category->getList($project_id), 'menu' => 'tasks', 'title' => t('New task') ))); @@ -164,6 +166,7 @@ class Task extends Base 'columns_list' => $this->board->getColumnsList($values['project_id']), 'users_list' => $this->project->getUsersList($values['project_id']), 'colors_list' => $this->task->getColors(), + 'categories_list' => $this->category->getList($values['project_id']), 'menu' => 'tasks', 'title' => t('New task') ))); @@ -196,6 +199,7 @@ class Task extends Base 'columns_list' => $this->board->getColumnsList($task['project_id']), 'users_list' => $this->project->getUsersList($task['project_id']), 'colors_list' => $this->task->getColors(), + 'categories_list' => $this->category->getList($task['project_id']), 'menu' => 'tasks', 'title' => t('Edit a task') ))); @@ -230,6 +234,7 @@ class Task extends Base 'columns_list' => $this->board->getColumnsList($values['project_id']), 'users_list' => $this->project->getUsersList($values['project_id']), 'colors_list' => $this->task->getColors(), + 'categories_list' => $this->category->getList($values['project_id']), 'menu' => 'tasks', 'title' => t('Edit a task') ))); @@ -383,6 +388,7 @@ class Task extends Base 'columns_list' => $this->board->getColumnsList($task['project_id']), 'users_list' => $this->project->getUsersList($task['project_id']), 'colors_list' => $this->task->getColors(), + 'categories_list' => $this->category->getList($task['project_id']), 'duplicate' => true, 'menu' => 'tasks', 'title' => t('New task') diff --git a/core/helper.php b/core/helper.php index 7e65543a..8351328a 100644 --- a/core/helper.php +++ b/core/helper.php @@ -112,13 +112,13 @@ function contains($haystack, $needle) return strpos($haystack, $needle) !== false; } -function in_list($id, array $listing) +function in_list($id, array $listing, $default_value = '?') { if (isset($listing[$id])) { return escape($listing[$id]); } - return '?'; + return $default_value; } function error_class(array $errors, $name) diff --git a/docs/automatic-actions.markdown b/docs/automatic-actions.markdown index 6cbc0937..9526e9af 100644 --- a/docs/automatic-actions.markdown +++ b/docs/automatic-actions.markdown @@ -24,6 +24,7 @@ List of available events - Task creation - Open a closed task - Closing a task +- Task creation or modification List of available actions ------------------------- @@ -33,6 +34,7 @@ List of available actions - Assign the task to the person who does the action - Duplicate the task to another project - Assign a color to a specific user +- Assign a color to a specific category Examples -------- @@ -65,8 +67,14 @@ Let's say we have two projects "Customer orders" and "Production", once the orde - Choose the action: **Duplicate the task to another project** - Define the action parameters: **Column = Validated** and **Project = Production** -### When I create a new task in the column "To do", assign a specific color to the user Bob +### I want to assign a specific color to the user Bob - Choose the event: **Task creation** - Choose the action: **Assign a color to a specific user** -- Define the action parameters: **Column = To do**, **Color = Green** and **Assignee = Bob** +- Define the action parameters: **Color = Green** and **Assignee = Bob** + +### I want to assign a specific color to the category "Feature Request" + +- Choose the event: **Task creation or modification** +- Choose the action: **Assign a color to a specific category** +- Define the action parameters: **Color = Blue** and **Category = Feature Request** diff --git a/locales/es_ES/translations.php b/locales/es_ES/translations.php index e1d54c03..ac97d46c 100644 --- a/locales/es_ES/translations.php +++ b/locales/es_ES/translations.php @@ -309,4 +309,25 @@ return array( // 'Unable to remove this task.' => '', // 'Remove a task' => '', // 'Do you really want to remove this task: "%s"?' => '', + // 'Assign a color to a specific category' => '', + // 'Task creation or modification' => '', + // 'Category' => '', + // 'Category:' => '', + // 'Categories' => '', + // 'Category not found.' => '', + // 'Your category have been created successfully.' => '', + // 'Unable to create your category.' => '', + // 'Your category have been updated successfully.' => '', + // 'Unable to update your category.' => '', + // 'Remove a category' => '', + // 'Category removed successfully.' => '', + // 'Unable to remove this category.' => '', + // 'Category modification for the project "%s"' => '', + // 'Category Name' => '', + // 'Categories for the project "%s"' => '', + // 'Add a new category' => '', + // 'Do you really want to remove this category: "%s"?' => '', + // 'Filter by category' => '', + // 'All categories' => '', + // 'No category' => '', ); diff --git a/locales/fr_FR/translations.php b/locales/fr_FR/translations.php index ac216c9b..46faf502 100644 --- a/locales/fr_FR/translations.php +++ b/locales/fr_FR/translations.php @@ -309,4 +309,25 @@ return array( 'Unable to remove this task.' => 'Impossible de supprimer cette tâche.', 'Remove a task' => 'Supprimer une tâche', 'Do you really want to remove this task: "%s"?' => 'Voulez-vous vraiment supprimer cette tâche « %s » ?', + 'Assign a color to a specific category' => 'Assigner une couleur à une catégorie spécifique', + 'Task creation or modification' => 'Création ou modification d\'une tâche', + 'Category' => 'Catégorie', + 'Category:' => 'Catégorie :', + 'Categories' => 'Catégories', + 'Category not found.' => 'Catégorie introuvable', + 'Your category have been created successfully.' => 'Votre catégorie a été créé avec succès.', + 'Unable to create your category.' => 'Impossible de créer votre catégorie.', + 'Your category have been updated successfully.' => 'Votre catégorie a été mise à jour avec succès.', + 'Unable to update your category.' => 'Impossible de mettre à jour votre catégorie.', + 'Remove a category' => 'Supprimer une catégorie', + 'Category removed successfully.' => 'Catégorie supprimée avec succès.', + 'Unable to remove this category.' => 'Impossible de supprimer cette catégorie.', + 'Category modification for the project "%s"' => 'Modification d\'une catégorie pour le projet « %s »', + 'Category Name' => 'Nom de la catégorie', + 'Categories for the project "%s"' => 'Catégories du projet « %s »', + 'Add a new category' => 'Ajouter une nouvelle catégorie', + 'Do you really want to remove this category: "%s"?' => 'Voulez-vous vraiment supprimer cette catégorie « %s » ?', + 'Filter by category' => 'Filtrer par catégorie', + 'All categories' => 'Toutes les catégories', + 'No category' => 'Aucune catégorie', ); diff --git a/locales/pl_PL/translations.php b/locales/pl_PL/translations.php index a8feb807..94726c20 100644 --- a/locales/pl_PL/translations.php +++ b/locales/pl_PL/translations.php @@ -314,4 +314,25 @@ return array( // 'Unable to remove this task.' => '', // 'Remove a task' => '', // 'Do you really want to remove this task: "%s"?' => '', + // 'Assign a color to a specific category' => '', + // 'Task creation or modification' => '', + // 'Category' => '', + // 'Category:' => '', + // 'Categories' => '', + // 'Category not found.' => '', + // 'Your category have been created successfully.' => '', + // 'Unable to create your category.' => '', + // 'Your category have been updated successfully.' => '', + // 'Unable to update your category.' => '', + // 'Remove a category' => '', + // 'Category removed successfully.' => '', + // 'Unable to remove this category.' => '', + // 'Category modification for the project "%s"' => '', + // 'Category Name' => '', + // 'Categories for the project "%s"' => '', + // 'Add a new category' => '', + // 'Do you really want to remove this category: "%s"?' => '', + // 'Filter by category' => '', + // 'All categories' => '', + // 'No category' => '', ); diff --git a/locales/pt_BR/translations.php b/locales/pt_BR/translations.php index ffb578c5..fc91b0b2 100644 --- a/locales/pt_BR/translations.php +++ b/locales/pt_BR/translations.php @@ -310,4 +310,25 @@ return array( // 'Unable to remove this task.' => '', // 'Remove a task' => '', // 'Do you really want to remove this task: "%s"?' => '', + // 'Assign a color to a specific category' => '', + // 'Task creation or modification' => '', + // 'Category' => '', + // 'Category:' => '', + // 'Categories' => '', + // 'Category not found.' => '', + // 'Your category have been created successfully.' => '', + // 'Unable to create your category.' => '', + // 'Your category have been updated successfully.' => '', + // 'Unable to update your category.' => '', + // 'Remove a category' => '', + // 'Category removed successfully.' => '', + // 'Unable to remove this category.' => '', + // 'Category modification for the project "%s"' => '', + // 'Category Name' => '', + // 'Categories for the project "%s"' => '', + // 'Add a new category' => '', + // 'Do you really want to remove this category: "%s"?' => '', + // 'Filter by category' => '', + // 'All categories' => '', + // 'No category' => '', ); diff --git a/models/action.php b/models/action.php index a0236eff..c8cbf3b1 100644 --- a/models/action.php +++ b/models/action.php @@ -44,6 +44,7 @@ class Action extends Base 'TaskAssignCurrentUser' => t('Assign the task to the person who does the action'), 'TaskDuplicateAnotherProject' => t('Duplicate the task to another project'), 'TaskAssignColorUser' => t('Assign a color to a specific user'), + 'TaskAssignColorCategory' => t('Assign a color to a specific category'), ); } @@ -62,6 +63,7 @@ class Action extends Base Task::EVENT_CREATE => t('Task creation'), Task::EVENT_OPEN => t('Open a closed task'), Task::EVENT_CLOSE => t('Closing a task'), + Task::EVENT_CREATE_UPDATE => t('Task creation or modification'), ); } @@ -239,6 +241,10 @@ class Action extends Base require_once __DIR__.'/../actions/task_assign_color_user.php'; $className = '\Action\TaskAssignColorUser'; return new $className($project_id, new Task($this->db, $this->event)); + case 'TaskAssignColorCategory': + require_once __DIR__.'/../actions/task_assign_color_category.php'; + $className = '\Action\TaskAssignColorCategory'; + return new $className($project_id, new Task($this->db, $this->event)); default: throw new \LogicException('Action not found: '.$name); } diff --git a/models/category.php b/models/category.php new file mode 100644 index 00000000..2a1891a5 --- /dev/null +++ b/models/category.php @@ -0,0 +1,152 @@ +db->table(self::TABLE)->eq('id', $category_id)->findOne(); + } + + /** + * Return the list of all categories + * + * @access public + * @param integer $project_id Project id + * @param bool $prepend_none If true, prepend to the list the value 'None' + * @param bool $prepend_all If true, prepend to the list the value 'All' + * @return array + */ + public function getList($project_id, $prepend_none = true, $prepend_all = false) + { + $listing = $this->db->table(self::TABLE) + ->eq('project_id', $project_id) + ->asc('name') + ->listing('id', 'name'); + + $prepend = array(); + + if ($prepend_all) { + $prepend[-1] = t('All categories'); + } + + if ($prepend_none) { + $prepend[0] = t('No category'); + } + + return $prepend + $listing; + } + + /** + * Create a category + * + * @access public + * @param array $values Form values + * @return bool + */ + public function create(array $values) + { + return $this->db->table(self::TABLE)->save($values); + } + + /** + * Update a category + * + * @access public + * @param array $values Form values + * @return bool + */ + public function update(array $values) + { + return $this->db->table(self::TABLE)->eq('id', $values['id'])->save($values); + } + + /** + * Remove a category + * + * @access public + * @param integer $category_id Category id + * @return bool + */ + public function remove($category_id) + { + $this->db->startTransaction(); + $r1 = $this->db->table(Task::TABLE)->eq('category_id', $category_id)->update(array('category_id' => 0)); + $r2 = $this->db->table(self::TABLE)->eq('id', $category_id)->remove(); + $this->db->closeTransaction(); + + return $r1 && $r2; + } + + /** + * Validate category creation + * + * @access public + * @param array $array Form values + * @return array $valid, $errors [0] = Success or not, [1] = List of errors + */ + public function validateCreation(array $values) + { + $v = new Validator($values, array( + new Validators\Required('project_id', t('The project id is required')), + new Validators\Integer('project_id', t('The project id must be an integer')), + new Validators\Required('name', t('The name is required')), + new Validators\MaxLength('name', t('The maximum length is %d characters', 50), 50) + )); + + return array( + $v->execute(), + $v->getErrors() + ); + } + + /** + * Validate category modification + * + * @access public + * @param array $array Form values + * @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('The id is required')), + new Validators\Integer('id', t('The id must be an integer')), + new Validators\Required('project_id', t('The project id is required')), + new Validators\Integer('project_id', t('The project id must be an integer')), + new Validators\Required('name', t('The name is required')), + new Validators\MaxLength('name', t('The maximum length is %d characters', 50), 50) + )); + + return array( + $v->execute(), + $v->getErrors() + ); + } +} diff --git a/models/project.php b/models/project.php index 680d0750..cbbbfda2 100644 --- a/models/project.php +++ b/models/project.php @@ -8,9 +8,9 @@ require_once __DIR__.'/board.php'; require_once __DIR__.'/task.php'; require_once __DIR__.'/../events/task_modification.php'; -use \SimpleValidator\Validator; -use \SimpleValidator\Validators; -use \Event\TaskModification; +use SimpleValidator\Validator; +use SimpleValidator\Validators; +use Event\TaskModification; /** * Project model diff --git a/models/task.php b/models/task.php index f6258e86..0c62a9f4 100644 --- a/models/task.php +++ b/models/task.php @@ -4,8 +4,8 @@ namespace Model; require_once __DIR__.'/base.php'; -use \SimpleValidator\Validator; -use \SimpleValidator\Validators; +use SimpleValidator\Validator; +use SimpleValidator\Validators; use DateTime; /** @@ -42,6 +42,7 @@ class Task extends Base const EVENT_CREATE = 'task.create'; const EVENT_CLOSE = 'task.close'; const EVENT_OPEN = 'task.open'; + const EVENT_CREATE_UPDATE = 'task.create_update'; /** * Get available colors @@ -90,10 +91,13 @@ class Task extends Base self::TABLE.'.position', self::TABLE.'.is_active', self::TABLE.'.score', + self::TABLE.'.category_id', + Category::TABLE.'.name AS category_name', Project::TABLE.'.name AS project_name', Board::TABLE.'.title AS column_title', User::TABLE.'.username' ) + ->join(Category::TABLE, 'id', 'category_id') ->join(Project::TABLE, 'id', 'project_id') ->join(Board::TABLE, 'id', 'column_id') ->join(User::TABLE, 'id', 'owner_id') @@ -150,6 +154,7 @@ class Task extends Base 'tasks.position', 'tasks.is_active', 'tasks.score', + 'tasks.category_id', 'users.username' ) ->join('users', 'id', 'owner_id'); @@ -236,6 +241,7 @@ class Task extends Base $this->db->closeTransaction(); // Trigger events + $this->event->trigger(self::EVENT_CREATE_UPDATE, array('task_id' => $task_id) + $task); $this->event->trigger(self::EVENT_CREATE, array('task_id' => $task_id) + $task); return $task_id; @@ -265,6 +271,7 @@ class Task extends Base // Assign new values $task['date_creation'] = time(); $task['owner_id'] = 0; + $task['category_id'] = 0; $task['is_active'] = 1; $task['column_id'] = $boardModel->getFirstColumn($project_id); $task['project_id'] = $project_id; @@ -281,6 +288,7 @@ class Task extends Base $this->db->closeTransaction(); // Trigger events + $this->event->trigger(self::EVENT_CREATE_UPDATE, array('task_id' => $task_id) + $task); $this->event->trigger(self::EVENT_CREATE, array('task_id' => $task_id) + $task); return $task_id; @@ -320,6 +328,7 @@ class Task extends Base $this->db->closeTransaction(); // Trigger events + $this->event->trigger(self::EVENT_CREATE_UPDATE, array('task_id' => $task_id) + $values); $this->event->trigger(self::EVENT_CREATE, array('task_id' => $task_id) + $values); return $task_id; @@ -355,7 +364,8 @@ class Task extends Base $events = array(); - if ($this->event->getLastTriggeredEvent() !== self::EVENT_UPDATE) { + if (! in_array($this->event->getLastTriggeredEvent(), array(self::EVENT_CREATE_UPDATE))) { + $events[] = self::EVENT_CREATE_UPDATE; $events[] = self::EVENT_UPDATE; } diff --git a/schemas/mysql.php b/schemas/mysql.php index 46f56e7b..eb869465 100644 --- a/schemas/mysql.php +++ b/schemas/mysql.php @@ -2,6 +2,22 @@ namespace Schema; +function version_16($pdo) +{ + $pdo->exec(" + CREATE TABLE project_has_categories ( + id INT NOT NULL AUTO_INCREMENT, + name VARCHAR(255), + project_id INT, + PRIMARY KEY (id), + UNIQUE KEY `idx_project_category` (project_id, name), + FOREIGN KEY(project_id) REFERENCES projects(id) ON DELETE CASCADE + ) ENGINE=InnoDB CHARSET=utf8" + ); + + $pdo->exec("ALTER TABLE tasks ADD COLUMN category_id INT DEFAULT 0"); +} + function version_15($pdo) { $pdo->exec("ALTER TABLE projects ADD COLUMN last_modified INT DEFAULT 0"); diff --git a/schemas/sqlite.php b/schemas/sqlite.php index 94580235..b444faa5 100644 --- a/schemas/sqlite.php +++ b/schemas/sqlite.php @@ -2,6 +2,21 @@ namespace Schema; +function version_16($pdo) +{ + $pdo->exec(" + CREATE TABLE project_has_categories ( + id INTEGER PRIMARY KEY, + name TEXT COLLATE NOCASE, + project_id INT, + UNIQUE (project_id, name), + FOREIGN KEY(project_id) REFERENCES projects(id) ON DELETE CASCADE + )" + ); + + $pdo->exec("ALTER TABLE tasks ADD COLUMN category_id INTEGER DEFAULT 0"); +} + function version_15($pdo) { $pdo->exec("ALTER TABLE projects ADD COLUMN last_modified INTEGER DEFAULT 0"); diff --git a/templates/action_index.php b/templates/action_index.php index eccc0ec4..b515ccaa 100644 --- a/templates/action_index.php +++ b/templates/action_index.php @@ -36,6 +36,8 @@ + + diff --git a/templates/action_params.php b/templates/action_params.php index ed9d8b8f..15a1d420 100644 --- a/templates/action_params.php +++ b/templates/action_params.php @@ -28,6 +28,9 @@
+ + +
diff --git a/templates/board_index.php b/templates/board_index.php index 523e3392..989c2e06 100644 --- a/templates/board_index.php +++ b/templates/board_index.php @@ -21,6 +21,10 @@ +
  • + + +
  • @@ -30,7 +34,7 @@

    - $current_project_id, 'board' => $board)) ?> + $current_project_id, 'board' => $board, 'categories' => $categories)) ?> diff --git a/templates/board_public.php b/templates/board_public.php index 2c2381ef..0808079e 100644 --- a/templates/board_public.php +++ b/templates/board_public.php @@ -39,7 +39,17 @@ + +
    + + + +
    + + + + diff --git a/templates/board_show.php b/templates/board_show.php index ad459399..719e3bdd 100644 --- a/templates/board_show.php +++ b/templates/board_show.php @@ -29,6 +29,7 @@
    @@ -50,7 +51,17 @@
    + +
    + + + +
    + + + + + diff --git a/templates/category_edit.php b/templates/category_edit.php new file mode 100644 index 00000000..99ba0c7c --- /dev/null +++ b/templates/category_edit.php @@ -0,0 +1,24 @@ +
    + +
    + +
    + + + + + + + +
    + +
    +
    + +
    +
    \ No newline at end of file diff --git a/templates/category_index.php b/templates/category_index.php new file mode 100644 index 00000000..db986143 --- /dev/null +++ b/templates/category_index.php @@ -0,0 +1,48 @@ +
    + +
    + + + + + + + + $category_name): ?> + + + + + +
    +
      +
    • + +
    • +
    • + +
    • +
    +
    + + +

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

    + +

    + +
    + + +
    +
    +
    \ No newline at end of file diff --git a/templates/project_index.php b/templates/project_index.php index c7c7d226..df153fe7 100644 --- a/templates/project_index.php +++ b/templates/project_index.php @@ -59,6 +59,9 @@