Add Task CSV export and Kanboard CLI
This commit is contained in:
parent
42ca8390d4
commit
833bd3f8a9
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
*
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -0,0 +1,75 @@
|
|||
<?php
|
||||
|
||||
namespace Core;
|
||||
|
||||
use Closure;
|
||||
|
||||
/**
|
||||
* CLI class
|
||||
*
|
||||
* @package core
|
||||
* @author Frederic Guillot
|
||||
*/
|
||||
class Cli
|
||||
{
|
||||
/**
|
||||
* Default command name
|
||||
*
|
||||
* @access public
|
||||
* @var string
|
||||
*/
|
||||
public $default_command = 'help';
|
||||
|
||||
/**
|
||||
* List of registered commands
|
||||
*
|
||||
* @access private
|
||||
* @var array
|
||||
*/
|
||||
private $commands = array();
|
||||
|
||||
/**
|
||||
*
|
||||
*
|
||||
* @access public
|
||||
* @param string $command Command name
|
||||
* @param Closure $callback Command callback
|
||||
*/
|
||||
public function register($command, Closure $callback)
|
||||
{
|
||||
$this->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);
|
||||
}
|
||||
}
|
||||
|
|
@ -1,6 +1,7 @@
|
|||
<?php
|
||||
|
||||
namespace Core;
|
||||
|
||||
use RuntimeException;
|
||||
|
||||
/**
|
||||
|
|
|
|||
|
|
@ -70,6 +70,22 @@ class Response
|
|||
exit;
|
||||
}
|
||||
|
||||
/**
|
||||
* Send a CSV response
|
||||
*
|
||||
* @access public
|
||||
* @param array $data Data to serialize in csv
|
||||
* @param integer $status_code HTTP status code
|
||||
*/
|
||||
public function csv(array $data, $status_code = 200)
|
||||
{
|
||||
$this->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;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,34 @@
|
|||
<?php
|
||||
|
||||
namespace Core;
|
||||
|
||||
/**
|
||||
* Tool class
|
||||
*
|
||||
* @package core
|
||||
* @author Frederic Guillot
|
||||
*/
|
||||
class Tool
|
||||
{
|
||||
/**
|
||||
* Write a CSV file
|
||||
*
|
||||
* @static
|
||||
* @access public
|
||||
* @param array $rows Array of rows
|
||||
* @param string $filename Output filename
|
||||
*/
|
||||
public static function csv(array $rows, $filename = 'php://output')
|
||||
{
|
||||
$fp = fopen($filename, 'w');
|
||||
|
||||
if (is_resource($fp)) {
|
||||
|
||||
foreach ($rows as $fields) {
|
||||
fputcsv($fp, $fields);
|
||||
}
|
||||
|
||||
fclose($fp);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -26,6 +26,26 @@ class Translator
|
|||
*/
|
||||
private static $locales = array();
|
||||
|
||||
/**
|
||||
* Flag to enable HTML escaping
|
||||
*
|
||||
* @static
|
||||
* @access private
|
||||
* @var boolean
|
||||
*/
|
||||
private static $enable_escaping = true;
|
||||
|
||||
/**
|
||||
* Disable HTML escaping for translations
|
||||
*
|
||||
* @static
|
||||
* @access public
|
||||
*/
|
||||
public static function disableEscaping()
|
||||
{
|
||||
self::$enable_escaping = false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a translation
|
||||
*
|
||||
|
|
@ -42,8 +62,10 @@ class Translator
|
|||
array_shift($args);
|
||||
array_unshift($args, $this->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(
|
||||
|
|
|
|||
|
|
@ -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' => '',
|
||||
);
|
||||
|
|
|
|||
|
|
@ -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' => '',
|
||||
);
|
||||
|
|
|
|||
|
|
@ -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',
|
||||
);
|
||||
|
|
|
|||
|
|
@ -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' => '',
|
||||
);
|
||||
|
|
|
|||
|
|
@ -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' => '',
|
||||
);
|
||||
|
|
|
|||
|
|
@ -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' => '',
|
||||
);
|
||||
|
|
|
|||
|
|
@ -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' => '',
|
||||
);
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,33 @@
|
|||
<section id="main">
|
||||
<div class="page-header">
|
||||
<h2>
|
||||
<?= t('Tasks exportation for "%s"', $project['name']) ?>
|
||||
</h2>
|
||||
<ul>
|
||||
<li><a href="?controller=board&action=show&project_id=<?= $project['id'] ?>"><?= t('Back to the board') ?></a></li>
|
||||
<li><a href="?controller=project&action=index"><?= t('List of projects') ?></a></li>
|
||||
</ul>
|
||||
</div>
|
||||
<section id="project-section">
|
||||
|
||||
<form method="get" action="?" autocomplete="off">
|
||||
|
||||
<?= Helper\form_hidden('controller', $values) ?>
|
||||
<?= Helper\form_hidden('action', $values) ?>
|
||||
<?= Helper\form_hidden('project_id', $values) ?>
|
||||
|
||||
<?= Helper\form_label(t('Start Date'), 'from') ?>
|
||||
<?= Helper\form_text('from', $values, $errors, array('required', 'placeholder="'.t('month/day/year').'"'), 'form-date') ?><br/>
|
||||
|
||||
<?= Helper\form_label(t('End Date'), 'to') ?>
|
||||
<?= Helper\form_text('to', $values, $errors, array('required', 'placeholder="'.t('month/day/year').'"'), 'form-date') ?>
|
||||
|
||||
<div class="form-help"><?= t('Others formats accepted: %s and %s', date('Y-m-d'), date('Y_m_d')) ?></div>
|
||||
|
||||
<div class="form-actions">
|
||||
<input type="submit" value="<?= t('Execute') ?>" class="btn btn-blue"/>
|
||||
</div>
|
||||
</form>
|
||||
|
||||
</section>
|
||||
</section>
|
||||
|
|
@ -89,6 +89,9 @@
|
|||
<li>
|
||||
<a href="?controller=board&action=readonly&token=<?= $project['token'] ?>" target="_blank"><?= t('Public link') ?></a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="?controller=project&action=export&project_id=<?= $project['id'] ?>"><?= t('Tasks Export') ?></a>
|
||||
</li>
|
||||
</ul>
|
||||
</td>
|
||||
<?php endif ?>
|
||||
|
|
|
|||
|
|
@ -7,7 +7,7 @@
|
|||
</ul>
|
||||
<?php endif ?>
|
||||
</div>
|
||||
<section>
|
||||
<section id="task-section">
|
||||
<form method="post" action="?controller=task&action=update&task_id=<?= $task['id'] ?>&ajax=<?= $ajax ?>" autocomplete="off">
|
||||
|
||||
<?= Helper\form_csrf() ?>
|
||||
|
|
@ -39,7 +39,7 @@
|
|||
<?= Helper\form_label(t('Color'), 'color_id') ?>
|
||||
<?= Helper\form_select('color_id', $colors_list, $values, $errors) ?><br/>
|
||||
|
||||
<?= Helper\form_label(t('Story Points'), 'score') ?>
|
||||
<?= Helper\form_label(t('Complexity'), 'score') ?>
|
||||
<?= Helper\form_number('score', $values, $errors) ?><br/>
|
||||
|
||||
<?= Helper\form_label(t('Due Date'), 'date_due') ?>
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@
|
|||
<li><a href="?controller=board&action=show&project_id=<?= $task['project_id'] ?>"><?= t('Back to the board') ?></a></li>
|
||||
</ul>
|
||||
</div>
|
||||
<section class="task-show">
|
||||
<section class="task-show" id="task-section">
|
||||
|
||||
<?= Helper\template('task_sidebar', array('task' => $task)) ?>
|
||||
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@
|
|||
<div class="page-header">
|
||||
<h2><?= t('New task') ?></h2>
|
||||
</div>
|
||||
<section>
|
||||
<section id="task-section">
|
||||
<form method="post" action="?controller=task&action=save" autocomplete="off">
|
||||
|
||||
<?= Helper\form_csrf() ?>
|
||||
|
|
@ -35,7 +35,7 @@
|
|||
<?= Helper\form_label(t('Color'), 'color_id') ?>
|
||||
<?= Helper\form_select('color_id', $colors_list, $values, $errors) ?><br/>
|
||||
|
||||
<?= Helper\form_label(t('Story Points'), 'score') ?>
|
||||
<?= Helper\form_label(t('Complexity'), 'score') ?>
|
||||
<?= Helper\form_number('score', $values, $errors) ?><br/>
|
||||
|
||||
<?= Helper\form_label(t('Due Date'), 'date_due') ?>
|
||||
|
|
|
|||
|
|
@ -28,8 +28,8 @@ var Kanboard = (function() {
|
|||
},
|
||||
|
||||
// Return true if the page is visible
|
||||
IsVisible: function()
|
||||
{
|
||||
IsVisible: function() {
|
||||
|
||||
var property = "";
|
||||
|
||||
if (typeof document.hidden !== "undefined") {
|
||||
|
|
@ -47,6 +47,17 @@ var Kanboard = (function() {
|
|||
}
|
||||
|
||||
return true;
|
||||
},
|
||||
|
||||
// Common init
|
||||
Before: function() {
|
||||
|
||||
// Datepicker
|
||||
$(".form-date").datepicker({
|
||||
showOtherMonths: true,
|
||||
selectOtherMonths: true,
|
||||
dateFormat: 'yy-mm-dd'
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
|
|
@ -228,12 +239,7 @@ Kanboard.Task = (function() {
|
|||
return {
|
||||
Init: function() {
|
||||
|
||||
// Datepicker for the due date
|
||||
$("#form-date_due").datepicker({
|
||||
showOtherMonths: true,
|
||||
selectOtherMonths: true,
|
||||
dateFormat: 'yy-mm-dd'
|
||||
});
|
||||
Kanboard.Before();
|
||||
|
||||
// Image preview for attachments
|
||||
$(".file-popover").click(Kanboard.Popover);
|
||||
|
|
@ -243,13 +249,28 @@ Kanboard.Task = (function() {
|
|||
})();
|
||||
|
||||
|
||||
// Project related functions
|
||||
Kanboard.Project = (function() {
|
||||
|
||||
return {
|
||||
Init: function() {
|
||||
Kanboard.Before();
|
||||
}
|
||||
};
|
||||
|
||||
})();
|
||||
|
||||
|
||||
// Initialization
|
||||
$(function() {
|
||||
|
||||
if ($("#board").length) {
|
||||
Kanboard.Board.Init();
|
||||
}
|
||||
else {
|
||||
else if ($("#task-section").length) {
|
||||
Kanboard.Task.Init();
|
||||
}
|
||||
else if ($("#project-section").length) {
|
||||
Kanboard.Project.Init();
|
||||
}
|
||||
});
|
||||
|
|
|
|||
|
|
@ -0,0 +1,51 @@
|
|||
#!/usr/bin/env php
|
||||
<?php
|
||||
|
||||
require __DIR__.'/app/common.php';
|
||||
|
||||
use Core\Cli;
|
||||
use Core\Tool;
|
||||
use Core\Translator;
|
||||
use Model\Config;
|
||||
use Model\Task;
|
||||
|
||||
$config = new Config($registry->shared('db'), $registry->shared('event'));
|
||||
|
||||
// Load translations
|
||||
$language = $config->get('language', 'en_US');
|
||||
if ($language !== 'en_US') Translator::load($language);
|
||||
|
||||
// Set timezone
|
||||
date_default_timezone_set($config->get('timezone', 'UTC'));
|
||||
|
||||
// Setup CLI
|
||||
$cli = new Cli;
|
||||
|
||||
// Usage
|
||||
$cli->register('help', function() {
|
||||
echo 'Kanboard command line interface'.PHP_EOL.'==============================='.PHP_EOL;
|
||||
echo '- Task export to stdout (CSV format): '.$GLOBALS['argv'][0].' export-csv <project_id> <start_date> <end_date>'.PHP_EOL;
|
||||
});
|
||||
|
||||
// CSV Export
|
||||
$cli->register('export-csv', function() use ($cli, $registry) {
|
||||
|
||||
if ($GLOBALS['argc'] !== 5) {
|
||||
$cli->call($cli->default_command);
|
||||
}
|
||||
|
||||
$project_id = $GLOBALS['argv'][2];
|
||||
$start_date = $GLOBALS['argv'][3];
|
||||
$end_date = $GLOBALS['argv'][4];
|
||||
|
||||
Translator::disableEscaping();
|
||||
|
||||
$task = new Task($registry->shared('db'), $registry->shared('event'));
|
||||
$data = $task->export($project_id, $start_date, $end_date);
|
||||
|
||||
if (is_array($data)) {
|
||||
Tool::csv($data);
|
||||
}
|
||||
});
|
||||
|
||||
$cli->execute();
|
||||
|
|
@ -173,6 +173,7 @@ class Api extends PHPUnit_Framework_TestCase
|
|||
$task['color_id'] = 'green';
|
||||
$task['column_id'] = 1;
|
||||
$task['description'] = 'test';
|
||||
$task['date_due'] = '';
|
||||
|
||||
$this->assertTrue($this->client->updateTask($task));
|
||||
}
|
||||
|
|
|
|||
|
|
@ -4,9 +4,45 @@ require_once __DIR__.'/Base.php';
|
|||
|
||||
use Model\Task;
|
||||
use Model\Project;
|
||||
use Model\Category;
|
||||
|
||||
class TaskTest extends Base
|
||||
{
|
||||
public function testExport()
|
||||
{
|
||||
$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' => 'Export Project')));
|
||||
$this->assertNotFalse($c->create(array('name' => 'Category #1', 'project_id' => 1)));
|
||||
$this->assertNotFalse($c->create(array('name' => 'Category #2', 'project_id' => 1)));
|
||||
$this->assertNotFalse($c->create(array('name' => 'Category #3', 'project_id' => 1)));
|
||||
|
||||
for ($i = 1; $i <= 100; $i++) {
|
||||
|
||||
$task = array(
|
||||
'title' => 'Task #'.$i,
|
||||
'project_id' => 1,
|
||||
'column_id' => rand(1, 3),
|
||||
'creator_id' => rand(0, 1),
|
||||
'owner_id' => rand(0, 1),
|
||||
'color_id' => rand(0, 1) === 0 ? 'green' : 'purple',
|
||||
'category_id' => rand(0, 3),
|
||||
'date_due' => array_rand(array(0, date('Y-m-d'), date('Y-m-d', strtotime('+'.$i.'day')))),
|
||||
'score' => rand(0, 21)
|
||||
);
|
||||
|
||||
$this->assertEquals($i, $t->create($task));
|
||||
}
|
||||
|
||||
$rows = $t->export(1, strtotime('-1 day'), strtotime('+1 day'));
|
||||
$this->assertEquals($i, count($rows));
|
||||
$this->assertEquals('Task Id', $rows[0][0]);
|
||||
$this->assertEquals(1, $rows[1][0]);
|
||||
$this->assertEquals('Task #'.($i - 1), $rows[$i - 1][11]);
|
||||
}
|
||||
|
||||
public function testFilter()
|
||||
{
|
||||
$t = new Task($this->db, $this->event);
|
||||
|
|
|
|||
Loading…
Reference in New Issue