From eab942537f60a5475e4fca38a3104e8c8f008ede Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fr=C3=A9d=C3=A9ric=20Guillot?= Date: Sat, 5 Apr 2014 20:24:13 -0400 Subject: [PATCH] Add support for multiple users/databases --- README.markdown | 2 + assets/css/app.css | 4 ++ common.php | 23 +++++-- controllers/common.php | 18 +++++- controllers/config.php | 47 +++++++++++++- controllers/user.php | 8 ++- cronjob.php | 7 +- locales/fr_FR/translations.php | 10 +++ models/database.php | 115 +++++++++++++++++++++++++++++++++ templates/config.php | 5 ++ templates/login.php | 20 +++++- templates/new_db.php | 25 +++++++ 12 files changed, 269 insertions(+), 15 deletions(-) create mode 100644 models/database.php create mode 100644 templates/new_db.php diff --git a/README.markdown b/README.markdown index 29fd00a..427ce83 100644 --- a/README.markdown +++ b/README.markdown @@ -25,6 +25,7 @@ Features - Alternative login with a Google Account or Mozilla Persona - **Full article download for feeds that display only a summary** (website scraper based on Xpath rules) - Auto-update from the user interface +- Multiple databases (each user has his own database) Todo and known bugs ------------------- @@ -156,6 +157,7 @@ You just need to be inside the directory `miniflux` and run the script `cronjob. Parameters | Type | Value --------------------|--------------------------------|----------------------------- +--database | optional | Database filename, default is db.sqlite (ex: db2.sqlite) --limit | optional | number of feeds --call-interval | optional, excluded by --limit, require --update-interval | time in minutes < update interval time --update-interval | optional, excluded by --limit, require --call-interval | time in minutes >= call interval time diff --git a/assets/css/app.css b/assets/css/app.css index 8a0c0af..0c8cf26 100644 --- a/assets/css/app.css +++ b/assets/css/app.css @@ -643,6 +643,10 @@ iframe { opacity: 0; } +.login-form { + margin-bottom: 20px; +} + /* desktop design */ @media only screen and (min-width: 480px) { diff --git a/common.php b/common.php index 2a50658..65b0a45 100644 --- a/common.php +++ b/common.php @@ -11,25 +11,34 @@ require __DIR__.'/models/feed.php'; require __DIR__.'/models/item.php'; require __DIR__.'/models/schema.php'; require __DIR__.'/models/auto_update.php'; +require __DIR__.'/models/database.php'; if (file_exists('config.php')) require 'config.php'; defined('APP_VERSION') or define('APP_VERSION', 'master'); defined('HTTP_TIMEOUT') or define('HTTP_TIMEOUT', 20); -defined('DB_FILENAME') or define('DB_FILENAME', 'data/db.sqlite'); + +defined('ROOT_DIRECTORY') or define('ROOT_DIRECTORY', __DIR__); +defined('DATA_DIRECTORY') or define('DATA_DIRECTORY', 'data'); + +defined('ENABLE_MULTIPLE_DB') or define('ENABLE_MULTIPLE_DB', true); +defined('DB_FILENAME') or define('DB_FILENAME', 'db.sqlite'); + defined('DEBUG') or define('DEBUG', true); -defined('DEBUG_FILENAME') or define('DEBUG_FILENAME', 'data/debug.log'); +defined('DEBUG_FILENAME') or define('DEBUG_FILENAME', DATA_DIRECTORY.DIRECTORY_SEPARATOR.'debug.log'); + defined('THEME_DIRECTORY') or define('THEME_DIRECTORY', 'themes'); defined('SESSION_SAVE_PATH') or define('SESSION_SAVE_PATH', ''); + defined('PROXY_HOSTNAME') or define('PROXY_HOSTNAME', ''); defined('PROXY_PORT') or define('PROXY_PORT', 3128); defined('PROXY_USERNAME') or define('PROXY_USERNAME', ''); defined('PROXY_PASSWORD') or define('PROXY_PASSWORD', ''); -defined('ROOT_DIRECTORY') or define('ROOT_DIRECTORY', __DIR__); + defined('ENABLE_AUTO_UPDATE') or define('ENABLE_AUTO_UPDATE', true); -defined('AUTO_UPDATE_DOWNLOAD_DIRECTORY') or define('AUTO_UPDATE_DOWNLOAD_DIRECTORY', 'data/download'); -defined('AUTO_UPDATE_ARCHIVE_DIRECTORY') or define('AUTO_UPDATE_ARCHIVE_DIRECTORY', 'data/archive'); -defined('AUTO_UPDATE_BACKUP_DIRECTORY') or define('AUTO_UPDATE_BACKUP_DIRECTORY', 'data/backup'); +defined('AUTO_UPDATE_DOWNLOAD_DIRECTORY') or define('AUTO_UPDATE_DOWNLOAD_DIRECTORY', DATA_DIRECTORY.DIRECTORY_SEPARATOR.'download'); +defined('AUTO_UPDATE_ARCHIVE_DIRECTORY') or define('AUTO_UPDATE_ARCHIVE_DIRECTORY', DATA_DIRECTORY.DIRECTORY_SEPARATOR.'archive'); +defined('AUTO_UPDATE_BACKUP_DIRECTORY') or define('AUTO_UPDATE_BACKUP_DIRECTORY', DATA_DIRECTORY.DIRECTORY_SEPARATOR.'backup'); PicoFeed\Client::proxy(PROXY_HOSTNAME, PROXY_PORT, PROXY_USERNAME, PROXY_PASSWORD); @@ -37,7 +46,7 @@ PicoDb\Database::bootstrap('db', function() { $db = new PicoDb\Database(array( 'driver' => 'sqlite', - 'filename' => DB_FILENAME + 'filename' => \Model\Database\get_path(), )); if ($db->schema()->check(Model\Config\DB_VERSION)) { diff --git a/controllers/common.php b/controllers/common.php index b996d7c..d22a1d8 100644 --- a/controllers/common.php +++ b/controllers/common.php @@ -11,7 +11,13 @@ Router\before(function($action) { Session\open(dirname($_SERVER['PHP_SELF']), SESSION_SAVE_PATH); - $ignore_actions = array('login', 'google-auth', 'google-redirect-auth', 'mozilla-auth', 'bookmark-feed'); + // Select another database + if (! empty($_SESSION['database'])) { + Model\Database\select($_SESSION['database']); + } + + // Redirect to the login form if the user is not authenticated + $ignore_actions = array('login', 'google-auth', 'google-redirect-auth', 'mozilla-auth', 'bookmark-feed', 'select-db'); if (! isset($_SESSION['user']) && ! in_array($action, $ignore_actions)) { Response\redirect('?action=login'); @@ -50,3 +56,13 @@ Router\get_action('more', function() { Response\html(Template\layout('show_more', array('menu' => 'more'))); }); + +// Select another database +Router\get_action('select-db', function() { + + if (ENABLE_MULTIPLE_DB) { + $_SESSION['database'] = \Model\Database\select(Request\param('database')); + } + + Response\redirect('?action=login'); +}); diff --git a/controllers/config.php b/controllers/config.php index 12b81ff..7f62826 100644 --- a/controllers/config.php +++ b/controllers/config.php @@ -7,6 +7,47 @@ use PicoFarad\Session; use PicoFarad\Template; use PicoDb\Database; +// Display a form to add a new database +Router\get_action('new-db', function() { + + if (ENABLE_MULTIPLE_DB) { + Response\html(Template\layout('new_db', array( + 'errors' => array(), + 'values' => array(), + 'menu' => 'config', + 'title' => t('New database') + ))); + } + + Response\redirect('?action=config'); +}); + +// Create a new database +Router\post_action('new-db', function() { + + $values = Request\values(); + list($valid, $errors) = Model\Database\validate($values); + + if ($valid) { + + if (Model\Database\create(strtolower($values['name']).'.sqlite', $values['username'], $values['password'])) { + Session\flash(t('Database created successfully.')); + } + else { + Session\flash_error(t('Unable to create the new database.')); + } + + Response\redirect('?action=config'); + } + + Response\html(Template\layout('new_db', array( + 'errors' => $errors, + 'values' => $values, + 'menu' => 'config', + 'title' => t('New database') + ))); +}); + // Auto-update Router\get_action('auto-update', function() { @@ -41,7 +82,7 @@ Router\get_action('optimize-db', function() { Router\get_action('download-db', function() { Response\force_download('db.sqlite.gz'); - Response\binary(gzencode(file_get_contents(DB_FILENAME))); + Response\binary(gzencode(file_get_contents(\Model\Database\get_path()))); }); // Display preferences page @@ -50,7 +91,7 @@ Router\get_action('config', function() { Response\html(Template\layout('config', array( 'errors' => array(), 'values' => Model\Config\get_all(), - 'db_size' => filesize(DB_FILENAME), + 'db_size' => filesize(\Model\Database\get_path()), 'languages' => Model\Config\get_languages(), 'timezones' => Model\Config\get_timezones(), 'autoflush_options' => Model\Config\get_autoflush_options(), @@ -84,7 +125,7 @@ Router\post_action('config', function() { Response\html(Template\layout('config', array( 'errors' => $errors, 'values' => Model\Config\get_all(), - 'db_size' => filesize(DB_FILENAME), + 'db_size' => filesize(\Model\Database\get_path()), 'languages' => Model\Config\get_languages(), 'timezones' => Model\Config\get_timezones(), 'autoflush_options' => Model\Config\get_autoflush_options(), diff --git a/controllers/user.php b/controllers/user.php index 59ec784..14154af 100644 --- a/controllers/user.php +++ b/controllers/user.php @@ -24,7 +24,9 @@ Router\get_action('login', function() { 'google_auth_enable' => Model\Config\get('auth_google_token') !== '', 'mozilla_auth_enable' => Model\Config\get('auth_mozilla_token') !== '', 'errors' => array(), - 'values' => array() + 'values' => array(), + 'databases' => Model\Database\get_list(), + 'current_database' => Model\Database\select() ))); }); @@ -40,7 +42,9 @@ Router\post_action('login', function() { 'google_auth_enable' => Model\Config\get('auth_google_token') !== '', 'mozilla_auth_enable' => Model\Config\get('auth_mozilla_token') !== '', 'errors' => $errors, - 'values' => $values + 'values' => $values, + 'databases' => Model\Database\get_list(), + 'current_database' => Model\Database\select() ))); }); diff --git a/cronjob.php b/cronjob.php index 14393bd..ec906ce 100644 --- a/cronjob.php +++ b/cronjob.php @@ -7,7 +7,8 @@ if (php_sapi_name() === 'cli') { $options = getopt('', array( 'limit::', 'call-interval::', - 'update-interval::' + 'update-interval::', + 'database::', )); } else { @@ -15,6 +16,10 @@ else { $options = $_GET; } +if (! empty($options['database'])) { + \Model\Database\select($options['database']); +} + $limit = ! empty($options['limit']) && ctype_digit($options['limit']) ? (int) $options['limit'] : Model\Feed\LIMIT_ALL; $update_interval = ! empty($options['update-interval']) && ctype_digit($options['update-interval']) ? (int) $options['update-interval'] : null; $call_interval = ! empty($options['call-interval']) && ctype_digit($options['call-interval']) ? (int) $options['call-interval'] : null; diff --git a/locales/fr_FR/translations.php b/locales/fr_FR/translations.php index 6170055..fabab14 100644 --- a/locales/fr_FR/translations.php +++ b/locales/fr_FR/translations.php @@ -213,4 +213,14 @@ return array( 'Miniflux is updated!' => 'Miniflux a été mis à jour avec succès !', 'Unable to update Miniflux, check the console for errors.' => 'Impossible de mettre à jour Miniflux, allez-voir les erreurs dans la console.', 'Don\'t forget to backup your database' => 'N\'oubliez pas de sauvegarder votre base de données', + 'The name must have only alpha-numeric characters' => 'Le nom doit avoir seulement des caractères alphanumériques', + 'New database' => 'Nouvelle base de données', + 'Database name' => 'Nom de la base de données', + 'Default database' => 'Base de données par défaut', + 'Select another database' => 'Choisir une autre base de données', + 'The database name is required' => 'Le nom de la base de données est obligatoire', + 'Database created successfully.' => 'Base de données créée avec succès.', + 'Unable to create the new database.' => 'Impossible créer la nouvelle base de données.', + 'Add a new database (new user)' => 'Ajouter une nouvelle base de données (nouvel utilisateur)', + 'Create' => 'Créer', ); diff --git a/models/database.php b/models/database.php new file mode 100644 index 0000000..6bf1b86 --- /dev/null +++ b/models/database.php @@ -0,0 +1,115 @@ + 'sqlite', + 'filename' => $filename, + )); + + if ($db->schema()->check(\Model\Config\DB_VERSION)) { + + $db->table('config')->update(array( + 'username' => $username, + 'password' => \password_hash($password, PASSWORD_BCRYPT) + )); + + return true; + } + } + + return false; +} + +// Get or set the current database +function select($filename = '') +{ + static $current_filename = DB_FILENAME; + + if ($filename !== '' && in_array($filename, get_all())) { + $current_filename = $filename; + $_SESSION['config'] = \Model\Config\get_all(); + } + + return $current_filename; +} + +// Get database path +function get_path() +{ + return DATA_DIRECTORY.DIRECTORY_SEPARATOR.\Model\Database\select(); +} + +// Get the list of available databases +function get_all() +{ + $listing = array(); + + $dir = new \DirectoryIterator(DATA_DIRECTORY); + + foreach ($dir as $fileinfo) { + if ($fileinfo->getExtension() === 'sqlite') { + $listing[] = $fileinfo->getFilename(); + } + } + + return $listing; +} + +// Get the formated db list +function get_list() +{ + $listing = array(); + + foreach (get_all() as $filename) { + + if ($filename === DB_FILENAME) { + $label = t('Default database'); + } + else { + $label = ucfirst(substr($filename, 0, -7)); + } + + $listing[$filename] = $label; + } + + return $listing; +} + +// Validate database form +function validate(array $values) +{ + $v = new Validator($values, array( + new Validators\Required('name', t('The database name is required')), + new Validators\AlphaNumeric('name', t('The name must have only alpha-numeric characters')), + new Validators\Required('username', t('The user name is required')), + new Validators\MaxLength('username', t('The maximum length is 50 characters'), 50), + new Validators\Required('password', t('The password is required')), + new Validators\MinLength('password', t('The minimum length is 6 characters'), 6), + new Validators\Required('confirmation', t('The confirmation is required')), + new Validators\Equals('password', 'confirmation', t('Passwords doesn\'t match')), + )); + + return array( + $v->execute(), + $v->getErrors() + ); +} diff --git a/templates/config.php b/templates/config.php index 0b10d18..193eac4 100644 --- a/templates/config.php +++ b/templates/config.php @@ -90,6 +90,11 @@
  • + +
  • +
  • + + diff --git a/templates/login.php b/templates/login.php index 7e636f9..9d67198 100644 --- a/templates/login.php +++ b/templates/login.php @@ -28,7 +28,7 @@

    -
    +
    @@ -48,6 +48,24 @@
    + + 1): ?> +
    +

    + +
    + + diff --git a/templates/new_db.php b/templates/new_db.php new file mode 100644 index 0000000..766d316 --- /dev/null +++ b/templates/new_db.php @@ -0,0 +1,25 @@ + + +
    + + +

    + + +
    + + + + + +
    + +
    + +
    +