summaryrefslogtreecommitdiff
path: root/app
diff options
context:
space:
mode:
Diffstat (limited to 'app')
-rw-r--r--app/Controller/Ical.php99
-rw-r--r--app/Controller/User.php29
-rw-r--r--app/Model/Acl.php1
-rw-r--r--app/Model/DateParser.php2
-rw-r--r--app/Model/Project.php4
-rw-r--r--app/Model/TaskFilter.php127
-rw-r--r--app/Model/User.php55
-rw-r--r--app/Schema/Mysql.php7
-rw-r--r--app/Schema/Postgres.php7
-rw-r--r--app/Schema/Sqlite.php7
-rw-r--r--app/Template/project/share.php2
-rw-r--r--app/Template/user/share.php17
-rw-r--r--app/Template/user/show.php29
-rw-r--r--app/Template/user/sidebar.php3
14 files changed, 376 insertions, 13 deletions
diff --git a/app/Controller/Ical.php b/app/Controller/Ical.php
new file mode 100644
index 00000000..52e10fa1
--- /dev/null
+++ b/app/Controller/Ical.php
@@ -0,0 +1,99 @@
+<?php
+
+namespace Controller;
+
+use Model\TaskFilter;
+use Model\Task as TaskModel;
+use Eluceo\iCal\Component\Calendar as iCalendar;
+
+/**
+ * iCalendar controller
+ *
+ * @package controller
+ * @author Frederic Guillot
+ */
+class Ical extends Base
+{
+ /**
+ * Get user iCalendar
+ *
+ * @access public
+ */
+ public function user()
+ {
+ $token = $this->request->getStringParam('token');
+ $user = $this->user->getByToken($token);
+
+ // Token verification
+ if (empty($user)) {
+ $this->forbidden(true);
+ }
+
+ // Common filter
+ $filter = $this->taskFilter
+ ->create()
+ ->filterByOwner($user['id']);
+
+ // Calendar properties
+ $calendar = new iCalendar('Kanboard');
+ $calendar->setName($user['name'] ?: $user['username']);
+ $calendar->setDescription($user['name'] ?: $user['username']);
+ $calendar->setPublishedTTL('PT1H');
+
+ $this->renderCalendar($filter, $calendar);
+ }
+
+ /**
+ * 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);
+ }
+
+ // Common filter
+ $filter = $this->taskFilter
+ ->create()
+ ->filterByProject($project['id']);
+
+ // Calendar properties
+ $calendar = new iCalendar('Kanboard');
+ $calendar->setName($project['name']);
+ $calendar->setDescription($project['name']);
+ $calendar->setPublishedTTL('PT1H');
+
+ $this->renderCalendar($filter, $calendar);
+ }
+
+ /**
+ * Common method to render iCal events
+ *
+ * @access private
+ */
+ private function renderCalendar(TaskFilter $filter, iCalendar $calendar)
+ {
+ $start = $this->request->getStringParam('start', strtotime('-1 month'));
+ $end = $this->request->getStringParam('end', strtotime('+2 months'));
+
+ // Tasks
+ if ($this->config->get('calendar_project_tasks', 'date_started') === 'date_creation') {
+ $filter->copy()->filterByCreationDateRange($start, $end)->addDateTimeIcalEvents('date_creation', 'date_completed', $calendar);
+ }
+ else {
+ $filter->copy()->filterByStartDateRange($start, $end)->addDateTimeIcalEvents('date_started', 'date_completed', $calendar);
+ }
+
+ // Tasks with due date
+ $filter->copy()->filterByDueDateRange($start, $end)->addAllDayIcalEvents('date_due', $calendar);
+
+ $this->response->contentType('text/calendar; charset=utf-8');
+ echo $calendar->render();
+ }
+}
diff --git a/app/Controller/User.php b/app/Controller/User.php
index 37f10969..c8496418 100644
--- a/app/Controller/User.php
+++ b/app/Controller/User.php
@@ -249,6 +249,35 @@ class User extends Base
}
/**
+ * Public access management
+ *
+ * @access public
+ */
+ public function share()
+ {
+ $user = $this->getUser();
+ $switch = $this->request->getStringParam('switch');
+
+ if ($switch === 'enable' || $switch === 'disable') {
+
+ $this->checkCSRFParam();
+
+ if ($this->user->{$switch.'PublicAccess'}($user['id'])) {
+ $this->session->flash(t('User updated successfully.'));
+ } else {
+ $this->session->flashError(t('Unable to update this user.'));
+ }
+
+ $this->response->redirect($this->helper->url('user', 'share', array('user_id' => $user['id'])));
+ }
+
+ $this->response->html($this->layout('user/share', array(
+ 'user' => $user,
+ 'title' => t('Public access'),
+ )));
+ }
+
+ /**
* Password modification
*
* @access public
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/Project.php b/app/Model/Project.php
index 158de295..80e17437 100644
--- a/app/Model/Project.php
+++ b/app/Model/Project.php
@@ -84,6 +84,10 @@ class Project extends Base
*/
public function getByToken($token)
{
+ if (empty($token)) {
+ return false;
+ }
+
return $this->db->table(self::TABLE)->eq('token', $token)->eq('is_public', 1)->findOne();
}
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/Model/User.php b/app/Model/User.php
index be2b034b..845efac0 100644
--- a/app/Model/User.php
+++ b/app/Model/User.php
@@ -5,6 +5,7 @@ namespace Model;
use SimpleValidator\Validator;
use SimpleValidator\Validators;
use Core\Session;
+use Core\Security;
/**
* User model
@@ -114,6 +115,10 @@ class User extends Base
*/
public function getByGoogleId($google_id)
{
+ if (empty($google_id)) {
+ return false;
+ }
+
return $this->db->table(self::TABLE)->eq('google_id', $google_id)->findOne();
}
@@ -126,6 +131,10 @@ class User extends Base
*/
public function getByGitHubId($github_id)
{
+ if (empty($github_id)) {
+ return false;
+ }
+
return $this->db->table(self::TABLE)->eq('github_id', $github_id)->findOne();
}
@@ -158,6 +167,22 @@ class User extends Base
}
/**
+ * Fetch user by using the token
+ *
+ * @access public
+ * @param string $token Token
+ * @return array
+ */
+ public function getByToken($token)
+ {
+ if (empty($token)) {
+ return false;
+ }
+
+ return $this->db->table(self::TABLE)->eq('token', $token)->findOne();
+ }
+
+ /**
* Get all users
*
* @access public
@@ -301,6 +326,36 @@ class User extends Base
}
/**
+ * Enable public access for a user
+ *
+ * @access public
+ * @param integer $user_id User id
+ * @return bool
+ */
+ public function enablePublicAccess($user_id)
+ {
+ return $this->db
+ ->table(self::TABLE)
+ ->eq('id', $user_id)
+ ->save(array('token' => Security::generateToken()));
+ }
+
+ /**
+ * Disable public access for a user
+ *
+ * @access public
+ * @param integer $user_id User id
+ * @return bool
+ */
+ public function disablePublicAccess($user_id)
+ {
+ return $this->db
+ ->table(self::TABLE)
+ ->eq('id', $user_id)
+ ->save(array('token' => ''));
+ }
+
+ /**
* Common validation rules
*
* @access private
diff --git a/app/Schema/Mysql.php b/app/Schema/Mysql.php
index 7195c7fe..593cca80 100644
--- a/app/Schema/Mysql.php
+++ b/app/Schema/Mysql.php
@@ -6,7 +6,12 @@ use PDO;
use Core\Security;
use Model\Link;
-const VERSION = 69;
+const VERSION = 70;
+
+function version_70($pdo)
+{
+ $pdo->exec("ALTER TABLE users ADD COLUMN token VARCHAR(255) DEFAULT ''");
+}
function version_69($pdo)
{
diff --git a/app/Schema/Postgres.php b/app/Schema/Postgres.php
index 27efe8c4..4ec6965f 100644
--- a/app/Schema/Postgres.php
+++ b/app/Schema/Postgres.php
@@ -6,7 +6,12 @@ use PDO;
use Core\Security;
use Model\Link;
-const VERSION = 50;
+const VERSION = 51;
+
+function version_51($pdo)
+{
+ $pdo->exec("ALTER TABLE users ADD COLUMN token VARCHAR(255) DEFAULT ''");
+}
function version_50($pdo)
{
diff --git a/app/Schema/Sqlite.php b/app/Schema/Sqlite.php
index 7af7bbe8..84892aaf 100644
--- a/app/Schema/Sqlite.php
+++ b/app/Schema/Sqlite.php
@@ -6,7 +6,12 @@ use Core\Security;
use PDO;
use Model\Link;
-const VERSION = 68;
+const VERSION = 69;
+
+function version_69($pdo)
+{
+ $pdo->exec("ALTER TABLE users ADD COLUMN token TEXT DEFAULT ''");
+}
function version_68($pdo)
{
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/app/Template/user/share.php b/app/Template/user/share.php
new file mode 100644
index 00000000..98010d4f
--- /dev/null
+++ b/app/Template/user/share.php
@@ -0,0 +1,17 @@
+<div class="page-header">
+ <h2><?= t('Public access') ?></h2>
+</div>
+
+<?php if (! empty($user['token'])): ?>
+
+ <div class="listing">
+ <ul class="no-bullet">
+ <li><strong><i class="fa fa-calendar"></i> <?= $this->a(t('iCalendar (iCal format, *.ics)'), 'ical', 'user', array('token' => $user['token']), false, '', '', true) ?></strong></li>
+ </ul>
+ </div>
+
+ <?= $this->a(t('Disable public access'), 'user', 'share', array('user_id' => $user['id'], 'switch' => 'disable'), true, 'btn btn-red') ?>
+
+<?php else: ?>
+ <?= $this->a(t('Enable public access'), 'user', 'share', array('user_id' => $user['id'], 'switch' => 'enable'), true, 'btn btn-blue') ?>
+<?php endif ?>
diff --git a/app/Template/user/show.php b/app/Template/user/show.php
index 490d8fb3..9473b382 100644
--- a/app/Template/user/show.php
+++ b/app/Template/user/show.php
@@ -5,10 +5,35 @@
<li><?= t('Username:') ?> <strong><?= $this->e($user['username']) ?></strong></li>
<li><?= t('Name:') ?> <strong><?= $this->e($user['name']) ?: t('None') ?></strong></li>
<li><?= t('Email:') ?> <strong><?= $this->e($user['email']) ?: t('None') ?></strong></li>
+</ul>
+
+<div class="page-header">
+ <h2><?= t('Security') ?></h2>
+</div>
+<ul class="listing">
+ <li><?= t('Group:') ?> <strong><?= $user['is_admin'] ? t('Administrator') : t('Regular user') ?></strong></li>
+ <li><?= t('Account type:') ?> <strong><?= $user['is_ldap_user'] ? t('Remote') : t('Local') ?></strong></li>
+ <li><?= $user['twofactor_activated'] == 1 ? t('Two factor authentication enabled') : t('Two factor authentication disabled') ?></li>
+</ul>
+
+<div class="page-header">
+ <h2><?= t('Preferences') ?></h2>
+</div>
+<ul class="listing">
<li><?= t('Default project:') ?> <strong><?= (isset($user['default_project_id']) && isset($projects[$user['default_project_id']])) ? $this->e($projects[$user['default_project_id']]) : t('None') ?></strong></li>
<li><?= t('Timezone:') ?> <strong><?= $this->inList($user['timezone'], $timezones) ?></strong></li>
<li><?= t('Language:') ?> <strong><?= $this->inList($user['language'], $languages) ?></strong></li>
<li><?= t('Notifications:') ?> <strong><?= $user['notifications_enabled'] == 1 ? t('Enabled') : t('Disabled') ?></strong></li>
- <li><?= t('Group:') ?> <strong><?= $user['is_admin'] ? t('Administrator') : t('Regular user') ?></strong></li>
- <li><?= t('Account type:') ?> <strong><?= $user['is_ldap_user'] ? t('Remote') : t('Local') ?></strong></li>
</ul>
+
+<?php if (! empty($user['token'])): ?>
+ <div class="page-header">
+ <h2><?= t('Public access') ?></h2>
+ </div>
+
+ <div class="listing">
+ <ul class="no-bullet">
+ <li><strong><i class="fa fa-calendar"></i> <?= $this->a(t('iCalendar (iCal format, *.ics)'), 'ical', 'user', array('token' => $user['token']), false, '', '', true) ?></strong></li>
+ </ul>
+ </div>
+<?php endif ?>
diff --git a/app/Template/user/sidebar.php b/app/Template/user/sidebar.php
index ff0fb009..8c7e3cf8 100644
--- a/app/Template/user/sidebar.php
+++ b/app/Template/user/sidebar.php
@@ -49,6 +49,9 @@
<?php endif ?>
<li>
+ <?= $this->a(t('Public access'), 'user', 'share', array('user_id' => $user['id'])) ?>
+ </li>
+ <li>
<?= $this->a(t('Email notifications'), 'user', 'notifications', array('user_id' => $user['id'])) ?>
</li>
<li>