Improve iCalendar feed to include tasks with start/end date and due date with a time

This commit is contained in:
Frederic Guillot 2017-11-08 14:50:02 -08:00
parent 5e98a66aeb
commit cafbd1f5a7
12 changed files with 101 additions and 144 deletions

View File

@ -12,6 +12,7 @@ New features:
Improvements:
* Improve iCalendar feed to include tasks with start/end date and due date with a time
* You can get an archive of Kanboard by using the download button in Github or the command git archive
Version 1.0.48 (October 23, 2017)

View File

@ -5,6 +5,7 @@ namespace Kanboard\Controller;
use Kanboard\Core\Controller\AccessForbiddenException;
use Kanboard\Core\Filter\QueryBuilder;
use Kanboard\Filter\TaskAssigneeFilter;
use Kanboard\Filter\TaskDueDateRangeFilter;
use Kanboard\Filter\TaskProjectFilter;
use Kanboard\Filter\TaskStatusFilter;
use Kanboard\Model\TaskModel;
@ -18,81 +19,97 @@ use Eluceo\iCal\Component\Calendar as iCalendar;
*/
class ICalendarController extends BaseController
{
/**
* Get user iCalendar
*
* @access public
*/
public function user()
{
$token = $this->request->getStringParam('token');
$user = $this->userModel->getByToken($token);
// Token verification
if (empty($user)) {
throw AccessForbiddenException::getInstance()->withoutLayout();
}
// Common filter
$queryBuilder = new QueryBuilder();
$queryBuilder
->withQuery($this->taskFinderModel->getICalQuery())
->withFilter(new TaskStatusFilter(TaskModel::STATUS_OPEN))
->withFilter(new TaskAssigneeFilter($user['id']));
$startRange = $this->request->getStringParam('start', strtotime('-2 months'));
$endRange = $this->request->getStringParam('end', strtotime('+6 months'));
$startColumn = $this->configModel->get('calendar_project_tasks', 'date_started');
// Calendar properties
$calendar = new iCalendar('Kanboard');
$calendar->setName($user['name'] ?: $user['username']);
$calendar->setDescription($user['name'] ?: $user['username']);
$calendar->setPublishedTTL('PT1H');
$this->renderCalendar($queryBuilder, $calendar);
$queryDueDateOnly = QueryBuilder::create()
->withQuery($this->taskFinderModel->getICalQuery())
->withFilter(new TaskStatusFilter(TaskModel::STATUS_OPEN))
->withFilter(new TaskDueDateRangeFilter(array($startRange, $endRange)))
->withFilter(new TaskAssigneeFilter($user['id']))
->getQuery();
$queryStartAndDueDate = QueryBuilder::create()
->withQuery($this->taskFinderModel->getICalQuery())
->withFilter(new TaskStatusFilter(TaskModel::STATUS_OPEN))
->withFilter(new TaskAssigneeFilter($user['id']))
->getQuery()
->addCondition($this->getConditionForTasksWithStartAndDueDate($startRange, $endRange, $startColumn, 'date_due'));
$this->response->ical($this->taskICalFormatter
->setCalendar($calendar)
->addTasksWithDueDateOnly($queryDueDateOnly)
->addTasksWithStartAndDueDate($queryStartAndDueDate, $startColumn, 'date_due')
->format());
}
/**
* Get project iCalendar
*
* @access public
*/
public function project()
{
$token = $this->request->getStringParam('token');
$project = $this->projectModel->getByToken($token);
// Token verification
if (empty($project)) {
throw AccessForbiddenException::getInstance()->withoutLayout();
}
// Common filter
$queryBuilder = new QueryBuilder();
$queryBuilder
->withQuery($this->taskFinderModel->getICalQuery())
->withFilter(new TaskStatusFilter(TaskModel::STATUS_OPEN))
->withFilter(new TaskProjectFilter($project['id']));
$startRange = $this->request->getStringParam('start', strtotime('-2 months'));
$endRange = $this->request->getStringParam('end', strtotime('+6 months'));
$startColumn = $this->configModel->get('calendar_project_tasks', 'date_started');
// Calendar properties
$calendar = new iCalendar('Kanboard');
$calendar->setName($project['name']);
$calendar->setDescription($project['name']);
$calendar->setPublishedTTL('PT1H');
$this->renderCalendar($queryBuilder, $calendar);
$queryDueDateOnly = QueryBuilder::create()
->withQuery($this->taskFinderModel->getICalQuery())
->withFilter(new TaskStatusFilter(TaskModel::STATUS_OPEN))
->withFilter(new TaskProjectFilter($project['id']))
->withFilter(new TaskDueDateRangeFilter(array($startRange, $endRange)))
->getQuery();
$queryStartAndDueDate = QueryBuilder::create()
->withQuery($this->taskFinderModel->getICalQuery())
->withFilter(new TaskStatusFilter(TaskModel::STATUS_OPEN))
->withFilter(new TaskProjectFilter($project['id']))
->getQuery()
->addCondition($this->getConditionForTasksWithStartAndDueDate($startRange, $endRange, $startColumn, 'date_due'));
$this->response->ical($this->taskICalFormatter
->setCalendar($calendar)
->addTasksWithDueDateOnly($queryDueDateOnly)
->addTasksWithStartAndDueDate($queryStartAndDueDate, $startColumn, 'date_due')
->format());
}
/**
* Common method to render iCal events
*
* @access private
* @param QueryBuilder $queryBuilder
* @param iCalendar $calendar
*/
private function renderCalendar(QueryBuilder $queryBuilder, iCalendar $calendar)
protected function getConditionForTasksWithStartAndDueDate($start_time, $end_time, $start_column, $end_column)
{
$start = $this->request->getStringParam('start', strtotime('-2 month'));
$end = $this->request->getStringParam('end', strtotime('+6 months'));
$start_column = $this->db->escapeIdentifier($start_column);
$end_column = $this->db->escapeIdentifier($end_column);
$this->helper->ical->addTaskDateDueEvents($queryBuilder, $calendar, $start, $end);
$this->response->ical($this->taskICalFormatter->setCalendar($calendar)->format());
$conditions = array(
"($start_column >= '$start_time' AND $start_column <= '$end_time')",
"($start_column <= '$start_time' AND $end_column >= '$start_time')",
"($start_column <= '$start_time' AND ($end_column = '0' OR $end_column IS NULL))",
);
return $start_column.' IS NOT NULL AND '.$start_column.' > 0 AND ('.implode(' OR ', $conditions).')';
}
}

