Add kiosk mode, public board access with read-only and auto-refresh
This commit is contained in:
@@ -45,6 +45,7 @@ abstract class Base
|
|||||||
$public = array(
|
$public = array(
|
||||||
'user' => array('login', 'check'),
|
'user' => array('login', 'check'),
|
||||||
'task' => array('add'),
|
'task' => array('add'),
|
||||||
|
'board' => array('readonly'),
|
||||||
);
|
);
|
||||||
|
|
||||||
if (isset($public[$controller])) {
|
if (isset($public[$controller])) {
|
||||||
|
|||||||
@@ -4,7 +4,29 @@ namespace Controller;
|
|||||||
|
|
||||||
class Board extends Base
|
class Board extends Base
|
||||||
{
|
{
|
||||||
// Display current board
|
// Display the public version of a board
|
||||||
|
// Access checked by a simple token, no user login, read only, auto-refresh
|
||||||
|
public function readonly()
|
||||||
|
{
|
||||||
|
$token = $this->request->getStringParam('token');
|
||||||
|
$project = $this->project->getByToken($token);
|
||||||
|
|
||||||
|
// Token verification
|
||||||
|
if (! $project) {
|
||||||
|
$this->response->text('Not Authorized', 401);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Display the board with a specific layout
|
||||||
|
$this->response->html($this->template->layout('board_public', array(
|
||||||
|
'project' => $project,
|
||||||
|
'columns' => $this->board->get($project['id']),
|
||||||
|
'title' => $project['name'],
|
||||||
|
'no_layout' => true,
|
||||||
|
'auto_refresh' => true,
|
||||||
|
)));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Display the default user project or the first project
|
||||||
public function index()
|
public function index()
|
||||||
{
|
{
|
||||||
$projects = $this->project->getListByStatus(\Model\Project::ACTIVE);
|
$projects = $this->project->getListByStatus(\Model\Project::ACTIVE);
|
||||||
@@ -30,7 +52,7 @@ class Board extends Base
|
|||||||
)));
|
)));
|
||||||
}
|
}
|
||||||
|
|
||||||
// Show a board
|
// Show a board for a given project
|
||||||
public function show()
|
public function show()
|
||||||
{
|
{
|
||||||
$projects = $this->project->getListByStatus(\Model\Project::ACTIVE);
|
$projects = $this->project->getListByStatus(\Model\Project::ACTIVE);
|
||||||
@@ -175,7 +197,7 @@ class Board extends Base
|
|||||||
$this->response->redirect('?controller=board&action=edit&project_id='.$column['project_id']);
|
$this->response->redirect('?controller=board&action=edit&project_id='.$column['project_id']);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Save the board (Ajax request made by drag and drop)
|
// Save the board (Ajax request made by the drag and drop)
|
||||||
public function save()
|
public function save()
|
||||||
{
|
{
|
||||||
$this->response->json(array(
|
$this->response->json(array(
|
||||||
|
|||||||
@@ -4,5 +4,10 @@ require __DIR__.'/check_setup.php';
|
|||||||
require __DIR__.'/controllers/base.php';
|
require __DIR__.'/controllers/base.php';
|
||||||
require __DIR__.'/lib/router.php';
|
require __DIR__.'/lib/router.php';
|
||||||
|
|
||||||
|
if (file_exists('config.php')) require 'config.php';
|
||||||
|
|
||||||
|
// Auto-refresh frequency in seconds for the public board view
|
||||||
|
defined('AUTO_REFRESH_DURATION') or define('AUTO_REFRESH_DURATION', 60);
|
||||||
|
|
||||||
$router = new Router;
|
$router = new Router;
|
||||||
$router->execute();
|
$router->execute();
|
||||||
|
|||||||
@@ -179,4 +179,5 @@ return array(
|
|||||||
'Completed tasks for "%s"' => 'Tâches terminées pour "%s"',
|
'Completed tasks for "%s"' => 'Tâches terminées pour "%s"',
|
||||||
'%d closed tasks' => '%d tâches terminées',
|
'%d closed tasks' => '%d tâches terminées',
|
||||||
'no task for this project' => 'aucune tâche pour ce projet',
|
'no task for this project' => 'aucune tâche pour ce projet',
|
||||||
|
'Public link' => 'Accès public',
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -181,5 +181,7 @@ return array(
|
|||||||
'List of projects' => '',
|
'List of projects' => '',
|
||||||
'Completed tasks for "%s"' => '',
|
'Completed tasks for "%s"' => '',
|
||||||
'%d closed tasks' => '',
|
'%d closed tasks' => '',
|
||||||
'no task for this project' => '',*/
|
'no task for this project' => '',
|
||||||
|
'Public link' => '',
|
||||||
|
*/
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -17,7 +17,7 @@ require __DIR__.'/schema.php';
|
|||||||
abstract class Base
|
abstract class Base
|
||||||
{
|
{
|
||||||
const APP_VERSION = 'master';
|
const APP_VERSION = 'master';
|
||||||
const DB_VERSION = 2;
|
const DB_VERSION = 3;
|
||||||
const DB_FILENAME = 'data/db.sqlite';
|
const DB_FILENAME = 'data/db.sqlite';
|
||||||
|
|
||||||
private static $dbInstance = null;
|
private static $dbInstance = null;
|
||||||
@@ -46,4 +46,17 @@ abstract class Base
|
|||||||
die('Unable to migrate database schema!');
|
die('Unable to migrate database schema!');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Generate a random token from /dev/urandom or with uniqid()
|
||||||
|
public static function generateToken()
|
||||||
|
{
|
||||||
|
if (ini_get('open_basedir') === '') {
|
||||||
|
$token = file_get_contents('/dev/urandom', false, null, 0, 30);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
$token = uniqid(mt_rand(), true);
|
||||||
|
}
|
||||||
|
|
||||||
|
return hash('crc32b', $token);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -66,16 +66,6 @@ class Config extends Base
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static function generateToken()
|
|
||||||
{
|
|
||||||
if (ini_get('open_basedir') === '') {
|
|
||||||
return substr(base64_encode(file_get_contents('/dev/urandom', false, null, 0, 20)), 0, 15);
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
return substr(base64_encode(uniqid(mt_rand(), true)), 0, 20);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public function optimizeDatabase()
|
public function optimizeDatabase()
|
||||||
{
|
{
|
||||||
$this->db->getconnection()->exec("VACUUM");
|
$this->db->getconnection()->exec("VACUUM");
|
||||||
|
|||||||
@@ -16,6 +16,11 @@ class Project extends Base
|
|||||||
return $this->db->table(self::TABLE)->eq('id', $project_id)->findOne();
|
return $this->db->table(self::TABLE)->eq('id', $project_id)->findOne();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function getByToken($token)
|
||||||
|
{
|
||||||
|
return $this->db->table(self::TABLE)->eq('token', $token)->findOne();
|
||||||
|
}
|
||||||
|
|
||||||
public function getFirst()
|
public function getFirst()
|
||||||
{
|
{
|
||||||
return $this->db->table(self::TABLE)->findOne();
|
return $this->db->table(self::TABLE)->findOne();
|
||||||
@@ -92,12 +97,12 @@ class Project extends Base
|
|||||||
{
|
{
|
||||||
$this->db->startTransaction();
|
$this->db->startTransaction();
|
||||||
|
|
||||||
|
$values['token'] = self::generateToken();
|
||||||
$this->db->table(self::TABLE)->save($values);
|
$this->db->table(self::TABLE)->save($values);
|
||||||
|
|
||||||
$project_id = $this->db->getConnection()->getLastId();
|
$project_id = $this->db->getConnection()->getLastId();
|
||||||
|
|
||||||
$boardModel = new \Model\Board;
|
$boardModel = new \Model\Board;
|
||||||
|
|
||||||
$boardModel->create($project_id, array(
|
$boardModel->create($project_id, array(
|
||||||
t('Backlog'),
|
t('Backlog'),
|
||||||
t('Ready'),
|
t('Ready'),
|
||||||
|
|||||||
@@ -2,11 +2,27 @@
|
|||||||
|
|
||||||
namespace Schema;
|
namespace Schema;
|
||||||
|
|
||||||
|
function version_3($pdo)
|
||||||
|
{
|
||||||
|
$pdo->exec('ALTER TABLE projects ADD column token TEXT');
|
||||||
|
|
||||||
|
// For each existing project, assign a different token
|
||||||
|
$rq = $pdo->prepare("SELECT id FROM projects WHERE token IS NULL");
|
||||||
|
$rq->execute();
|
||||||
|
$results = $rq->fetchAll(\PDO::FETCH_ASSOC);
|
||||||
|
|
||||||
|
if ($results !== false) {
|
||||||
|
|
||||||
|
foreach ($results as &$result) {
|
||||||
|
$rq = $pdo->prepare('UPDATE projects SET token=? WHERE id=?');
|
||||||
|
$rq->execute(array(\Model\Base::generateToken(), $result['id']));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
function version_2($pdo)
|
function version_2($pdo)
|
||||||
{
|
{
|
||||||
$pdo->exec('ALTER TABLE tasks ADD column date_completed INTEGER');
|
$pdo->exec('ALTER TABLE tasks ADD column date_completed INTEGER');
|
||||||
|
|
||||||
// For all existing completed tasks, set the date of creation as a date of completion
|
|
||||||
$pdo->exec('UPDATE tasks SET date_completed=date_creation WHERE is_active=0');
|
$pdo->exec('UPDATE tasks SET date_completed=date_creation WHERE is_active=0');
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -74,6 +90,6 @@ function version_1($pdo)
|
|||||||
$pdo->exec("
|
$pdo->exec("
|
||||||
INSERT INTO config
|
INSERT INTO config
|
||||||
(language, webhooks_token)
|
(language, webhooks_token)
|
||||||
VALUES ('en_US', '".\Model\Config::generateToken()."')
|
VALUES ('en_US', '".\Model\Base::generateToken()."')
|
||||||
");
|
");
|
||||||
}
|
}
|
||||||
|
|||||||
41
templates/board_public.php
Normal file
41
templates/board_public.php
Normal file
@@ -0,0 +1,41 @@
|
|||||||
|
<section id="main">
|
||||||
|
|
||||||
|
<table id="board">
|
||||||
|
<tr>
|
||||||
|
<?php $column_with = round(100 / count($columns), 2); ?>
|
||||||
|
<?php foreach ($columns as $column): ?>
|
||||||
|
<th width="<?= $column_with ?>%">
|
||||||
|
<?= Helper\escape($column['title']) ?>
|
||||||
|
</th>
|
||||||
|
<?php endforeach ?>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<?php foreach ($columns as $column): ?>
|
||||||
|
<td class="column">
|
||||||
|
<?php foreach ($column['tasks'] as $task): ?>
|
||||||
|
<div class="draggable-item">
|
||||||
|
<div class="task task-<?= $task['color_id'] ?>">
|
||||||
|
|
||||||
|
#<?= $task['id'] ?> -
|
||||||
|
|
||||||
|
<span class="task-user">
|
||||||
|
<?php if (! empty($task['owner_id'])): ?>
|
||||||
|
<?= t('Assigned to %s', $task['username']) ?>
|
||||||
|
<?php else: ?>
|
||||||
|
<span class="task-nobody"><?= t('No body assigned') ?></span>
|
||||||
|
<?php endif ?>
|
||||||
|
</span>
|
||||||
|
|
||||||
|
<div class="task-title">
|
||||||
|
<?= Helper\escape($task['title']) ?>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<?php endforeach ?>
|
||||||
|
</td>
|
||||||
|
<?php endforeach ?>
|
||||||
|
</tr>
|
||||||
|
</table>
|
||||||
|
|
||||||
|
</section>
|
||||||
@@ -10,6 +10,9 @@
|
|||||||
<link rel="apple-touch-icon" sizes="114x114" href="assets/img/touch-icon-iphone-retina.png">
|
<link rel="apple-touch-icon" sizes="114x114" href="assets/img/touch-icon-iphone-retina.png">
|
||||||
<link rel="apple-touch-icon" sizes="144x144" href="assets/img/touch-icon-ipad-retina.png">
|
<link rel="apple-touch-icon" sizes="144x144" href="assets/img/touch-icon-ipad-retina.png">
|
||||||
<title><?= isset($title) ? Helper\escape($title) : 'Kanboard' ?></title>
|
<title><?= isset($title) ? Helper\escape($title) : 'Kanboard' ?></title>
|
||||||
|
<?php if (isset($auto_refresh)): ?>
|
||||||
|
<meta http-equiv="refresh" content="<?= AUTO_REFRESH_DURATION ?>" >
|
||||||
|
<?php endif ?>
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<?php if (isset($no_layout)): ?>
|
<?php if (isset($no_layout)): ?>
|
||||||
|
|||||||
@@ -75,6 +75,9 @@
|
|||||||
<li>
|
<li>
|
||||||
<a href="?controller=project&action=confirm&project_id=<?= $project['id'] ?>"><?= t('Remove') ?></a>
|
<a href="?controller=project&action=confirm&project_id=<?= $project['id'] ?>"><?= t('Remove') ?></a>
|
||||||
</li>
|
</li>
|
||||||
|
<li>
|
||||||
|
<a href="?controller=board&action=readonly&token=<?= $project['token'] ?>" target="_blank"><?= t('Public link') ?></a>
|
||||||
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
</td>
|
</td>
|
||||||
<?php endif ?>
|
<?php endif ?>
|
||||||
|
|||||||
Reference in New Issue
Block a user