Add iCalendar public access for projects
This commit is contained in:
52
app/Controller/Ical.php
Normal file
52
app/Controller/Ical.php
Normal file
@@ -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();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -24,6 +24,7 @@ class Acl extends Base
|
|||||||
'project' => array('feed'),
|
'project' => array('feed'),
|
||||||
'webhook' => '*',
|
'webhook' => '*',
|
||||||
'app' => array('colors'),
|
'app' => array('colors'),
|
||||||
|
'ical' => '*',
|
||||||
);
|
);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -148,7 +148,7 @@ class DateParser extends Base
|
|||||||
*/
|
*/
|
||||||
public function getTimestampFromIsoFormat($date)
|
public function getTimestampFromIsoFormat($date)
|
||||||
{
|
{
|
||||||
return $this->removeTimeFromTimestamp(strtotime($date));
|
return $this->removeTimeFromTimestamp(ctype_digit($date) ? $date : strtotime($date));
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -2,6 +2,11 @@
|
|||||||
|
|
||||||
namespace Model;
|
namespace Model;
|
||||||
|
|
||||||
|
use DateTime;
|
||||||
|
use Eluceo\iCal\Component\Calendar;
|
||||||
|
use Eluceo\iCal\Component\Event;
|
||||||
|
use Eluceo\iCal\Property\Event\Attendees;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Task Filter
|
* Task Filter
|
||||||
*
|
*
|
||||||
@@ -27,6 +32,17 @@ class TaskFilter extends Base
|
|||||||
public function create()
|
public function create()
|
||||||
{
|
{
|
||||||
$this->query = $this->db->table(Task::TABLE);
|
$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;
|
return $this;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -214,8 +230,8 @@ class TaskFilter extends Base
|
|||||||
* Filter by due date (range)
|
* Filter by due date (range)
|
||||||
*
|
*
|
||||||
* @access public
|
* @access public
|
||||||
* @param integer $start
|
* @param string $start
|
||||||
* @param integer $end
|
* @param string $end
|
||||||
* @return TaskFilter
|
* @return TaskFilter
|
||||||
*/
|
*/
|
||||||
public function filterByDueDateRange($start, $end)
|
public function filterByDueDateRange($start, $end)
|
||||||
@@ -230,8 +246,8 @@ class TaskFilter extends Base
|
|||||||
* Filter by start date (range)
|
* Filter by start date (range)
|
||||||
*
|
*
|
||||||
* @access public
|
* @access public
|
||||||
* @param integer $start
|
* @param string $start
|
||||||
* @param integer $end
|
* @param strings $end
|
||||||
* @return TaskFilter
|
* @return TaskFilter
|
||||||
*/
|
*/
|
||||||
public function filterByStartDateRange($start, $end)
|
public function filterByStartDateRange($start, $end)
|
||||||
@@ -250,8 +266,8 @@ class TaskFilter extends Base
|
|||||||
* Filter by creation date
|
* Filter by creation date
|
||||||
*
|
*
|
||||||
* @access public
|
* @access public
|
||||||
* @param integer $start
|
* @param string $start
|
||||||
* @param integer $end
|
* @param string $end
|
||||||
* @return TaskFilter
|
* @return TaskFilter
|
||||||
*/
|
*/
|
||||||
public function filterByCreationDateRange($start, $end)
|
public function filterByCreationDateRange($start, $end)
|
||||||
@@ -349,4 +365,103 @@ class TaskFilter extends Base
|
|||||||
|
|
||||||
return $events;
|
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;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,8 +8,8 @@
|
|||||||
<ul class="no-bullet">
|
<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-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-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>
|
</ul>
|
||||||
<input type="text" class="auto-select" readonly="readonly" value="<?= $this->getCurrentBaseUrl().$this->u('board', 'readonly', array('token' => $project['token'])) ?>"/>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<?= $this->a(t('Disable public access'), 'project', 'share', array('project_id' => $project['id'], 'switch' => 'disable'), true, 'btn btn-red') ?>
|
<?= $this->a(t('Disable public access'), 'project', 'share', array('project_id' => $project['id'], 'switch' => 'disable'), true, 'btn btn-red') ?>
|
||||||
|
|||||||
@@ -15,7 +15,8 @@
|
|||||||
"fguillot/simpleLogger" : "0.0.1",
|
"fguillot/simpleLogger" : "0.0.1",
|
||||||
"christian-riesen/otp" : "1.4",
|
"christian-riesen/otp" : "1.4",
|
||||||
"nickcernis/html-to-markdown" : "2.2.1",
|
"nickcernis/html-to-markdown" : "2.2.1",
|
||||||
"fabiang/xmpp" : "0.6.1"
|
"fabiang/xmpp" : "0.6.1",
|
||||||
|
"eluceo/ical": "*"
|
||||||
},
|
},
|
||||||
"autoload" : {
|
"autoload" : {
|
||||||
"psr-0" : {
|
"psr-0" : {
|
||||||
|
|||||||
63
composer.lock
generated
63
composer.lock
generated
@@ -4,7 +4,7 @@
|
|||||||
"Read more about it at http://getcomposer.org/doc/01-basic-usage.md#composer-lock-the-lock-file",
|
"Read more about it at http://getcomposer.org/doc/01-basic-usage.md#composer-lock-the-lock-file",
|
||||||
"This file is @generated automatically"
|
"This file is @generated automatically"
|
||||||
],
|
],
|
||||||
"hash": "9a3f10be467a1640c3c6645ce2c318fe",
|
"hash": "d5e3dbbd2a1a260e2d0bfa0750d11754",
|
||||||
"packages": [
|
"packages": [
|
||||||
{
|
{
|
||||||
"name": "christian-riesen/base32",
|
"name": "christian-riesen/base32",
|
||||||
@@ -108,6 +108,59 @@
|
|||||||
],
|
],
|
||||||
"time": "2015-02-12 09:11:49"
|
"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",
|
"name": "erusev/parsedown",
|
||||||
"version": "1.5.1",
|
"version": "1.5.1",
|
||||||
@@ -248,12 +301,12 @@
|
|||||||
"source": {
|
"source": {
|
||||||
"type": "git",
|
"type": "git",
|
||||||
"url": "https://github.com/fguillot/picoDb.git",
|
"url": "https://github.com/fguillot/picoDb.git",
|
||||||
"reference": "bc75f3dab79bf6beb6e07b5ace0252e1d0d374fa"
|
"reference": "f65d11cb52de34e0fd236a34184ca1a310da244a"
|
||||||
},
|
},
|
||||||
"dist": {
|
"dist": {
|
||||||
"type": "zip",
|
"type": "zip",
|
||||||
"url": "https://api.github.com/repos/fguillot/picoDb/zipball/bc75f3dab79bf6beb6e07b5ace0252e1d0d374fa",
|
"url": "https://api.github.com/repos/fguillot/picoDb/zipball/f65d11cb52de34e0fd236a34184ca1a310da244a",
|
||||||
"reference": "bc75f3dab79bf6beb6e07b5ace0252e1d0d374fa",
|
"reference": "f65d11cb52de34e0fd236a34184ca1a310da244a",
|
||||||
"shasum": ""
|
"shasum": ""
|
||||||
},
|
},
|
||||||
"require": {
|
"require": {
|
||||||
@@ -277,7 +330,7 @@
|
|||||||
],
|
],
|
||||||
"description": "Minimalist database query builder",
|
"description": "Minimalist database query builder",
|
||||||
"homepage": "https://github.com/fguillot/picoDb",
|
"homepage": "https://github.com/fguillot/picoDb",
|
||||||
"time": "2015-04-14 01:59:06"
|
"time": "2015-05-17 23:57:05"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "fguillot/simple-validator",
|
"name": "fguillot/simple-validator",
|
||||||
|
|||||||
Reference in New Issue
Block a user