diff options
author | Frederic Guillot <fred@kanboard.net> | 2015-05-17 22:09:44 -0400 |
---|---|---|
committer | Frederic Guillot <fred@kanboard.net> | 2015-05-17 22:09:44 -0400 |
commit | ac6e7bdfbf3479c655d7b883e50b6b01aa08784d (patch) | |
tree | acf23fc0c3417bb8a3ad8423b945942ab69969a2 /app | |
parent | 16973bb2220be77e8969e29f560b46a0fbf80f66 (diff) |
Add iCalendar public access for projects
Diffstat (limited to 'app')
-rw-r--r-- | app/Controller/Ical.php | 52 | ||||
-rw-r--r-- | app/Model/Acl.php | 1 | ||||
-rw-r--r-- | app/Model/DateParser.php | 2 | ||||
-rw-r--r-- | app/Model/TaskFilter.php | 127 | ||||
-rw-r--r-- | app/Template/project/share.php | 2 |
5 files changed, 176 insertions, 8 deletions
diff --git a/app/Controller/Ical.php b/app/Controller/Ical.php new file mode 100644 index 00000000..55cc64a6 --- /dev/null +++ b/app/Controller/Ical.php @@ -0,0 +1,52 @@ +<?php + +namespace Controller; + +use Model\Task as TaskModel; + +/** + * iCalendar controller + * + * @package controller + * @author Frederic Guillot + */ +class Ical extends Base +{ + /** + * Get project iCalendar + * + * @access public + */ + public function project() + { + $token = $this->request->getStringParam('token'); + $project = $this->project->getByToken($token); + + // Token verification + if (empty($project)) { + $this->forbidden(true); + } + + $start = $this->request->getStringParam('start', strtotime('-1 month')); + $end = $this->request->getStringParam('end', strtotime('+2 months')); + + // Common filter + $filter = $this->taskFilter + ->create() + ->filterByProject($project['id']); + + // Tasks + if ($this->config->get('calendar_project_tasks', 'date_started') === 'date_creation') { + $calendar = $filter->copy()->filterByCreationDateRange($start, $end)->addDateTimeIcalEvents('date_creation', 'date_completed'); + } + else { + $calendar = $filter->copy()->filterByStartDateRange($start, $end)->addDateTimeIcalEvents('date_started', 'date_completed'); + } + + // Tasks with due date + $calendar = $filter->copy()->filterByDueDateRange($start, $end)->addAllDayIcalEvents('date_due', $calendar); + + $this->response->contentType('text/calendar; charset=utf-8'); + echo $calendar->render(); + } +} diff --git a/app/Model/Acl.php b/app/Model/Acl.php index d7b96b06..6d5e6d50 100644 --- a/app/Model/Acl.php +++ b/app/Model/Acl.php @@ -24,6 +24,7 @@ class Acl extends Base 'project' => array('feed'), 'webhook' => '*', 'app' => array('colors'), + 'ical' => '*', ); /** diff --git a/app/Model/DateParser.php b/app/Model/DateParser.php index a0d10a36..be79a92e 100644 --- a/app/Model/DateParser.php +++ b/app/Model/DateParser.php @@ -148,7 +148,7 @@ class DateParser extends Base */ public function getTimestampFromIsoFormat($date) { - return $this->removeTimeFromTimestamp(strtotime($date)); + return $this->removeTimeFromTimestamp(ctype_digit($date) ? $date : strtotime($date)); } /** diff --git a/app/Model/TaskFilter.php b/app/Model/TaskFilter.php index 21e77e63..c4c56800 100644 --- a/app/Model/TaskFilter.php +++ b/app/Model/TaskFilter.php @@ -2,6 +2,11 @@ namespace Model; +use DateTime; +use Eluceo\iCal\Component\Calendar; +use Eluceo\iCal\Component\Event; +use Eluceo\iCal\Property\Event\Attendees; + /** * Task Filter * @@ -27,6 +32,17 @@ class TaskFilter extends Base public function create() { $this->query = $this->db->table(Task::TABLE); + $this->query->left(User::TABLE, 'ua', 'id', Task::TABLE, 'owner_id'); + $this->query->left(User::TABLE, 'uc', 'id', Task::TABLE, 'creator_id'); + + $this->query->columns( + Task::TABLE.'.*', + 'ua.email AS assignee_email', + 'ua.username AS assignee_username', + 'uc.email AS creator_email', + 'uc.username AS creator_username' + ); + return $this; } @@ -214,8 +230,8 @@ class TaskFilter extends Base * Filter by due date (range) * * @access public - * @param integer $start - * @param integer $end + * @param string $start + * @param string $end * @return TaskFilter */ public function filterByDueDateRange($start, $end) @@ -230,8 +246,8 @@ class TaskFilter extends Base * Filter by start date (range) * * @access public - * @param integer $start - * @param integer $end + * @param string $start + * @param strings $end * @return TaskFilter */ public function filterByStartDateRange($start, $end) @@ -250,8 +266,8 @@ class TaskFilter extends Base * Filter by creation date * * @access public - * @param integer $start - * @param integer $end + * @param string $start + * @param string $end * @return TaskFilter */ public function filterByCreationDateRange($start, $end) @@ -349,4 +365,103 @@ class TaskFilter extends Base return $events; } + + /** + * Transform results to ical events + * + * @access public + * @param string $start_column Column name for the start date + * @param string $end_column Column name for the end date + * @param Eluceo\iCal\Component\Calendar $vCalendar Calendar object + * @return Eluceo\iCal\Component\Calendar + */ + public function addDateTimeIcalEvents($start_column, $end_column, Calendar $vCalendar = null) + { + if ($vCalendar === null) { + $vCalendar = new Calendar('Kanboard'); + } + + foreach ($this->query->findAll() as $task) { + + $start = new DateTime; + $start->setTimestamp($task[$start_column]); + + $end = new DateTime; + $end->setTimestamp($task[$end_column] ?: time()); + + $vEvent = $this->getTaskIcalEvent($task, 'task-#'.$task['id'].'-'.$start_column.'-'.$end_column); + $vEvent->setDtStart($start); + $vEvent->setDtEnd($end); + + $vCalendar->addComponent($vEvent); + } + + return $vCalendar; + } + + /** + * Transform results to all day ical events + * + * @access public + * @param string $column Column name for the date + * @param Eluceo\iCal\Component\Calendar $vCalendar Calendar object + * @return Eluceo\iCal\Component\Calendar + */ + public function addAllDayIcalEvents($column = 'date_due', Calendar $vCalendar = null) + { + if ($vCalendar === null) { + $vCalendar = new Calendar('Kanboard'); + } + + foreach ($this->query->findAll() as $task) { + + $date = new DateTime; + $date->setTimestamp($task[$column]); + + $vEvent = $this->getTaskIcalEvent($task, 'task-#'.$task['id'].'-'.$column); + $vEvent->setDtStart($date); + $vEvent->setDtEnd($date); + $vEvent->setNoTime(true); + + $vCalendar->addComponent($vEvent); + } + + return $vCalendar; + } + + /** + * Get common events for task ical events + * + * @access protected + * @param array $task + * @param string $uid + * @return Eluceo\iCal\Component\Event + */ + protected function getTaskIcalEvent(array &$task, $uid) + { + $dateCreation = new DateTime; + $dateCreation->setTimestamp($task['date_creation']); + + $dateModif = new DateTime; + $dateModif->setTimestamp($task['date_modification']); + + $vEvent = new Event($uid); + $vEvent->setCreated($dateCreation); + $vEvent->setModified($dateModif); + $vEvent->setUseTimezone(true); + $vEvent->setSummary(t('#%d', $task['id']).' '.$task['title']); + $vEvent->setUrl($this->helper->getCurrentBaseUrl().$this->helper->url('task', 'show', array('task_id' => $task['id'], 'project_id' => $task['project_id']))); + + if (! empty($task['creator_id'])) { + $vEvent->setOrganizer('MAILTO:'.($task['creator_email'] ?: $task['creator_username'].'@kanboard.local')); + } + + if (! empty($task['owner_id'])) { + $attendees = new Attendees; + $attendees->add('MAILTO:'.($task['creator_email'] ?: $task['creator_username'].'@kanboard.local')); + $vEvent->setAttendees($attendees); + } + + return $vEvent; + } } diff --git a/app/Template/project/share.php b/app/Template/project/share.php index 7c490008..f28ed6b2 100644 --- a/app/Template/project/share.php +++ b/app/Template/project/share.php @@ -8,8 +8,8 @@ <ul class="no-bullet"> <li><strong><i class="fa fa-share-alt"></i> <?= $this->a(t('Public link'), 'board', 'readonly', array('token' => $project['token']), false, '', '', true) ?></strong></li> <li><strong><i class="fa fa-rss-square"></i> <?= $this->a(t('RSS feed'), 'project', 'feed', array('token' => $project['token']), false, '', '', true) ?></strong></li> + <li><strong><i class="fa fa-calendar"></i> <?= $this->a(t('iCalendar (iCal format, *.ics)'), 'ical', 'project', array('token' => $project['token']), false, '', '', true) ?></strong></li> </ul> - <input type="text" class="auto-select" readonly="readonly" value="<?= $this->getCurrentBaseUrl().$this->u('board', 'readonly', array('token' => $project['token'])) ?>"/> </div> <?= $this->a(t('Disable public access'), 'project', 'share', array('project_id' => $project['id'], 'switch' => 'disable'), true, 'btn btn-red') ?> |