Add Task CSV export and Kanboard CLI

This commit is contained in:
Frédéric Guillot 2014-07-19 22:08:07 -02:30
parent 42ca8390d4
commit 833bd3f8a9
25 changed files with 555 additions and 89 deletions

View File

@ -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;
}
}

View File

@ -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)
*

View File

@ -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,

75
app/Core/Cli.php Normal file
View File

@ -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);
}
}

View File

@ -1,6 +1,7 @@
<?php
namespace Core;
use RuntimeException;
/**

View File

@ -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;
}

34
app/Core/Tool.php Normal file
View File

@ -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);
}
}
}

View File

@ -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(

View File

@ -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' => '',
);

View File

@ -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' => '',
);

View File

@ -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',
);

View File

@ -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' => '',
);

View File

@ -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' => '',
);

View File

@ -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' => '',
);

View File

@ -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' => '',
);

View File

@ -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;
}
}

View File

@ -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&amp;action=show&amp;project_id=<?= $project['id'] ?>"><?= t('Back to the board') ?></a></li>
<li><a href="?controller=project&amp;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>

View File

@ -89,6 +89,9 @@
<li>
<a href="?controller=board&amp;action=readonly&amp;token=<?= $project['token'] ?>" target="_blank"><?= t('Public link') ?></a>
</li>
<li>
<a href="?controller=project&amp;action=export&amp;project_id=<?= $project['id'] ?>"><?= t('Tasks Export') ?></a>
</li>
</ul>
</td>
<?php endif ?>

View File

@ -7,7 +7,7 @@
</ul>
<?php endif ?>
</div>
<section>
<section id="task-section">
<form method="post" action="?controller=task&amp;action=update&amp;task_id=<?= $task['id'] ?>&amp;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') ?>

View File

@ -5,7 +5,7 @@
<li><a href="?controller=board&amp;action=show&amp;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)) ?>

View File

@ -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&amp;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') ?>

View File

@ -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();
}
});

51
kanboard Executable file
View File

@ -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();

View File

@ -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));
}

View File

@ -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);