diff --git a/app/Controller/Base.php b/app/Controller/Base.php
index 8890db4c0..462529b17 100644
--- a/app/Controller/Base.php
+++ b/app/Controller/Base.php
@@ -246,4 +246,25 @@ abstract class Base
return $task;
}
+
+ /**
+ * Common method to get a project
+ *
+ * @access protected
+ * @return array
+ */
+ protected function getProject()
+ {
+ $project_id = $this->request->getIntegerParam('project_id');
+ $project = $this->project->getById($project_id);
+
+ if (! $project) {
+ $this->session->flashError(t('Project not found.'));
+ $this->response->redirect('?controller=project');
+ }
+
+ $this->checkProjectPermissions($project['id']);
+
+ return $project;
+ }
}
diff --git a/app/Controller/Category.php b/app/Controller/Category.php
index 9e2bcdbbe..5fd59c0a6 100644
--- a/app/Controller/Category.php
+++ b/app/Controller/Category.php
@@ -10,25 +10,6 @@ namespace Controller;
*/
class Category extends Base
{
- /**
- * Get the current project (common method between actions)
- *
- * @access private
- * @return array
- */
- private function getProject()
- {
- $project_id = $this->request->getIntegerParam('project_id');
- $project = $this->project->getById($project_id);
-
- if (! $project) {
- $this->session->flashError(t('Project not found.'));
- $this->response->redirect('?controller=project');
- }
-
- return $project;
- }
-
/**
* Get the category (common method between actions)
*
diff --git a/app/Controller/Project.php b/app/Controller/Project.php
index 0de676919..8c21801b7 100644
--- a/app/Controller/Project.php
+++ b/app/Controller/Project.php
@@ -3,6 +3,7 @@
namespace Controller;
use Model\Task as TaskModel;
+use Core\Translator;
/**
* Project controller
@@ -12,6 +13,39 @@ use Model\Task as TaskModel;
*/
class Project extends Base
{
+ /**
+ * Task export
+ *
+ * @access public
+ */
+ public function export()
+ {
+ $project = $this->getProject();
+ $from = $this->request->getStringParam('from');
+ $to = $this->request->getStringParam('to');
+
+ if ($from && $to) {
+ Translator::disableEscaping();
+ $data = $this->task->export($project['id'], $from, $to);
+ $this->response->forceDownload('Export_'.date('Y_m_d_H_i_S').'.csv');
+ $this->response->csv($data);
+ }
+
+ $this->response->html($this->template->layout('project_export', array(
+ 'values' => array(
+ 'controller' => 'project',
+ 'action' => 'export',
+ 'project_id' => $project['id'],
+ 'from' => $from,
+ 'to' => $to,
+ ),
+ 'errors' => array(),
+ 'menu' => 'projects',
+ 'project' => $project,
+ 'title' => t('Tasks Export')
+ )));
+ }
+
/**
* Task search for a given project
*
@@ -19,24 +53,15 @@ class Project extends Base
*/
public function search()
{
- $project_id = $this->request->getIntegerParam('project_id');
+ $project = $this->getProject();
$search = $this->request->getStringParam('search');
-
- $project = $this->project->getById($project_id);
$tasks = array();
$nb_tasks = 0;
- if (! $project) {
- $this->session->flashError(t('Project not found.'));
- $this->response->redirect('?controller=project');
- }
-
- $this->checkProjectPermissions($project['id']);
-
if ($search !== '') {
$filters = array(
- array('column' => 'project_id', 'operator' => 'eq', 'value' => $project_id),
+ array('column' => 'project_id', 'operator' => 'eq', 'value' => $project['id']),
'or' => array(
array('column' => 'title', 'operator' => 'like', 'value' => '%'.$search.'%'),
//array('column' => 'description', 'operator' => 'like', 'value' => '%'.$search.'%'),
@@ -58,7 +83,7 @@ class Project extends Base
),
'menu' => 'projects',
'project' => $project,
- 'columns' => $this->board->getColumnsList($project_id),
+ 'columns' => $this->board->getColumnsList($project['id']),
'categories' => $this->category->getList($project['id'], false),
'title' => $project['name'].($nb_tasks > 0 ? ' ('.$nb_tasks.')' : '')
)));
@@ -71,18 +96,10 @@ class Project extends Base
*/
public function tasks()
{
- $project_id = $this->request->getIntegerParam('project_id');
- $project = $this->project->getById($project_id);
-
- if (! $project) {
- $this->session->flashError(t('Project not found.'));
- $this->response->redirect('?controller=project');
- }
-
- $this->checkProjectPermissions($project['id']);
+ $project = $this->getProject();
$filters = array(
- array('column' => 'project_id', 'operator' => 'eq', 'value' => $project_id),
+ array('column' => 'project_id', 'operator' => 'eq', 'value' => $project['id']),
array('column' => 'is_active', 'operator' => 'eq', 'value' => TaskModel::STATUS_CLOSED),
);
@@ -92,7 +109,7 @@ class Project extends Base
$this->response->html($this->template->layout('project_tasks', array(
'menu' => 'projects',
'project' => $project,
- 'columns' => $this->board->getColumnsList($project_id),
+ 'columns' => $this->board->getColumnsList($project['id']),
'categories' => $this->category->getList($project['id'], false),
'tasks' => $tasks,
'nb_tasks' => $nb_tasks,
@@ -169,12 +186,7 @@ class Project extends Base
*/
public function edit()
{
- $project = $this->project->getById($this->request->getIntegerParam('project_id'));
-
- if (! $project) {
- $this->session->flashError(t('Project not found.'));
- $this->response->redirect('?controller=project');
- }
+ $project = $this->getProject();
$this->response->html($this->template->layout('project_edit', array(
'errors' => array(),
@@ -220,12 +232,7 @@ class Project extends Base
*/
public function confirm()
{
- $project = $this->project->getById($this->request->getIntegerParam('project_id'));
-
- if (! $project) {
- $this->session->flashError(t('Project not found.'));
- $this->response->redirect('?controller=project');
- }
+ $project = $this->getProject();
$this->response->html($this->template->layout('project_remove', array(
'project' => $project,
@@ -298,12 +305,7 @@ class Project extends Base
*/
public function users()
{
- $project = $this->project->getById($this->request->getIntegerParam('project_id'));
-
- if (! $project) {
- $this->session->flashError(t('Project not found.'));
- $this->response->redirect('?controller=project');
- }
+ $project = $this->getProject();
$this->response->html($this->template->layout('project_users', array(
'project' => $project,
diff --git a/app/Core/Cli.php b/app/Core/Cli.php
new file mode 100644
index 000000000..13533b9a1
--- /dev/null
+++ b/app/Core/Cli.php
@@ -0,0 +1,75 @@
+commands[$command] = $callback;
+ }
+
+ /**
+ * Execute a command
+ *
+ * @access public
+ * @param string $command Command name
+ */
+ public function call($command)
+ {
+ if (isset($this->commands[$command])) {
+ $this->commands[$command]();
+ exit;
+ }
+ }
+
+ /**
+ * Determine which command to execute
+ *
+ * @access public
+ */
+ public function execute()
+ {
+ if (php_sapi_name() !== 'cli') {
+ die('This script work only from the command line.');
+ }
+
+ if ($GLOBALS['argc'] === 1) {
+ $this->call($this->default_command);
+ }
+
+ $this->call($GLOBALS['argv'][1]);
+ $this->call($this->default_command);
+ }
+}
diff --git a/app/Core/Registry.php b/app/Core/Registry.php
index 0311dc62c..d8b9063e7 100644
--- a/app/Core/Registry.php
+++ b/app/Core/Registry.php
@@ -1,6 +1,7 @@
status($status_code);
+ $this->nocache();
+ header('Content-Type: text/csv');
+ Tool::csv($data);
+ exit;
+ }
+
/**
* Send a Json response
*
@@ -83,7 +99,6 @@ class Response
$this->nocache();
header('Content-Type: application/json');
echo json_encode($data);
-
exit;
}
@@ -100,7 +115,6 @@ class Response
$this->nocache();
header('Content-Type: text/plain; charset=utf-8');
echo $data;
-
exit;
}
@@ -117,7 +131,6 @@ class Response
$this->nocache();
header('Content-Type: text/html; charset=utf-8');
echo $data;
-
exit;
}
@@ -134,7 +147,6 @@ class Response
$this->nocache();
header('Content-Type: text/xml; charset=utf-8');
echo $data;
-
exit;
}
@@ -169,7 +181,6 @@ class Response
header('Content-Transfer-Encoding: binary');
header('Content-Type: application/octet-stream');
echo $data;
-
exit;
}
diff --git a/app/Core/Tool.php b/app/Core/Tool.php
new file mode 100644
index 000000000..ade99cad1
--- /dev/null
+++ b/app/Core/Tool.php
@@ -0,0 +1,34 @@
+get($identifier, $identifier));
- foreach ($args as &$arg) {
- $arg = htmlspecialchars($arg, ENT_QUOTES, 'UTF-8', false);
+ if (self::$enable_escaping) {
+ foreach ($args as &$arg) {
+ $arg = htmlspecialchars($arg, ENT_QUOTES, 'UTF-8', false);
+ }
}
return call_user_func_array(
diff --git a/app/Locales/de_DE/translations.php b/app/Locales/de_DE/translations.php
index b4dd8a382..01be45c77 100644
--- a/app/Locales/de_DE/translations.php
+++ b/app/Locales/de_DE/translations.php
@@ -387,4 +387,13 @@ return array(
'Unlink my GitHub Account' => 'Verbindung mit meinem GitHub Account trennen',
'Created by %s' => 'Erstellt durch %s',
'Last modified on %B %e, %G at %k:%M %p' => 'Letzte Änderung am %d.%m.%Y um %H:%M',
+ // 'Tasks Export' => '',
+ // 'Tasks exportation for "%s"' => '',
+ // 'Start Date' => '',
+ // 'End Date' => '',
+ // 'Execute' => '',
+ // 'Task Id' => '',
+ // 'Creator' => '',
+ // 'Modification date' => '',
+ // 'Completion date' => '',
);
diff --git a/app/Locales/es_ES/translations.php b/app/Locales/es_ES/translations.php
index 2dd3765f2..2b7420d98 100644
--- a/app/Locales/es_ES/translations.php
+++ b/app/Locales/es_ES/translations.php
@@ -190,7 +190,7 @@ return array(
'Timezone' => 'Zona horaria',
'Sorry, I didn\'t found this information in my database!' => 'Lo siento no he encontrado información en la base de datos!',
'Page not found' => 'Página no encontrada',
- 'Story Points' => 'Complejidad',
+ 'Complexity' => 'Complejidad',
'limit' => 'límite',
'Task limit' => 'Número máximo de tareas',
'This value must be greater than %d' => 'Este valor no debe de ser más grande que %d',
@@ -386,4 +386,13 @@ return array(
// 'Unlink my GitHub Account' => '',
// 'Created by %s' => 'Créé par %s',
// 'Last modified on %B %e, %G at %k:%M %p' => '',
+ // 'Tasks Export' => '',
+ // 'Tasks exportation for "%s"' => '',
+ // 'Start Date' => '',
+ // 'End Date' => '',
+ // 'Execute' => '',
+ // 'Task Id' => '',
+ // 'Creator' => '',
+ // 'Modification date' => '',
+ // 'Completion date' => '',
);
diff --git a/app/Locales/fr_FR/translations.php b/app/Locales/fr_FR/translations.php
index 5067ea617..3d1d313b6 100644
--- a/app/Locales/fr_FR/translations.php
+++ b/app/Locales/fr_FR/translations.php
@@ -190,7 +190,7 @@ return array(
'Timezone' => 'Fuseau horaire',
'Sorry, I didn\'t found this information in my database!' => 'Désolé, je n\'ai pas trouvé cette information dans ma base de données !',
'Page not found' => 'Page introuvable',
- 'Story Points' => 'Complexité',
+ 'Complexity' => 'Complexité',
'limit' => 'limite',
'Task limit' => 'Nombre maximum de tâches',
'This value must be greater than %d' => 'Cette valeur doit être plus grande que %d',
@@ -384,4 +384,13 @@ return array(
'Unlink my GitHub Account' => 'Ne plus utiliser mon compte Github',
'Created by %s' => 'Créé par %s',
'Last modified on %B %e, %G at %k:%M %p' => 'Modifié le %d/%m/%Y à %H:%M',
+ 'Tasks Export' => 'Exportation des tâches',
+ 'Tasks exportation for "%s"' => 'Exportation des tâches pour « %s »',
+ 'Start Date' => 'Date de début',
+ 'End Date' => 'Date de fin',
+ 'Execute' => 'Exécuter',
+ 'Task Id' => 'Identifiant de la tâche',
+ 'Creator' => 'Créateur',
+ 'Modification date' => 'Date de modification',
+ 'Completion date' => 'Date de complétion',
);
diff --git a/app/Locales/pl_PL/translations.php b/app/Locales/pl_PL/translations.php
index a96d56727..eaafe7c5e 100644
--- a/app/Locales/pl_PL/translations.php
+++ b/app/Locales/pl_PL/translations.php
@@ -192,7 +192,7 @@ return array(
'Description' => 'Opis',
'Sorry, I didn\'t found this information in my database!' => 'Niestety nie znaleziono tej informacji w bazie danych',
'Page not found' => 'Strona nie istnieje',
- 'Story Points' => 'Poziom trudności',
+ 'Complexity' => 'Poziom trudności',
'limit' => 'limit',
'Task limit' => 'Limit zadań',
'This value must be greater than %d' => 'Wartość musi być większa niż %d',
@@ -387,4 +387,13 @@ return array(
// 'Unlink my GitHub Account' => '',
// 'Created by %s' => 'Créé par %s',
// 'Last modified on %B %e, %G at %k:%M %p' => '',
+ // 'Tasks Export' => '',
+ // 'Tasks exportation for "%s"' => '',
+ // 'Start Date' => '',
+ // 'End Date' => '',
+ // 'Execute' => '',
+ // 'Task Id' => '',
+ // 'Creator' => '',
+ // 'Modification date' => '',
+ // 'Completion date' => '',
);
diff --git a/app/Locales/pt_BR/translations.php b/app/Locales/pt_BR/translations.php
index 8ba9b64a5..a422a6602 100644
--- a/app/Locales/pt_BR/translations.php
+++ b/app/Locales/pt_BR/translations.php
@@ -189,7 +189,7 @@ return array(
'Timezone' => 'Fuso horário',
'Sorry, I didn\'t found this information in my database!' => 'Desculpe, não encontrei esta informação no meu banco de dados!',
'Page not found' => 'Página não encontrada',
- 'Story Points' => 'Complexidade',
+ 'Complexity' => 'Complexidade',
'limit' => 'limite',
'Task limit' => 'Limite da tarefa',
'This value must be greater than %d' => 'Este valor deve ser maior que %d',
@@ -384,4 +384,13 @@ return array(
// 'Unlink my GitHub Account' => '',
// 'Created by %s' => 'Créé par %s',
// 'Last modified on %B %e, %G at %k:%M %p' => '',
+ // 'Tasks Export' => '',
+ // 'Tasks exportation for "%s"' => '',
+ // 'Start Date' => '',
+ // 'End Date' => '',
+ // 'Execute' => '',
+ // 'Task Id' => '',
+ // 'Creator' => '',
+ // 'Modification date' => '',
+ // 'Completion date' => '',
);
diff --git a/app/Locales/sv_SE/translations.php b/app/Locales/sv_SE/translations.php
index cae457b10..d69f66046 100644
--- a/app/Locales/sv_SE/translations.php
+++ b/app/Locales/sv_SE/translations.php
@@ -190,7 +190,7 @@ return array(
'Timezone' => 'Tidszon',
'Sorry, I didn\'t found this information in my database!' => 'Informationen kunde inte hittas i databasen.',
'Page not found' => 'Sidan hittas inte',
- 'Story Points' => 'Ungefärligt antal timmar',
+ 'Complexity' => 'Ungefärligt antal timmar',
'limit' => 'max',
'Task limit' => 'Uppgiftsbegränsning',
'This value must be greater than %d' => 'Värdet måste vara större än %d',
@@ -385,5 +385,14 @@ return array(
'Link my GitHub Account' => 'Anslut mitt GitHub-konto',
'Unlink my GitHub Account' => 'Koppla ifrån mitt GitHub-konto',
'Created by %s' => 'Skapad av %s',
- 'Last modified on %B %e, %G at %k:%M %p' => 'Senaste ändring %B %e, %G kl %k:%M %p'',
+ 'Last modified on %B %e, %G at %k:%M %p' => 'Senaste ändring %B %e, %G kl %k:%M %p',
+ // 'Tasks Export' => '',
+ // 'Tasks exportation for "%s"' => '',
+ // 'Start Date' => '',
+ // 'End Date' => '',
+ // 'Execute' => '',
+ // 'Task Id' => '',
+ // 'Creator' => '',
+ // 'Modification date' => '',
+ // 'Completion date' => '',
);
diff --git a/app/Locales/zh_CN/translations.php b/app/Locales/zh_CN/translations.php
index 9ed0c8a3f..de12c4248 100644
--- a/app/Locales/zh_CN/translations.php
+++ b/app/Locales/zh_CN/translations.php
@@ -193,7 +193,7 @@ return array(
'Timezone' => '时区',
'Sorry, I didn\'t found this information in my database!' => '抱歉,无法在数据库中找到该信息!',
'Page not found' => '页面未找到',
- 'Story Points' => '评估分值',
+ 'Complexity' => '评估分值',
'limit' => '限制',
'Task limit' => '任务限制',
'This value must be greater than %d' => '该数值必须大于%d',
@@ -392,4 +392,13 @@ return array(
// 'Unlink my GitHub Account' => '',
// 'Created by %s' => 'Créé par %s',
// 'Last modified on %B %e, %G at %k:%M %p' => '',
+ // 'Tasks Export' => '',
+ // 'Tasks exportation for "%s"' => '',
+ // 'Start Date' => '',
+ // 'End Date' => '',
+ // 'Execute' => '',
+ // 'Task Id' => '',
+ // 'Creator' => '',
+ // 'Modification date' => '',
+ // 'Completion date' => '',
);
diff --git a/app/Model/Task.php b/app/Model/Task.php
index 8933cb148..0b2b9cf9b 100644
--- a/app/Model/Task.php
+++ b/app/Model/Task.php
@@ -5,6 +5,7 @@ namespace Model;
use SimpleValidator\Validator;
use SimpleValidator\Validators;
use DateTime;
+use PDO;
/**
* Task model
@@ -106,7 +107,7 @@ class Task extends Base
';
$rq = $this->db->execute($sql, array($task_id));
- return $rq->fetch(\PDO::FETCH_ASSOC);
+ return $rq->fetch(PDO::FETCH_ASSOC);
}
else {
@@ -167,12 +168,14 @@ class Task extends Base
'tasks.title',
'tasks.description',
'tasks.date_creation',
+ 'tasks.date_modification',
'tasks.date_completed',
'tasks.date_due',
'tasks.color_id',
'tasks.project_id',
'tasks.column_id',
'tasks.owner_id',
+ 'tasks.creator_id',
'tasks.position',
'tasks.is_active',
'tasks.score',
@@ -667,4 +670,112 @@ class Task extends Base
'Y_m_d',
);
}
+
+ /**
+ * For a given timestamp, reset the date to midnight
+ *
+ * @access public
+ * @param integer $timestamp Timestamp
+ * @return integer
+ */
+ public function resetDateToMidnight($timestamp)
+ {
+ return mktime(0, 0, 0, date('m', $timestamp), date('d', $timestamp), date('Y', $timestamp));
+ }
+
+ /**
+ * Export a list of tasks for a given project and date range
+ *
+ * @access public
+ * @param integer $project_id Project id
+ * @param mixed $from Start date (timestamp or user formatted date)
+ * @param mixed $to End date (timestamp or user formatted date)
+ * @return array
+ */
+ public function export($project_id, $from, $to)
+ {
+ $sql = '
+ SELECT
+ tasks.id,
+ projects.name AS project_name,
+ tasks.is_active,
+ project_has_categories.name AS category_name,
+ columns.title AS column_title,
+ tasks.position,
+ tasks.color_id,
+ tasks.date_due,
+ creators.username AS creator_username,
+ users.username AS assignee_username,
+ tasks.score,
+ tasks.title,
+ tasks.date_creation,
+ tasks.date_modification,
+ tasks.date_completed
+ FROM tasks
+ LEFT JOIN users ON users.id = tasks.owner_id
+ LEFT JOIN users AS creators ON creators.id = tasks.creator_id
+ LEFT JOIN project_has_categories ON project_has_categories.id = tasks.category_id
+ LEFT JOIN columns ON columns.id = tasks.column_id
+ LEFT JOIN projects ON projects.id = tasks.project_id
+ WHERE tasks.date_creation >= ? AND tasks.date_creation <= ? AND tasks.project_id = ?
+ ';
+
+ if (! is_numeric($from)) {
+ $from = $this->resetDateToMidnight($this->parseDate($from));
+ }
+
+ if (! is_numeric($to)) {
+ $to = $this->resetDateToMidnight(strtotime('+1 day', $this->parseDate($to)));
+ }
+
+ $rq = $this->db->execute($sql, array($from, $to, $project_id));
+ $tasks = $rq->fetchAll(PDO::FETCH_ASSOC);
+
+ $columns = array(
+ t('Task Id'),
+ t('Project'),
+ t('Status'),
+ t('Category'),
+ t('Column'),
+ t('Position'),
+ t('Color'),
+ t('Due date'),
+ t('Creator'),
+ t('Assignee'),
+ t('Complexity'),
+ t('Title'),
+ t('Creation date'),
+ t('Modification date'),
+ t('Completion date'),
+ );
+
+ $results = array($columns);
+
+ foreach ($tasks as &$task) {
+ $results[] = array_values($this->formatOutput($task));
+ }
+
+ return $results;
+ }
+
+ /**
+ * Format the output of a task array
+ *
+ * @access public
+ * @param array $task Task properties
+ * @return array
+ */
+ public function formatOutput(array &$task)
+ {
+ $colors = $this->getColors();
+ $task['score'] = $task['score'] ?: '';
+ $task['is_active'] = $task['is_active'] == self::STATUS_OPEN ? t('Open') : t('Closed');
+ $task['color_id'] = $colors[$task['color_id']];
+ $task['date_creation'] = date('Y-m-d', $task['date_creation']);
+ $task['date_due'] = $task['date_due'] ? date('Y-m-d', $task['date_due']) : '';
+ $task['date_modification'] = $task['date_modification'] ? date('Y-m-d', $task['date_modification']) : '';
+ $task['date_completed'] = $task['date_completed'] ? date('Y-m-d', $task['date_completed']) : '';
+
+ return $task;
+ }
}
diff --git a/app/Templates/project_export.php b/app/Templates/project_export.php
new file mode 100644
index 000000000..946a68a87
--- /dev/null
+++ b/app/Templates/project_export.php
@@ -0,0 +1,33 @@
+
+ = t('Tasks exportation for "%s"', $project['name']) ?>
+
+
+