Add kiosk mode, public board access with read-only and auto-refresh

This commit is contained in:
Frédéric Guillot 2014-02-22 16:30:03 -05:00
parent a1923d3d7f
commit 2f4651411b
12 changed files with 121 additions and 19 deletions

View File

@ -45,6 +45,7 @@ abstract class Base
$public = array(
'user' => array('login', 'check'),
'task' => array('add'),
'board' => array('readonly'),
);
if (isset($public[$controller])) {

View File

@ -4,7 +4,29 @@ namespace Controller;
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()
{
$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()
{
$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']);
}
// Save the board (Ajax request made by drag and drop)
// Save the board (Ajax request made by the drag and drop)
public function save()
{
$this->response->json(array(

View File

@ -4,5 +4,10 @@ require __DIR__.'/check_setup.php';
require __DIR__.'/controllers/base.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->execute();

View File

@ -179,4 +179,5 @@ return array(
'Completed tasks for "%s"' => 'Tâches terminées pour "%s"',
'%d closed tasks' => '%d tâches terminées',
'no task for this project' => 'aucune tâche pour ce projet',
'Public link' => 'Accès public',
);

View File

@ -181,5 +181,7 @@ return array(
'List of projects' => '',
'Completed tasks for "%s"' => '',
'%d closed tasks' => '',
'no task for this project' => '',*/
'no task for this project' => '',
'Public link' => '',
*/
);

View File

@ -17,7 +17,7 @@ require __DIR__.'/schema.php';
abstract class Base
{
const APP_VERSION = 'master';
const DB_VERSION = 2;
const DB_VERSION = 3;
const DB_FILENAME = 'data/db.sqlite';
private static $dbInstance = null;
@ -46,4 +46,17 @@ abstract class Base
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);
}
}

View File

@ -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()
{
$this->db->getconnection()->exec("VACUUM");

View File

@ -16,6 +16,11 @@ class Project extends Base
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()
{
return $this->db->table(self::TABLE)->findOne();
@ -92,12 +97,12 @@ class Project extends Base
{
$this->db->startTransaction();
$values['token'] = self::generateToken();
$this->db->table(self::TABLE)->save($values);
$project_id = $this->db->getConnection()->getLastId();
$boardModel = new \Model\Board;
$boardModel->create($project_id, array(
t('Backlog'),
t('Ready'),

View File

@ -2,11 +2,27 @@
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)
{
$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');
}
@ -74,6 +90,6 @@ function version_1($pdo)
$pdo->exec("
INSERT INTO config
(language, webhooks_token)
VALUES ('en_US', '".\Model\Config::generateToken()."')
VALUES ('en_US', '".\Model\Base::generateToken()."')
");
}

View 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>

View File

@ -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="144x144" href="assets/img/touch-icon-ipad-retina.png">
<title><?= isset($title) ? Helper\escape($title) : 'Kanboard' ?></title>
<?php if (isset($auto_refresh)): ?>
<meta http-equiv="refresh" content="<?= AUTO_REFRESH_DURATION ?>" >
<?php endif ?>
</head>
<body>
<?php if (isset($no_layout)): ?>

View File

@ -75,6 +75,9 @@
<li>
<a href="?controller=project&amp;action=confirm&amp;project_id=<?= $project['id'] ?>"><?= t('Remove') ?></a>
</li>
<li>
<a href="?controller=board&amp;action=readonly&amp;token=<?= $project['token'] ?>" target="_blank"><?= t('Public link') ?></a>
</li>
</ul>
</td>
<?php endif ?>