View File

@ -20,6 +20,18 @@ class QueryBuilder
*/
protected $query;
/**
* Create a new class instance
*
* @static
* @access public
* @return static
*/
public static function create()
{
return new static();
}
/**
* Set the query
*

View File

@ -19,7 +19,6 @@ use Pimple\Container;
* @property \Kanboard\Helper\FileHelper $file
* @property \Kanboard\Helper\FormHelper $form
* @property \Kanboard\Helper\HookHelper $hook
* @property \Kanboard\Helper\ICalHelper $ical
* @property \Kanboard\Helper\ModalHelper $modal
* @property \Kanboard\Helper\ModelHelper $model
* @property \Kanboard\Helper\SubtaskHelper $subtask

View File

@ -51,7 +51,7 @@ abstract class BaseFilter
*
* @access public
* @param Table $query
* @return \Kanboard\Core\Filter\FilterInterface
* @return $this
*/
public function withQuery(Table $query)
{
@ -64,7 +64,7 @@ abstract class BaseFilter
*
* @access public
* @param string $value
* @return \Kanboard\Core\Filter\FilterInterface
* @return $this
*/
public function withValue($value)
{

View File

@ -32,6 +32,11 @@ class TaskDueDateRangeFilter extends BaseFilter implements FilterInterface
*/
public function apply()
{
$this->query->beginOr();
$this->query->isNull(TaskModel::TABLE.'.date_started');
$this->query->eq(TaskModel::TABLE.'.date_started', 0);
$this->query->closeOr();
$this->query->gte(TaskModel::TABLE.'.date_due', is_numeric($this->value[0]) ? $this->value[0] : strtotime($this->value[0]));
$this->query->lte(TaskModel::TABLE.'.date_due', is_numeric($this->value[1]) ? $this->value[1] : strtotime($this->value[1]));
return $this;

View File

@ -1,43 +0,0 @@
<?php
namespace Kanboard\Formatter;
/**
* Common class to handle calendar events
*
* @package formatter
* @author Frederic Guillot
*/
abstract class BaseTaskCalendarFormatter extends BaseFormatter
{
/**
* Column used for event start date
*
* @access protected
* @var string
*/
protected $startColumn = 'date_started';
/**
* Column used for event end date
*
* @access protected
* @var string
*/
protected $endColumn = 'date_completed';
/**
* Transform results to calendar events
*
* @access public
* @param string $start_column Column name for the start date
* @param string $end_column Column name for the end date
* @return $this
*/
public function setColumns($start_column, $end_column = '')
{
$this->startColumn = $start_column;
$this->endColumn = $end_column ?: $start_column;
return $this;
}
}

View File

@ -8,6 +8,7 @@ use Eluceo\iCal\Component\Event;
use Eluceo\iCal\Property\Event\Attendees;
use Eluceo\iCal\Property\Event\Organizer;
use Kanboard\Core\Filter\FormatterInterface;
use PicoDb\Table;
/**
* iCal event formatter for tasks
@ -15,15 +16,15 @@ use Kanboard\Core\Filter\FormatterInterface;
* @package formatter
* @author Frederic Guillot
*/
class TaskICalFormatter extends BaseTaskCalendarFormatter implements FormatterInterface
class TaskICalFormatter extends BaseFormatter implements FormatterInterface
{
/**
* Calendar object
*
* @access private
* @access protected
* @var \Eluceo\iCal\Component\Calendar
*/
private $vCalendar;
protected $vCalendar;
/**
* Get Ical events
@ -41,7 +42,7 @@ class TaskICalFormatter extends BaseTaskCalendarFormatter implements FormatterIn
*
* @access public
* @param \Eluceo\iCal\Component\Calendar $vCalendar
* @return FormatterInterface
* @return $this
*/
public function setCalendar(Calendar $vCalendar)
{
@ -53,18 +54,21 @@ class TaskICalFormatter extends BaseTaskCalendarFormatter implements FormatterIn
* Transform results to iCal events
*
* @access public
* @return FormatterInterface
* @param Table $query
* @param string $startColumn
* @param string $endColumn
* @return $this
*/
public function addDateTimeEvents()
public function addTasksWithStartAndDueDate(Table $query, $startColumn, $endColumn)
{
foreach ($this->query->findAll() as $task) {
foreach ($query->findAll() as $task) {
$start = new DateTime;
$start->setTimestamp($task[$this->startColumn]);
$start->setTimestamp($task[$startColumn]);
$end = new DateTime;
$end->setTimestamp($task[$this->endColumn] ?: time());
$end->setTimestamp($task[$endColumn] ?: time());
$vEvent = $this->getTaskIcalEvent($task, 'task-#'.$task['id'].'-'.$this->startColumn.'-'.$this->endColumn);
$vEvent = $this->getTaskIcalEvent($task, 'task-#'.$task['id'].'-'.$startColumn.'-'.$endColumn);
$vEvent->setDtStart($start);
$vEvent->setDtEnd($end);
@ -78,18 +82,22 @@ class TaskICalFormatter extends BaseTaskCalendarFormatter implements FormatterIn
* Transform results to all day iCal events
*
* @access public
* @return FormatterInterface
* @param Table $query
* @return $this
*/
public function addFullDayEvents()
public function addTasksWithDueDateOnly(Table $query)
{
foreach ($this->query->findAll() as $task) {
foreach ($query->findAll() as $task) {
$date = new DateTime;
$date->setTimestamp($task[$this->startColumn]);
$date->setTimestamp($task['date_due']);
$vEvent = $this->getTaskIcalEvent($task, 'task-#'.$task['id'].'-'.$this->startColumn);
$vEvent = $this->getTaskIcalEvent($task, 'task-#'.$task['id'].'-date_due');
$vEvent->setDtStart($date);
$vEvent->setDtEnd($date);
$vEvent->setNoTime(true);
if ($date->format('Hi') === '0000') {
$vEvent->setNoTime(true);
}
$this->vCalendar->addComponent($vEvent);
}

View File

@ -1,37 +0,0 @@
<?php
namespace Kanboard\Helper;
use Kanboard\Core\Base;
use Kanboard\Core\Filter\QueryBuilder;
use Kanboard\Filter\TaskDueDateRangeFilter;
use Eluceo\iCal\Component\Calendar as iCalendar;
/**
* ICal Helper
*
* @package helper
* @author Frederic Guillot
*/
class ICalHelper extends Base
{
/**
* Get formatted calendar task due events
*
* @access public
* @param QueryBuilder $queryBuilder
* @param iCalendar $calendar
* @param string $start
* @param string $end
*/
public function addTaskDateDueEvents(QueryBuilder $queryBuilder, iCalendar $calendar, $start, $end)
{
$queryBuilder->withFilter(new TaskDueDateRangeFilter(array($start, $end)));
$this->taskICalFormatter
->setColumns('date_due')
->setCalendar($calendar)
->withQuery($queryBuilder->getQuery())
->addFullDayEvents();
}
}

View File

@ -26,7 +26,6 @@ class HelperProvider implements ServiceProviderInterface
$container['helper']->register('file', '\Kanboard\Helper\FileHelper');
$container['helper']->register('form', '\Kanboard\Helper\FormHelper');
$container['helper']->register('hook', '\Kanboard\Helper\HookHelper');
$container['helper']->register('ical', '\Kanboard\Helper\ICalHelper');
$container['helper']->register('layout', '\Kanboard\Helper\LayoutHelper');
$container['helper']->register('model', '\Kanboard\Helper\ModelHelper');
$container['helper']->register('subtask', '\Kanboard\Helper\SubtaskHelper');

View File

@ -436,7 +436,6 @@ return array(
'Kanboard\\Filter\\TaskTitleFilter' => $baseDir . '/app/Filter/TaskTitleFilter.php',
'Kanboard\\Filter\\UserNameFilter' => $baseDir . '/app/Filter/UserNameFilter.php',
'Kanboard\\Formatter\\BaseFormatter' => $baseDir . '/app/Formatter/BaseFormatter.php',
'Kanboard\\Formatter\\BaseTaskCalendarFormatter' => $baseDir . '/app/Formatter/BaseTaskCalendarFormatter.php',
'Kanboard\\Formatter\\BoardColumnFormatter' => $baseDir . '/app/Formatter/BoardColumnFormatter.php',
'Kanboard\\Formatter\\BoardFormatter' => $baseDir . '/app/Formatter/BoardFormatter.php',
'Kanboard\\Formatter\\BoardSwimlaneFormatter' => $baseDir . '/app/Formatter/BoardSwimlaneFormatter.php',
@ -470,7 +469,6 @@ return array(
'Kanboard\\Helper\\FileHelper' => $baseDir . '/app/Helper/FileHelper.php',
'Kanboard\\Helper\\FormHelper' => $baseDir . '/app/Helper/FormHelper.php',
'Kanboard\\Helper\\HookHelper' => $baseDir . '/app/Helper/HookHelper.php',
'Kanboard\\Helper\\ICalHelper' => $baseDir . '/app/Helper/ICalHelper.php',
'Kanboard\\Helper\\LayoutHelper' => $baseDir . '/app/Helper/LayoutHelper.php',
'Kanboard\\Helper\\MailHelper' => $baseDir . '/app/Helper/MailHelper.php',
'Kanboard\\Helper\\ModalHelper' => $baseDir . '/app/Helper/ModalHelper.php',

View File

@ -572,7 +572,6 @@ class ComposerStaticInit6edea6294a88689e3f5c56484bb70c9b
'Kanboard\\Filter\\TaskTitleFilter' => __DIR__ . '/../..' . '/app/Filter/TaskTitleFilter.php',
'Kanboard\\Filter\\UserNameFilter' => __DIR__ . '/../..' . '/app/Filter/UserNameFilter.php',
'Kanboard\\Formatter\\BaseFormatter' => __DIR__ . '/../..' . '/app/Formatter/BaseFormatter.php',
'Kanboard\\Formatter\\BaseTaskCalendarFormatter' => __DIR__ . '/../..' . '/app/Formatter/BaseTaskCalendarFormatter.php',
'Kanboard\\Formatter\\BoardColumnFormatter' => __DIR__ . '/../..' . '/app/Formatter/BoardColumnFormatter.php',
'Kanboard\\Formatter\\BoardFormatter' => __DIR__ . '/../..' . '/app/Formatter/BoardFormatter.php',
'Kanboard\\Formatter\\BoardSwimlaneFormatter' => __DIR__ . '/../..' . '/app/Formatter/BoardSwimlaneFormatter.php',
@ -606,7 +605,6 @@ class ComposerStaticInit6edea6294a88689e3f5c56484bb70c9b
'Kanboard\\Helper\\FileHelper' => __DIR__ . '/../..' . '/app/Helper/FileHelper.php',
'Kanboard\\Helper\\FormHelper' => __DIR__ . '/../..' . '/app/Helper/FormHelper.php',
'Kanboard\\Helper\\HookHelper' => __DIR__ . '/../..' . '/app/Helper/HookHelper.php',
'Kanboard\\Helper\\ICalHelper' => __DIR__ . '/../..' . '/app/Helper/ICalHelper.php',
'Kanboard\\Helper\\LayoutHelper' => __DIR__ . '/../..' . '/app/Helper/LayoutHelper.php',
'Kanboard\\Helper\\MailHelper' => __DIR__ . '/../..' . '/app/Helper/MailHelper.php',
'Kanboard\\Helper\\ModalHelper' => __DIR__ . '/../..' . '/app/Helper/ModalHelper.php',