Merge remote-tracking branch 'upstream/master'
This commit is contained in:
commit
bfd1db4136
|
|
@ -21,7 +21,7 @@ Features
|
|||
|
||||
- Multiple boards/projects
|
||||
- Boards customization, rename or add columns
|
||||
- Tasks with different colors, Markdown support for the description
|
||||
- Tasks with different colors, categories, sub-tasks, attachments, Markdown support for the description
|
||||
- Automatic actions
|
||||
- Users management with a basic privileges separation (administrator or regular user)
|
||||
- External authentication: Google Account and LDAP/ActiveDirectory
|
||||
|
|
|
|||
|
|
@ -0,0 +1,85 @@
|
|||
<?php
|
||||
|
||||
namespace Action;
|
||||
|
||||
use Model\Task;
|
||||
|
||||
/**
|
||||
* Set a category automatically according to the color
|
||||
*
|
||||
* @package action
|
||||
* @author Frederic Guillot
|
||||
*/
|
||||
class TaskAssignCategoryColor extends Base
|
||||
{
|
||||
/**
|
||||
* Task model
|
||||
*
|
||||
* @accesss private
|
||||
* @var \Model\Task
|
||||
*/
|
||||
private $task;
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
*
|
||||
* @access public
|
||||
* @param integer $project_id Project id
|
||||
* @param \Model\Task $task Task model instance
|
||||
*/
|
||||
public function __construct($project_id, Task $task)
|
||||
{
|
||||
parent::__construct($project_id);
|
||||
$this->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',
|
||||
'color_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['color_id'] == $this->getParam('color_id')) {
|
||||
|
||||
$this->task->update(array(
|
||||
'id' => $data['task_id'],
|
||||
'category_id' => $this->getParam('category_id'),
|
||||
));
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
|
@ -129,6 +129,7 @@ class Action extends Base
|
|||
*/
|
||||
public function remove()
|
||||
{
|
||||
$this->checkCSRFParam();
|
||||
$action = $this->action->getById($this->request->getIntegerParam('action_id'));
|
||||
|
||||
if ($action && $this->action->remove($action['id'])) {
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@
|
|||
namespace Controller;
|
||||
|
||||
use Core\Registry;
|
||||
use Core\Security;
|
||||
use Core\Translator;
|
||||
use Model\LastLogin;
|
||||
|
||||
|
|
@ -23,6 +24,7 @@ use Model\LastLogin;
|
|||
* @property \Model\Ldap $ldap
|
||||
* @property \Model\Project $project
|
||||
* @property \Model\RememberMe $rememberMe
|
||||
* @property \Model\SubTask $subTask
|
||||
* @property \Model\Task $task
|
||||
* @property \Model\User $user
|
||||
*/
|
||||
|
|
@ -159,6 +161,28 @@ abstract class Base
|
|||
$this->response->html($this->template->layout('app_notfound', array('title' => t('Page not found'))));
|
||||
}
|
||||
|
||||
/**
|
||||
* Application forbidden page
|
||||
*
|
||||
* @access public
|
||||
*/
|
||||
public function forbidden()
|
||||
{
|
||||
$this->response->html($this->template->layout('app_forbidden', array('title' => t('Access Forbidden'))));
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if the CSRF token from the URL is correct
|
||||
*
|
||||
* @access protected
|
||||
*/
|
||||
protected function checkCSRFParam()
|
||||
{
|
||||
if (! Security::validateCSRFToken($this->request->getStringParam('csrf_token'))) {
|
||||
$this->forbidden();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if the current user have access to the given project
|
||||
*
|
||||
|
|
@ -170,7 +194,7 @@ abstract class Base
|
|||
if ($this->acl->isRegularUser()) {
|
||||
|
||||
if ($project_id > 0 && ! $this->project->isUserAllowed($project_id, $this->acl->getUserId())) {
|
||||
$this->response->redirect('?controller=project&action=forbidden');
|
||||
$this->forbidden();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@ namespace Controller;
|
|||
|
||||
use Model\Project as ProjectModel;
|
||||
use Model\User as UserModel;
|
||||
use Core\Security;
|
||||
|
||||
/**
|
||||
* Board controller
|
||||
|
|
@ -20,6 +21,7 @@ class Board extends Base
|
|||
*/
|
||||
public function moveUp()
|
||||
{
|
||||
$this->checkCSRFParam();
|
||||
$project_id = $this->request->getIntegerParam('project_id');
|
||||
$column_id = $this->request->getIntegerParam('column_id');
|
||||
|
||||
|
|
@ -35,6 +37,7 @@ class Board extends Base
|
|||
*/
|
||||
public function moveDown()
|
||||
{
|
||||
$this->checkCSRFParam();
|
||||
$project_id = $this->request->getIntegerParam('project_id');
|
||||
$column_id = $this->request->getIntegerParam('column_id');
|
||||
|
||||
|
|
@ -344,6 +347,7 @@ class Board extends Base
|
|||
*/
|
||||
public function remove()
|
||||
{
|
||||
$this->checkCSRFParam();
|
||||
$column = $this->board->getColumn($this->request->getIntegerParam('column_id'));
|
||||
|
||||
if ($column && $this->board->removeColumn($column['id'])) {
|
||||
|
|
@ -362,25 +366,31 @@ class Board extends Base
|
|||
*/
|
||||
public function save()
|
||||
{
|
||||
$project_id = $this->request->getIntegerParam('project_id');
|
||||
$values = $this->request->getValues();
|
||||
if ($this->request->isAjax()) {
|
||||
|
||||
if ($project_id > 0 && ! $this->project->isUserAllowed($project_id, $this->acl->getUserId())) {
|
||||
$this->response->text('Not Authorized', 401);
|
||||
$project_id = $this->request->getIntegerParam('project_id');
|
||||
$values = $this->request->getValues();
|
||||
|
||||
if ($project_id > 0 && ! $this->project->isUserAllowed($project_id, $this->acl->getUserId())) {
|
||||
$this->response->text('Not Authorized', 401);
|
||||
}
|
||||
|
||||
if (isset($values['positions'])) {
|
||||
$this->board->saveTasksPosition($values['positions']);
|
||||
}
|
||||
|
||||
$this->response->html(
|
||||
$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
|
||||
);
|
||||
}
|
||||
|
||||
if (isset($values['positions'])) {
|
||||
$this->board->saveTasksPosition($values['positions']);
|
||||
else {
|
||||
$this->response->status(401);
|
||||
}
|
||||
|
||||
$this->response->html(
|
||||
$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,24 +400,30 @@ class Board extends Base
|
|||
*/
|
||||
public function check()
|
||||
{
|
||||
$project_id = $this->request->getIntegerParam('project_id');
|
||||
$timestamp = $this->request->getIntegerParam('timestamp');
|
||||
if ($this->request->isAjax()) {
|
||||
|
||||
if ($project_id > 0 && ! $this->project->isUserAllowed($project_id, $this->acl->getUserId())) {
|
||||
$this->response->text('Not Authorized', 401);
|
||||
}
|
||||
$project_id = $this->request->getIntegerParam('project_id');
|
||||
$timestamp = $this->request->getIntegerParam('timestamp');
|
||||
|
||||
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),
|
||||
'categories' => $this->category->getList($project_id, false),
|
||||
))
|
||||
);
|
||||
if ($project_id > 0 && ! $this->project->isUserAllowed($project_id, $this->acl->getUserId())) {
|
||||
$this->response->text('Not Authorized', 401);
|
||||
}
|
||||
|
||||
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),
|
||||
'categories' => $this->category->getList($project_id, false),
|
||||
))
|
||||
);
|
||||
}
|
||||
else {
|
||||
$this->response->status(304);
|
||||
}
|
||||
}
|
||||
else {
|
||||
$this->response->status(304);
|
||||
$this->response->status(401);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -175,6 +175,7 @@ class Category extends Base
|
|||
*/
|
||||
public function remove()
|
||||
{
|
||||
$this->checkCSRFParam();
|
||||
$project = $this->getProject();
|
||||
$category = $this->getCategory($project['id']);
|
||||
|
||||
|
|
|
|||
|
|
@ -178,6 +178,7 @@ class Comment extends Base
|
|||
*/
|
||||
public function remove()
|
||||
{
|
||||
$this->checkCSRFParam();
|
||||
$task = $this->getTask();
|
||||
$comment = $this->getComment();
|
||||
|
||||
|
|
|
|||
|
|
@ -76,6 +76,7 @@ class Config extends Base
|
|||
*/
|
||||
public function downloadDb()
|
||||
{
|
||||
$this->checkCSRFParam();
|
||||
$this->response->forceDownload('db.sqlite.gz');
|
||||
$this->response->binary($this->config->downloadDatabase());
|
||||
}
|
||||
|
|
@ -87,6 +88,7 @@ class Config extends Base
|
|||
*/
|
||||
public function optimizeDb()
|
||||
{
|
||||
$this->checkCSRFParam();
|
||||
$this->config->optimizeDatabase();
|
||||
$this->session->flash(t('Database optimization done.'));
|
||||
$this->response->redirect('?controller=config');
|
||||
|
|
@ -99,6 +101,7 @@ class Config extends Base
|
|||
*/
|
||||
public function tokens()
|
||||
{
|
||||
$this->checkCSRFParam();
|
||||
$this->config->regenerateTokens();
|
||||
$this->session->flash(t('All tokens have been regenerated.'));
|
||||
$this->response->redirect('?controller=config');
|
||||
|
|
@ -111,6 +114,7 @@ class Config extends Base
|
|||
*/
|
||||
public function removeRememberMeToken()
|
||||
{
|
||||
$this->checkCSRFParam();
|
||||
$this->rememberMe->remove($this->request->getIntegerParam('id'));
|
||||
$this->response->redirect('?controller=config&action=index#remember-me');
|
||||
}
|
||||
|
|
|
|||
|
|
@ -24,6 +24,7 @@ class File extends Base
|
|||
$this->response->html($this->taskLayout('file_new', array(
|
||||
'task' => $task,
|
||||
'menu' => 'tasks',
|
||||
'max_size' => ini_get('upload_max_filesize'),
|
||||
'title' => t('Attach a document')
|
||||
)));
|
||||
}
|
||||
|
|
@ -36,8 +37,14 @@ class File extends Base
|
|||
public function save()
|
||||
{
|
||||
$task = $this->getTask();
|
||||
$this->file->upload($task['project_id'], $task['id'], 'files');
|
||||
$this->response->redirect('?controller=task&action=show&task_id='.$task['id'].'#attachments');
|
||||
|
||||
if ($this->file->upload($task['project_id'], $task['id'], 'files') === true) {
|
||||
$this->response->redirect('?controller=task&action=show&task_id='.$task['id'].'#attachments');
|
||||
}
|
||||
else {
|
||||
$this->session->flashError(t('Unable to upload the file.'));
|
||||
$this->response->redirect('?controller=file&action=create&task_id='.$task['id']);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -104,6 +111,7 @@ class File extends Base
|
|||
*/
|
||||
public function remove()
|
||||
{
|
||||
$this->checkCSRFParam();
|
||||
$task = $this->getTask();
|
||||
$file = $this->file->getById($this->request->getIntegerParam('file_id'));
|
||||
|
||||
|
|
|
|||
|
|
@ -12,19 +12,6 @@ use Model\Task as TaskModel;
|
|||
*/
|
||||
class Project extends Base
|
||||
{
|
||||
/**
|
||||
* Display access forbidden page
|
||||
*
|
||||
* @access public
|
||||
*/
|
||||
public function forbidden()
|
||||
{
|
||||
$this->response->html($this->template->layout('project_forbidden', array(
|
||||
'menu' => 'projects',
|
||||
'title' => t('Access Forbidden')
|
||||
)));
|
||||
}
|
||||
|
||||
/**
|
||||
* Task search for a given project
|
||||
*
|
||||
|
|
@ -254,6 +241,7 @@ class Project extends Base
|
|||
*/
|
||||
public function remove()
|
||||
{
|
||||
$this->checkCSRFParam();
|
||||
$project_id = $this->request->getIntegerParam('project_id');
|
||||
|
||||
if ($project_id && $this->project->remove($project_id)) {
|
||||
|
|
@ -272,6 +260,7 @@ class Project extends Base
|
|||
*/
|
||||
public function enable()
|
||||
{
|
||||
$this->checkCSRFParam();
|
||||
$project_id = $this->request->getIntegerParam('project_id');
|
||||
|
||||
if ($project_id && $this->project->enable($project_id)) {
|
||||
|
|
@ -290,6 +279,7 @@ class Project extends Base
|
|||
*/
|
||||
public function disable()
|
||||
{
|
||||
$this->checkCSRFParam();
|
||||
$project_id = $this->request->getIntegerParam('project_id');
|
||||
|
||||
if ($project_id && $this->project->disable($project_id)) {
|
||||
|
|
@ -353,6 +343,8 @@ class Project extends Base
|
|||
*/
|
||||
public function revoke()
|
||||
{
|
||||
$this->checkCSRFParam();
|
||||
|
||||
$values = array(
|
||||
'project_id' => $this->request->getIntegerParam('project_id'),
|
||||
'user_id' => $this->request->getIntegerParam('user_id'),
|
||||
|
|
|
|||
|
|
@ -0,0 +1,186 @@
|
|||
<?php
|
||||
|
||||
namespace Controller;
|
||||
|
||||
/**
|
||||
* SubTask controller
|
||||
*
|
||||
* @package controller
|
||||
* @author Frederic Guillot
|
||||
*/
|
||||
class Subtask extends Base
|
||||
{
|
||||
/**
|
||||
* Get the current subtask
|
||||
*
|
||||
* @access private
|
||||
* @return array
|
||||
*/
|
||||
private function getSubtask()
|
||||
{
|
||||
$subtask = $this->subTask->getById($this->request->getIntegerParam('subtask_id'));
|
||||
|
||||
if (! $subtask) {
|
||||
$this->notfound();
|
||||
}
|
||||
|
||||
return $subtask;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creation form
|
||||
*
|
||||
* @access public
|
||||
*/
|
||||
public function create()
|
||||
{
|
||||
$task = $this->getTask();
|
||||
|
||||
$this->response->html($this->taskLayout('subtask_create', array(
|
||||
'values' => array(
|
||||
'task_id' => $task['id'],
|
||||
),
|
||||
'errors' => array(),
|
||||
'users_list' => $this->project->getUsersList($task['project_id']),
|
||||
'task' => $task,
|
||||
'menu' => 'tasks',
|
||||
'title' => t('Add a sub-task')
|
||||
)));
|
||||
}
|
||||
|
||||
/**
|
||||
* Validation and creation
|
||||
*
|
||||
* @access public
|
||||
*/
|
||||
public function save()
|
||||
{
|
||||
$task = $this->getTask();
|
||||
$values = $this->request->getValues();
|
||||
|
||||
list($valid, $errors) = $this->subTask->validate($values);
|
||||
|
||||
if ($valid) {
|
||||
|
||||
if ($this->subTask->create($values)) {
|
||||
$this->session->flash(t('Sub-task added successfully.'));
|
||||
}
|
||||
else {
|
||||
$this->session->flashError(t('Unable to create your sub-task.'));
|
||||
}
|
||||
|
||||
if (isset($values['another_subtask']) && $values['another_subtask'] == 1) {
|
||||
$this->response->redirect('?controller=subtask&action=create&task_id='.$task['id']);
|
||||
}
|
||||
|
||||
$this->response->redirect('?controller=task&action=show&task_id='.$task['id'].'#subtasks');
|
||||
}
|
||||
|
||||
$this->response->html($this->taskLayout('subtask_create', array(
|
||||
'values' => $values,
|
||||
'errors' => $errors,
|
||||
'users_list' => $this->project->getUsersList($task['project_id']),
|
||||
'task' => $task,
|
||||
'menu' => 'tasks',
|
||||
'title' => t('Add a sub-task')
|
||||
)));
|
||||
}
|
||||
|
||||
/**
|
||||
* Edit form
|
||||
*
|
||||
* @access public
|
||||
*/
|
||||
public function edit()
|
||||
{
|
||||
$task = $this->getTask();
|
||||
$subtask = $this->getSubTask();
|
||||
|
||||
$this->response->html($this->taskLayout('subtask_edit', array(
|
||||
'values' => $subtask,
|
||||
'errors' => array(),
|
||||
'users_list' => $this->project->getUsersList($task['project_id']),
|
||||
'status_list' => $this->subTask->getStatusList(),
|
||||
'subtask' => $subtask,
|
||||
'task' => $task,
|
||||
'menu' => 'tasks',
|
||||
'title' => t('Edit a sub-task')
|
||||
)));
|
||||
}
|
||||
|
||||
/**
|
||||
* Update and validate a subtask
|
||||
*
|
||||
* @access public
|
||||
*/
|
||||
public function update()
|
||||
{
|
||||
$task = $this->getTask();
|
||||
$subtask = $this->getSubtask();
|
||||
|
||||
$values = $this->request->getValues();
|
||||
list($valid, $errors) = $this->subTask->validate($values);
|
||||
|
||||
if ($valid) {
|
||||
|
||||
if ($this->subTask->update($values)) {
|
||||
$this->session->flash(t('Sub-task updated successfully.'));
|
||||
}
|
||||
else {
|
||||
$this->session->flashError(t('Unable to update your sub-task.'));
|
||||
}
|
||||
|
||||
$this->response->redirect('?controller=task&action=show&task_id='.$task['id'].'#subtasks');
|
||||
}
|
||||
|
||||
$this->response->html($this->taskLayout('subtask_edit', array(
|
||||
'values' => $values,
|
||||
'errors' => $errors,
|
||||
'users_list' => $this->project->getUsersList($task['project_id']),
|
||||
'status_list' => $this->subTask->getStatusList(),
|
||||
'subtask' => $subtask,
|
||||
'task' => $task,
|
||||
'menu' => 'tasks',
|
||||
'title' => t('Edit a sub-task')
|
||||
)));
|
||||
}
|
||||
|
||||
/**
|
||||
* Confirmation dialog before removing a subtask
|
||||
*
|
||||
* @access public
|
||||
*/
|
||||
public function confirm()
|
||||
{
|
||||
$task = $this->getTask();
|
||||
$subtask = $this->getSubtask();
|
||||
|
||||
$this->response->html($this->taskLayout('subtask_remove', array(
|
||||
'subtask' => $subtask,
|
||||
'task' => $task,
|
||||
'menu' => 'tasks',
|
||||
'title' => t('Remove a sub-task')
|
||||
)));
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove a subtask
|
||||
*
|
||||
* @access public
|
||||
*/
|
||||
public function remove()
|
||||
{
|
||||
$this->checkCSRFParam();
|
||||
$task = $this->getTask();
|
||||
$subtask = $this->getSubtask();
|
||||
|
||||
if ($this->subTask->remove($subtask['id'])) {
|
||||
$this->session->flash(t('Sub-task removed successfully.'));
|
||||
}
|
||||
else {
|
||||
$this->session->flashError(t('Unable to remove this sub-task.'));
|
||||
}
|
||||
|
||||
$this->response->redirect('?controller=task&action=show&task_id='.$task['id'].'#subtasks');
|
||||
}
|
||||
}
|
||||
|
|
@ -62,6 +62,7 @@ class Task extends Base
|
|||
$this->response->html($this->taskLayout('task_show', array(
|
||||
'files' => $this->file->getAll($task['id']),
|
||||
'comments' => $this->comment->getAll($task['id']),
|
||||
'subtasks' => $this->subTask->getAll($task['id']),
|
||||
'task' => $task,
|
||||
'columns_list' => $this->board->getColumnsList($task['project_id']),
|
||||
'colors_list' => $this->task->getColors(),
|
||||
|
|
@ -217,6 +218,7 @@ class Task extends Base
|
|||
*/
|
||||
public function close()
|
||||
{
|
||||
$this->checkCSRFParam();
|
||||
$task = $this->getTask();
|
||||
|
||||
if ($this->task->close($task['id'])) {
|
||||
|
|
@ -251,6 +253,7 @@ class Task extends Base
|
|||
*/
|
||||
public function open()
|
||||
{
|
||||
$this->checkCSRFParam();
|
||||
$task = $this->getTask();
|
||||
|
||||
if ($this->task->open($task['id'])) {
|
||||
|
|
@ -285,6 +288,7 @@ class Task extends Base
|
|||
*/
|
||||
public function remove()
|
||||
{
|
||||
$this->checkCSRFParam();
|
||||
$task = $this->getTask();
|
||||
|
||||
if ($this->task->remove($task['id'])) {
|
||||
|
|
|
|||
|
|
@ -10,19 +10,6 @@ namespace Controller;
|
|||
*/
|
||||
class User extends Base
|
||||
{
|
||||
/**
|
||||
* Display access forbidden page
|
||||
*
|
||||
* @access public
|
||||
*/
|
||||
public function forbidden()
|
||||
{
|
||||
$this->response->html($this->template->layout('user_forbidden', array(
|
||||
'menu' => 'users',
|
||||
'title' => t('Access Forbidden')
|
||||
)));
|
||||
}
|
||||
|
||||
/**
|
||||
* Logout and destroy session
|
||||
*
|
||||
|
|
@ -30,6 +17,7 @@ class User extends Base
|
|||
*/
|
||||
public function logout()
|
||||
{
|
||||
$this->checkCSRFParam();
|
||||
$this->rememberMe->destroy($this->acl->getUserId());
|
||||
$this->session->close();
|
||||
$this->response->redirect('?controller=user&action=login');
|
||||
|
|
@ -42,7 +30,9 @@ class User extends Base
|
|||
*/
|
||||
public function login()
|
||||
{
|
||||
if (isset($_SESSION['user'])) $this->response->redirect('?controller=app');
|
||||
if (isset($_SESSION['user'])) {
|
||||
$this->response->redirect('?controller=app');
|
||||
}
|
||||
|
||||
$this->response->html($this->template->layout('user_login', array(
|
||||
'errors' => array(),
|
||||
|
|
@ -236,6 +226,7 @@ class User extends Base
|
|||
*/
|
||||
public function remove()
|
||||
{
|
||||
$this->checkCSRFParam();
|
||||
$user_id = $this->request->getIntegerParam('user_id');
|
||||
|
||||
if ($user_id && $this->user->remove($user_id)) {
|
||||
|
|
@ -298,6 +289,7 @@ class User extends Base
|
|||
*/
|
||||
public function unlinkGoogle()
|
||||
{
|
||||
$this->checkCSRFParam();
|
||||
if ($this->google->unlink($this->acl->getUserId())) {
|
||||
$this->session->flash(t('Your Google Account is not linked anymore to your profile.'));
|
||||
}
|
||||
|
|
|
|||
|
|
@ -67,13 +67,16 @@ class Event
|
|||
*/
|
||||
public function trigger($eventName, array $data)
|
||||
{
|
||||
$this->lastEvent = $eventName;
|
||||
$this->events[] = $eventName;
|
||||
if (! $this->isEventTriggered($eventName)) {
|
||||
|
||||
if (isset($this->listeners[$eventName])) {
|
||||
foreach ($this->listeners[$eventName] as $listener) {
|
||||
if ($listener->execute($data)) {
|
||||
$this->lastListener = get_class($listener);
|
||||
$this->lastEvent = $eventName;
|
||||
$this->events[] = $eventName;
|
||||
|
||||
if (isset($this->listeners[$eventName])) {
|
||||
foreach ($this->listeners[$eventName] as $listener) {
|
||||
if ($listener->execute($data)) {
|
||||
$this->lastListener = get_class($listener);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -112,6 +115,29 @@ class Event
|
|||
return $this->events;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if an event have been triggered
|
||||
*
|
||||
* @access public
|
||||
* @param string $eventName Event name
|
||||
* @return bool
|
||||
*/
|
||||
public function isEventTriggered($eventName)
|
||||
{
|
||||
return in_array($eventName, $this->events);
|
||||
}
|
||||
|
||||
/**
|
||||
* Flush the list of triggered events
|
||||
*
|
||||
* @access public
|
||||
*/
|
||||
public function clearTriggeredEvents()
|
||||
{
|
||||
$this->events = array();
|
||||
$this->lastEvent = '';
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if a listener bind to an event
|
||||
*
|
||||
|
|
|
|||
|
|
@ -2,6 +2,8 @@
|
|||
|
||||
namespace Core;
|
||||
|
||||
use Core\Security;
|
||||
|
||||
/**
|
||||
* Request class
|
||||
*
|
||||
|
|
@ -58,7 +60,12 @@ class Request
|
|||
public function getValues()
|
||||
{
|
||||
if (! empty($_POST)) {
|
||||
return $_POST;
|
||||
|
||||
if (Security::validateCSRFFormToken($_POST)) {
|
||||
return $_POST;
|
||||
}
|
||||
|
||||
return array();
|
||||
}
|
||||
|
||||
$result = json_decode($this->getBody(), true);
|
||||
|
|
@ -116,6 +123,19 @@ class Request
|
|||
*/
|
||||
public function isAjax()
|
||||
{
|
||||
return isset($_SERVER['HTTP_X_REQUESTED_WITH']) && $_SERVER['HTTP_X_REQUESTED_WITH'] === 'XMLHttpRequest';
|
||||
return $this->getHeader('X-Requested-With') === 'XMLHttpRequest';
|
||||
}
|
||||
|
||||
/**
|
||||
* Return a HTTP header value
|
||||
*
|
||||
* @access public
|
||||
* @param string $name Header name
|
||||
* @return string
|
||||
*/
|
||||
public function getHeader($name)
|
||||
{
|
||||
$name = 'HTTP_'.str_replace('-', '_', strtoupper($name));
|
||||
return isset($_SERVER[$name]) ? $_SERVER[$name] : '';
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -10,6 +10,20 @@ namespace Core;
|
|||
*/
|
||||
class Response
|
||||
{
|
||||
/**
|
||||
* Send no cache headers
|
||||
*
|
||||
* @access public
|
||||
*/
|
||||
public function nocache()
|
||||
{
|
||||
header('Pragma: no-cache');
|
||||
header('Expires: Sat, 26 Jul 1997 05:00:00 GMT');
|
||||
|
||||
// Use no-store due to a Chrome bug: https://code.google.com/p/chromium/issues/detail?id=28035
|
||||
header('Cache-Control: no-store, must-revalidate');
|
||||
}
|
||||
|
||||
/**
|
||||
* Send a custom Content-Type header
|
||||
*
|
||||
|
|
@ -66,7 +80,7 @@ class Response
|
|||
public function json(array $data, $status_code = 200)
|
||||
{
|
||||
$this->status($status_code);
|
||||
|
||||
$this->nocache();
|
||||
header('Content-Type: application/json');
|
||||
echo json_encode($data);
|
||||
|
||||
|
|
@ -83,7 +97,7 @@ class Response
|
|||
public function text($data, $status_code = 200)
|
||||
{
|
||||
$this->status($status_code);
|
||||
|
||||
$this->nocache();
|
||||
header('Content-Type: text/plain; charset=utf-8');
|
||||
echo $data;
|
||||
|
||||
|
|
@ -100,7 +114,7 @@ class Response
|
|||
public function html($data, $status_code = 200)
|
||||
{
|
||||
$this->status($status_code);
|
||||
|
||||
$this->nocache();
|
||||
header('Content-Type: text/html; charset=utf-8');
|
||||
echo $data;
|
||||
|
||||
|
|
@ -117,7 +131,7 @@ class Response
|
|||
public function xml($data, $status_code = 200)
|
||||
{
|
||||
$this->status($status_code);
|
||||
|
||||
$this->nocache();
|
||||
header('Content-Type: text/xml; charset=utf-8');
|
||||
echo $data;
|
||||
|
||||
|
|
@ -151,7 +165,7 @@ class Response
|
|||
public function binary($data, $status_code = 200)
|
||||
{
|
||||
$this->status($status_code);
|
||||
|
||||
$this->nocache();
|
||||
header('Content-Transfer-Encoding: binary');
|
||||
header('Content-Type: application/octet-stream');
|
||||
echo $data;
|
||||
|
|
|
|||
|
|
@ -0,0 +1,87 @@
|
|||
<?php
|
||||
|
||||
namespace Core;
|
||||
|
||||
/**
|
||||
* Security class
|
||||
*
|
||||
* @package core
|
||||
* @author Frederic Guillot
|
||||
*/
|
||||
class Security
|
||||
{
|
||||
/**
|
||||
* Generate a random token with different methods: openssl or /dev/urandom or fallback to uniqid()
|
||||
*
|
||||
* @static
|
||||
* @access public
|
||||
* @return string Random token
|
||||
*/
|
||||
public static function generateToken()
|
||||
{
|
||||
if (function_exists('openssl_random_pseudo_bytes')) {
|
||||
return bin2hex(\openssl_random_pseudo_bytes(30));
|
||||
}
|
||||
else if (ini_get('open_basedir') === '' && strtoupper(substr(PHP_OS, 0, 3)) !== 'WIN') {
|
||||
return hash('sha256', file_get_contents('/dev/urandom', false, null, 0, 30));
|
||||
}
|
||||
|
||||
return hash('sha256', uniqid(mt_rand(), true));
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate and store a CSRF token in the current session
|
||||
*
|
||||
* @static
|
||||
* @access public
|
||||
* @return string Random token
|
||||
*/
|
||||
public static function getCSRFToken()
|
||||
{
|
||||
$nonce = self::generateToken();
|
||||
|
||||
if (empty($_SESSION['csrf_tokens'])) {
|
||||
$_SESSION['csrf_tokens'] = array();
|
||||
}
|
||||
|
||||
$_SESSION['csrf_tokens'][$nonce] = true;
|
||||
|
||||
return $nonce;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if the token exists for the current session (a token can be used only one time)
|
||||
*
|
||||
* @static
|
||||
* @access public
|
||||
* @param string $token CSRF token
|
||||
* @return bool
|
||||
*/
|
||||
public static function validateCSRFToken($token)
|
||||
{
|
||||
if (isset($_SESSION['csrf_tokens'][$token])) {
|
||||
unset($_SESSION['csrf_tokens'][$token]);
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if the token used in a form is correct and then remove the value
|
||||
*
|
||||
* @static
|
||||
* @access public
|
||||
* @param array $values Form values
|
||||
* @return bool
|
||||
*/
|
||||
public static function validateCSRFFormToken(array &$values)
|
||||
{
|
||||
if (! empty($values['csrf_token']) && self::validateCSRFToken($values['csrf_token'])) {
|
||||
unset($values['csrf_token']);
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
|
@ -15,7 +15,7 @@ class Session
|
|||
*
|
||||
* @var integer
|
||||
*/
|
||||
const SESSION_LIFETIME = 86400; // 1 day
|
||||
const SESSION_LIFETIME = 7200; // 2 hours
|
||||
|
||||
/**
|
||||
* Open a session
|
||||
|
|
@ -35,7 +35,7 @@ class Session
|
|||
self::SESSION_LIFETIME,
|
||||
$base_path ?: '/',
|
||||
null,
|
||||
isset($_SERVER['HTTPS']) && $_SERVER['HTTPS'] === 'on',
|
||||
! empty($_SERVER['HTTPS']),
|
||||
true
|
||||
);
|
||||
|
||||
|
|
@ -66,6 +66,25 @@ class Session
|
|||
*/
|
||||
public function close()
|
||||
{
|
||||
// Flush all sessions variables
|
||||
$_SESSION = array();
|
||||
|
||||
// Destroy the session cookie
|
||||
if (ini_get('session.use_cookies')) {
|
||||
$params = session_get_cookie_params();
|
||||
|
||||
setcookie(
|
||||
session_name(),
|
||||
'',
|
||||
time() - 42000,
|
||||
$params['path'],
|
||||
$params['domain'],
|
||||
$params['secure'],
|
||||
$params['httponly']
|
||||
);
|
||||
}
|
||||
|
||||
// Destroy session data
|
||||
session_destroy();
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -114,7 +114,15 @@ class Translator
|
|||
return '';
|
||||
}
|
||||
|
||||
return strftime($this->get($format, $format), (int) $timestamp);
|
||||
$format = $this->get($format, $format);
|
||||
|
||||
if (strtoupper(substr(PHP_OS, 0, 3)) === 'WIN') {
|
||||
$format = str_replace('%e', '%d', $format);
|
||||
$format = str_replace('%G', '%Y', $format);
|
||||
$format = str_replace('%k', '%H', $format);
|
||||
}
|
||||
|
||||
return strftime($format, (int) $timestamp);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
|||
|
|
@ -126,7 +126,7 @@ return array(
|
|||
'The username must be unique' => 'Der Benutzername muss eindeutig sein',
|
||||
'The username must be alphanumeric' => 'Der Benutzername muss alphanumerisch sein',
|
||||
'The user id is required' => 'Die Benutzer ID wird benötigt',
|
||||
'Passwords doesn\'t matches' => 'Passwörter passen nicht zusammen',
|
||||
'Passwords don\'t match' => 'Passwörter passen nicht zusammen',
|
||||
'The confirmation is required' => 'Die Bestätigung wird benötigt',
|
||||
'The column is required' => 'Die Spalte wird benötigt',
|
||||
'The project is required' => 'Das Projekt wird benötigt',
|
||||
|
|
@ -312,7 +312,8 @@ 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' => '',
|
||||
// 'Assign automatically a color based on a category' => '',
|
||||
// 'Assign automatically a category based on a color' => '',
|
||||
// 'Task creation or modification' => '',
|
||||
// 'Category' => '',
|
||||
// 'Category:' => '',
|
||||
|
|
@ -346,4 +347,31 @@ return array(
|
|||
// 'Add a comment' => '',
|
||||
// 'Edit a comment' => '',
|
||||
// 'Summary' => '',
|
||||
// 'Time tracking' => '',
|
||||
// 'Estimate:' => '',
|
||||
// 'Spent:' => '',
|
||||
// 'Do you really want to remove this sub-task?' => '',
|
||||
// 'Remaining:' => '',
|
||||
// 'hours' => '',
|
||||
// 'spent' => '',
|
||||
// 'estimated' => '',
|
||||
// 'Sub-Tasks' => '',
|
||||
// 'Add a sub-task' => '',
|
||||
// 'Original Estimate' => '',
|
||||
// 'Create another sub-task' => '',
|
||||
// 'Time Spent' => '',
|
||||
// 'Edit a sub-task' => '',
|
||||
// 'Remove a sub-task' => '',
|
||||
// 'The time must be a numeric value' => '',
|
||||
// 'Todo' => '',
|
||||
// 'In progress' => '',
|
||||
// 'Done' => '',
|
||||
// 'Sub-task removed successfully.' => '',
|
||||
// 'Unable to remove this sub-task.' => '',
|
||||
// 'Sub-task updated successfully.' => '',
|
||||
// 'Unable to update your sub-task.' => '',
|
||||
// 'Unable to create your sub-task.' => '',
|
||||
// 'Sub-task added successfully.' => '',
|
||||
// 'Maximum size: ' => '',
|
||||
// 'Unable to upload the file.' => '',
|
||||
);
|
||||
|
|
|
|||
|
|
@ -125,7 +125,7 @@ return array(
|
|||
'The username must be unique' => 'El nombre de usuario debe ser único',
|
||||
'The username must be alphanumeric' => 'El nombre de usuario debe ser alfanumérico',
|
||||
'The user id is required' => 'El identificador del usuario es obligatorio',
|
||||
'Passwords doesn\'t matches' => 'Las contraseñas no corresponden',
|
||||
'Passwords don\'t match' => 'Las contraseñas no corresponden',
|
||||
'The confirmation is required' => 'La confirmación es obligatoria',
|
||||
'The column is required' => 'La columna es obligatoria',
|
||||
'The project is required' => 'El proyecto es obligatorio',
|
||||
|
|
@ -310,7 +310,8 @@ 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' => '',
|
||||
// 'Assign automatically a color based on a category' => '',
|
||||
// 'Assign automatically a category based on a color' => '',
|
||||
// 'Task creation or modification' => '',
|
||||
// 'Category' => '',
|
||||
// 'Category:' => '',
|
||||
|
|
@ -344,4 +345,31 @@ return array(
|
|||
// 'Add a comment' => '',
|
||||
// 'Edit a comment' => '',
|
||||
// 'Summary' => '',
|
||||
// 'Time tracking' => '',
|
||||
// 'Estimate:' => '',
|
||||
// 'Spent:' => '',
|
||||
// 'Do you really want to remove this sub-task?' => '',
|
||||
// 'Remaining:' => '',
|
||||
// 'hours' => '',
|
||||
// 'spent' => '',
|
||||
// 'estimated' => '',
|
||||
// 'Sub-Tasks' => '',
|
||||
// 'Add a sub-task' => '',
|
||||
// 'Original Estimate' => '',
|
||||
// 'Create another sub-task' => '',
|
||||
// 'Time Spent' => '',
|
||||
// 'Edit a sub-task' => '',
|
||||
// 'Remove a sub-task' => '',
|
||||
// 'The time must be a numeric value' => '',
|
||||
// 'Todo' => '',
|
||||
// 'In progress' => '',
|
||||
// 'Done' => '',
|
||||
// 'Sub-task removed successfully.' => '',
|
||||
// 'Unable to remove this sub-task.' => '',
|
||||
// 'Sub-task updated successfully.' => '',
|
||||
// 'Unable to update your sub-task.' => '',
|
||||
// 'Unable to create your sub-task.' => '',
|
||||
// 'Sub-task added successfully.' => '',
|
||||
// 'Maximum size: ' => '',
|
||||
// 'Unable to upload the file.' => '',
|
||||
);
|
||||
|
|
|
|||
|
|
@ -125,7 +125,7 @@ return array(
|
|||
'The username must be unique' => 'Le nom d\'utilisateur doit être unique',
|
||||
'The username must be alphanumeric' => 'Le nom d\'utilisateur doit être alpha-numérique',
|
||||
'The user id is required' => 'L\'id de l\'utilisateur est obligatoire',
|
||||
'Passwords doesn\'t matches' => 'Les mots de passe ne correspondent pas',
|
||||
'Passwords don\'t match' => 'Les mots de passe ne correspondent pas',
|
||||
'The confirmation is required' => 'Le confirmation est requise',
|
||||
'The column is required' => 'La colonne est obligatoire',
|
||||
'The project is required' => 'Le projet est obligatoire',
|
||||
|
|
@ -310,7 +310,8 @@ 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',
|
||||
'Assign automatically a color based on a category' => 'Assigner automatiquement une couleur par rapport à une catégorie définie',
|
||||
'Assign automatically a category based on a color' => 'Assigner automatiquement une catégorie par rapport à une couleur définie',
|
||||
'Task creation or modification' => 'Création ou modification d\'une tâche',
|
||||
'Category' => 'Catégorie',
|
||||
'Category:' => 'Catégorie :',
|
||||
|
|
@ -344,4 +345,31 @@ return array(
|
|||
'Add a comment' => 'Ajouter un commentaire',
|
||||
'Edit a comment' => 'Modifier un commentaire',
|
||||
'Summary' => 'Résumé',
|
||||
'Time tracking' => 'Gestion du temps',
|
||||
'Estimate:' => 'Estimation :',
|
||||
'Spent:' => 'Passé :',
|
||||
'Do you really want to remove this sub-task?' => 'Voulez-vous vraiment supprimer cette sous-tâche ?',
|
||||
'Remaining:' => 'Restant :',
|
||||
'hours' => 'heures',
|
||||
'spent' => 'passé',
|
||||
'estimated' => 'estimé',
|
||||
'Sub-Tasks' => 'Sous-Tâches',
|
||||
'Add a sub-task' => 'Ajouter une sous-tâche',
|
||||
'Original Estimate' => 'Estimation originale',
|
||||
'Create another sub-task' => 'Créer une autre sous-tâche',
|
||||
'Time Spent' => 'Temps passé',
|
||||
'Edit a sub-task' => 'Modifier une sous-tâche',
|
||||
'Remove a sub-task' => 'Supprimer une sous-tâche',
|
||||
'The time must be a numeric value' => 'Le temps doit-être une valeur numérique',
|
||||
'Todo' => 'À faire',
|
||||
'In progress' => 'En cours',
|
||||
'Done' => 'Terminé',
|
||||
'Sub-task removed successfully.' => 'Sous-tâche supprimée avec succès.',
|
||||
'Unable to remove this sub-task.' => 'Impossible de supprimer cette sous-tâche.',
|
||||
'Sub-task updated successfully.' => 'Sous-tâche mise à jour avec succès.',
|
||||
'Unable to update your sub-task.' => 'Impossible de mettre à jour votre sous-tâche.',
|
||||
'Unable to create your sub-task.' => 'Impossible de créer votre sous-tâche.',
|
||||
'Sub-task added successfully.' => 'Sous-tâche ajouté avec succès.',
|
||||
'Maximum size: ' => 'Taille maximum : ',
|
||||
'Unable to upload the file.' => 'Impossible de transférer le fichier.',
|
||||
);
|
||||
|
|
|
|||
|
|
@ -125,7 +125,7 @@ return array(
|
|||
'The username must be unique' => 'Nazwa użytkownika musi być unikalna',
|
||||
'The username must be alphanumeric' => 'Nazwa użytkownika musi być alfanumeryczna',
|
||||
'The user id is required' => 'ID użytkownika jest wymagane',
|
||||
'Passwords doesn\'t matches' => 'Hasła nie pasują do siebie',
|
||||
'Passwords don\'t match' => 'Hasła nie pasują do siebie',
|
||||
'The confirmation is required' => 'Wymagane jest potwierdzenie',
|
||||
'The column is required' => 'Kolumna jest wymagana',
|
||||
'The project is required' => 'Projekt jest wymagany',
|
||||
|
|
@ -315,7 +315,8 @@ 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' => '',
|
||||
// 'Assign automatically a color based on a category' => '',
|
||||
// 'Assign automatically a category based on a color' => '',
|
||||
// 'Task creation or modification' => '',
|
||||
// 'Category' => '',
|
||||
// 'Category:' => '',
|
||||
|
|
@ -349,4 +350,31 @@ return array(
|
|||
// 'Add a comment' => '',
|
||||
// 'Edit a comment' => '',
|
||||
// 'Summary' => '',
|
||||
// 'Time tracking' => '',
|
||||
// 'Estimate:' => '',
|
||||
// 'Spent:' => '',
|
||||
// 'Do you really want to remove this sub-task?' => '',
|
||||
// 'Remaining:' => '',
|
||||
// 'hours' => '',
|
||||
// 'spent' => '',
|
||||
// 'estimated' => '',
|
||||
// 'Sub-Tasks' => '',
|
||||
// 'Add a sub-task' => '',
|
||||
// 'Original Estimate' => '',
|
||||
// 'Create another sub-task' => '',
|
||||
// 'Time Spent' => '',
|
||||
// 'Edit a sub-task' => '',
|
||||
// 'Remove a sub-task' => '',
|
||||
// 'The time must be a numeric value' => '',
|
||||
// 'Todo' => '',
|
||||
// 'In progress' => '',
|
||||
// 'Done' => '',
|
||||
// 'Sub-task removed successfully.' => '',
|
||||
// 'Unable to remove this sub-task.' => '',
|
||||
// 'Sub-task updated successfully.' => '',
|
||||
// 'Unable to update your sub-task.' => '',
|
||||
// 'Unable to create your sub-task.' => '',
|
||||
// 'Sub-task added successfully.' => '',
|
||||
// 'Maximum size: ' => '',
|
||||
// 'Unable to upload the file.' => '',
|
||||
);
|
||||
|
|
|
|||
|
|
@ -125,7 +125,7 @@ return array(
|
|||
'The username must be unique' => 'O nome de usuário deve ser único',
|
||||
'The username must be alphanumeric' => 'O nome de usuário deve ser alfanumérico, sem espaços ou _',
|
||||
'The user id is required' => 'O id de usuário é obrigatório',
|
||||
'Passwords doesn\'t matches' => 'As senhas não conferem',
|
||||
'Passwords don\'t match' => 'As senhas não conferem',
|
||||
'The confirmation is required' => 'A confirmação é obrigatória',
|
||||
'The column is required' => 'A coluna é obrigatória',
|
||||
'The project is required' => 'O projeto é obrigatório',
|
||||
|
|
@ -311,7 +311,8 @@ 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' => '',
|
||||
// 'Assign automatically a color based on a category' => '',
|
||||
// 'Assign automatically a category based on a color' => '',
|
||||
// 'Task creation or modification' => '',
|
||||
// 'Category' => '',
|
||||
// 'Category:' => '',
|
||||
|
|
@ -345,4 +346,31 @@ return array(
|
|||
// 'Add a comment' => '',
|
||||
// 'Edit a comment' => '',
|
||||
// 'Summary' => '',
|
||||
// 'Time tracking' => '',
|
||||
// 'Estimate:' => '',
|
||||
// 'Spent:' => '',
|
||||
// 'Do you really want to remove this sub-task?' => '',
|
||||
// 'Remaining:' => '',
|
||||
// 'hours' => '',
|
||||
// 'spent' => '',
|
||||
// 'estimated' => '',
|
||||
// 'Sub-Tasks' => '',
|
||||
// 'Add a sub-task' => '',
|
||||
// 'Original Estimate' => '',
|
||||
// 'Create another sub-task' => '',
|
||||
// 'Time Spent' => '',
|
||||
// 'Edit a sub-task' => '',
|
||||
// 'Remove a sub-task' => '',
|
||||
// 'The time must be a numeric value' => '',
|
||||
// 'Todo' => '',
|
||||
// 'In progress' => '',
|
||||
// 'Done' => '',
|
||||
// 'Sub-task removed successfully.' => '',
|
||||
// 'Unable to remove this sub-task.' => '',
|
||||
// 'Sub-task updated successfully.' => '',
|
||||
// 'Unable to update your sub-task.' => '',
|
||||
// 'Unable to create your sub-task.' => '',
|
||||
// 'Sub-task added successfully.' => '',
|
||||
// 'Maximum size: ' => '',
|
||||
// 'Unable to upload the file.' => '',
|
||||
);
|
||||
|
|
|
|||
|
|
@ -36,6 +36,7 @@ class Acl extends Base
|
|||
'config' => array('index', 'removeremembermetoken'),
|
||||
'comment' => array('create', 'save', 'confirm', 'remove', 'update', 'edit', 'forbidden'),
|
||||
'file' => array('create', 'save', 'download', 'confirm', 'remove', 'open', 'image'),
|
||||
'subtask' => array('create', 'save', 'edit', 'update', 'confirm', 'remove'),
|
||||
'task' => array(
|
||||
'show',
|
||||
'create',
|
||||
|
|
|
|||
|
|
@ -42,7 +42,8 @@ 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'),
|
||||
'TaskAssignColorCategory' => t('Assign automatically a color based on a category'),
|
||||
'TaskAssignCategoryColor' => t('Assign automatically a category based on a color'),
|
||||
);
|
||||
}
|
||||
|
||||
|
|
@ -237,6 +238,9 @@ class Action extends Base
|
|||
case 'TaskAssignColorCategory':
|
||||
$className = '\Action\TaskAssignColorCategory';
|
||||
return new $className($project_id, new Task($this->db, $this->event));
|
||||
case 'TaskAssignCategoryColor':
|
||||
$className = '\Action\TaskAssignCategoryColor';
|
||||
return new $className($project_id, new Task($this->db, $this->event));
|
||||
default:
|
||||
throw new LogicException('Action not found: '.$name);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -14,6 +14,7 @@ require __DIR__.'/../../vendor/SimpleValidator/Validators/AlphaNumeric.php';
|
|||
require __DIR__.'/../../vendor/SimpleValidator/Validators/GreaterThan.php';
|
||||
require __DIR__.'/../../vendor/SimpleValidator/Validators/Date.php';
|
||||
require __DIR__.'/../../vendor/SimpleValidator/Validators/Email.php';
|
||||
require __DIR__.'/../../vendor/SimpleValidator/Validators/Numeric.php';
|
||||
|
||||
use Core\Event;
|
||||
use PicoDb\Database;
|
||||
|
|
@ -54,23 +55,4 @@ abstract class Base
|
|||
$this->db = $db;
|
||||
$this->event = $event;
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate a random token with different methods: openssl or /dev/urandom or fallback to uniqid()
|
||||
*
|
||||
* @static
|
||||
* @access public
|
||||
* @return string Random token
|
||||
*/
|
||||
public static function generateToken()
|
||||
{
|
||||
if (function_exists('openssl_random_pseudo_bytes')) {
|
||||
return bin2hex(\openssl_random_pseudo_bytes(16));
|
||||
}
|
||||
else if (ini_get('open_basedir') === '' && strtoupper(substr(PHP_OS, 0, 3)) !== 'WIN') {
|
||||
return hash('sha256', file_get_contents('/dev/urandom', false, null, 0, 30));
|
||||
}
|
||||
|
||||
return hash('sha256', uniqid(mt_rand(), true));
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@ namespace Model;
|
|||
use SimpleValidator\Validator;
|
||||
use SimpleValidator\Validators;
|
||||
use Core\Translator;
|
||||
use Core\Security;
|
||||
|
||||
/**
|
||||
* Config model
|
||||
|
|
@ -29,7 +30,7 @@ class Config extends Base
|
|||
*/
|
||||
public function getTimezones()
|
||||
{
|
||||
$timezones = \timezone_identifiers_list();
|
||||
$timezones = timezone_identifiers_list();
|
||||
return array_combine(array_values($timezones), $timezones);
|
||||
}
|
||||
|
||||
|
|
@ -171,12 +172,12 @@ class Config extends Base
|
|||
*/
|
||||
public function regenerateTokens()
|
||||
{
|
||||
$this->db->table(self::TABLE)->update(array('webhooks_token' => $this->generateToken()));
|
||||
$this->db->table(self::TABLE)->update(array('webhooks_token' => Security::generateToken()));
|
||||
|
||||
$projects = $this->db->table(Project::TABLE)->findAllByColumn('id');
|
||||
|
||||
foreach ($projects as $project_id) {
|
||||
$this->db->table(Project::TABLE)->eq('id', $project_id)->update(array('token' => $this->generateToken()));
|
||||
$this->db->table(Project::TABLE)->eq('id', $project_id)->update(array('token' => Security::generateToken()));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -54,6 +54,22 @@ class File extends Base
|
|||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove all files for a given task
|
||||
*
|
||||
* @access public
|
||||
* @param integer $task_id Task id
|
||||
* @return bool
|
||||
*/
|
||||
public function removeAll($task_id)
|
||||
{
|
||||
$files = $this->getAll($task_id);
|
||||
|
||||
foreach ($files as $file) {
|
||||
$this->remove($file['id']);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a file entry in the database
|
||||
*
|
||||
|
|
@ -144,6 +160,7 @@ class File extends Base
|
|||
public function upload($project_id, $task_id, $form_name)
|
||||
{
|
||||
$this->setup();
|
||||
$result = array();
|
||||
|
||||
if (! empty($_FILES[$form_name])) {
|
||||
|
||||
|
|
@ -159,7 +176,7 @@ class File extends Base
|
|||
|
||||
if (@move_uploaded_file($uploaded_filename, self::BASE_PATH.$destination_filename)) {
|
||||
|
||||
$this->create(
|
||||
$result[] = $this->create(
|
||||
$task_id,
|
||||
$original_filename,
|
||||
$destination_filename,
|
||||
|
|
@ -169,5 +186,7 @@ class File extends Base
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
return count(array_unique($result)) === 1;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@ namespace Model;
|
|||
use SimpleValidator\Validator;
|
||||
use SimpleValidator\Validators;
|
||||
use Event\TaskModification;
|
||||
use Core\Security;
|
||||
|
||||
/**
|
||||
* Project model
|
||||
|
|
@ -363,7 +364,7 @@ class Project extends Base
|
|||
{
|
||||
$this->db->startTransaction();
|
||||
|
||||
$values['token'] = self::generateToken();
|
||||
$values['token'] = Security::generateToken();
|
||||
|
||||
if (! $this->db->table(self::TABLE)->save($values)) {
|
||||
$this->db->cancelTransaction();
|
||||
|
|
|
|||
|
|
@ -2,6 +2,8 @@
|
|||
|
||||
namespace Model;
|
||||
|
||||
use Core\Security;
|
||||
|
||||
/**
|
||||
* RememberMe model
|
||||
*
|
||||
|
|
@ -174,8 +176,8 @@ class RememberMe extends Base
|
|||
*/
|
||||
public function create($user_id, $ip, $user_agent)
|
||||
{
|
||||
$token = hash('sha256', $user_id.$user_agent.$ip.$this->generateToken());
|
||||
$sequence = $this->generateToken();
|
||||
$token = hash('sha256', $user_id.$user_agent.$ip.Security::generateToken());
|
||||
$sequence = Security::generateToken();
|
||||
$expiration = time() + self::EXPIRATION;
|
||||
|
||||
$this->cleanup($user_id);
|
||||
|
|
@ -225,7 +227,7 @@ class RememberMe extends Base
|
|||
*/
|
||||
public function update($token, $sequence)
|
||||
{
|
||||
$new_sequence = $this->generateToken();
|
||||
$new_sequence = Security::generateToken();
|
||||
|
||||
$this->db
|
||||
->table(self::TABLE)
|
||||
|
|
|
|||
|
|
@ -0,0 +1,179 @@
|
|||
<?php
|
||||
|
||||
namespace Model;
|
||||
|
||||
use SimpleValidator\Validator;
|
||||
use SimpleValidator\Validators;
|
||||
|
||||
/**
|
||||
* Subtask model
|
||||
*
|
||||
* @package model
|
||||
* @author Frederic Guillot
|
||||
*/
|
||||
class SubTask extends Base
|
||||
{
|
||||
/**
|
||||
* SQL table name
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
const TABLE = 'task_has_subtasks';
|
||||
|
||||
/**
|
||||
* Task "done" status
|
||||
*
|
||||
* @var integer
|
||||
*/
|
||||
const STATUS_DONE = 2;
|
||||
|
||||
/**
|
||||
* Task "in progress" status
|
||||
*
|
||||
* @var integer
|
||||
*/
|
||||
const STATUS_INPROGRESS = 1;
|
||||
|
||||
/**
|
||||
* Task "todo" status
|
||||
*
|
||||
* @var integer
|
||||
*/
|
||||
const STATUS_TODO = 0;
|
||||
|
||||
/**
|
||||
* Get available status
|
||||
*
|
||||
* @access public
|
||||
* @return array
|
||||
*/
|
||||
public function getStatusList()
|
||||
{
|
||||
$status = array(
|
||||
self::STATUS_TODO => t('Todo'),
|
||||
self::STATUS_INPROGRESS => t('In progress'),
|
||||
self::STATUS_DONE => t('Done'),
|
||||
);
|
||||
|
||||
asort($status);
|
||||
|
||||
return $status;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all subtasks for a given task
|
||||
*
|
||||
* @access public
|
||||
* @param integer $task_id Task id
|
||||
* @return array
|
||||
*/
|
||||
public function getAll($task_id)
|
||||
{
|
||||
$status = $this->getStatusList();
|
||||
$subtasks = $this->db->table(self::TABLE)
|
||||
->eq('task_id', $task_id)
|
||||
->columns(self::TABLE.'.*', User::TABLE.'.username')
|
||||
->join(User::TABLE, 'id', 'user_id')
|
||||
->findAll();
|
||||
|
||||
foreach ($subtasks as &$subtask) {
|
||||
$subtask['status_name'] = $status[$subtask['status']];
|
||||
}
|
||||
|
||||
return $subtasks;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a subtask by the id
|
||||
*
|
||||
* @access public
|
||||
* @param integer $subtask_id Subtask id
|
||||
* @return array
|
||||
*/
|
||||
public function getById($subtask_id)
|
||||
{
|
||||
return $this->db->table(self::TABLE)->eq('id', $subtask_id)->findOne();
|
||||
}
|
||||
|
||||
/**
|
||||
* Create
|
||||
*
|
||||
* @access public
|
||||
* @param array $values Form values
|
||||
* @return bool
|
||||
*/
|
||||
public function create(array $values)
|
||||
{
|
||||
if (isset($values['another_subtask'])) {
|
||||
unset($values['another_subtask']);
|
||||
}
|
||||
|
||||
if (isset($values['time_estimated']) && empty($values['time_estimated'])) {
|
||||
$values['time_estimated'] = 0;
|
||||
}
|
||||
|
||||
if (isset($values['time_spent']) && empty($values['time_spent'])) {
|
||||
$values['time_spent'] = 0;
|
||||
}
|
||||
|
||||
return $this->db->table(self::TABLE)->save($values);
|
||||
}
|
||||
|
||||
/**
|
||||
* Update
|
||||
*
|
||||
* @access public
|
||||
* @param array $values Form values
|
||||
* @return bool
|
||||
*/
|
||||
public function update(array $values)
|
||||
{
|
||||
if (isset($values['time_estimated']) && empty($values['time_estimated'])) {
|
||||
$values['time_estimated'] = 0;
|
||||
}
|
||||
|
||||
if (isset($values['time_spent']) && empty($values['time_spent'])) {
|
||||
$values['time_spent'] = 0;
|
||||
}
|
||||
|
||||
return $this->db->table(self::TABLE)->eq('id', $values['id'])->save($values);
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove
|
||||
*
|
||||
* @access public
|
||||
* @param integer $subtask_id Subtask id
|
||||
* @return bool
|
||||
*/
|
||||
public function remove($subtask_id)
|
||||
{
|
||||
return $this->db->table(self::TABLE)->eq('id', $subtask_id)->remove();
|
||||
}
|
||||
|
||||
/**
|
||||
* Validate creation/modification
|
||||
*
|
||||
* @access public
|
||||
* @param array $values Form values
|
||||
* @return array $valid, $errors [0] = Success or not, [1] = List of errors
|
||||
*/
|
||||
public function validate(array $values)
|
||||
{
|
||||
$v = new Validator($values, array(
|
||||
new Validators\Required('task_id', t('The task id is required')),
|
||||
new Validators\Integer('task_id', t('The task id must be an integer')),
|
||||
new Validators\Required('title', t('The title is required')),
|
||||
new Validators\MaxLength('title', t('The maximum length is %d characters', 100), 100),
|
||||
new Validators\Integer('user_id', t('The user id must be an integer')),
|
||||
new Validators\Integer('status', t('The status must be an integer')),
|
||||
new Validators\Numeric('time_estimated', t('The time must be a numeric value')),
|
||||
new Validators\Numeric('time_spent', t('The time must be a numeric value')),
|
||||
));
|
||||
|
||||
return array(
|
||||
$v->execute(),
|
||||
$v->getErrors()
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
@ -359,12 +359,10 @@ class Task extends Base
|
|||
// Trigger events
|
||||
if ($result) {
|
||||
|
||||
$events = array();
|
||||
|
||||
if (! in_array($this->event->getLastTriggeredEvent(), array(self::EVENT_CREATE_UPDATE))) {
|
||||
$events[] = self::EVENT_CREATE_UPDATE;
|
||||
$events[] = self::EVENT_UPDATE;
|
||||
}
|
||||
$events = array(
|
||||
self::EVENT_CREATE_UPDATE,
|
||||
self::EVENT_UPDATE,
|
||||
);
|
||||
|
||||
if (isset($values['column_id']) && $original_task['column_id'] != $values['column_id']) {
|
||||
$events[] = self::EVENT_MOVE_COLUMN;
|
||||
|
|
@ -441,6 +439,9 @@ class Task extends Base
|
|||
*/
|
||||
public function remove($task_id)
|
||||
{
|
||||
$file = new File($this->db, $this->event);
|
||||
$file->removeAll($task_id);
|
||||
|
||||
return $this->db->table(self::TABLE)->eq('id', $task_id)->remove();
|
||||
}
|
||||
|
||||
|
|
@ -455,6 +456,8 @@ class Task extends Base
|
|||
*/
|
||||
public function move($task_id, $column_id, $position)
|
||||
{
|
||||
$this->event->clearTriggeredEvents();
|
||||
|
||||
return $this->update(array(
|
||||
'id' => $task_id,
|
||||
'column_id' => $column_id,
|
||||
|
|
|
|||
|
|
@ -203,7 +203,7 @@ class User extends Base
|
|||
new Validators\Required('password', t('The password is required')),
|
||||
new Validators\MinLength('password', t('The minimum length is %d characters', 6), 6),
|
||||
new Validators\Required('confirmation', t('The confirmation is required')),
|
||||
new Validators\Equals('password', 'confirmation', t('Passwords doesn\'t matches')),
|
||||
new Validators\Equals('password', 'confirmation', t('Passwords don\'t match')),
|
||||
new Validators\Integer('default_project_id', t('This value must be an integer')),
|
||||
new Validators\Integer('is_admin', t('This value must be an integer')),
|
||||
new Validators\Email('email', t('Email address invalid')),
|
||||
|
|
@ -264,7 +264,7 @@ class User extends Base
|
|||
new Validators\Required('password', t('The password is required')),
|
||||
new Validators\MinLength('password', t('The minimum length is %d characters', 6), 6),
|
||||
new Validators\Required('confirmation', t('The confirmation is required')),
|
||||
new Validators\Equals('password', 'confirmation', t('Passwords doesn\'t matches')),
|
||||
new Validators\Equals('password', 'confirmation', t('Passwords don\'t match')),
|
||||
new Validators\Integer('default_project_id', t('This value must be an integer')),
|
||||
new Validators\Integer('is_admin', t('This value must be an integer')),
|
||||
new Validators\Email('email', t('Email address invalid')),
|
||||
|
|
@ -359,7 +359,6 @@ class User extends Base
|
|||
|
||||
// LDAP authentication
|
||||
if (! $authenticated && LDAP_AUTH) {
|
||||
require __DIR__.'/ldap.php';
|
||||
$ldap = new Ldap($this->db, $this->event);
|
||||
$authenticated = $ldap->authenticate($username, $password);
|
||||
$method = LastLogin::AUTH_LDAP;
|
||||
|
|
|
|||
|
|
@ -2,7 +2,24 @@
|
|||
|
||||
namespace Schema;
|
||||
|
||||
const VERSION = 17;
|
||||
const VERSION = 18;
|
||||
|
||||
function version_18($pdo)
|
||||
{
|
||||
$pdo->exec("
|
||||
CREATE TABLE task_has_subtasks (
|
||||
id INT NOT NULL AUTO_INCREMENT,
|
||||
title VARCHAR(255),
|
||||
status INT DEFAULT 0,
|
||||
time_estimated INT DEFAULT 0,
|
||||
time_spent INT DEFAULT 0,
|
||||
task_id INT,
|
||||
user_id INT,
|
||||
PRIMARY KEY (id),
|
||||
FOREIGN KEY(task_id) REFERENCES tasks(id) ON DELETE CASCADE
|
||||
) ENGINE=InnoDB CHARSET=utf8"
|
||||
);
|
||||
}
|
||||
|
||||
function version_17($pdo)
|
||||
{
|
||||
|
|
@ -246,6 +263,6 @@ function version_1($pdo)
|
|||
$pdo->exec("
|
||||
INSERT INTO config
|
||||
(webhooks_token)
|
||||
VALUES ('".\Model\Base::generateToken()."')
|
||||
VALUES ('".\Core\Security::generateToken()."')
|
||||
");
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,7 +2,23 @@
|
|||
|
||||
namespace Schema;
|
||||
|
||||
const VERSION = 17;
|
||||
const VERSION = 18;
|
||||
|
||||
function version_18($pdo)
|
||||
{
|
||||
$pdo->exec("
|
||||
CREATE TABLE task_has_subtasks (
|
||||
id INTEGER PRIMARY KEY,
|
||||
title TEXT COLLATE NOCASE,
|
||||
status INTEGER DEFAULT 0,
|
||||
time_estimated INTEGER DEFAULT 0,
|
||||
time_spent INTEGER DEFAULT 0,
|
||||
task_id INTEGER,
|
||||
user_id INTEGER,
|
||||
FOREIGN KEY(task_id) REFERENCES tasks(id) ON DELETE CASCADE
|
||||
)"
|
||||
);
|
||||
}
|
||||
|
||||
function version_17($pdo)
|
||||
{
|
||||
|
|
@ -193,7 +209,7 @@ function version_3($pdo)
|
|||
|
||||
foreach ($results as &$result) {
|
||||
$rq = $pdo->prepare('UPDATE projects SET token=? WHERE id=?');
|
||||
$rq->execute(array(\Model\Base::generateToken(), $result['id']));
|
||||
$rq->execute(array(\Core\Security::generateToken(), $result['id']));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -268,6 +284,6 @@ function version_1($pdo)
|
|||
$pdo->exec("
|
||||
INSERT INTO config
|
||||
(language, webhooks_token)
|
||||
VALUES ('en_US', '".\Model\Base::generateToken()."')
|
||||
VALUES ('en_US', '".\Core\Security::generateToken()."')
|
||||
");
|
||||
}
|
||||
|
|
|
|||
|
|
@ -56,7 +56,7 @@
|
|||
|
||||
<h3><?= t('Add an action') ?></h3>
|
||||
<form method="post" action="?controller=action&action=params&project_id=<?= $project['id'] ?>" autocomplete="off">
|
||||
|
||||
<?= Helper\form_csrf() ?>
|
||||
<?= Helper\form_hidden('project_id', $values) ?>
|
||||
|
||||
<?= Helper\form_label(t('Event'), 'event_name') ?>
|
||||
|
|
|
|||
|
|
@ -9,7 +9,7 @@
|
|||
|
||||
<h3><?= t('Define action parameters') ?></h3>
|
||||
<form method="post" action="?controller=action&action=create&project_id=<?= $project['id'] ?>" autocomplete="off">
|
||||
|
||||
<?= Helper\form_csrf() ?>
|
||||
<?= Helper\form_hidden('project_id', $values) ?>
|
||||
<?= Helper\form_hidden('event_name', $values) ?>
|
||||
<?= Helper\form_hidden('action_name', $values) ?>
|
||||
|
|
|
|||
|
|
@ -9,7 +9,7 @@
|
|||
</p>
|
||||
|
||||
<div class="form-actions">
|
||||
<a href="?controller=action&action=remove&action_id=<?= $action['id'] ?>" class="btn btn-red"><?= t('Yes') ?></a>
|
||||
<a href="?controller=action&action=remove&action_id=<?= $action['id'].Helper\param_csrf() ?>" class="btn btn-red"><?= t('Yes') ?></a>
|
||||
<?= t('or') ?> <a href="?controller=action&action=index&project_id=<?= $action['project_id'] ?>"><?= t('cancel') ?></a>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -4,6 +4,6 @@
|
|||
</div>
|
||||
|
||||
<p class="alert alert-error">
|
||||
<?= t('Only administrators can access to this page.') ?>
|
||||
<?= t('Access Forbidden') ?>
|
||||
</p>
|
||||
</section>
|
||||
|
|
@ -18,7 +18,7 @@
|
|||
<section>
|
||||
<h3><?= t('Change assignee for the task "%s"', $values['title']) ?></h3>
|
||||
<form method="post" action="?controller=board&action=assignTask" autocomplete="off">
|
||||
|
||||
<?= Helper\form_csrf() ?>
|
||||
<?= Helper\form_hidden('id', $values) ?>
|
||||
<?= Helper\form_hidden('project_id', $values) ?>
|
||||
|
||||
|
|
|
|||
|
|
@ -9,7 +9,7 @@
|
|||
|
||||
<h3><?= t('Change columns') ?></h3>
|
||||
<form method="post" action="?controller=board&action=update&project_id=<?= $project['id'] ?>" autocomplete="off">
|
||||
|
||||
<?= Helper\form_csrf() ?>
|
||||
<?php $i = 0; ?>
|
||||
<table>
|
||||
<tr>
|
||||
|
|
@ -27,12 +27,12 @@
|
|||
<ul>
|
||||
<?php if ($column['position'] != 1): ?>
|
||||
<li>
|
||||
<a href="?controller=board&action=moveUp&project_id=<?= $project['id'] ?>&column_id=<?= $column['id'] ?>"><?= t('Move Up') ?></a>
|
||||
<a href="?controller=board&action=moveUp&project_id=<?= $project['id'] ?>&column_id=<?= $column['id'].Helper\param_csrf() ?>"><?= t('Move Up') ?></a>
|
||||
</li>
|
||||
<?php endif ?>
|
||||
<?php if ($column['position'] != count($columns)): ?>
|
||||
<li>
|
||||
<a href="?controller=board&action=moveDown&project_id=<?= $project['id'] ?>&column_id=<?= $column['id'] ?>"><?= t('Move Down') ?></a>
|
||||
<a href="?controller=board&action=moveDown&project_id=<?= $project['id'] ?>&column_id=<?= $column['id'].Helper\param_csrf() ?>"><?= t('Move Down') ?></a>
|
||||
</li>
|
||||
<?php endif ?>
|
||||
<li>
|
||||
|
|
@ -52,7 +52,7 @@
|
|||
|
||||
<h3><?= t('Add a new column') ?></h3>
|
||||
<form method="post" action="?controller=board&action=add&project_id=<?= $project['id'] ?>" autocomplete="off">
|
||||
|
||||
<?= Helper\form_csrf() ?>
|
||||
<?= Helper\form_hidden('project_id', $values) ?>
|
||||
<?= Helper\form_label(t('Title'), 'title') ?>
|
||||
<?= Helper\form_text('title', $values, $errors, array('required')) ?>
|
||||
|
|
|
|||
|
|
@ -39,4 +39,4 @@
|
|||
|
||||
</section>
|
||||
|
||||
<script type="text/javascript" src="assets/js/board.js"></script>
|
||||
<?= Helper\js('assets/js/board.js') ?>
|
||||
|
|
|
|||
|
|
@ -10,7 +10,7 @@
|
|||
</p>
|
||||
|
||||
<div class="form-actions">
|
||||
<a href="?controller=board&action=remove&column_id=<?= $column['id'] ?>" class="btn btn-red"><?= t('Yes') ?></a>
|
||||
<a href="?controller=board&action=remove&column_id=<?= $column['id'].Helper\param_csrf() ?>" class="btn btn-red"><?= t('Yes') ?></a>
|
||||
<?= t('or') ?> <a href="?controller=board&action=edit&project_id=<?= $column['project_id'] ?>"><?= t('cancel') ?></a>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
<table id="board" data-project-id="<?= $current_project_id ?>" data-time="<?= time() ?>" data-check-interval="<?= BOARD_CHECK_INTERVAL ?>">
|
||||
<table id="board" data-project-id="<?= $current_project_id ?>" data-time="<?= time() ?>" data-check-interval="<?= BOARD_CHECK_INTERVAL ?>" data-csrf-token=<?= \Core\Security::getCSRFToken() ?>>
|
||||
<tr>
|
||||
<?php $column_with = round(100 / count($board), 2); ?>
|
||||
<?php foreach ($board as $column): ?>
|
||||
|
|
|
|||
|
|
@ -8,7 +8,7 @@
|
|||
<section>
|
||||
|
||||
<form method="post" action="?controller=category&action=update&project_id=<?= $project['id'] ?>" autocomplete="off">
|
||||
|
||||
<?= Helper\form_csrf() ?>
|
||||
<?= Helper\form_hidden('id', $values) ?>
|
||||
<?= Helper\form_hidden('project_id', $values) ?>
|
||||
|
||||
|
|
|
|||
|
|
@ -34,6 +34,7 @@
|
|||
<h3><?= t('Add a new category') ?></h3>
|
||||
<form method="post" action="?controller=category&action=save&project_id=<?= $project['id'] ?>" autocomplete="off">
|
||||
|
||||
<?= Helper\form_csrf() ?>
|
||||
<?= Helper\form_hidden('project_id', $values) ?>
|
||||
|
||||
<?= Helper\form_label(t('Category Name'), 'name') ?>
|
||||
|
|
|
|||
|
|
@ -9,7 +9,7 @@
|
|||
</p>
|
||||
|
||||
<div class="form-actions">
|
||||
<a href="?controller=category&action=remove&project_id=<?= $project['id'] ?>&category_id=<?= $category['id'] ?>" class="btn btn-red"><?= t('Yes') ?></a>
|
||||
<a href="?controller=category&action=remove&project_id=<?= $project['id'] ?>&category_id=<?= $category['id'].Helper\param_csrf() ?>" class="btn btn-red"><?= t('Yes') ?></a>
|
||||
<?= t('or') ?> <a href="?controller=category&project_id=<?= $project['id'] ?>"><?= t('cancel') ?></a>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@
|
|||
</div>
|
||||
|
||||
<form method="post" action="?controller=comment&action=save&task_id=<?= $task['id'] ?>" autocomplete="off">
|
||||
|
||||
<?= Helper\form_csrf() ?>
|
||||
<?= Helper\form_hidden('task_id', $values) ?>
|
||||
<?= Helper\form_hidden('user_id', $values) ?>
|
||||
<?= Helper\form_textarea('comment', $values, $errors, array('required', 'placeholder="'.t('Leave a comment').'"'), 'comment-textarea') ?><br/>
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@
|
|||
|
||||
<form method="post" action="?controller=comment&action=update&task_id=<?= $task['id'] ?>&comment_id=<?= $comment['id'] ?>" autocomplete="off">
|
||||
|
||||
<?= Helper\form_csrf() ?>
|
||||
<?= Helper\form_hidden('id', $values) ?>
|
||||
<?= Helper\form_textarea('comment', $values, $errors, array('required', 'placeholder="'.t('Leave a comment').'"')) ?><br/>
|
||||
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
<div class="page-header">
|
||||
<h2><?= t('Add a comment') ?></h2>
|
||||
<h2><?= t('Remove a comment') ?></h2>
|
||||
</div>
|
||||
|
||||
<div class="confirm">
|
||||
|
|
@ -10,7 +10,7 @@
|
|||
<?= Helper\template('comment_show', array('comment' => $comment, 'task' => $task, 'preview' => true)) ?>
|
||||
|
||||
<div class="form-actions">
|
||||
<a href="?controller=comment&action=remove&task_id=<?= $task['id'] ?>&comment_id=<?= $comment['id'] ?>" class="btn btn-red"><?= t('Yes') ?></a>
|
||||
<a href="?controller=comment&action=remove&task_id=<?= $task['id'] ?>&comment_id=<?= $comment['id'].Helper\param_csrf() ?>" class="btn btn-red"><?= t('Yes') ?></a>
|
||||
<?= t('or') ?> <a href="?controller=task&action=show&task_id=<?= $task['id'] ?>#comment-<?= $comment['id'] ?>"><?= t('cancel') ?></a>
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -7,6 +7,8 @@
|
|||
<section>
|
||||
<form method="post" action="?controller=config&action=save" autocomplete="off">
|
||||
|
||||
<?= Helper\form_csrf() ?>
|
||||
|
||||
<?= Helper\form_label(t('Language'), 'language') ?>
|
||||
<?= Helper\form_select('language', $languages, $values, $errors) ?><br/>
|
||||
|
||||
|
|
@ -39,7 +41,7 @@
|
|||
</div>
|
||||
<section class="settings">
|
||||
<ul>
|
||||
<li><a href="?controller=config&action=tokens"><?= t('Reset all tokens') ?></a></li>
|
||||
<li><a href="?controller=config&action=tokens<?= Helper\param_csrf() ?>"><?= t('Reset all tokens') ?></a></li>
|
||||
<li>
|
||||
<?= t('Webhooks token:') ?>
|
||||
<strong><?= Helper\escape($values['webhooks_token']) ?></strong>
|
||||
|
|
@ -50,11 +52,11 @@
|
|||
<strong><?= Helper\format_bytes($db_size) ?></strong>
|
||||
</li>
|
||||
<li>
|
||||
<a href="?controller=config&action=downloadDb"><?= t('Download the database') ?></a>
|
||||
<a href="?controller=config&action=downloadDb<?= Helper\param_csrf() ?>"><?= t('Download the database') ?></a>
|
||||
<?= t('(Gzip compressed Sqlite file)') ?>
|
||||
</li>
|
||||
<li>
|
||||
<a href="?controller=config&action=optimizeDb"><?= t('Optimize the database') ?></a>
|
||||
<a href="?controller=config&action=optimizeDb <?= Helper\param_csrf() ?>"><?= t('Optimize the database') ?></a>
|
||||
<?= t('(VACUUM command)') ?>
|
||||
</li>
|
||||
<?php endif ?>
|
||||
|
|
@ -112,7 +114,7 @@
|
|||
<td><?= dt('%B %e, %G at %k:%M %p', $session['expiration']) ?></td>
|
||||
<td><?= Helper\escape($session['ip']) ?></td>
|
||||
<td><?= Helper\escape($session['user_agent']) ?></td>
|
||||
<td><a href="?controller=config&action=removeRememberMeToken&id=<?= $session['id'] ?>"><?= t('Remove') ?></a></td>
|
||||
<td><a href="?controller=config&action=removeRememberMeToken&id=<?= $session['id'].Helper\param_csrf() ?>"><?= t('Remove') ?></a></td>
|
||||
</tr>
|
||||
<?php endforeach ?>
|
||||
</table>
|
||||
|
|
|
|||
|
|
@ -3,7 +3,9 @@
|
|||
</div>
|
||||
|
||||
<form action="?controller=file&action=save&task_id=<?= $task['id'] ?>" method="post" enctype="multipart/form-data">
|
||||
<?= Helper\form_csrf() ?>
|
||||
<input type="file" name="files[]" multiple />
|
||||
<div class="form-help"><?= t('Maximum size: ') ?><?= is_integer($max_size) ? Helper\format_bytes($max_size) : $max_size ?></div>
|
||||
<div class="form-actions">
|
||||
<input type="submit" value="<?= t('Save') ?>" class="btn btn-blue"/>
|
||||
<?= t('or') ?>
|
||||
|
|
|
|||
|
|
@ -8,7 +8,7 @@
|
|||
</p>
|
||||
|
||||
<div class="form-actions">
|
||||
<a href="?controller=file&action=remove&task_id=<?= $task['id'] ?>&file_id=<?= $file['id'] ?>" class="btn btn-red"><?= t('Yes') ?></a>
|
||||
<a href="?controller=file&action=remove&task_id=<?= $task['id'] ?>&file_id=<?= $file['id'].Helper\param_csrf() ?>" class="btn btn-red"><?= t('Yes') ?></a>
|
||||
<?= t('or') ?> <a href="?controller=task&action=show&task_id=<?= $task['id'] ?>"><?= t('cancel') ?></a>
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -0,0 +1,17 @@
|
|||
<div class="page-header">
|
||||
<h2><?= t('Attachments') ?></h2>
|
||||
</div>
|
||||
|
||||
<ul class="task-show-files">
|
||||
<?php foreach ($files as $file): ?>
|
||||
<li>
|
||||
<a href="?controller=file&action=download&file_id=<?= $file['id'] ?>&task_id=<?= $task['id'] ?>"><?= Helper\escape($file['name']) ?></a>
|
||||
<span class="task-show-file-actions">
|
||||
<?php if ($file['is_image']): ?>
|
||||
<a href="?controller=file&action=open&file_id=<?= $file['id'] ?>&task_id=<?= $task['id'] ?>" class="popover"><?= t('open') ?></a>,
|
||||
<?php endif ?>
|
||||
<a href="?controller=file&action=confirm&file_id=<?= $file['id'] ?>&task_id=<?= $task['id'] ?>"><?= t('remove') ?></a>
|
||||
</span>
|
||||
</li>
|
||||
<?php endforeach ?>
|
||||
</ul>
|
||||
|
|
@ -6,12 +6,12 @@
|
|||
<meta name="viewport" content="width=device-width">
|
||||
<meta name="mobile-web-app-capable" content="yes">
|
||||
|
||||
<script src="assets/js/jquery-1.11.1.min.js"></script>
|
||||
<script src="assets/js/jquery-ui-1.10.4.custom.min.js"></script>
|
||||
<script src="assets/js/jquery.ui.touch-punch.min.js"></script>
|
||||
<?= Helper\js('assets/js/jquery-1.11.1.min.js') ?>
|
||||
<?= Helper\js('assets/js/jquery-ui-1.10.4.custom.min.js') ?>
|
||||
<?= Helper\js('assets/js/jquery.ui.touch-punch.min.js') ?>
|
||||
|
||||
<link rel="stylesheet" href="assets/css/app.css" media="screen">
|
||||
<link rel="stylesheet" href="assets/css/font-awesome.min.css" media="screen">
|
||||
<?= Helper\css('assets/css/app.css') ?>
|
||||
<?= Helper\css('assets/css/font-awesome.min.css') ?>
|
||||
|
||||
<link rel="icon" type="image/png" href="assets/img/favicon.png">
|
||||
<link rel="apple-touch-icon" href="assets/img/touch-icon-iphone.png">
|
||||
|
|
@ -45,7 +45,7 @@
|
|||
<a href="?controller=config"><?= t('Settings') ?></a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="?controller=user&action=logout"><?= t('Logout') ?></a>
|
||||
<a href="?controller=user&action=logout<?= Helper\param_csrf() ?>"><?= t('Logout') ?></a>
|
||||
(<?= Helper\escape(Helper\get_username()) ?>)
|
||||
</li>
|
||||
</ul>
|
||||
|
|
|
|||
|
|
@ -8,6 +8,7 @@
|
|||
<section>
|
||||
<form method="post" action="?controller=project&action=update&project_id=<?= $values['id'] ?>" autocomplete="off">
|
||||
|
||||
<?= Helper\form_csrf() ?>
|
||||
<?= Helper\form_hidden('id', $values) ?>
|
||||
|
||||
<?= Helper\form_label(t('Name'), 'name') ?>
|
||||
|
|
|
|||
|
|
@ -1,9 +0,0 @@
|
|||
<section id="main">
|
||||
<div class="page-header">
|
||||
<h2><?= t('Forbidden') ?></h2>
|
||||
</div>
|
||||
|
||||
<p class="alert alert-error">
|
||||
<?= t('You are not allowed to access to this project.') ?>
|
||||
</p>
|
||||
</section>
|
||||
|
|
@ -78,9 +78,9 @@
|
|||
</li>
|
||||
<li>
|
||||
<?php if ($project['is_active']): ?>
|
||||
<a href="?controller=project&action=disable&project_id=<?= $project['id'] ?>"><?= t('Disable') ?></a>
|
||||
<a href="?controller=project&action=disable&project_id=<?= $project['id'].Helper\param_csrf() ?>"><?= t('Disable') ?></a>
|
||||
<?php else: ?>
|
||||
<a href="?controller=project&action=enable&project_id=<?= $project['id'] ?>"><?= t('Enable') ?></a>
|
||||
<a href="?controller=project&action=enable&project_id=<?= $project['id'].Helper\param_csrf() ?>"><?= t('Enable') ?></a>
|
||||
<?php endif ?>
|
||||
</li>
|
||||
<li>
|
||||
|
|
|
|||
|
|
@ -8,6 +8,7 @@
|
|||
<section>
|
||||
<form method="post" action="?controller=project&action=save" autocomplete="off">
|
||||
|
||||
<?= Helper\form_csrf() ?>
|
||||
<?= Helper\form_label(t('Name'), 'name') ?>
|
||||
<?= Helper\form_text('name', $values, $errors, array('autofocus', 'required')) ?>
|
||||
|
||||
|
|
|
|||
|
|
@ -9,7 +9,7 @@
|
|||
</p>
|
||||
|
||||
<div class="form-actions">
|
||||
<a href="?controller=project&action=remove&project_id=<?= $project['id'] ?>" class="btn btn-red"><?= t('Yes') ?></a>
|
||||
<a href="?controller=project&action=remove&project_id=<?= $project['id'].Helper\param_csrf() ?>" class="btn btn-red"><?= t('Yes') ?></a>
|
||||
<?= t('or') ?> <a href="?controller=project"><?= t('cancel') ?></a>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -10,6 +10,8 @@
|
|||
<?php if (! empty($users['not_allowed'])): ?>
|
||||
<form method="post" action="?controller=project&action=allow&project_id=<?= $project['id'] ?>" autocomplete="off">
|
||||
|
||||
<?= Helper\form_csrf() ?>
|
||||
|
||||
<?= Helper\form_hidden('project_id', array('project_id' => $project['id'])) ?>
|
||||
|
||||
<?= Helper\form_label(t('User'), 'user_id') ?>
|
||||
|
|
@ -32,7 +34,7 @@
|
|||
<?php foreach ($users['allowed'] as $user_id => $username): ?>
|
||||
<li>
|
||||
<strong><?= Helper\escape($username) ?></strong>
|
||||
(<a href="?controller=project&action=revoke&project_id=<?= $project['id'] ?>&user_id=<?= $user_id ?>"><?= t('revoke') ?></a>)
|
||||
(<a href="?controller=project&action=revoke&project_id=<?= $project['id'] ?>&user_id=<?= $user_id.Helper\param_csrf() ?>"><?= t('revoke') ?></a>)
|
||||
</li>
|
||||
<?php endforeach ?>
|
||||
</ul>
|
||||
|
|
|
|||
|
|
@ -0,0 +1,27 @@
|
|||
<div class="page-header">
|
||||
<h2><?= t('Add a sub-task') ?></h2>
|
||||
</div>
|
||||
|
||||
<form method="post" action="?controller=subtask&action=save&task_id=<?= $task['id'] ?>" autocomplete="off">
|
||||
|
||||
<?= Helper\form_csrf() ?>
|
||||
|
||||
<?= Helper\form_hidden('task_id', $values) ?>
|
||||
|
||||
<?= Helper\form_label(t('Title'), 'title') ?>
|
||||
<?= Helper\form_text('title', $values, $errors, array('required autofocus')) ?><br/>
|
||||
|
||||
<?= Helper\form_label(t('Assignee'), 'user_id') ?>
|
||||
<?= Helper\form_select('user_id', $users_list, $values, $errors) ?><br/>
|
||||
|
||||
<?= Helper\form_label(t('Original Estimate'), 'time_estimated') ?>
|
||||
<?= Helper\form_numeric('time_estimated', $values, $errors) ?> <?= t('hours') ?><br/>
|
||||
|
||||
<?= Helper\form_checkbox('another_subtask', t('Create another sub-task'), 1, isset($values['another_subtask']) && $values['another_subtask'] == 1) ?>
|
||||
|
||||
<div class="form-actions">
|
||||
<input type="submit" value="<?= t('Save') ?>" class="btn btn-blue"/>
|
||||
<?= t('or') ?>
|
||||
<a href="?controller=task&action=show&task_id=<?= $task['id'] ?>"><?= t('cancel') ?></a>
|
||||
</div>
|
||||
</form>
|
||||
|
|
@ -0,0 +1,32 @@
|
|||
<div class="page-header">
|
||||
<h2><?= t('Edit a sub-task') ?></h2>
|
||||
</div>
|
||||
|
||||
<form method="post" action="?controller=subtask&action=update&task_id=<?= $task['id'] ?>&subtask_id=<?= $subtask['id'] ?>" autocomplete="off">
|
||||
|
||||
<?= Helper\form_csrf() ?>
|
||||
|
||||
<?= Helper\form_hidden('id', $values) ?>
|
||||
<?= Helper\form_hidden('task_id', $values) ?>
|
||||
|
||||
<?= Helper\form_label(t('Title'), 'title') ?>
|
||||
<?= Helper\form_text('title', $values, $errors, array('required autofocus')) ?><br/>
|
||||
|
||||
<?= Helper\form_label(t('Status'), 'status') ?>
|
||||
<?= Helper\form_select('status', $status_list, $values, $errors) ?><br/>
|
||||
|
||||
<?= Helper\form_label(t('Assignee'), 'user_id') ?>
|
||||
<?= Helper\form_select('user_id', $users_list, $values, $errors) ?><br/>
|
||||
|
||||
<?= Helper\form_label(t('Original Estimate'), 'time_estimated') ?>
|
||||
<?= Helper\form_numeric('time_estimated', $values, $errors) ?> <?= t('hours') ?><br/>
|
||||
|
||||
<?= Helper\form_label(t('Time Spent'), 'time_spent') ?>
|
||||
<?= Helper\form_numeric('time_spent', $values, $errors) ?> <?= t('hours') ?><br/>
|
||||
|
||||
<div class="form-actions">
|
||||
<input type="submit" value="<?= t('Save') ?>" class="btn btn-blue"/>
|
||||
<?= t('or') ?>
|
||||
<a href="?controller=task&action=show&task_id=<?= $task['id'] ?>"><?= t('cancel') ?></a>
|
||||
</div>
|
||||
</form>
|
||||
|
|
@ -0,0 +1,16 @@
|
|||
<div class="page-header">
|
||||
<h2><?= t('Remove a sub-task') ?></h2>
|
||||
</div>
|
||||
|
||||
<div class="confirm">
|
||||
<p class="alert alert-info">
|
||||
<?= t('Do you really want to remove this sub-task?') ?>
|
||||
</p>
|
||||
|
||||
<p><strong><?= Helper\escape($subtask['title']) ?></strong></p>
|
||||
|
||||
<div class="form-actions">
|
||||
<a href="?controller=subtask&action=remove&task_id=<?= $task['id'] ?>&subtask_id=<?= $subtask['id'].Helper\param_csrf() ?>" class="btn btn-red"><?= t('Yes') ?></a>
|
||||
<?= t('or') ?> <a href="?controller=task&action=show&task_id=<?= $task['id'] ?>#subtasks"><?= t('cancel') ?></a>
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -0,0 +1,60 @@
|
|||
<div class="page-header">
|
||||
<h2><?= t('Sub-Tasks') ?></h2>
|
||||
</div>
|
||||
|
||||
<?php
|
||||
|
||||
$total_spent = 0;
|
||||
$total_estimated = 0;
|
||||
$total_remaining = 0;
|
||||
|
||||
?>
|
||||
|
||||
<table class="subtasks-table">
|
||||
<tr>
|
||||
<th width="40%"><?= t('Title') ?></th>
|
||||
<th><?= t('Status') ?></th>
|
||||
<th><?= t('Assignee') ?></th>
|
||||
<th><?= t('Time tracking') ?></th>
|
||||
<th><?= t('Actions') ?></th>
|
||||
</tr>
|
||||
<?php foreach ($subtasks as $subtask): ?>
|
||||
<tr>
|
||||
<td><?= Helper\escape($subtask['title']) ?></td>
|
||||
<td><?= Helper\escape($subtask['status_name']) ?></td>
|
||||
<td>
|
||||
<?php if (! empty($subtask['username'])): ?>
|
||||
<?= Helper\escape($subtask['username']) ?>
|
||||
<?php endif ?>
|
||||
</td>
|
||||
<td>
|
||||
<?php if (! empty($subtask['time_spent'])): ?>
|
||||
<strong><?= Helper\escape($subtask['time_spent']).'h' ?></strong> <?= t('spent') ?>
|
||||
<?php endif ?>
|
||||
|
||||
<?php if (! empty($subtask['time_estimated'])): ?>
|
||||
<strong><?= Helper\escape($subtask['time_estimated']).'h' ?></strong> <?= t('estimated') ?>
|
||||
<?php endif ?>
|
||||
</td>
|
||||
<td>
|
||||
<a href="?controller=subtask&action=edit&task_id=<?= $task['id'] ?>&subtask_id=<?= $subtask['id'] ?>"><?= t('Edit') ?></a>
|
||||
<?= t('or') ?>
|
||||
<a href="?controller=subtask&action=confirm&task_id=<?= $task['id'] ?>&subtask_id=<?= $subtask['id'] ?>"><?= t('Remove') ?></a>
|
||||
</td>
|
||||
</tr>
|
||||
<?php
|
||||
$total_estimated += $subtask['time_estimated'];
|
||||
$total_spent += $subtask['time_spent'];
|
||||
$total_remaining = $total_estimated - $total_spent;
|
||||
?>
|
||||
<?php endforeach ?>
|
||||
</table>
|
||||
|
||||
<div class="subtasks-time-tracking">
|
||||
<h4><?= t('Time tracking') ?></h4>
|
||||
<ul>
|
||||
<li><?= t('Estimate:') ?> <strong><?= Helper\escape($total_estimated) ?></strong> <?= t('hours') ?></li>
|
||||
<li><?= t('Spent:') ?> <strong><?= Helper\escape($total_spent) ?></strong> <?= t('hours') ?></li>
|
||||
<li><?= t('Remaining:') ?> <strong><?= Helper\escape($total_remaining > 0 ? $total_remaining : 0) ?></strong> <?= t('hours') ?></li>
|
||||
</ul>
|
||||
</div>
|
||||
|
|
@ -8,7 +8,7 @@
|
|||
</p>
|
||||
|
||||
<div class="form-actions">
|
||||
<a href="?controller=task&action=close&task_id=<?= $task['id'] ?>" class="btn btn-red"><?= t('Yes') ?></a>
|
||||
<a href="?controller=task&action=close&task_id=<?= $task['id'].Helper\param_csrf() ?>" class="btn btn-red"><?= t('Yes') ?></a>
|
||||
<?= t('or') ?> <a href="?controller=task&action=show&task_id=<?= $task['id'] ?>"><?= t('cancel') ?></a>
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -8,6 +8,8 @@
|
|||
<section>
|
||||
<form method="post" action="?controller=task&action=update&task_id=<?= $task['id'] ?>" autocomplete="off">
|
||||
|
||||
<?= Helper\form_csrf() ?>
|
||||
|
||||
<div class="form-column">
|
||||
|
||||
<?= Helper\form_label(t('Title'), 'title') ?>
|
||||
|
|
|
|||
|
|
@ -4,6 +4,8 @@
|
|||
|
||||
<form method="post" action="?controller=task&action=saveDescription&task_id=<?= $task['id'] ?>" autocomplete="off">
|
||||
|
||||
<?= Helper\form_csrf() ?>
|
||||
|
||||
<?= Helper\form_hidden('id', $values) ?>
|
||||
<?= Helper\form_textarea('description', $values, $errors, array('required', 'placeholder="'.t('Leave a description').'"'), 'description-textarea') ?><br/>
|
||||
<div class="form-help"><a href="http://kanboard.net/documentation/syntax-guide" target="_blank" rel="noreferrer"><?= t('Write your text in Markdown') ?></a></div>
|
||||
|
|
|
|||
|
|
@ -14,4 +14,5 @@
|
|||
</div>
|
||||
</section>
|
||||
</section>
|
||||
<script type="text/javascript" src="assets/js/task.js"></script>
|
||||
|
||||
<?= Helper\js('assets/js/task.js') ?>
|
||||
|
|
|
|||
|
|
@ -5,6 +5,8 @@
|
|||
<section>
|
||||
<form method="post" action="?controller=task&action=save" autocomplete="off">
|
||||
|
||||
<?= Helper\form_csrf() ?>
|
||||
|
||||
<div class="form-column">
|
||||
<?= Helper\form_label(t('Title'), 'title') ?>
|
||||
<?= Helper\form_text('title', $values, $errors, array('autofocus', 'required')) ?><br/>
|
||||
|
|
|
|||
|
|
@ -8,7 +8,7 @@
|
|||
</p>
|
||||
|
||||
<div class="form-actions">
|
||||
<a href="?controller=task&action=open&task_id=<?= $task['id'] ?>" class="btn btn-red"><?= t('Yes') ?></a>
|
||||
<a href="?controller=task&action=open&task_id=<?= $task['id'].Helper\param_csrf() ?>" class="btn btn-red"><?= t('Yes') ?></a>
|
||||
<?= t('or') ?> <a href="?controller=task&action=show&task_id=<?= $task['id'] ?>"><?= t('cancel') ?></a>
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -8,7 +8,7 @@
|
|||
</p>
|
||||
|
||||
<div class="form-actions">
|
||||
<a href="?controller=task&action=remove&task_id=<?= $task['id'] ?>" class="btn btn-red"><?= t('Yes') ?></a>
|
||||
<a href="?controller=task&action=remove&task_id=<?= $task['id'].Helper\param_csrf() ?>" class="btn btn-red"><?= t('Yes') ?></a>
|
||||
<?= t('or') ?> <a href="?controller=task&action=show&task_id=<?= $task['id'] ?>"><?= t('cancel') ?></a>
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -62,23 +62,14 @@
|
|||
|
||||
<?php if (! empty($files)): ?>
|
||||
<div id="attachments" class="task-show-section">
|
||||
<div class="page-header">
|
||||
<h2><?= t('Attachments') ?></h2>
|
||||
</div>
|
||||
<?= Helper\template('file_show', array('task' => $task, 'files' => $files)) ?>
|
||||
</div>
|
||||
<?php endif ?>
|
||||
|
||||
<ul class="task-show-files">
|
||||
<?php foreach ($files as $file): ?>
|
||||
<li>
|
||||
<a href="?controller=file&action=download&file_id=<?= $file['id'] ?>&task_id=<?= $task['id'] ?>"><?= Helper\escape($file['name']) ?></a>
|
||||
<span class="task-show-file-actions">
|
||||
<?php if ($file['is_image']): ?>
|
||||
<a href="?controller=file&action=open&file_id=<?= $file['id'] ?>&task_id=<?= $task['id'] ?>" class="popover"><?= t('open') ?></a>,
|
||||
<?php endif ?>
|
||||
<a href="?controller=file&action=confirm&file_id=<?= $file['id'] ?>&task_id=<?= $task['id'] ?>"><?= t('remove') ?></a>
|
||||
</span>
|
||||
</li>
|
||||
<?php endforeach ?>
|
||||
</ul>
|
||||
|
||||
<?php if (! empty($subtasks)): ?>
|
||||
<div id="subtasks" class="task-show-section">
|
||||
<?= Helper\template('subtask_show', array('task' => $task, 'subtasks' => $subtasks)) ?>
|
||||
</div>
|
||||
<?php endif ?>
|
||||
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@
|
|||
<li><a href="?controller=task&action=show&task_id=<?= $task['id'] ?>"><?= t('Summary') ?></a></li>
|
||||
<li><a href="?controller=task&action=edit&task_id=<?= $task['id'] ?>"><?= t('Edit the task') ?></a></li>
|
||||
<li><a href="?controller=task&action=editDescription&task_id=<?= $task['id'] ?>"><?= t('Edit the description') ?></a></li>
|
||||
<li><a href="?controller=subtask&action=create&task_id=<?= $task['id'] ?>"><?= t('Add a sub-task') ?></a></li>
|
||||
<li><a href="?controller=comment&action=create&task_id=<?= $task['id'] ?>"><?= t('Add a comment') ?></a></li>
|
||||
<li><a href="?controller=file&action=create&task_id=<?= $task['id'] ?>"><?= t('Attach a document') ?></a></li>
|
||||
<li><a href="?controller=task&action=duplicate&project_id=<?= $task['project_id'] ?>&task_id=<?= $task['id'] ?>"><?= t('Duplicate') ?></a></li>
|
||||
|
|
|
|||
|
|
@ -8,6 +8,8 @@
|
|||
<section>
|
||||
<form method="post" action="?controller=user&action=update" autocomplete="off">
|
||||
|
||||
<?= Helper\form_csrf() ?>
|
||||
|
||||
<div class="form-column">
|
||||
|
||||
<?= Helper\form_hidden('id', $values) ?>
|
||||
|
|
@ -48,9 +50,9 @@
|
|||
|
||||
<?php if (GOOGLE_AUTH && Helper\is_current_user($values['id'])): ?>
|
||||
<?php if (empty($values['google_id'])): ?>
|
||||
<a href="?controller=user&action=google"><?= t('Link my Google Account') ?></a>
|
||||
<a href="?controller=user&action=google<?= Helper\param_csrf() ?>"><?= t('Link my Google Account') ?></a>
|
||||
<?php else: ?>
|
||||
<a href="?controller=user&action=unlinkGoogle"><?= t('Unlink my Google Account') ?></a>
|
||||
<a href="?controller=user&action=unlinkGoogle<?= Helper\param_csrf() ?>"><?= t('Unlink my Google Account') ?></a>
|
||||
<?php endif ?>
|
||||
<?php endif ?>
|
||||
|
||||
|
|
|
|||
|
|
@ -8,6 +8,8 @@
|
|||
|
||||
<form method="post" action="?controller=user&action=check" class="form-login">
|
||||
|
||||
<?= Helper\form_csrf() ?>
|
||||
|
||||
<?= Helper\form_label(t('Username'), 'username') ?>
|
||||
<?= Helper\form_text('username', $values, $errors, array('autofocus', 'required')) ?><br/>
|
||||
|
||||
|
|
|
|||
|
|
@ -8,6 +8,8 @@
|
|||
<section>
|
||||
<form method="post" action="?controller=user&action=save" autocomplete="off">
|
||||
|
||||
<?= Helper\form_csrf() ?>
|
||||
|
||||
<div class="form-column">
|
||||
|
||||
<?= Helper\form_label(t('Username'), 'username') ?>
|
||||
|
|
|
|||
|
|
@ -7,7 +7,7 @@
|
|||
<p class="alert alert-info"><?= t('Do you really want to remove this user: "%s"?', $user['username']) ?></p>
|
||||
|
||||
<div class="form-actions">
|
||||
<a href="?controller=user&action=remove&user_id=<?= $user['id'] ?>" class="btn btn-red"><?= t('Yes') ?></a>
|
||||
<a href="?controller=user&action=remove&user_id=<?= $user['id'].Helper\param_csrf() ?>" class="btn btn-red"><?= t('Yes') ?></a>
|
||||
<?= t('or') ?> <a href="?controller=user"><?= t('cancel') ?></a>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -2,6 +2,21 @@
|
|||
|
||||
namespace Helper;
|
||||
|
||||
function param_csrf()
|
||||
{
|
||||
return '&csrf_token='.\Core\Security::getCSRFToken();
|
||||
}
|
||||
|
||||
function js($filename)
|
||||
{
|
||||
return '<script type="text/javascript" src="'.$filename.'?'.filemtime($filename).'"></script>';
|
||||
}
|
||||
|
||||
function css($filename)
|
||||
{
|
||||
return '<link rel="stylesheet" href="'.$filename.'?'.filemtime($filename).'" media="screen">';
|
||||
}
|
||||
|
||||
function template($name, array $args = array())
|
||||
{
|
||||
$tpl = new \Core\Template;
|
||||
|
|
@ -153,6 +168,11 @@ function form_value($values, $name)
|
|||
return isset($values[$name]) ? 'value="'.escape($values[$name]).'"' : '';
|
||||
}
|
||||
|
||||
function form_csrf()
|
||||
{
|
||||
return '<input type="hidden" name="csrf_token" value="'.\Core\Security::getCSRFToken().'"/>';
|
||||
}
|
||||
|
||||
function form_hidden($name, $values = array())
|
||||
{
|
||||
return '<input type="hidden" name="'.$name.'" id="form-'.$name.'" '.form_value($values, $name).'/>';
|
||||
|
|
@ -260,3 +280,8 @@ function form_number($name, $values = array(), array $errors = array(), array $a
|
|||
{
|
||||
return form_input('number', $name, $values, $errors, $attributes, $class);
|
||||
}
|
||||
|
||||
function form_numeric($name, $values = array(), array $errors = array(), array $attributes = array(), $class = '')
|
||||
{
|
||||
return form_input('text', $name, $values, $errors, $attributes, $class.' form-numeric');
|
||||
}
|
||||
|
|
|
|||
|
|
@ -158,6 +158,7 @@ textarea:focus {
|
|||
box-shadow: 0 0 8px rgba(82, 168, 236, 0.6);
|
||||
}
|
||||
|
||||
input.form-numeric,
|
||||
input[type="number"] {
|
||||
width: 70px;
|
||||
}
|
||||
|
|
@ -797,6 +798,24 @@ a.task-board-nobody {
|
|||
max-width: 800px;
|
||||
}
|
||||
|
||||
/* subtasks */
|
||||
.subtasks-table {
|
||||
font-size: 0.85em;
|
||||
}
|
||||
|
||||
.subtasks-table td {
|
||||
vertical-align: middle;
|
||||
}
|
||||
|
||||
.subtasks-time-tracking h4 {
|
||||
margin-bottom: 5px;
|
||||
}
|
||||
|
||||
.subtasks-time-tracking li {
|
||||
list-style-type: square;
|
||||
margin-left: 30px;
|
||||
}
|
||||
|
||||
/* markdown content */
|
||||
.markdown {
|
||||
line-height: 1.4em;
|
||||
|
|
|
|||
|
|
@ -70,8 +70,9 @@
|
|||
});
|
||||
|
||||
$.ajax({
|
||||
cache: false,
|
||||
url: "?controller=board&action=save&project_id=" + projectId,
|
||||
data: {positions: data},
|
||||
data: {"positions": data, "csrf_token": $("#board").attr("data-csrf-token")},
|
||||
type: "POST",
|
||||
success: function(data) {
|
||||
$("#board").remove();
|
||||
|
|
@ -90,6 +91,7 @@
|
|||
|
||||
if (is_visible() && projectId != undefined && timestamp != undefined) {
|
||||
$.ajax({
|
||||
cache: false,
|
||||
url: "?controller=board&action=check&project_id=" + projectId + "×tamp=" + timestamp,
|
||||
statusCode: {
|
||||
200: function(data) {
|
||||
|
|
|
|||
|
|
@ -34,7 +34,8 @@ 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
|
||||
- Assign automatically a color based on a category
|
||||
- Assign automatically a category based on a color
|
||||
|
||||
Examples
|
||||
--------
|
||||
|
|
@ -67,14 +68,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**
|
||||
|
||||
### I want to assign a specific color to the user Bob
|
||||
### I want to assign automatically a color to the user Bob
|
||||
|
||||
- Choose the event: **Task creation**
|
||||
- Choose the action: **Assign a color to a specific user**
|
||||
- Define the action parameters: **Color = Green** and **Assignee = Bob**
|
||||
|
||||
### I want to assign a specific color to the category "Feature Request"
|
||||
### I want to assign automatically a color to the defined category "Feature Request"
|
||||
|
||||
- Choose the event: **Task creation or modification**
|
||||
- Choose the action: **Assign a color to a specific category**
|
||||
- Choose the action: **Assign automatically a color based on a category**
|
||||
- Define the action parameters: **Color = Blue** and **Category = Feature Request**
|
||||
|
|
|
|||
|
|
@ -25,10 +25,10 @@ Install Kanboard:
|
|||
|
||||
```bash
|
||||
cd /var/www/html
|
||||
wget http://kanboard.net/kanboard-VERSION.zip
|
||||
unzip kanboard-VERSION.zip
|
||||
wget http://kanboard.net/kanboard-latest.zip
|
||||
unzip kanboard-latest.zip
|
||||
chown -R apache:apache kanboard/data
|
||||
rm kanboard-VERSION.zip
|
||||
rm kanboard-latest.zip
|
||||
```
|
||||
|
||||
Go to `http://your_server/kanboard/`.
|
||||
|
|
@ -15,10 +15,10 @@ Install Kanboard:
|
|||
|
||||
```bash
|
||||
cd /var/www
|
||||
wget http://kanboard.net/kanboard-VERSION.zip
|
||||
unzip kanboard-VERSION.zip
|
||||
wget http://kanboard.net/kanboard-latest.zip
|
||||
unzip kanboard-latest.zip
|
||||
chown -R www-data:www-data kanboard/data
|
||||
rm kanboard-VERSION.zip
|
||||
rm kanboard-latest.zip
|
||||
```
|
||||
|
||||
Debian 6 (Squeeze)
|
||||
|
|
@ -35,8 +35,8 @@ Install Kanboard:
|
|||
|
||||
```bash
|
||||
cd /var/www
|
||||
wget http://kanboard.net/kanboard-VERSION.zip
|
||||
unzip kanboard-VERSION.zip
|
||||
wget http://kanboard.net/kanboard-latest.zip
|
||||
unzip kanboard-latest.zip
|
||||
chown -R www-data:www-data kanboard/data
|
||||
rm kanboard-VERSION.zip
|
||||
rm kanboard-latest.zip
|
||||
```
|
||||
|
|
@ -15,8 +15,8 @@ Install Kanboard:
|
|||
|
||||
```bash
|
||||
cd /var/www/html
|
||||
sudo wget http://kanboard.net/kanboard-VERSION.zip
|
||||
sudo unzip kanboard-VERSION.zip
|
||||
sudo wget http://kanboard.net/kanboard-latest.zip
|
||||
sudo unzip kanboard-latest.zip
|
||||
sudo chown -R www-data:www-data kanboard/data
|
||||
sudo rm kanboard-VERSION.zip
|
||||
sudo rm kanboard-latest.zip
|
||||
```
|
||||
|
|
|
|||
|
|
@ -6,9 +6,8 @@ APP="kanboard"
|
|||
cd /tmp
|
||||
rm -rf /tmp/$APP /tmp/$APP-*.zip 2>/dev/null
|
||||
git clone git@github.com:fguillot/$APP.git
|
||||
rm -rf $APP/data/*.sqlite $APP/.git $APP/.gitignore $APP/scripts $APP/tests $APP/Vagrantfile
|
||||
sed -i.bak s/master/$VERSION/g $APP/common.php && rm -f $APP/*.bak
|
||||
rm -rf $APP/data/*.sqlite $APP/.git $APP/.gitignore $APP/scripts $APP/tests $APP/Vagrantfile $APP/.*.yml $APP/phpunit.xml $APP/README.markdown $APP/docs
|
||||
sed -i.bak s/master/$VERSION/g $APP/app/common.php && rm -f $APP/app/*.bak
|
||||
zip -r $APP-$VERSION.zip $APP
|
||||
mv $APP-*.zip ~/Devel/websites/$APP
|
||||
rm -rf /tmp/$APP 2>/dev/null
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,76 @@
|
|||
<?php
|
||||
|
||||
require_once __DIR__.'/Base.php';
|
||||
|
||||
use Model\Task;
|
||||
use Model\Project;
|
||||
use Model\Category;
|
||||
|
||||
class ActionTaskAssignColorCategory extends Base
|
||||
{
|
||||
public function testBadProject()
|
||||
{
|
||||
$action = new Action\TaskAssignColorCategory(3, new Task($this->db, $this->event));
|
||||
|
||||
$event = array(
|
||||
'project_id' => 2,
|
||||
'task_id' => 3,
|
||||
'column_id' => 5,
|
||||
);
|
||||
|
||||
$this->assertFalse($action->isExecutable($event));
|
||||
$this->assertFalse($action->execute($event));
|
||||
}
|
||||
|
||||
public function testExecute()
|
||||
{
|
||||
$action = new Action\TaskAssignColorCategory(1, new Task($this->db, $this->event));
|
||||
$action->setParam('category_id', 1);
|
||||
$action->setParam('color_id', 'blue');
|
||||
|
||||
// We create a task in the first column
|
||||
$t = new Task($this->db, $this->event);
|
||||
$p = new Project($this->db, $this->event);
|
||||
$c = new Category($this->db, $this->event);
|
||||
|
||||
$this->assertEquals(1, $p->create(array('name' => 'test')));
|
||||
$this->assertEquals(1, $c->create(array('name' => 'c1')));
|
||||
$this->assertEquals(2, $c->create(array('name' => 'c2')));
|
||||
$this->assertEquals(1, $t->create(array('title' => 'test', 'project_id' => 1, 'column_id' => 1, 'color_id' => 'green', 'category_id' => 2)));
|
||||
|
||||
// We create an event but we don't do anything
|
||||
$event = array(
|
||||
'project_id' => 1,
|
||||
'task_id' => 1,
|
||||
'column_id' => 1,
|
||||
'category_id' => 2,
|
||||
'position' => 2,
|
||||
);
|
||||
|
||||
// Our event should NOT be executed
|
||||
$this->assertFalse($action->execute($event));
|
||||
|
||||
// Our task should be assigned to the ategory_id=1 and have the green color
|
||||
$task = $t->getById(1);
|
||||
$this->assertNotEmpty($task);
|
||||
$this->assertEquals(2, $task['category_id']);
|
||||
$this->assertEquals('green', $task['color_id']);
|
||||
|
||||
// We create an event to move the task
|
||||
$event = array(
|
||||
'project_id' => 1,
|
||||
'task_id' => 1,
|
||||
'column_id' => 1,
|
||||
'position' => 5,
|
||||
'category_id' => 1,
|
||||
);
|
||||
|
||||
// Our event should be executed
|
||||
$this->assertTrue($action->execute($event));
|
||||
|
||||
// Our task should have the blue color
|
||||
$task = $t->getById(1);
|
||||
$this->assertNotEmpty($task);
|
||||
$this->assertEquals('blue', $task['color_id']);
|
||||
}
|
||||
}
|
||||
|
|
@ -10,7 +10,6 @@ class ActionTaskAssignColorUser extends Base
|
|||
public function testBadProject()
|
||||
{
|
||||
$action = new Action\TaskAssignColorUser(3, new Task($this->db, $this->event));
|
||||
$action->setParam('column_id', 5);
|
||||
|
||||
$event = array(
|
||||
'project_id' => 2,
|
||||
|
|
@ -22,24 +21,9 @@ class ActionTaskAssignColorUser extends Base
|
|||
$this->assertFalse($action->execute($event));
|
||||
}
|
||||
|
||||
public function testBadColumn()
|
||||
{
|
||||
$action = new Action\TaskAssignColorUser(3, new Task($this->db, $this->event));
|
||||
$action->setParam('column_id', 5);
|
||||
|
||||
$event = array(
|
||||
'project_id' => 3,
|
||||
'task_id' => 3,
|
||||
'column_id' => 3,
|
||||
);
|
||||
|
||||
$this->assertFalse($action->execute($event));
|
||||
}
|
||||
|
||||
public function testExecute()
|
||||
{
|
||||
$action = new Action\TaskAssignColorUser(1, new Task($this->db, $this->event));
|
||||
$action->setParam('column_id', 2);
|
||||
$action->setParam('user_id', 1);
|
||||
$action->setParam('color_id', 'blue');
|
||||
|
||||
|
|
|
|||
|
|
@ -6,6 +6,7 @@ use Model\Action;
|
|||
use Model\Project;
|
||||
use Model\Board;
|
||||
use Model\Task;
|
||||
use Model\Category;
|
||||
|
||||
class ActionTest extends Base
|
||||
{
|
||||
|
|
@ -45,7 +46,7 @@ class ActionTest extends Base
|
|||
$this->assertEquals(4, $actions[0]['params'][0]['value']);
|
||||
}
|
||||
|
||||
public function testExecuteAction()
|
||||
public function testEventMoveColumn()
|
||||
{
|
||||
$task = new Task($this->db, $this->event);
|
||||
$board = new Board($this->db, $this->event);
|
||||
|
|
@ -85,12 +86,89 @@ class ActionTest extends Base
|
|||
// We move our task
|
||||
$task->move(1, 4, 1);
|
||||
|
||||
$this->assertTrue($this->event->isEventTriggered(Task::EVENT_MOVE_COLUMN));
|
||||
$this->assertTrue($this->event->isEventTriggered(Task::EVENT_UPDATE));
|
||||
|
||||
// Our task should be closed
|
||||
$t1 = $task->getById(1);
|
||||
$this->assertEquals(4, $t1['column_id']);
|
||||
$this->assertEquals(0, $t1['is_active']);
|
||||
}
|
||||
|
||||
public function testEventMovePosition()
|
||||
{
|
||||
$task = new Task($this->db, $this->event);
|
||||
$board = new Board($this->db, $this->event);
|
||||
$project = new Project($this->db, $this->event);
|
||||
$action = new Action($this->db, $this->event);
|
||||
|
||||
// We create a project
|
||||
$this->assertEquals(1, $project->create(array('name' => 'unit_test')));
|
||||
|
||||
// We create a task
|
||||
$this->assertEquals(1, $task->create(array(
|
||||
'title' => 'unit_test 0',
|
||||
'project_id' => 1,
|
||||
'owner_id' => 1,
|
||||
'color_id' => 'red',
|
||||
'column_id' => 1,
|
||||
'category_id' => 1,
|
||||
)));
|
||||
|
||||
$this->assertEquals(2, $task->create(array(
|
||||
'title' => 'unit_test 1',
|
||||
'project_id' => 1,
|
||||
'owner_id' => 1,
|
||||
'color_id' => 'yellow',
|
||||
'column_id' => 1,
|
||||
'category_id' => 1,
|
||||
)));
|
||||
|
||||
// We create a new action, when the category_id=2 then the color_id should be green
|
||||
$this->assertTrue($action->create(array(
|
||||
'project_id' => 1,
|
||||
'event_name' => Task::EVENT_MOVE_POSITION,
|
||||
'action_name' => 'TaskAssignColorCategory',
|
||||
'params' => array(
|
||||
'category_id' => 1,
|
||||
'color_id' => 'green',
|
||||
)
|
||||
)));
|
||||
|
||||
// We bind events
|
||||
$action->attachEvents();
|
||||
|
||||
$this->assertTrue($this->event->hasListener(Task::EVENT_MOVE_POSITION, 'Action\TaskAssignColorCategory'));
|
||||
|
||||
// Our task should have the color red and position=0
|
||||
$t1 = $task->getById(1);
|
||||
$this->assertEquals(0, $t1['position']);
|
||||
$this->assertEquals(1, $t1['is_active']);
|
||||
$this->assertEquals('red', $t1['color_id']);
|
||||
|
||||
$t1 = $task->getById(2);
|
||||
$this->assertEquals(1, $t1['position']);
|
||||
$this->assertEquals(1, $t1['is_active']);
|
||||
$this->assertEquals('yellow', $t1['color_id']);
|
||||
|
||||
// We move our tasks
|
||||
$task->move(1, 1, 1); // task #1 to position 1
|
||||
$task->move(2, 1, 0); // task #2 to position 0
|
||||
|
||||
$this->assertTrue($this->event->isEventTriggered(Task::EVENT_MOVE_POSITION));
|
||||
|
||||
// Both tasks should be green
|
||||
$t1 = $task->getById(1);
|
||||
$this->assertEquals(1, $t1['position']);
|
||||
$this->assertEquals(1, $t1['is_active']);
|
||||
$this->assertEquals('green', $t1['color_id']);
|
||||
|
||||
$t1 = $task->getById(2);
|
||||
$this->assertEquals(0, $t1['position']);
|
||||
$this->assertEquals(1, $t1['is_active']);
|
||||
$this->assertEquals('green', $t1['color_id']);
|
||||
}
|
||||
|
||||
public function testExecuteMultipleActions()
|
||||
{
|
||||
$task = new Task($this->db, $this->event);
|
||||
|
|
@ -146,7 +224,9 @@ class ActionTest extends Base
|
|||
|
||||
// We move our task
|
||||
$task->move(1, 4, 1);
|
||||
$this->assertEquals(Task::EVENT_CREATE, $this->event->getLastTriggeredEvent());
|
||||
|
||||
$this->assertTrue($this->event->isEventTriggered(Task::EVENT_CLOSE));
|
||||
$this->assertTrue($this->event->isEventTriggered(Task::EVENT_MOVE_COLUMN));
|
||||
|
||||
// Our task should be closed
|
||||
$t1 = $task->getById(1);
|
||||
|
|
|
|||
|
|
@ -4,6 +4,8 @@ if (version_compare(PHP_VERSION, '5.5.0', '<')) {
|
|||
require __DIR__.'/../vendor/password.php';
|
||||
}
|
||||
|
||||
require_once __DIR__.'/../app/Core/Security.php';
|
||||
|
||||
require_once __DIR__.'/../vendor/PicoDb/Database.php';
|
||||
require_once __DIR__.'/../app/Schema/Sqlite.php';
|
||||
|
||||
|
|
@ -20,11 +22,13 @@ require_once __DIR__.'/../app/Model/Project.php';
|
|||
require_once __DIR__.'/../app/Model/User.php';
|
||||
require_once __DIR__.'/../app/Model/Board.php';
|
||||
require_once __DIR__.'/../app/Model/Action.php';
|
||||
require_once __DIR__.'/../app/Model/Category.php';
|
||||
|
||||
require_once __DIR__.'/../app/Action/Base.php';
|
||||
require_once __DIR__.'/../app/Action/TaskClose.php';
|
||||
require_once __DIR__.'/../app/Action/TaskAssignSpecificUser.php';
|
||||
require_once __DIR__.'/../app/Action/TaskAssignColorUser.php';
|
||||
require_once __DIR__.'/../app/Action/TaskAssignColorCategory.php';
|
||||
require_once __DIR__.'/../app/Action/TaskAssignCurrentUser.php';
|
||||
require_once __DIR__.'/../app/Action/TaskDuplicateAnotherProject.php';
|
||||
|
||||
|
|
|
|||
|
|
@ -110,7 +110,7 @@ class TaskTest extends Base
|
|||
|
||||
// We duplicate our task
|
||||
$this->assertEquals(2, $t->duplicate(1));
|
||||
$this->assertEquals(Task::EVENT_CREATE, $this->event->getLastTriggeredEvent());
|
||||
$this->assertTrue($this->event->isEventTriggered(Task::EVENT_CREATE));
|
||||
|
||||
// Check the values of the duplicated task
|
||||
$task = $t->getById(2);
|
||||
|
|
@ -136,7 +136,7 @@ class TaskTest extends Base
|
|||
|
||||
// We duplicate our task to the 2nd project
|
||||
$this->assertEquals(2, $t->duplicateToAnotherProject(1, 2));
|
||||
$this->assertEquals(Task::EVENT_CREATE, $this->event->getLastTriggeredEvent());
|
||||
$this->assertTrue($this->event->isEventTriggered(Task::EVENT_CREATE));
|
||||
|
||||
// Check the values of the duplicated task
|
||||
$task = $t->getById(2);
|
||||
|
|
@ -157,30 +157,31 @@ class TaskTest extends Base
|
|||
|
||||
// We create task
|
||||
$this->assertEquals(1, $t->create(array('title' => 'test', 'project_id' => 1, 'column_id' => 1)));
|
||||
$this->assertEquals(Task::EVENT_CREATE, $this->event->getLastTriggeredEvent());
|
||||
$this->assertTrue($this->event->isEventTriggered(Task::EVENT_CREATE));
|
||||
|
||||
// We update a task
|
||||
$this->assertTrue($t->update(array('title' => 'test2', 'id' => 1)));
|
||||
$this->assertEquals(Task::EVENT_UPDATE, $this->event->getLastTriggeredEvent());
|
||||
$this->assertTrue($this->event->isEventTriggered(Task::EVENT_UPDATE));
|
||||
$this->assertTrue($this->event->isEventTriggered(Task::EVENT_CREATE_UPDATE));
|
||||
|
||||
// We close our task
|
||||
$this->assertTrue($t->close(1));
|
||||
$this->assertEquals(Task::EVENT_CLOSE, $this->event->getLastTriggeredEvent());
|
||||
$this->assertTrue($this->event->isEventTriggered(Task::EVENT_CLOSE));
|
||||
|
||||
// We open our task
|
||||
$this->assertTrue($t->open(1));
|
||||
$this->assertEquals(Task::EVENT_OPEN, $this->event->getLastTriggeredEvent());
|
||||
$this->assertTrue($this->event->isEventTriggered(Task::EVENT_OPEN));
|
||||
|
||||
// We change the column of our task
|
||||
$this->assertTrue($t->move(1, 2, 1));
|
||||
$this->assertEquals(Task::EVENT_MOVE_COLUMN, $this->event->getLastTriggeredEvent());
|
||||
$this->assertTrue($this->event->isEventTriggered(Task::EVENT_MOVE_COLUMN));
|
||||
|
||||
// We change the position of our task
|
||||
$this->assertTrue($t->move(1, 2, 2));
|
||||
$this->assertEquals(Task::EVENT_MOVE_POSITION, $this->event->getLastTriggeredEvent());
|
||||
$this->assertTrue($this->event->isEventTriggered(Task::EVENT_MOVE_POSITION));
|
||||
|
||||
// We change the column and the position of our task
|
||||
$this->assertTrue($t->move(1, 1, 3));
|
||||
$this->assertEquals(Task::EVENT_MOVE_COLUMN, $this->event->getLastTriggeredEvent());
|
||||
$this->assertTrue($this->event->isEventTriggered(Task::EVENT_MOVE_COLUMN));
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue