summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorFrederic Guillot <fred@kanboard.net>2015-05-17 22:09:44 -0400
committerFrederic Guillot <fred@kanboard.net>2015-05-17 22:09:44 -0400
commitac6e7bdfbf3479c655d7b883e50b6b01aa08784d (patch)
treeacf23fc0c3417bb8a3ad8423b945942ab69969a2
parent16973bb2220be77e8969e29f560b46a0fbf80f66 (diff)
Add iCalendar public access for projects
-rw-r--r--app/Controller/Ical.php52
-rw-r--r--app/Model/Acl.php1
-rw-r--r--app/Model/DateParser.php2
-rw-r--r--app/Model/TaskFilter.php127
-rw-r--r--app/Template/project/share.php2
-rw-r--r--composer.json3
-rw-r--r--composer.lock63
7 files changed, 236 insertions, 14 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') ?>
diff --git a/composer.json b/composer.json
index 766f2372..c1381c02 100644
--- a/composer.json
+++ b/composer.json
@@ -15,7 +15,8 @@
"fguillot/simpleLogger" : "0.0.1",
"christian-riesen/otp" : "1.4",
"nickcernis/html-to-markdown" : "2.2.1",
- "fabiang/xmpp" : "0.6.1"
+ "fabiang/xmpp" : "0.6.1",
+ "eluceo/ical": "*"
},
"autoload" : {
"psr-0" : {
diff --git a/composer.lock b/composer.lock
index d13f1aee..cebfcbbc 100644
--- a/composer.lock
+++ b/composer.lock
@@ -4,7 +4,7 @@
"Read more about it at http://getcomposer.org/doc/01-basic-usage.md#composer-lock-the-lock-file",
"This file is @generated automatically"
],
- "hash": "9a3f10be467a1640c3c6645ce2c318fe",
+ "hash": "d5e3dbbd2a1a260e2d0bfa0750d11754",
"packages": [
{
"name": "christian-riesen/base32",
@@ -109,6 +109,59 @@
"time": "2015-02-12 09:11:49"
},
{
+ "name": "eluceo/ical",
+ "version": "0.7.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/markuspoerschke/iCal.git",
+ "reference": "0d79c35b9e5f7f1dcfb5315cc1e8507f74093083"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/markuspoerschke/iCal/zipball/0d79c35b9e5f7f1dcfb5315cc1e8507f74093083",
+ "reference": "0d79c35b9e5f7f1dcfb5315cc1e8507f74093083",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=5.3.0"
+ },
+ "require-dev": {
+ "phpunit/phpunit": "~4.3"
+ },
+ "type": "library",
+ "autoload": {
+ "psr-0": {
+ "Eluceo\\iCal": "src/"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Maciej Ɓebkowski",
+ "email": "m.lebkowski@gmail.com",
+ "role": "Contributor"
+ },
+ {
+ "name": "Markus Poerschke",
+ "email": "markus@eluceo.de",
+ "role": "Developer"
+ }
+ ],
+ "description": "The eluceo/iCal package offers a abstraction layer for creating iCalendars. You can easily create iCal files by using PHP object instead of typing your *.ics file by hand. The output will follow RFC 2445 as best as possible.",
+ "homepage": "https://github.com/markuspoerschke/iCal",
+ "keywords": [
+ "calendar",
+ "iCalendar",
+ "ical",
+ "ics",
+ "php calendar"
+ ],
+ "time": "2015-02-21 23:14:47"
+ },
+ {
"name": "erusev/parsedown",
"version": "1.5.1",
"source": {
@@ -248,12 +301,12 @@
"source": {
"type": "git",
"url": "https://github.com/fguillot/picoDb.git",
- "reference": "bc75f3dab79bf6beb6e07b5ace0252e1d0d374fa"
+ "reference": "f65d11cb52de34e0fd236a34184ca1a310da244a"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/fguillot/picoDb/zipball/bc75f3dab79bf6beb6e07b5ace0252e1d0d374fa",
- "reference": "bc75f3dab79bf6beb6e07b5ace0252e1d0d374fa",
+ "url": "https://api.github.com/repos/fguillot/picoDb/zipball/f65d11cb52de34e0fd236a34184ca1a310da244a",
+ "reference": "f65d11cb52de34e0fd236a34184ca1a310da244a",
"shasum": ""
},
"require": {
@@ -277,7 +330,7 @@
],
"description": "Minimalist database query builder",
"homepage": "https://github.com/fguillot/picoDb",
- "time": "2015-04-14 01:59:06"
+ "time": "2015-05-17 23:57:05"
},
{
"name": "fguillot/simple-validator",