diff --git a/.gitignore b/.gitignore index 85d8898..f435b4d 100644 --- a/.gitignore +++ b/.gitignore @@ -43,3 +43,5 @@ Thumbs.db # App specific # ################ config.php +!models/* +!controllers/* diff --git a/controllers/.htaccess b/controllers/.htaccess new file mode 100644 index 0000000..14249c5 --- /dev/null +++ b/controllers/.htaccess @@ -0,0 +1 @@ +Deny from all \ No newline at end of file diff --git a/controllers/config.php b/controllers/config.php new file mode 100644 index 0000000..6a76394 --- /dev/null +++ b/controllers/config.php @@ -0,0 +1,77 @@ +getConnection()->exec('VACUUM'); + Response\redirect('?action=config'); +}); + +// Download the compressed database +Router\get_action('download-db', function() { + + Response\force_download('db.sqlite.gz'); + Response\binary(gzencode(file_get_contents(DB_FILENAME))); +}); + +// Display preferences page +Router\get_action('config', function() { + + Response\html(Template\layout('config', array( + 'errors' => array(), + 'values' => Model\Config\get_all(), + 'db_size' => filesize(DB_FILENAME), + 'languages' => Model\Config\get_languages(), + 'autoflush_options' => Model\Config\get_autoflush_options(), + 'paging_options' => Model\Config\get_paging_options(), + 'theme_options' => Model\Config\get_themes(), + 'sorting_options' => Model\Config\get_sorting_directions(), + 'menu' => 'config', + 'title' => t('Preferences') + ))); +}); + +// Update preferences +Router\post_action('config', function() { + + $values = Request\values() + array('nocontent' => 0); + list($valid, $errors) = Model\Config\validate_modification($values); + + if ($valid) { + + if (Model\Config\save($values)) { + Session\flash(t('Your preferences are updated.')); + } + else { + Session\flash_error(t('Unable to update your preferences.')); + } + + Response\redirect('?action=config'); + } + + Response\html(Template\layout('config', array( + 'errors' => $errors, + 'values' => $values, + 'db_size' => filesize(DB_FILENAME), + 'languages' => Model\Config\get_languages(), + 'autoflush_options' => Model\Config\get_autoflush_options(), + 'paging_options' => Model\Config\get_paging_options(), + 'theme_options' => Model\Config\get_themes(), + 'sorting_options' => Model\Config\get_sorting_directions(), + 'menu' => 'config', + 'title' => t('Preferences') + ))); +}); diff --git a/models/.htaccess b/models/.htaccess new file mode 100644 index 0000000..14249c5 --- /dev/null +++ b/models/.htaccess @@ -0,0 +1 @@ +Deny from all \ No newline at end of file diff --git a/models/config.php b/models/config.php new file mode 100644 index 0000000..902175c --- /dev/null +++ b/models/config.php @@ -0,0 +1,257 @@ + t('Czech'), + 'de_DE' => t('German'), + 'en_US' => t('English'), + 'es_ES' => t('Spanish'), + 'fr_FR' => t('French'), + 'it_IT' => t('Italian'), + 'pt_BR' => t('Portuguese'), + 'zh_CN' => t('Simplified Chinese'), + ); + + asort($languages); + + return $languages; +} + +// Get all skins +function get_themes() +{ + $themes = array( + 'original' => t('Original') + ); + + if (file_exists(THEME_DIRECTORY)) { + + $dir = new \DirectoryIterator(THEME_DIRECTORY); + + foreach ($dir as $fileinfo) { + + if (! $fileinfo->isDot() && $fileinfo->isDir()) { + $themes[$dir->getFilename()] = ucfirst($dir->getFilename()); + } + } + } + + return $themes; +} + +// Sorting direction choices for items +function get_sorting_directions() +{ + return array( + 'asc' => t('Older items first'), + 'desc' => t('Most recent first'), + ); +} + +// Autoflush choices for items +function get_autoflush_options() +{ + return array( + '0' => t('Never'), + '-1' => t('Immediately'), + '1' => t('After %d day', 1), + '5' => t('After %d days', 5), + '15' => t('After %d days', 15), + '30' => t('After %d days', 30) + ); +} + +// Number of items per pages +function get_paging_options() +{ + return array( + 50 => 50, + 100 => 100, + 150 => 150, + 200 => 200, + 250 => 250, + ); +} + +// Generate a token from /dev/urandom or with uniqid() if open_basedir is enabled +function generate_token() +{ + 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); + } +} + +// Regenerate tokens for the API and bookmark feed +function new_tokens() +{ + $values = array( + 'api_token' => generate_token(), + 'feed_token' => generate_token(), + ); + + return \PicoTools\singleton('db')->table('config')->update($values); +} + +// Save tokens for external authentication +function save_auth_token($type, $value) +{ + return \PicoTools\singleton('db') + ->table('config') + ->update(array( + 'auth_'.$type.'_token' => $value + )); +} + +// Clear authentication tokens +function remove_auth_token($type) +{ + \PicoTools\singleton('db') + ->table('config') + ->update(array( + 'auth_'.$type.'_token' => '' + )); + + $_SESSION['config'] = get_all(); +} + +// Get a config value from the DB or from the session +function get($name) +{ + if (! isset($_SESSION)) { + return \PicoTools\singleton('db')->table('config')->findOneColumn($name); + } + else { + + if (! isset($_SESSION['config'])) { + $_SESSION['config'] = get_all(); + } + + if (isset($_SESSION['config'][$name])) { + return $_SESSION['config'][$name]; + } + } + + return null; +} + +// Get all config parameters +function get_all() +{ + return \PicoTools\singleton('db') + ->table('config') + ->columns( + 'username', + 'language', + 'autoflush', + 'nocontent', + 'items_per_page', + 'theme', + 'api_token', + 'feed_token', + 'auth_google_token', + 'auth_mozilla_token', + 'items_sorting_direction' + ) + ->findOne(); +} + +// Validation for edit action +function validate_modification(array $values) +{ + if (! empty($values['password'])) { + + $v = new Validator($values, array( + 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')), + new Validators\Required('autoflush', t('Value required')), + new Validators\Required('items_per_page', t('Value required')), + new Validators\Integer('items_per_page', t('Must be an integer')), + new Validators\Required('theme', t('Value required')), + )); + } + else { + + $v = new Validator($values, array( + 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('autoflush', t('Value required')), + new Validators\Required('items_per_page', t('Value required')), + new Validators\Integer('items_per_page', t('Must be an integer')), + new Validators\Required('theme', t('Value required')), + )); + } + + return array( + $v->execute(), + $v->getErrors() + ); +} + +// Save config into the database and update the session +function save(array $values) +{ + // Update the password if needed + if (! empty($values['password'])) { + $values['password'] = \password_hash($values['password'], PASSWORD_BCRYPT); + } else { + unset($values['password']); + } + + unset($values['confirmation']); + + // Reload configuration in session + $_SESSION['config'] = $values; + + // Reload translations for flash session message + \PicoTools\Translator\load($values['language']); + + // If the user does not want content of feeds, remove it in previous ones + if (isset($values['nocontent']) && (bool) $values['nocontent']) { + \PicoTools\singleton('db')->table('items')->update(array('content' => '')); + } + + return \PicoTools\singleton('db')->table('config')->update($values); +} diff --git a/models/item.php b/models/item.php index c66066c..e10b75b 100644 --- a/models/item.php +++ b/models/item.php @@ -303,10 +303,11 @@ function mark_feed_as_read($feed_id) // Mark all read items to removed after X days function autoflush() { - $autoflush = \Model\Config\get('autoflush'); + $autoflush = (int) \Model\Config\get('autoflush'); - if ($autoflush) { + if ($autoflush > 0) { + // Mark read items removed after X days \PicoTools\singleton('db') ->table('items') ->eq('bookmark', 0) @@ -314,6 +315,15 @@ function autoflush() ->lt('updated', strtotime('-'.$autoflush.'day')) ->save(array('status' => 'removed', 'content' => '')); } + else if ($autoflush === -1) { + + // Mark read items removed immediately + \PicoTools\singleton('db') + ->table('items') + ->eq('bookmark', 0) + ->eq('status', 'read') + ->save(array('status' => 'removed', 'content' => '')); + } } // Update all items