Improve settings page and move some config parameters to the database

This commit is contained in:
Frédéric Guillot
2014-10-04 23:31:03 -04:00
parent f531d57dc2
commit 8e5673e3d2
64 changed files with 710 additions and 248 deletions

View File

@@ -5,7 +5,6 @@ namespace Controller;
use Core\Tool;
use Core\Registry;
use Core\Security;
use Core\Translator;
use Model\LastLogin;
/**
@@ -123,12 +122,8 @@ abstract class Base
$this->response->hsts();
}
// Load translations
$language = $this->config->get('language', 'en_US');
if ($language !== 'en_US') Translator::load($language);
// Set timezone
date_default_timezone_set($this->config->get('timezone', 'UTC'));
$this->config->setupTranslations();
$this->config->setupTimezone();
// Authentication
if (! $this->authentication->isAuthenticated($controller, $action)) {

View File

@@ -177,8 +177,8 @@ class Board extends Base
'categories' => $this->category->getList($project['id'], false),
'title' => $project['name'],
'no_layout' => true,
'auto_refresh' => true,
'not_editable' => true,
'board_public_refresh_interval' => $this->config->get('board_public_refresh_interval'),
)));
}
@@ -238,6 +238,8 @@ class Board extends Base
'menu' => 'boards',
'title' => $projects[$project['id']],
'board_selector' => $board_selector,
'board_private_refresh_interval' => $this->config->get('board_private_refresh_interval'),
'board_highlight_period' => $this->config->get('board_highlight_period'),
)));
}
@@ -407,6 +409,8 @@ class Board extends Base
'current_project_id' => $project_id,
'board' => $this->board->get($project_id),
'categories' => $this->category->getList($project_id, false),
'board_private_refresh_interval' => $this->config->get('board_private_refresh_interval'),
'board_highlight_period' => $this->config->get('board_highlight_period'),
)),
201
);
@@ -443,6 +447,8 @@ class Board extends Base
'current_project_id' => $project_id,
'board' => $this->board->get($project_id),
'categories' => $this->category->getList($project_id, false),
'board_private_refresh_interval' => $this->config->get('board_private_refresh_interval'),
'board_highlight_period' => $this->config->get('board_highlight_period'),
))
);
}

View File

@@ -11,55 +11,114 @@ namespace Controller;
class Config extends Base
{
/**
* Display the settings page
* Common layout for config views
*
* @access private
* @param string $template Template name
* @param array $params Template parameters
* @return string
*/
private function layout($template, array $params)
{
$params['values'] = $this->config->getAll();
$params['errors'] = array();
$params['menu'] = 'config';
$params['config_content_for_layout'] = $this->template->load($template, $params);
return $this->template->layout('config_layout', $params);
}
/**
* Common method between pages
*
* @access private
* @param string $redirect Action to redirect after saving the form
*/
private function common($redirect)
{
if ($this->request->isPost()) {
$values = $this->request->getValues();
if ($this->config->save($values)) {
$this->config->reload();
$this->session->flash(t('Settings saved successfully.'));
}
else {
$this->session->flashError(t('Unable to save your settings.'));
}
$this->response->redirect('?controller=config&action='.$redirect);
}
}
/**
* Display the about page
*
* @access public
*/
public function index()
{
$this->response->html($this->template->layout('config_index', array(
$this->response->html($this->layout('config_about', array(
'db_size' => $this->config->getDatabaseSize(),
'title' => t('About'),
)));
}
/**
* Display the application settings page
*
* @access public
*/
public function application()
{
$this->common('application');
$this->response->html($this->layout('config_application', array(
'title' => t('Application settings'),
'languages' => $this->config->getLanguages(),
'values' => $this->config->getAll(),
'errors' => array(),
'menu' => 'config',
'title' => t('Settings'),
'timezones' => $this->config->getTimezones(),
)));
}
/**
* Display the board settings page
*
* @access public
*/
public function board()
{
$this->common('board');
$this->response->html($this->layout('config_board', array(
'title' => t('Board settings'),
'default_columns' => implode(', ', $this->board->getDefaultColumns()),
)));
}
/**
* Validate and save settings
* Display the webhook settings page
*
* @access public
*/
public function save()
public function webhook()
{
$values = $this->request->getValues();
list($valid, $errors) = $this->config->validateModification($values);
$this->common('webhook');
if ($valid) {
$this->response->html($this->layout('config_webhook', array(
'title' => t('Webhook settings'),
)));
}
if ($this->config->save($values)) {
$this->config->reload();
$this->session->flash(t('Settings saved successfully.'));
} else {
$this->session->flashError(t('Unable to save your settings.'));
}
$this->response->redirect('?controller=config');
}
$this->response->html($this->template->layout('config_index', array(
'db_size' => $this->config->getDatabaseSize(),
'languages' => $this->config->getLanguages(),
'values' => $values,
'errors' => $errors,
'menu' => 'config',
'title' => t('Settings'),
'timezones' => $this->config->getTimezones(),
'default_columns' => implode(', ', $this->board->getDefaultColumns()),
/**
* Display the api settings page
*
* @access public
*/
public function api()
{
$this->response->html($this->layout('config_api', array(
'title' => t('API'),
)));
}
@@ -89,15 +148,18 @@ class Config extends Base
}
/**
* Regenerate all application tokens
* Regenerate webhook token
*
* @access public
*/
public function tokens()
public function token()
{
$type = $this->request->getStringParam('type');
$this->checkCSRFParam();
$this->config->regenerateTokens();
$this->session->flash(t('All tokens have been regenerated.'));
$this->response->redirect('?controller=config');
$this->config->regenerateToken($type.'_token');
$this->session->flash(t('Token regenerated.'));
$this->response->redirect('?controller=config&action='.$type);
}
}

View File

@@ -73,7 +73,7 @@ class User extends Base
}
/**
* Common layout for project views
* Common layout for user views
*
* @access private
* @param string $template Template name

View File

@@ -17,7 +17,7 @@ class Webhook extends Base
*/
public function task()
{
if ($this->config->get('webhooks_token') !== $this->request->getStringParam('token')) {
if ($this->config->get('webhook_token') !== $this->request->getStringParam('token')) {
$this->response->text('Not Authorized', 401);
}
@@ -49,7 +49,7 @@ class Webhook extends Base
*/
public function github()
{
if ($this->config->get('webhooks_token') !== $this->request->getStringParam('token')) {
if ($this->config->get('webhook_token') !== $this->request->getStringParam('token')) {
$this->response->text('Not Authorized', 401);
}

View File

@@ -83,7 +83,7 @@ return array(
'Settings' => 'Einstellungen',
'Application settings' => 'Anwendungskonfiguration',
'Language' => 'Sprache',
'Webhooks token:' => 'Webhooks Token:',
// 'Webhook token:' => '',
'API token:' => 'API Token:',
'More information' => 'Mehr Informationen',
'Database size:' => 'Datenbankgröße:',
@@ -516,4 +516,22 @@ return array(
// 'Reference' => '',
// 'Reference: %s' => '',
// 'Label' => '',
// 'Database' => '',
// 'About' => '',
// 'Database driver:' => '',
// 'Board settings' => '',
// 'URL and token' => '',
// 'Webhook settings' => '',
// 'URL for task creation:' => '',
// 'Reset token' => '',
// 'API endpoint:' => '',
// 'Refresh interval for private board' => '',
// 'Refresh interval for public board' => '',
// 'Task highlight period' => '',
// 'Period (in second) to consider a task was modified recently (0 to disable, 2 days by default)' => '',
// 'Frequency in second (60 seconds by default)' => '',
// 'Frequency in second (0 to disable this feature, 10 seconds by default)' => '',
// 'Application URL' => '',
// 'Example: http://example.kanboard.net/ (used by email notifications)' => '',
// 'Token regenerated.' => '',
);

View File

@@ -83,7 +83,7 @@ return array(
'Settings' => 'Preferencias',
'Application settings' => 'Parámetros de la aplicación',
'Language' => 'Idioma',
'Webhooks token:' => 'Ficha de seguridad (token) para los webhooks :',
'Webhook token:' => 'Ficha de seguridad (token) para los webhooks :',
'API token:' => 'Ficha de seguridad (token) para API:',
'More information' => 'Más informaciones',
'Database size:' => 'Tamaño de la base de datos:',
@@ -516,4 +516,22 @@ return array(
// 'Reference' => '',
// 'Reference: %s' => '',
// 'Label' => '',
// 'Database' => '',
// 'About' => '',
// 'Database driver:' => '',
// 'Board settings' => '',
// 'URL and token' => '',
// 'Webhook settings' => '',
// 'URL for task creation:' => '',
// 'Reset token' => '',
// 'API endpoint:' => '',
// 'Refresh interval for private board' => '',
// 'Refresh interval for public board' => '',
// 'Task highlight period' => '',
// 'Period (in second) to consider a task was modified recently (0 to disable, 2 days by default)' => '',
// 'Frequency in second (60 seconds by default)' => '',
// 'Frequency in second (0 to disable this feature, 10 seconds by default)' => '',
// 'Application URL' => '',
// 'Example: http://example.kanboard.net/ (used by email notifications)' => '',
// 'Token regenerated.' => '',
);

View File

@@ -83,7 +83,7 @@ return array(
'Settings' => 'Asetukset',
'Application settings' => 'Ohjelman asetukset',
'Language' => 'Kieli',
'Webhooks token:' => 'Webhooks avain:',
'Webhook token:' => 'Webhooks avain:',
// 'API token:' => '',
'More information' => 'Lisätietoja',
'Database size:' => 'Tietokannan koko:',
@@ -516,4 +516,22 @@ return array(
// 'Reference' => '',
// 'Reference: %s' => '',
// 'Label' => '',
// 'Database' => '',
// 'About' => '',
// 'Database driver:' => '',
// 'Board settings' => '',
// 'URL and token' => '',
// 'Webhook settings' => '',
// 'URL for task creation:' => '',
// 'Reset token' => '',
// 'API endpoint:' => '',
// 'Refresh interval for private board' => '',
// 'Refresh interval for public board' => '',
// 'Task highlight period' => '',
// 'Period (in second) to consider a task was modified recently (0 to disable, 2 days by default)' => '',
// 'Frequency in second (60 seconds by default)' => '',
// 'Frequency in second (0 to disable this feature, 10 seconds by default)' => '',
// 'Application URL' => '',
// 'Example: http://example.kanboard.net/ (used by email notifications)' => '',
// 'Token regenerated.' => '',
);

View File

@@ -83,7 +83,7 @@ return array(
'Settings' => 'Préférences',
'Application settings' => 'Paramètres de l\'application',
'Language' => 'Langue',
'Webhooks token:' => 'Jeton de securité pour les webhooks :',
'Webhook token:' => 'Jeton de securité pour les webhooks :',
'API token:' => 'Jeton de securité pour l\'API :',
'More information' => 'Plus d\'informations',
'Database size:' => 'Taille de la base de données :',
@@ -516,4 +516,22 @@ return array(
'Reference' => 'Référence',
'Reference: %s' => 'Référence : %s',
'Label' => 'Libellé',
'Database' => 'Base de données',
'About' => 'A propos',
'Database driver:' => 'Type de base de données :',
'Board settings' => 'Paramètres du tableau',
'URL and token' => 'URL et jeton de sécurité',
'Webhook settings' => 'Paramètres pour les webhooks',
'URL for task creation:' => 'URL pour la création de tâche :',
'Reset token' => 'Regénérer le jeton de sécurité',
'API endpoint:' => 'URL de l\'API :',
'Refresh interval for private board' => 'Intervalle pour rafraîchir un tableau privé',
'Refresh interval for public board' => 'Intervalle pour rafraîchir un tableau public',
'Task highlight period' => 'Durée pour mettre une tâche en évidence',
'Period (in second) to consider a task was modified recently (0 to disable, 2 days by default)' => 'Durée en seconde pour considérer une tâche comme récemment modifiée (0 pour désactiver, 2 jours par défaut)',
'Frequency in second (60 seconds by default)' => 'Fréquence en seconde (60 secondes par défaut)',
'Frequency in second (0 to disable this feature, 10 seconds by default)' => 'Fréquence en seconde (0 pour désactiver, 10 secondes par défaut)',
'Application URL' => 'URL de l\'application',
'Example: http://example.kanboard.net/ (used by email notifications)' => 'Exemple : http://exemple.kanboard.net/ (utilisé pour les notifications)',
'Token regenerated.' => 'Jeton de sécurité regénéré.',
);

View File

@@ -83,7 +83,7 @@ return array(
'Settings' => 'Impostazioni',
'Application settings' => 'Impostazioni dell\'applicazione',
'Language' => 'Lingua',
'Webhooks token:' => 'Identificatore (token) per i webhooks :',
'Webhook token:' => 'Identificatore (token) per i webhooks :',
// 'API token:' => '',
'More information' => 'Più informazioni',
'Database size:' => 'Dimensioni della base dati:',
@@ -516,4 +516,22 @@ return array(
// 'Reference' => '',
// 'Reference: %s' => '',
// 'Label' => '',
// 'Database' => '',
// 'About' => '',
// 'Database driver:' => '',
// 'Board settings' => '',
// 'URL and token' => '',
// 'Webhook settings' => '',
// 'URL for task creation:' => '',
// 'Reset token' => '',
// 'API endpoint:' => '',
// 'Refresh interval for private board' => '',
// 'Refresh interval for public board' => '',
// 'Task highlight period' => '',
// 'Period (in second) to consider a task was modified recently (0 to disable, 2 days by default)' => '',
// 'Frequency in second (60 seconds by default)' => '',
// 'Frequency in second (0 to disable this feature, 10 seconds by default)' => '',
// 'Application URL' => '',
// 'Example: http://example.kanboard.net/ (used by email notifications)' => '',
// 'Token regenerated.' => '',
);

View File

@@ -83,7 +83,7 @@ return array(
'Settings' => 'Ustawienia',
'Application settings' => 'Ustawienia aplikacji',
'Language' => 'Język',
'Webhooks token:' => 'Token :',
'Webhook token:' => 'Token :',
// 'API token:' => '',
'More information' => 'Więcej informacji',
'Database size:' => 'Rozmiar bazy danych :',
@@ -516,4 +516,22 @@ return array(
// 'Reference' => '',
// 'Reference: %s' => '',
// 'Label' => '',
// 'Database' => '',
// 'About' => '',
// 'Database driver:' => '',
// 'Board settings' => '',
// 'URL and token' => '',
// 'Webhook settings' => '',
// 'URL for task creation:' => '',
// 'Reset token' => '',
// 'API endpoint:' => '',
// 'Refresh interval for private board' => '',
// 'Refresh interval for public board' => '',
// 'Task highlight period' => '',
// 'Period (in second) to consider a task was modified recently (0 to disable, 2 days by default)' => '',
// 'Frequency in second (60 seconds by default)' => '',
// 'Frequency in second (0 to disable this feature, 10 seconds by default)' => '',
// 'Application URL' => '',
// 'Example: http://example.kanboard.net/ (used by email notifications)' => '',
// 'Token regenerated.' => '',
);

View File

@@ -83,7 +83,7 @@ return array(
'Settings' => 'Preferências',
'Application settings' => 'Preferências da aplicação',
'Language' => 'Idioma',
'Webhooks token:' => 'Token de webhooks:',
'Webhook token:' => 'Token de webhooks:',
'API token:' => 'API Token:',
'More information' => 'Mais informação',
'Database size:' => 'Tamanho do banco de dados:',
@@ -516,4 +516,22 @@ return array(
// 'Reference' => '',
// 'Reference: %s' => '',
// 'Label' => '',
// 'Database' => '',
// 'About' => '',
// 'Database driver:' => '',
// 'Board settings' => '',
// 'URL and token' => '',
// 'Webhook settings' => '',
// 'URL for task creation:' => '',
// 'Reset token' => '',
// 'API endpoint:' => '',
// 'Refresh interval for private board' => '',
// 'Refresh interval for public board' => '',
// 'Task highlight period' => '',
// 'Period (in second) to consider a task was modified recently (0 to disable, 2 days by default)' => '',
// 'Frequency in second (60 seconds by default)' => '',
// 'Frequency in second (0 to disable this feature, 10 seconds by default)' => '',
// 'Application URL' => '',
// 'Example: http://example.kanboard.net/ (used by email notifications)' => '',
// 'Token regenerated.' => '',
);

View File

@@ -83,7 +83,7 @@ return array(
'Settings' => 'Настройки',
'Application settings' => 'Настройки приложения',
'Language' => 'Язык',
'Webhooks token:' => 'Webhooks токен :',
'Webhook token:' => 'Webhooks токен :',
'API token:' => 'API токен :',
'More information' => 'Подробнее',
'Database size:' => 'Размер базы данных :',
@@ -516,4 +516,22 @@ return array(
// 'Reference' => '',
// 'Reference: %s' => '',
// 'Label' => '',
// 'Database' => '',
// 'About' => '',
// 'Database driver:' => '',
// 'Board settings' => '',
// 'URL and token' => '',
// 'Webhook settings' => '',
// 'URL for task creation:' => '',
// 'Reset token' => '',
// 'API endpoint:' => '',
// 'Refresh interval for private board' => '',
// 'Refresh interval for public board' => '',
// 'Task highlight period' => '',
// 'Period (in second) to consider a task was modified recently (0 to disable, 2 days by default)' => '',
// 'Frequency in second (60 seconds by default)' => '',
// 'Frequency in second (0 to disable this feature, 10 seconds by default)' => '',
// 'Application URL' => '',
// 'Example: http://example.kanboard.net/ (used by email notifications)' => '',
// 'Token regenerated.' => '',
);

View File

@@ -83,7 +83,7 @@ return array(
'Settings' => 'Inställningar',
'Application settings' => 'Applikationsinställningar',
'Language' => 'Språk',
'Webhooks token:' => 'Token för webhooks:',
'Webhook token:' => 'Token för webhooks:',
'API token:' => 'API token:',
'More information' => 'Mer information',
'Database size:' => 'Databasstorlek:',
@@ -516,4 +516,22 @@ return array(
// 'Reference' => '',
// 'Reference: %s' => '',
// 'Label' => '',
// 'Database' => '',
// 'About' => '',
// 'Database driver:' => '',
// 'Board settings' => '',
// 'URL and token' => '',
// 'Webhook settings' => '',
// 'URL for task creation:' => '',
// 'Reset token' => '',
// 'API endpoint:' => '',
// 'Refresh interval for private board' => '',
// 'Refresh interval for public board' => '',
// 'Task highlight period' => '',
// 'Period (in second) to consider a task was modified recently (0 to disable, 2 days by default)' => '',
// 'Frequency in second (60 seconds by default)' => '',
// 'Frequency in second (0 to disable this feature, 10 seconds by default)' => '',
// 'Application URL' => '',
// 'Example: http://example.kanboard.net/ (used by email notifications)' => '',
// 'Token regenerated.' => '',
);

View File

@@ -83,7 +83,7 @@ return array(
'Settings' => '设置',
'Application settings' => '应用设置',
'Language' => '语言',
'Webhooks token:' => '页面钩子令牌:',
'Webhook token:' => '页面钩子令牌:',
// 'API token:' => '',
'More information' => '更多信息',
'Database size:' => '数据库大小:',
@@ -516,4 +516,22 @@ return array(
// 'Reference' => '',
// 'Reference: %s' => '',
// 'Label' => '',
// 'Database' => '',
// 'About' => '',
// 'Database driver:' => '',
// 'Board settings' => '',
// 'URL and token' => '',
// 'Webhook settings' => '',
// 'URL for task creation:' => '',
// 'Reset token' => '',
// 'API endpoint:' => '',
// 'Refresh interval for private board' => '',
// 'Refresh interval for public board' => '',
// 'Task highlight period' => '',
// 'Period (in second) to consider a task was modified recently (0 to disable, 2 days by default)' => '',
// 'Frequency in second (60 seconds by default)' => '',
// 'Frequency in second (0 to disable this feature, 10 seconds by default)' => '',
// 'Application URL' => '',
// 'Example: http://example.kanboard.net/ (used by email notifications)' => '',
// 'Token regenerated.' => '',
);

View File

@@ -6,6 +6,7 @@ use SimpleValidator\Validator;
use SimpleValidator\Validators;
use Core\Translator;
use Core\Security;
use Core\Session;
/**
* Config model
@@ -20,7 +21,7 @@ class Config extends Base
*
* @var string
*/
const TABLE = 'config';
const TABLE = 'settings';
/**
* Get available timezones
@@ -68,6 +69,11 @@ class Config extends Base
*/
public function get($name, $default_value = '')
{
if (! Session::isOpen()) {
$value = $this->db->table(self::TABLE)->eq('option', $name)->findOneColumn('value');
return $value ?: $default_value;
}
if (! isset($_SESSION['config'][$name])) {
$_SESSION['config'] = $this->getAll();
}
@@ -87,7 +93,7 @@ class Config extends Base
*/
public function getAll()
{
return $this->db->table(self::TABLE)->findOne();
return $this->db->table(self::TABLE)->listing('option', 'value');
}
/**
@@ -99,8 +105,16 @@ class Config extends Base
*/
public function save(array $values)
{
$_SESSION['config'] = $values;
return $this->db->table(self::TABLE)->update($values);
foreach ($values as $option => $value) {
$result = $this->db->table(self::TABLE)->eq('option', $option)->update(array('value' => $value));
if (! $result) {
return false;
}
}
return true;
}
/**
@@ -111,27 +125,31 @@ class Config extends Base
public function reload()
{
$_SESSION['config'] = $this->getAll();
Translator::load($this->get('language', 'en_US'));
$this->setupTranslations();
}
/**
* Validate settings modification
* Load translations
*
* @access public
* @param array $values Form values
* @return array $valid, $errors [0] = Success or not, [1] = List of errors
*/
public function validateModification(array $values)
public function setupTranslations()
{
$v = new Validator($values, array(
new Validators\Required('language', t('The language is required')),
new Validators\Required('timezone', t('The timezone is required')),
));
$language = $this->get('application_language', 'en_US');
return array(
$v->execute(),
$v->getErrors()
);
if ($language !== 'en_US') {
Translator::load($language);
}
}
/**
* Set timezone
*
* @access public
*/
public function setupTimezone()
{
date_default_timezone_set($this->get('application_timezone', 'UTC'));
}
/**
@@ -168,21 +186,15 @@ class Config extends Base
}
/**
* Regenerate all tokens (projects and webhooks)
* Regenerate a token
*
* @access public
* @param string $option Parameter name
*/
public function regenerateTokens()
public function regenerateToken($option)
{
$this->db->table(self::TABLE)->update(array(
'webhooks_token' => Security::generateToken(),
'api_token' => Security::generateToken(),
));
$projects = $this->db->table(Project::TABLE)->findAllByColumn('id');
foreach ($projects as $project_id) {
$this->db->table(Project::TABLE)->eq('id', $project_id)->update(array('token' => Security::generateToken()));
}
return $this->db->table(self::TABLE)
->eq('option', $option)
->update(array('value' => Security::generateToken()));
}
}

View File

@@ -182,7 +182,7 @@ class Notification extends Base
public function getMailContent($template, array $data)
{
$tpl = new Template;
return $tpl->load($template, $data);
return $tpl->load($template, $data + array('application_url' => $this->config->get('application_url')));
}
/**

View File

@@ -287,7 +287,7 @@ class Project extends Base
}
$project_id = $this->db->getConnection()->getLastId();
$column_names = explode(',', $this->config->get('default_columns', implode(',', $this->board->getDefaultColumns())));
$column_names = explode(',', $this->config->get('board_columns', implode(',', $this->board->getDefaultColumns())));
$columns = array();
foreach ($column_names as $column_name) {

View File

@@ -64,9 +64,9 @@ class Webhook extends Base
*/
public function attachEvents()
{
$this->url_task_creation = $this->config->get('webhooks_url_task_creation');
$this->url_task_modification = $this->config->get('webhooks_url_task_modification');
$this->token = $this->config->get('webhooks_token');
$this->url_task_creation = $this->config->get('webhook_url_task_creation');
$this->url_task_modification = $this->config->get('webhook_url_task_modification');
$this->token = $this->config->get('webhook_token');
if ($this->url_task_creation) {
$this->attachCreateEvents();

View File

@@ -2,9 +2,40 @@
namespace Schema;
use PDO;
use Core\Security;
const VERSION = 28;
const VERSION = 29;
function version_29($pdo)
{
$pdo->exec("
CREATE TABLE settings (
option VARCHAR(100) PRIMARY KEY,
value VARCHAR(255) DEFAULT ''
)
");
// Migrate old config parameters
$rq = $pdo->prepare('SELECT * FROM config');
$rq->execute();
$parameters = $rq->fetch(PDO::FETCH_ASSOC);
$rq = $pdo->prepare('INSERT INTO settings VALUES (?, ?)');
$rq->execute(array('board_highlight_period', defined('RECENT_TASK_PERIOD') ? RECENT_TASK_PERIOD : 48*60*60));
$rq->execute(array('board_public_refresh_interval', defined('BOARD_PUBLIC_CHECK_INTERVAL') ? BOARD_PUBLIC_CHECK_INTERVAL : 60));
$rq->execute(array('board_private_refresh_interval', defined('BOARD_CHECK_INTERVAL') ? BOARD_CHECK_INTERVAL : 10));
$rq->execute(array('board_columns', $parameters['default_columns']));
$rq->execute(array('webhook_url_task_creation', $parameters['webhooks_url_task_creation']));
$rq->execute(array('webhook_url_task_modification', $parameters['webhooks_url_task_modification']));
$rq->execute(array('webhook_token', $parameters['webhooks_token']));
$rq->execute(array('api_token', $parameters['api_token']));
$rq->execute(array('application_language', $parameters['language']));
$rq->execute(array('application_timezone', $parameters['timezone']));
$rq->execute(array('application_url', defined('KANBOARD_URL') ? KANBOARD_URL : ''));
$pdo->exec('DROP TABLE config');
}
function version_28($pdo)
{

View File

@@ -2,9 +2,40 @@
namespace Schema;
use PDO;
use Core\Security;
const VERSION = 9;
const VERSION = 10;
function version_10($pdo)
{
$pdo->exec("
CREATE TABLE settings (
option VARCHAR(100) PRIMARY KEY,
value VARCHAR(255) DEFAULT ''
)
");
// Migrate old config parameters
$rq = $pdo->prepare('SELECT * FROM config');
$rq->execute();
$parameters = $rq->fetch(PDO::FETCH_ASSOC);
$rq = $pdo->prepare('INSERT INTO settings VALUES (?, ?)');
$rq->execute(array('board_highlight_period', defined('RECENT_TASK_PERIOD') ? RECENT_TASK_PERIOD : 48*60*60));
$rq->execute(array('board_public_refresh_interval', defined('BOARD_PUBLIC_CHECK_INTERVAL') ? BOARD_PUBLIC_CHECK_INTERVAL : 60));
$rq->execute(array('board_private_refresh_interval', defined('BOARD_CHECK_INTERVAL') ? BOARD_CHECK_INTERVAL : 10));
$rq->execute(array('board_columns', $parameters['default_columns']));
$rq->execute(array('webhook_url_task_creation', $parameters['webhooks_url_task_creation']));
$rq->execute(array('webhook_url_task_modification', $parameters['webhooks_url_task_modification']));
$rq->execute(array('webhook_token', $parameters['webhooks_token']));
$rq->execute(array('api_token', $parameters['api_token']));
$rq->execute(array('application_language', $parameters['language']));
$rq->execute(array('application_timezone', $parameters['timezone']));
$rq->execute(array('application_url', defined('KANBOARD_URL') ? KANBOARD_URL : ''));
$pdo->exec('DROP TABLE config');
}
function version_9($pdo)
{

View File

@@ -3,8 +3,39 @@
namespace Schema;
use Core\Security;
use PDO;
const VERSION = 28;
const VERSION = 29;
function version_29($pdo)
{
$pdo->exec("
CREATE TABLE settings (
option TEXT PRIMARY KEY,
value TEXT DEFAULT ''
)
");
// Migrate old config parameters
$rq = $pdo->prepare('SELECT * FROM config');
$rq->execute();
$parameters = $rq->fetch(PDO::FETCH_ASSOC);
$rq = $pdo->prepare('INSERT INTO settings VALUES (?, ?)');
$rq->execute(array('board_highlight_period', defined('RECENT_TASK_PERIOD') ? RECENT_TASK_PERIOD : 48*60*60));
$rq->execute(array('board_public_refresh_interval', defined('BOARD_PUBLIC_CHECK_INTERVAL') ? BOARD_PUBLIC_CHECK_INTERVAL : 60));
$rq->execute(array('board_private_refresh_interval', defined('BOARD_CHECK_INTERVAL') ? BOARD_CHECK_INTERVAL : 10));
$rq->execute(array('board_columns', $parameters['default_columns']));
$rq->execute(array('webhook_url_task_creation', $parameters['webhooks_url_task_creation']));
$rq->execute(array('webhook_url_task_modification', $parameters['webhooks_url_task_modification']));
$rq->execute(array('webhook_token', $parameters['webhooks_token']));
$rq->execute(array('api_token', $parameters['api_token']));
$rq->execute(array('application_language', $parameters['language']));
$rq->execute(array('application_timezone', $parameters['timezone']));
$rq->execute(array('application_url', defined('KANBOARD_URL') ? KANBOARD_URL : ''));
$pdo->exec('DROP TABLE config');
}
function version_28($pdo)
{

View File

@@ -26,7 +26,13 @@
<?php if (empty($board)): ?>
<p class="alert alert-error"><?= t('There is no column in your project!') ?></p>
<?php else: ?>
<?= Helper\template('board_show', array('current_project_id' => $current_project_id, 'board' => $board, 'categories' => $categories)) ?>
<?= Helper\template('board_show', array(
'current_project_id' => $current_project_id,
'board' => $board,
'categories' => $categories,
'board_private_refresh_interval' => $board_private_refresh_interval,
'board_highlight_period' => $board_highlight_period,
)) ?>
<?php endif ?>
</section>

View File

@@ -1,4 +1,4 @@
<table id="board" data-project-id="<?= $current_project_id ?>" data-time="<?= time() ?>" data-check-interval="<?= BOARD_CHECK_INTERVAL ?>" data-csrf-token=<?= \Core\Security::getCSRFToken() ?>>
<table id="board" data-project-id="<?= $current_project_id ?>" data-time="<?= time() ?>" data-check-interval="<?= $board_private_refresh_interval ?>" data-csrf-token=<?= \Core\Security::getCSRFToken() ?>>
<tr>
<?php $column_with = round(100 / count($board), 2); ?>
<?php foreach ($board as $column): ?>
@@ -32,7 +32,7 @@
data-task-limit="<?= $column['task_limit'] ?>"
>
<?php foreach ($column['tasks'] as $task): ?>
<div class="task-board draggable-item task-<?= $task['color_id'] ?> <?= $task['date_modification'] > time() - RECENT_TASK_PERIOD ? 'task-board-recent' : '' ?>"
<div class="task-board draggable-item task-<?= $task['color_id'] ?> <?= $task['date_modification'] > time() - $board_highlight_period ? 'task-board-recent' : '' ?>"
data-task-id="<?= $task['id'] ?>"
data-owner-id="<?= $task['owner_id'] ?>"
data-category-id="<?= $task['category_id'] ?>"

View File

@@ -0,0 +1,41 @@
<div class="page-header">
<h2><?= t('About') ?></h2>
</div>
<section class="listing">
<ul>
<li>
<?= t('Official website:') ?>
<a href="http://kanboard.net/" target="_blank" rel="noreferer">http://kanboard.net/</a>
</li>
<li>
<?= t('Application version:') ?>
<strong><?= APP_VERSION ?></strong>
</li>
</ul>
</section>
<div class="page-header">
<h2><?= t('Database') ?></h2>
</div>
<section class="listing">
<ul>
<li>
<?= t('Database driver:') ?>
<strong><?= Helper\escape(DB_DRIVER) ?></strong>
</li>
<?php if (DB_DRIVER === 'sqlite'): ?>
<li>
<?= t('Database size:') ?>
<strong><?= Helper\format_bytes($db_size) ?></strong>
</li>
<li>
<?= Helper\a(t('Download the database'), 'config', 'downloadDb', array(), true) ?>&nbsp;
<?= t('(Gzip compressed Sqlite file)') ?>
</li>
<li>
<?= Helper\a(t('Optimize the database'), 'config', 'optimizeDb', array(), true) ?>&nbsp;
<?= t('(VACUUM command)') ?>
</li>
<?php endif ?>
</ul>
</section>

View File

@@ -0,0 +1,18 @@
<div class="page-header">
<h2><?= t('API') ?></h2>
</div>
<section class="listing">
<ul>
<li>
<?= t('API token:') ?>
<strong><?= Helper\escape($values['api_token']) ?></strong>
</li>
<li>
<?= t('API endpoint:') ?>
<input type="text" readonly="readonly" value="<?= Helper\get_current_base_url().'jsonrpc.php' ?>">
</li>
<li>
<?= Helper\a(t('Reset token'), 'config', 'token', array('type' => 'api'), true) ?>
</li>
</ul>
</section>

View File

@@ -0,0 +1,23 @@
<div class="page-header">
<h2><?= t('Application settings') ?></h2>
</div>
<section>
<form method="post" action="<?= Helper\u('config', 'application') ?>" autocomplete="off">
<?= Helper\form_csrf() ?>
<?= Helper\form_label(t('Application URL'), 'application_url') ?>
<?= Helper\form_text('application_url', $values, $errors, array('placeholder="http://example.kanboar.net/"')) ?><br/>
<p class="form-help"><?= t('Example: http://example.kanboard.net/ (used by email notifications)') ?></p>
<?= Helper\form_label(t('Language'), 'application_language') ?>
<?= Helper\form_select('application_language', $languages, $values, $errors) ?><br/>
<?= Helper\form_label(t('Timezone'), 'application_timezone') ?>
<?= Helper\form_select('application_timezone', $timezones, $values, $errors) ?><br/>
<div class="form-actions">
<input type="submit" value="<?= t('Save') ?>" class="btn btn-blue"/>
</div>
</form>
</section>

View File

@@ -0,0 +1,29 @@
<div class="page-header">
<h2><?= t('Board settings') ?></h2>
</div>
<section>
<form method="post" action="<?= Helper\u('config', 'board') ?>" autocomplete="off">
<?= Helper\form_csrf() ?>
<?= Helper\form_label(t('Task highlight period'), 'board_highlight_period') ?>
<?= Helper\form_number('board_highlight_period', $values, $errors) ?><br/>
<p class="form-help"><?= t('Period (in second) to consider a task was modified recently (0 to disable, 2 days by default)') ?></p>
<?= Helper\form_label(t('Refresh interval for public board'), 'board_public_refresh_interval') ?>
<?= Helper\form_number('board_public_refresh_interval', $values, $errors) ?><br/>
<p class="form-help"><?= t('Frequency in second (60 seconds by default)') ?></p>
<?= Helper\form_label(t('Refresh interval for private board'), 'board_private_refresh_interval') ?>
<?= Helper\form_number('board_private_refresh_interval', $values, $errors) ?><br/>
<p class="form-help"><?= t('Frequency in second (0 to disable this feature, 10 seconds by default)') ?></p>
<?= Helper\form_label(t('Default columns for new projects (Comma-separated)'), 'board_columns') ?>
<?= Helper\form_text('board_columns', $values, $errors) ?><br/>
<p class="form-help"><?= t('Default values are "%s"', $default_columns) ?></p>
<div class="form-actions">
<input type="submit" value="<?= t('Save') ?>" class="btn btn-blue"/>
</div>
</form>
</section>

View File

@@ -1,71 +0,0 @@
<section id="main">
<div class="page-header">
<h2><?= t('Application settings') ?></h2>
</div>
<section>
<form method="post" action="?controller=config&amp;action=save" autocomplete="off">
<?= Helper\form_csrf() ?>
<?= Helper\form_label(t('Language'), 'language') ?>
<?= Helper\form_select('language', $languages, $values, $errors) ?><br/>
<?= Helper\form_label(t('Timezone'), 'timezone') ?>
<?= Helper\form_select('timezone', $timezones, $values, $errors) ?><br/>
<?= Helper\form_label(t('Webhook URL for task creation'), 'webhooks_url_task_creation') ?>
<?= Helper\form_text('webhooks_url_task_creation', $values, $errors) ?><br/>
<?= Helper\form_label(t('Webhook URL for task modification'), 'webhooks_url_task_modification') ?>
<?= Helper\form_text('webhooks_url_task_modification', $values, $errors) ?><br/>
<?= Helper\form_label(t('Default columns for new projects (Comma-separated)'), 'default_columns') ?>
<?= Helper\form_text('default_columns', $values, $errors) ?><br/>
<p class="form-help"><?= t('Default values are "%s"', $default_columns) ?></p>
<div class="form-actions">
<input type="submit" value="<?= t('Save') ?>" class="btn btn-blue"/>
</div>
</form>
</section>
<div class="page-header">
<h2><?= t('More information') ?></h2>
</div>
<section class="settings">
<ul>
<li><a href="?controller=config&amp;action=tokens<?= Helper\param_csrf() ?>"><?= t('Reset all tokens') ?></a></li>
<li>
<?= t('Webhooks token:') ?>
<strong><?= Helper\escape($values['webhooks_token']) ?></strong>
</li>
<li>
<?= t('API token:') ?>
<strong><?= Helper\escape($values['api_token']) ?></strong>
</li>
<?php if (DB_DRIVER === 'sqlite'): ?>
<li>
<?= t('Database size:') ?>
<strong><?= Helper\format_bytes($db_size) ?></strong>
</li>
<li>
<a href="?controller=config&amp;action=downloadDb<?= Helper\param_csrf() ?>"><?= t('Download the database') ?></a>
<?= t('(Gzip compressed Sqlite file)') ?>
</li>
<li>
<a href="?controller=config&amp;action=optimizeDb <?= Helper\param_csrf() ?>"><?= t('Optimize the database') ?></a>
<?= t('(VACUUM command)') ?>
</li>
<?php endif ?>
<li>
<?= t('Official website:') ?>
<a href="http://kanboard.net/" target="_blank" rel="noreferer">http://kanboard.net/</a>
</li>
<li>
<?= t('Application version:') ?>
<?= APP_VERSION ?>
</li>
</ul>
</section>
</section>

View File

@@ -0,0 +1,13 @@
<section id="main">
<div class="page-header">
<h2><?= t('Settings') ?></h2>
</div>
<section class="config-show" id="config-section">
<?= Helper\template('config_sidebar') ?>
<div class="config-show-main">
<?= $config_content_for_layout ?>
</div>
</section>
</section>

View File

@@ -0,0 +1,22 @@
<div class="config-show-sidebar">
<h2><?= t('Actions') ?></h2>
<div class="config-show-actions">
<ul>
<li>
<?= Helper\a(t('About'), 'config', 'index') ?>
</li>
<li>
<?= Helper\a(t('Application settings'), 'config', 'application') ?>
</li>
<li>
<?= Helper\a(t('Board settings'), 'config', 'board') ?>
</li>
<li>
<?= Helper\a(t('Webhooks'), 'config', 'webhook') ?>
</li>
<li>
<?= Helper\a(t('API'), 'config', 'api') ?>
</li>
</ul>
</div>
</div>

View File

@@ -0,0 +1,38 @@
<div class="page-header">
<h2><?= t('Webhook settings') ?></h2>
</div>
<section>
<form method="post" action="<?= Helper\u('config', 'webhook') ?>" autocomplete="off">
<?= Helper\form_csrf() ?>
<?= Helper\form_label(t('Webhook URL for task creation'), 'webhook_url_task_creation') ?>
<?= Helper\form_text('webhook_url_task_creation', $values, $errors) ?><br/>
<?= Helper\form_label(t('Webhook URL for task modification'), 'webhook_url_task_modification') ?>
<?= Helper\form_text('webhook_url_task_modification', $values, $errors) ?><br/>
<div class="form-actions">
<input type="submit" value="<?= t('Save') ?>" class="btn btn-blue"/>
</div>
</form>
</section>
<div class="page-header">
<h2><?= t('URL and token') ?></h2>
</div>
<section class="listing">
<ul>
<li>
<?= t('Webhook token:') ?>
<strong><?= Helper\escape($values['webhook_token']) ?></strong>
</li>
<li>
<?= t('URL for task creation:') ?>
<input type="text" readonly="readonly" value="<?= Helper\get_current_base_url().Helper\u('webhook', 'task', array('token' => $values['webhook_token'])) ?>">
</li>
<li>
<?= Helper\a(t('Reset token'), 'config', 'token', array('type' => 'webhook'), true) ?>
</li>
</ul>
</section>

View File

@@ -6,8 +6,8 @@
<meta name="mobile-web-app-capable" content="yes">
<meta name="robots" content="noindex,nofollow">
<?php if (isset($auto_refresh)): ?>
<meta http-equiv="refresh" content="<?= BOARD_PUBLIC_CHECK_INTERVAL ?>" >
<?php if (isset($board_public_refresh_interval)): ?>
<meta http-equiv="refresh" content="<?= $board_public_refresh_interval ?>" >
<?php endif ?>
<?php if (! isset($not_editable)): ?>

View File

@@ -4,4 +4,4 @@
<?= Helper\markdown($comment['comment']) ?>
<?= Helper\template('notification_footer', array('task' => $task)) ?>
<?= Helper\template('notification_footer', array('task' => $task, 'application_url' => $application_url)) ?>

View File

@@ -4,4 +4,4 @@
<?= Helper\markdown($comment['comment']) ?>
<?= Helper\template('notification_footer', array('task' => $task)) ?>
<?= Helper\template('notification_footer', array('task' => $task, 'application_url' => $application_url)) ?>

View File

@@ -2,4 +2,4 @@
<h3><?= t('New attachment added "%s"', $file['name']) ?></h3>
<?= Helper\template('notification_footer', array('task' => $task)) ?>
<?= Helper\template('notification_footer', array('task' => $task, 'application_url' => $application_url)) ?>

View File

@@ -1,6 +1,6 @@
<hr/>
Kanboard
<?php if (defined('KANBOARD_URL')): ?>
- <a href="<?= KANBOARD_URL.'?controller=task&action=show&task_id='.$task['id'] ?>"><?= t('view the task on Kanboard') ?></a>.
<?php if ($application_url): ?>
- <a href="<?= $application_url.'?controller=task&action=show&task_id='.$task['id'] ?>"><?= t('view the task on Kanboard') ?></a>.
<?php endif ?>

View File

@@ -14,4 +14,4 @@
</li>
</ul>
<?= Helper\template('notification_footer', array('task' => $task)) ?>
<?= Helper\template('notification_footer', array('task' => $task, 'application_url' => $application_url)) ?>

View File

@@ -18,4 +18,4 @@
</li>
</ul>
<?= Helper\template('notification_footer', array('task' => $task)) ?>
<?= Helper\template('notification_footer', array('task' => $task, 'application_url' => $application_url)) ?>

View File

@@ -17,4 +17,4 @@
<?= Helper\markdown($task['description']) ?: t('There is no description.') ?>
<?php endif ?>
<?= Helper\template('notification_footer', array('task' => $task)) ?>
<?= Helper\template('notification_footer', array('task' => $task, 'application_url' => $application_url)) ?>

View File

@@ -2,4 +2,4 @@
<p><?= t('The task #%d have been closed.', $task['id']) ?></p>
<?= Helper\template('notification_footer', array('task' => $task)) ?>
<?= Helper\template('notification_footer', array('task' => $task, 'application_url' => $application_url)) ?>

View File

@@ -40,4 +40,4 @@
<?= Helper\markdown($task['description']) ?>
<?php endif ?>
<?= Helper\template('notification_footer', array('task' => $task)) ?>
<?= Helper\template('notification_footer', array('task' => $task, 'application_url' => $application_url)) ?>

View File

@@ -12,4 +12,4 @@
<?php endforeach ?>
</ul>
<?= Helper\template('notification_footer', array('task' => $task)) ?>
<?= Helper\template('notification_footer', array('task' => $task, 'application_url' => $application_url)) ?>

View File

@@ -8,4 +8,4 @@
<li><?= t('Task position:').' '.Helper\escape($task['position']) ?></li>
</ul>
<?= Helper\template('notification_footer', array('task' => $task)) ?>
<?= Helper\template('notification_footer', array('task' => $task, 'application_url' => $application_url)) ?>

View File

@@ -8,4 +8,4 @@
<li><?= t('Task position:').' '.Helper\escape($task['position']) ?></li>
</ul>
<?= Helper\template('notification_footer', array('task' => $task)) ?>
<?= Helper\template('notification_footer', array('task' => $task, 'application_url' => $application_url)) ?>

View File

@@ -2,4 +2,4 @@
<p><?= t('The task #%d have been opened.', $task['id']) ?></p>
<?= Helper\template('notification_footer', array('task' => $task)) ?>
<?= Helper\template('notification_footer', array('task' => $task, 'application_url' => $application_url)) ?>

View File

@@ -40,4 +40,4 @@
<?= Helper\markdown($task['description']) ?: t('There is no description.') ?>
<?php endif ?>
<?= Helper\template('notification_footer', array('task' => $task)) ?>
<?= Helper\template('notification_footer', array('task' => $task, 'application_url' => $application_url)) ?>

View File

@@ -4,7 +4,7 @@
<?php if ($project['is_public']): ?>
<div class="settings">
<div class="listing">
<ul class="no-bullet">
<li><strong><i class="fa fa-share-alt"></i> <a href="?controller=board&amp;action=readonly&amp;token=<?= $project['token'] ?>" target="_blank"><?= t('Public link') ?></a></strong></li>
<li><strong><i class="fa fa-rss-square"></i> <a href="?controller=project&amp;action=feed&amp;token=<?= $project['token'] ?>" target="_blank"><?= t('RSS feed') ?></a></strong></li>

View File

@@ -1,7 +1,7 @@
<div class="page-header">
<h2><?= t('Summary') ?></h2>
</div>
<ul class="settings">
<ul class="listing">
<li><strong><?= $project['is_active'] ? t('Active') : t('Inactive') ?></strong></li>
<?php if ($project['is_public']): ?>

View File

@@ -5,7 +5,7 @@
<?php if (GOOGLE_AUTH): ?>
<h3><i class="fa fa-google"></i> <?= t('Google Account') ?></h3>
<p class="settings">
<p class="listing">
<?php if (Helper\is_current_user($user['id'])): ?>
<?php if (empty($user['google_id'])): ?>
<a href="?controller=user&amp;action=google<?= Helper\param_csrf() ?>"><?= t('Link my Google Account') ?></a>
@@ -21,7 +21,7 @@
<?php if (GITHUB_AUTH): ?>
<h3><i class="fa fa-github"></i> <?= t('Github Account') ?></h3>
<p class="settings">
<p class="listing">
<?php if (Helper\is_current_user($user['id'])): ?>
<?php if (empty($user['github_id'])): ?>
<a href="?controller=user&amp;action=gitHub<?= Helper\param_csrf() ?>"><?= t('Link my GitHub Account') ?></a>

View File

@@ -1,7 +1,7 @@
<div class="page-header">
<h2><?= t('Summary') ?></h2>
</div>
<ul class="settings">
<ul class="listing">
<li><?= t('Username:') ?> <strong><?= Helper\escape($user['username']) ?></strong></li>
<li><?= t('Name:') ?> <strong><?= Helper\escape($user['name']) ?></strong></li>
<li><?= t('Email:') ?> <strong><?= Helper\escape($user['email']) ?></strong></li>

View File

@@ -1,14 +1,5 @@
<?php
// Board refresh frequency in seconds for the public board view
defined('BOARD_PUBLIC_CHECK_INTERVAL') or define('BOARD_PUBLIC_CHECK_INTERVAL', 60);
// Board refresh frequency in seconds (the value 0 disable this feature)
defined('BOARD_CHECK_INTERVAL') or define('BOARD_CHECK_INTERVAL', 10);
// Period (in second) to consider a task was modified recently
defined('RECENT_TASK_PERIOD') or define('RECENT_TASK_PERIOD', 48*60*60);
// Custom session save path
defined('SESSION_SAVE_PATH') or define('SESSION_SAVE_PATH', '');

View File

@@ -556,16 +556,37 @@ function form_numeric($name, $values = array(), array $errors = array(), array $
* @param string $controller Controller name
* @param string $action Action name
* @param array $params Url parameters
* @param boolean $csrf Add a CSRF token
* @param string $class CSS class attribute
* @return string
*/
function a($label, $controller, $action, array $params = array(), $css = '')
function a($label, $controller, $action, array $params = array(), $csrf = false, $class = '')
{
$html = '<a href="?controller='.$controller.'&amp;action='.$action;
return '<a href="'.u($controller, $action, $params, $csrf).'" class="'.$class.'"/>'.$label.'</a>';
}
/**
* URL
*
* a('link', 'task', 'show', array('task_id' => $task_id))
*
* @param string $controller Controller name
* @param string $action Action name
* @param array $params Url parameters
* @param boolean $csrf Add a CSRF token
* @return string
*/
function u($controller, $action, array $params = array(), $csrf = false)
{
$html = '?controller='.$controller.'&amp;action='.$action;
if ($csrf) {
$params['csrf_token'] = Security::getCSRFToken();
}
foreach ($params as $key => $value) {
$html .= '&amp;'.$key.'='.$value;
}
return '" class="'.$class.'"/>'.$label.'</a>';
}
return $html;
}