From ce367a24fca09bb1fa05da167f36db54712c8fa1 Mon Sep 17 00:00:00 2001 From: Frederic Guillot Date: Fri, 24 Jun 2016 22:10:14 -0400 Subject: Added tag modification from the user interface --- app/Controller/ProjectTagController.php | 134 +++++++++++++++++++++++++ app/Controller/TagController.php | 121 ++++++++++++++++++++++ app/Core/Base.php | 3 +- app/Model/TagModel.php | 23 +++++ app/ServiceProvider/AuthenticationProvider.php | 2 + app/ServiceProvider/ClassProvider.php | 3 +- app/ServiceProvider/RouteProvider.php | 2 + app/Template/config/sidebar.php | 3 + app/Template/project/sidebar.php | 3 + app/Template/project_tag/create.php | 16 +++ app/Template/project_tag/edit.php | 17 ++++ app/Template/project_tag/index.php | 31 ++++++ app/Template/project_tag/remove.php | 15 +++ app/Template/tag/create.php | 16 +++ app/Template/tag/edit.php | 17 ++++ app/Template/tag/index.php | 31 ++++++ app/Template/tag/remove.php | 15 +++ app/Validator/TagValidator.php | 77 ++++++++++++++ 18 files changed, 527 insertions(+), 2 deletions(-) create mode 100644 app/Controller/ProjectTagController.php create mode 100644 app/Controller/TagController.php create mode 100644 app/Template/project_tag/create.php create mode 100644 app/Template/project_tag/edit.php create mode 100644 app/Template/project_tag/index.php create mode 100644 app/Template/project_tag/remove.php create mode 100644 app/Template/tag/create.php create mode 100644 app/Template/tag/edit.php create mode 100644 app/Template/tag/index.php create mode 100644 app/Template/tag/remove.php create mode 100644 app/Validator/TagValidator.php (limited to 'app') diff --git a/app/Controller/ProjectTagController.php b/app/Controller/ProjectTagController.php new file mode 100644 index 00000000..acf514d3 --- /dev/null +++ b/app/Controller/ProjectTagController.php @@ -0,0 +1,134 @@ +getProject(); + + $this->response->html($this->helper->layout->project('project_tag/index', array( + 'project' => $project, + 'tags' => $this->tagModel->getAllByProject($project['id']), + 'title' => t('Project tags management'), + ))); + } + + public function create(array $values = array(), array $errors = array()) + { + $project = $this->getProject(); + + if (empty($values)) { + $values['project_id'] = $project['id']; + } + + $this->response->html($this->template->render('project_tag/create', array( + 'project' => $project, + 'values' => $values, + 'errors' => $errors, + ))); + } + + public function save() + { + $project = $this->getProject(); + $values = $this->request->getValues(); + list($valid, $errors) = $this->tagValidator->validateCreation($values); + + if ($valid) { + if ($this->tagModel->create($project['id'], $values['name']) > 0) { + $this->flash->success(t('Tag created successfully.')); + } else { + $this->flash->failure(t('Unable to create this tag.')); + } + + $this->response->redirect($this->helper->url->to('ProjectTagController', 'index', array('project_id' => $project['id']))); + } else { + $this->create($values, $errors); + } + } + + public function edit(array $values = array(), array $errors = array()) + { + $project = $this->getProject(); + $tag_id = $this->request->getIntegerParam('tag_id'); + $tag = $this->tagModel->getById($tag_id); + + if (empty($values)) { + $values = $tag; + } + + $this->response->html($this->template->render('project_tag/edit', array( + 'project' => $project, + 'tag' => $tag, + 'values' => $values, + 'errors' => $errors, + ))); + } + + public function update() + { + $project = $this->getProject(); + $tag_id = $this->request->getIntegerParam('tag_id'); + $tag = $this->tagModel->getById($tag_id); + $values = $this->request->getValues(); + list($valid, $errors) = $this->tagValidator->validateModification($values); + + if ($tag['project_id'] != $project['id']) { + throw new AccessForbiddenException(); + } + + if ($valid) { + if ($this->tagModel->update($values['id'], $values['name'])) { + $this->flash->success(t('Tag updated successfully.')); + } else { + $this->flash->failure(t('Unable to update this tag.')); + } + + $this->response->redirect($this->helper->url->to('ProjectTagController', 'index', array('project_id' => $project['id']))); + } else { + $this->edit($values, $errors); + } + } + + public function confirm() + { + $project = $this->getProject(); + $tag_id = $this->request->getIntegerParam('tag_id'); + $tag = $this->tagModel->getById($tag_id); + + $this->response->html($this->template->render('project_tag/remove', array( + 'tag' => $tag, + 'project' => $project, + ))); + } + + public function remove() + { + $this->checkCSRFParam(); + $project = $this->getProject(); + $tag_id = $this->request->getIntegerParam('tag_id'); + $tag = $this->tagModel->getById($tag_id); + + if ($tag['project_id'] != $project['id']) { + throw new AccessForbiddenException(); + } + + if ($this->tagModel->remove($tag_id)) { + $this->flash->success(t('Tag removed successfully.')); + } else { + $this->flash->failure(t('Unable to remove this tag.')); + } + + $this->response->redirect($this->helper->url->to('ProjectTagController', 'index', array('project_id' => $project['id']))); + } +} diff --git a/app/Controller/TagController.php b/app/Controller/TagController.php new file mode 100644 index 00000000..b8389910 --- /dev/null +++ b/app/Controller/TagController.php @@ -0,0 +1,121 @@ +response->html($this->helper->layout->config('tag/index', array( + 'tags' => $this->tagModel->getAllByProject(0), + 'title' => t('Settings').' > '.t('Global tags management'), + ))); + } + + public function create(array $values = array(), array $errors = array()) + { + if (empty($values)) { + $values['project_id'] = 0; + } + + $this->response->html($this->template->render('tag/create', array( + 'values' => $values, + 'errors' => $errors, + ))); + } + + public function save() + { + $values = $this->request->getValues(); + list($valid, $errors) = $this->tagValidator->validateCreation($values); + + if ($valid) { + if ($this->tagModel->create(0, $values['name']) > 0) { + $this->flash->success(t('Tag created successfully.')); + } else { + $this->flash->failure(t('Unable to create this tag.')); + } + + $this->response->redirect($this->helper->url->to('TagController', 'index')); + } else { + $this->create($values, $errors); + } + } + + public function edit(array $values = array(), array $errors = array()) + { + $tag_id = $this->request->getIntegerParam('tag_id'); + $tag = $this->tagModel->getById($tag_id); + + if (empty($values)) { + $values = $tag; + } + + $this->response->html($this->template->render('tag/edit', array( + 'tag' => $tag, + 'values' => $values, + 'errors' => $errors, + ))); + } + + public function update() + { + $tag_id = $this->request->getIntegerParam('tag_id'); + $tag = $this->tagModel->getById($tag_id); + $values = $this->request->getValues(); + list($valid, $errors) = $this->tagValidator->validateModification($values); + + if ($tag['project_id'] != 0) { + throw new AccessForbiddenException(); + } + + if ($valid) { + if ($this->tagModel->update($values['id'], $values['name'])) { + $this->flash->success(t('Tag updated successfully.')); + } else { + $this->flash->failure(t('Unable to update this tag.')); + } + + $this->response->redirect($this->helper->url->to('TagController', 'index')); + } else { + $this->edit($values, $errors); + } + } + + public function confirm() + { + $tag_id = $this->request->getIntegerParam('tag_id'); + $tag = $this->tagModel->getById($tag_id); + + $this->response->html($this->template->render('tag/remove', array( + 'tag' => $tag, + ))); + } + + public function remove() + { + $this->checkCSRFParam(); + $tag_id = $this->request->getIntegerParam('tag_id'); + $tag = $this->tagModel->getById($tag_id); + + if ($tag['project_id'] != 0) { + throw new AccessForbiddenException(); + } + + if ($this->tagModel->remove($tag_id)) { + $this->flash->success(t('Tag removed successfully.')); + } else { + $this->flash->failure(t('Unable to remove this tag.')); + } + + $this->response->redirect($this->helper->url->to('TagController', 'index')); + } +} diff --git a/app/Core/Base.php b/app/Core/Base.php index 6712cbce..e5dd6ad9 100644 --- a/app/Core/Base.php +++ b/app/Core/Base.php @@ -116,14 +116,15 @@ use Pimple\Container; * @property \Kanboard\Validator\CommentValidator $commentValidator * @property \Kanboard\Validator\CurrencyValidator $currencyValidator * @property \Kanboard\Validator\CustomFilterValidator $customFilterValidator + * @property \Kanboard\Validator\ExternalLinkValidator $externalLinkValidator * @property \Kanboard\Validator\GroupValidator $groupValidator * @property \Kanboard\Validator\LinkValidator $linkValidator * @property \Kanboard\Validator\PasswordResetValidator $passwordResetValidator * @property \Kanboard\Validator\ProjectValidator $projectValidator * @property \Kanboard\Validator\SubtaskValidator $subtaskValidator * @property \Kanboard\Validator\SwimlaneValidator $swimlaneValidator + * @property \Kanboard\Validator\TagValidator $tagValidator * @property \Kanboard\Validator\TaskLinkValidator $taskLinkValidator - * @property \Kanboard\Validator\ExternalLinkValidator $externalLinkValidator * @property \Kanboard\Validator\TaskValidator $taskValidator * @property \Kanboard\Validator\UserValidator $userValidator * @property \Kanboard\Import\TaskImport $taskImport diff --git a/app/Model/TagModel.php b/app/Model/TagModel.php index 8eb5e5ba..e85c5a87 100644 --- a/app/Model/TagModel.php +++ b/app/Model/TagModel.php @@ -93,6 +93,29 @@ class TagModel extends Base ->findOneColumn('id'); } + /** + * Return true if the tag exists + * + * @access public + * @param integer $project_id + * @param string $tag + * @param integer $tag_id + * @return boolean + */ + public function exists($project_id, $tag, $tag_id = 0) + { + return $this->db + ->table(self::TABLE) + ->neq('id', $tag_id) + ->beginOr() + ->eq('project_id', 0) + ->eq('project_id', $project_id) + ->closeOr() + ->ilike('name', $tag) + ->asc('project_id') + ->exists(); + } + /** * Return tag id and create a new tag if necessary * diff --git a/app/ServiceProvider/AuthenticationProvider.php b/app/ServiceProvider/AuthenticationProvider.php index 2fad8a3a..84e4354d 100644 --- a/app/ServiceProvider/AuthenticationProvider.php +++ b/app/ServiceProvider/AuthenticationProvider.php @@ -88,6 +88,7 @@ class AuthenticationProvider implements ServiceProviderInterface $acl->add('ProjectFileController', '*', Role::PROJECT_MEMBER); $acl->add('ProjectUserOverviewController', '*', Role::PROJECT_MANAGER); $acl->add('ProjectStatusController', '*', Role::PROJECT_MANAGER); + $acl->add('ProjectTagController', '*', Role::PROJECT_MANAGER); $acl->add('SubtaskController', '*', Role::PROJECT_MEMBER); $acl->add('SubtaskRestrictionController', '*', Role::PROJECT_MEMBER); $acl->add('SubtaskStatusController', '*', Role::PROJECT_MEMBER); @@ -131,6 +132,7 @@ class AuthenticationProvider implements ServiceProviderInterface $acl->add('AvatarFileController', 'show', Role::APP_PUBLIC); $acl->add('ConfigController', '*', Role::APP_ADMIN); + $acl->add('TagController', '*', Role::APP_ADMIN); $acl->add('PluginController', '*', Role::APP_ADMIN); $acl->add('CurrencyController', '*', Role::APP_ADMIN); $acl->add('ProjectGanttController', '*', Role::APP_MANAGER); diff --git a/app/ServiceProvider/ClassProvider.php b/app/ServiceProvider/ClassProvider.php index 778b4f9e..c0fb93bd 100644 --- a/app/ServiceProvider/ClassProvider.php +++ b/app/ServiceProvider/ClassProvider.php @@ -99,8 +99,9 @@ class ClassProvider implements ServiceProviderInterface 'ProjectValidator', 'SubtaskValidator', 'SwimlaneValidator', - 'TaskValidator', + 'TagValidator', 'TaskLinkValidator', + 'TaskValidator', 'UserValidator', ), 'Import' => array( diff --git a/app/ServiceProvider/RouteProvider.php b/app/ServiceProvider/RouteProvider.php index 3d1391df..8801e3d0 100644 --- a/app/ServiceProvider/RouteProvider.php +++ b/app/ServiceProvider/RouteProvider.php @@ -59,6 +59,7 @@ class RouteProvider implements ServiceProviderInterface $container['route']->addRoute('project/:project_id/duplicate', 'ProjectViewController', 'duplicate'); $container['route']->addRoute('project/:project_id/permissions', 'ProjectPermissionController', 'index'); $container['route']->addRoute('project/:project_id/activity', 'ActivityController', 'project'); + $container['route']->addRoute('project/:project_id/tags', 'ProjectTagController', 'index'); // Project Overview $container['route']->addRoute('project/:project_id/overview', 'ProjectOverviewController', 'show'); @@ -174,6 +175,7 @@ class RouteProvider implements ServiceProviderInterface $container['route']->addRoute('settings/api', 'ConfigController', 'api'); $container['route']->addRoute('settings/links', 'LinkController', 'index'); $container['route']->addRoute('settings/currencies', 'CurrencyController', 'index'); + $container['route']->addRoute('settings/tags', 'TagController', 'index'); // Plugins $container['route']->addRoute('extensions', 'PluginController', 'show'); diff --git a/app/Template/config/sidebar.php b/app/Template/config/sidebar.php index 29caa0ef..e304f0d0 100644 --- a/app/Template/config/sidebar.php +++ b/app/Template/config/sidebar.php @@ -19,6 +19,9 @@
  • app->checkMenuSelection('ConfigController', 'calendar') ?>> url->link(t('Calendar settings'), 'ConfigController', 'calendar') ?>
  • +
  • app->checkMenuSelection('TagController', 'index') ?>> + url->link(t('Tags management'), 'TagController', 'index') ?> +
  • app->checkMenuSelection('LinkController') ?>> url->link(t('Link settings'), 'LinkController', 'index') ?>
  • diff --git a/app/Template/project/sidebar.php b/app/Template/project/sidebar.php index 9bc0c9c4..d0f50596 100644 --- a/app/Template/project/sidebar.php +++ b/app/Template/project/sidebar.php @@ -32,6 +32,9 @@
  • app->checkMenuSelection('CategoryController') ?>> url->link(t('Categories'), 'CategoryController', 'index', array('project_id' => $project['id'])) ?>
  • +
  • app->checkMenuSelection('ProjectTagController') ?>> + url->link(t('Tags'), 'ProjectTagController', 'index', array('project_id' => $project['id'])) ?> +
  • app->checkMenuSelection('ProjectPermissionController') ?>> url->link(t('Permissions'), 'ProjectPermissionController', 'index', array('project_id' => $project['id'])) ?> diff --git a/app/Template/project_tag/create.php b/app/Template/project_tag/create.php new file mode 100644 index 00000000..bfd1084a --- /dev/null +++ b/app/Template/project_tag/create.php @@ -0,0 +1,16 @@ + +
    + form->csrf() ?> + form->hidden('project_id', $values) ?> + + form->label(t('Name'), 'name') ?> + form->text('name', $values, $errors, array('autofocus', 'required', 'maxlength="255"')) ?> + +
    + + + url->link(t('cancel'), 'ProjectTagController', 'index', array('project_id' => $project['id']), false, 'close-popover') ?> +
    +
    diff --git a/app/Template/project_tag/edit.php b/app/Template/project_tag/edit.php new file mode 100644 index 00000000..9bf261bd --- /dev/null +++ b/app/Template/project_tag/edit.php @@ -0,0 +1,17 @@ + +
    + form->csrf() ?> + form->hidden('id', $values) ?> + form->hidden('project_id', $values) ?> + + form->label(t('Name'), 'name') ?> + form->text('name', $values, $errors, array('autofocus', 'required', 'maxlength="255"')) ?> + +
    + + + url->link(t('cancel'), 'ProjectTagController', 'index', array(), false, 'close-popover') ?> +
    +
    diff --git a/app/Template/project_tag/index.php b/app/Template/project_tag/index.php new file mode 100644 index 00000000..8e8dd96c --- /dev/null +++ b/app/Template/project_tag/index.php @@ -0,0 +1,31 @@ + + + +

    + + + + + + + + + + + + +
    text->e($tag['name']) ?> + + url->link(t('Remove'), 'ProjectTagController', 'confirm', array('tag_id' => $tag['id'], 'project_id' => $project['id']), false, 'popover') ?> + + url->link(t('Edit'), 'ProjectTagController', 'edit', array('tag_id' => $tag['id'], 'project_id' => $project['id']), false, 'popover') ?> +
    + diff --git a/app/Template/project_tag/remove.php b/app/Template/project_tag/remove.php new file mode 100644 index 00000000..f4aadab1 --- /dev/null +++ b/app/Template/project_tag/remove.php @@ -0,0 +1,15 @@ + + +
    +

    + +

    + +
    + url->link(t('Yes'), 'ProjectTagController', 'remove', array('tag_id' => $tag['id'], 'project_id' => $project['id']), true, 'btn btn-red popover-link') ?> + + url->link(t('cancel'), 'ProjectTagController', 'index', array('project_id' => $project['id']), false, 'close-popover') ?> +
    +
    diff --git a/app/Template/tag/create.php b/app/Template/tag/create.php new file mode 100644 index 00000000..9b32bc46 --- /dev/null +++ b/app/Template/tag/create.php @@ -0,0 +1,16 @@ + +
    + form->csrf() ?> + form->hidden('project_id', $values) ?> + + form->label(t('Name'), 'name') ?> + form->text('name', $values, $errors, array('autofocus', 'required', 'maxlength="255"')) ?> + +
    + + + url->link(t('cancel'), 'TagController', 'index', array(), false, 'close-popover') ?> +
    +
    diff --git a/app/Template/tag/edit.php b/app/Template/tag/edit.php new file mode 100644 index 00000000..f751ff49 --- /dev/null +++ b/app/Template/tag/edit.php @@ -0,0 +1,17 @@ + +
    + form->csrf() ?> + form->hidden('id', $values) ?> + form->hidden('project_id', $values) ?> + + form->label(t('Name'), 'name') ?> + form->text('name', $values, $errors, array('autofocus', 'required', 'maxlength="255"')) ?> + +
    + + + url->link(t('cancel'), 'TagController', 'index', array(), false, 'close-popover') ?> +
    +
    diff --git a/app/Template/tag/index.php b/app/Template/tag/index.php new file mode 100644 index 00000000..2a495eb3 --- /dev/null +++ b/app/Template/tag/index.php @@ -0,0 +1,31 @@ + + + +

    + + + + + + + + + + + + +
    text->e($tag['name']) ?> + + url->link(t('Remove'), 'TagController', 'confirm', array('tag_id' => $tag['id']), false, 'popover') ?> + + url->link(t('Edit'), 'TagController', 'edit', array('tag_id' => $tag['id']), false, 'popover') ?> +
    + diff --git a/app/Template/tag/remove.php b/app/Template/tag/remove.php new file mode 100644 index 00000000..46ea3f99 --- /dev/null +++ b/app/Template/tag/remove.php @@ -0,0 +1,15 @@ + + +
    +

    + +

    + +
    + url->link(t('Yes'), 'TagController', 'remove', array('tag_id' => $tag['id']), true, 'btn btn-red popover-link') ?> + + url->link(t('cancel'), 'TagController', 'index', array(), false, 'close-popover') ?> +
    +
    diff --git a/app/Validator/TagValidator.php b/app/Validator/TagValidator.php new file mode 100644 index 00000000..32622583 --- /dev/null +++ b/app/Validator/TagValidator.php @@ -0,0 +1,77 @@ +commonValidationRules()); + $result = $v->execute(); + $errors = $v->getErrors(); + + if ($result && $this->tagModel->exists($values['project_id'], $values['name'])) { + $result = false; + $errors = array('name' => array(t('The name must be unique'))); + } + + return array($result, $errors); + } + + /** + * Validate modification + * + * @access public + * @param array $values Form values + * @return array $valid, $errors [0] = Success or not, [1] = List of errors + */ + public function validateModification(array $values) + { + $rules = array( + new Validators\Required('id', t('Field required')), + ); + + $v = new Validator($values, array_merge($rules, $this->commonValidationRules())); + $result = $v->execute(); + $errors = $v->getErrors(); + + if ($result && $this->tagModel->exists($values['project_id'], $values['name'], $values['id'])) { + $result = false; + $errors = array('name' => array(t('The name must be unique'))); + } + + return array($result, $errors); + } + + /** + * Common validation rules + * + * @access protected + * @return array + */ + protected function commonValidationRules() + { + return array( + new Validators\Required('project_id', t('Field required')), + new Validators\Required('name', t('Field required')), + new Validators\MaxLength('name', t('The maximum length is %d characters', 255), 255), + ); + } +} -- cgit v1.2.3