2013-12-23 03:25:54 +01:00
|
|
|
<?php
|
|
|
|
|
|
|
|
namespace Model\Config;
|
|
|
|
|
2014-10-30 02:28:23 +01:00
|
|
|
use DirectoryIterator;
|
2013-12-23 03:25:54 +01:00
|
|
|
use SimpleValidator\Validator;
|
|
|
|
use SimpleValidator\Validators;
|
2014-02-08 20:13:14 +01:00
|
|
|
use PicoDb\Database;
|
2014-12-24 03:28:26 +01:00
|
|
|
use PicoFeed\Config\Config as ReaderConfig;
|
|
|
|
use PicoFeed\Logging\Logger;
|
2014-05-20 20:20:27 +02:00
|
|
|
|
|
|
|
const HTTP_USER_AGENT = 'Miniflux (http://miniflux.net)';
|
|
|
|
|
|
|
|
// Get PicoFeed config
|
|
|
|
function get_reader_config()
|
|
|
|
{
|
|
|
|
$config = new ReaderConfig;
|
|
|
|
$config->setTimezone(get('timezone'));
|
2013-12-23 03:25:54 +01:00
|
|
|
|
2014-12-24 03:28:26 +01:00
|
|
|
// Client
|
2014-05-20 20:20:27 +02:00
|
|
|
$config->setClientTimeout(HTTP_TIMEOUT);
|
|
|
|
$config->setClientUserAgent(HTTP_USER_AGENT);
|
|
|
|
$config->setGrabberUserAgent(HTTP_USER_AGENT);
|
2013-12-23 03:25:54 +01:00
|
|
|
|
2014-12-24 03:28:26 +01:00
|
|
|
// Proxy
|
2014-05-20 20:20:27 +02:00
|
|
|
$config->setProxyHostname(PROXY_HOSTNAME);
|
|
|
|
$config->setProxyPort(PROXY_PORT);
|
|
|
|
$config->setProxyUsername(PROXY_USERNAME);
|
|
|
|
$config->setProxyPassword(PROXY_PASSWORD);
|
|
|
|
|
2014-12-24 03:28:26 +01:00
|
|
|
// Filter
|
2014-05-20 20:20:27 +02:00
|
|
|
$config->setFilterIframeWhitelist(get_iframe_whitelist());
|
|
|
|
|
2014-12-24 21:58:24 +01:00
|
|
|
if ((bool) get('image_proxy')) {
|
|
|
|
$config->setFilterImageProxyUrl('?action=proxy&url=%s');
|
|
|
|
}
|
|
|
|
|
2014-12-24 03:28:26 +01:00
|
|
|
// Parser
|
|
|
|
$config->setParserHashAlgo('crc32b');
|
|
|
|
|
2014-05-20 20:20:27 +02:00
|
|
|
return $config;
|
|
|
|
}
|
|
|
|
|
|
|
|
function get_iframe_whitelist()
|
|
|
|
{
|
|
|
|
return array(
|
|
|
|
'http://www.youtube.com',
|
|
|
|
'https://www.youtube.com',
|
|
|
|
'http://player.vimeo.com',
|
|
|
|
'https://player.vimeo.com',
|
|
|
|
'http://www.dailymotion.com',
|
|
|
|
'https://www.dailymotion.com',
|
|
|
|
);
|
|
|
|
}
|
2014-03-30 21:59:26 +02:00
|
|
|
|
|
|
|
// Send a debug message to the console
|
|
|
|
function debug($line)
|
|
|
|
{
|
2014-12-24 03:28:26 +01:00
|
|
|
Logger::setMessage($line);
|
2014-03-30 21:59:26 +02:00
|
|
|
write_debug();
|
|
|
|
}
|
|
|
|
|
2013-12-23 03:25:54 +01:00
|
|
|
// Write PicoFeed debug output to a file
|
|
|
|
function write_debug()
|
|
|
|
{
|
|
|
|
if (DEBUG) {
|
2014-12-24 03:28:26 +01:00
|
|
|
file_put_contents(DEBUG_FILENAME, implode(PHP_EOL, Logger::getMessages()));
|
2013-12-23 03:25:54 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2014-02-26 01:03:46 +01:00
|
|
|
// Get available timezone
|
|
|
|
function get_timezones()
|
|
|
|
{
|
2014-10-30 02:28:23 +01:00
|
|
|
$timezones = timezone_identifiers_list();
|
2014-02-26 01:03:46 +01:00
|
|
|
return array_combine(array_values($timezones), $timezones);
|
|
|
|
}
|
|
|
|
|
2013-12-23 03:25:54 +01:00
|
|
|
// Get all supported languages
|
|
|
|
function get_languages()
|
|
|
|
{
|
2014-10-30 02:28:23 +01:00
|
|
|
return array(
|
|
|
|
'cs_CZ' => 'Čeština',
|
|
|
|
'de_DE' => 'Deutsch',
|
|
|
|
'en_US' => 'English',
|
|
|
|
'es_ES' => 'Español',
|
|
|
|
'fr_FR' => 'Français',
|
|
|
|
'it_IT' => 'Italiano',
|
|
|
|
'pt_BR' => 'Português',
|
|
|
|
'zh_CN' => '简体中国',
|
2013-12-23 03:25:54 +01:00
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
// Get all skins
|
|
|
|
function get_themes()
|
|
|
|
{
|
|
|
|
$themes = array(
|
2014-11-14 18:48:27 +01:00
|
|
|
'original' => t('Default')
|
2013-12-23 03:25:54 +01:00
|
|
|
);
|
|
|
|
|
|
|
|
if (file_exists(THEME_DIRECTORY)) {
|
|
|
|
|
2014-10-30 02:28:23 +01:00
|
|
|
$dir = new DirectoryIterator(THEME_DIRECTORY);
|
2013-12-23 03:25:54 +01:00
|
|
|
|
|
|
|
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'),
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
2014-05-29 16:57:23 +02:00
|
|
|
// Display summaries or full contents on lists
|
|
|
|
function get_display_mode()
|
|
|
|
{
|
|
|
|
return array(
|
|
|
|
'summaries' => t('Summaries'),
|
|
|
|
'full' => t('Full contents')
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
2014-12-16 02:38:35 +01:00
|
|
|
// Autoflush choices for read items
|
|
|
|
function get_autoflush_read_options()
|
2013-12-23 03:25:54 +01:00
|
|
|
{
|
|
|
|
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)
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
2014-12-16 02:38:35 +01:00
|
|
|
// Autoflush choices for unread items
|
|
|
|
function get_autoflush_unread_options()
|
|
|
|
{
|
|
|
|
return array(
|
|
|
|
'0' => t('Never'),
|
|
|
|
'15' => t('After %d days', 15),
|
|
|
|
'30' => t('After %d days', 30),
|
|
|
|
'45' => t('After %d days', 45),
|
|
|
|
'60' => t('After %d days', 60),
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
2013-12-23 03:25:54 +01:00
|
|
|
// Number of items per pages
|
|
|
|
function get_paging_options()
|
|
|
|
{
|
|
|
|
return array(
|
|
|
|
50 => 50,
|
|
|
|
100 => 100,
|
|
|
|
150 => 150,
|
|
|
|
200 => 200,
|
|
|
|
250 => 250,
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
2013-12-23 19:33:16 +01:00
|
|
|
// Get redirect options when there is nothing to read
|
|
|
|
function get_nothing_to_read_redirections()
|
|
|
|
{
|
|
|
|
return array(
|
2014-11-14 18:48:27 +01:00
|
|
|
'feeds' => t('Subscriptions'),
|
|
|
|
'history' => t('History'),
|
|
|
|
'bookmarks' => t('Bookmarks'),
|
2013-12-23 19:33:16 +01:00
|
|
|
);
|
|
|
|
}
|
|
|
|
|
2014-11-08 03:44:20 +01:00
|
|
|
// Create a CSRF token
|
|
|
|
function generate_csrf()
|
|
|
|
{
|
|
|
|
if (empty($_SESSION['csrf'])) {
|
|
|
|
$_SESSION['csrf'] = array();
|
|
|
|
}
|
|
|
|
|
|
|
|
$token = generate_token();
|
|
|
|
$_SESSION['csrf'][$token] = true;
|
|
|
|
|
|
|
|
return $token;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Check CSRF token (form values)
|
|
|
|
function check_csrf_values(array &$values)
|
|
|
|
{
|
|
|
|
if (empty($values['csrf']) || ! isset($_SESSION['csrf'][$values['csrf']])) {
|
|
|
|
$values = array();
|
|
|
|
}
|
|
|
|
else {
|
|
|
|
|
|
|
|
unset($_SESSION['csrf'][$values['csrf']]);
|
|
|
|
unset($values['csrf']);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Check CSRF token
|
|
|
|
function check_csrf($token)
|
|
|
|
{
|
|
|
|
if (isset($_SESSION['csrf'][$token])) {
|
2014-11-08 23:14:18 +01:00
|
|
|
unset($_SESSION['csrf'][$token]);
|
2014-11-08 03:44:20 +01:00
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
2013-12-23 03:25:54 +01:00
|
|
|
// Generate a token from /dev/urandom or with uniqid() if open_basedir is enabled
|
|
|
|
function generate_token()
|
|
|
|
{
|
2014-03-15 02:26:14 +01:00
|
|
|
if (function_exists('openssl_random_pseudo_bytes')) {
|
2014-05-28 22:44:25 +02:00
|
|
|
return bin2hex(\openssl_random_pseudo_bytes(25));
|
2013-12-23 03:25:54 +01:00
|
|
|
}
|
2014-03-15 02:26:14 +01:00
|
|
|
else if (ini_get('open_basedir') === '' && strtoupper(substr(PHP_OS, 0, 3)) !== 'WIN') {
|
|
|
|
return hash('sha256', file_get_contents('/dev/urandom', false, null, 0, 30));
|
2013-12-23 03:25:54 +01:00
|
|
|
}
|
2014-03-15 02:26:14 +01:00
|
|
|
|
|
|
|
return hash('sha256', uniqid(mt_rand(), true));
|
2013-12-23 03:25:54 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
// Regenerate tokens for the API and bookmark feed
|
|
|
|
function new_tokens()
|
|
|
|
{
|
|
|
|
$values = array(
|
|
|
|
'api_token' => generate_token(),
|
|
|
|
'feed_token' => generate_token(),
|
2014-05-28 22:44:25 +02:00
|
|
|
'bookmarklet_token' => generate_token(),
|
2014-10-30 02:28:23 +01:00
|
|
|
'fever_token' => substr(generate_token(), 0, 8),
|
2013-12-23 03:25:54 +01:00
|
|
|
);
|
|
|
|
|
2014-02-08 20:13:14 +01:00
|
|
|
return Database::get('db')->table('config')->update($values);
|
2013-12-23 03:25:54 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
// Save tokens for external authentication
|
|
|
|
function save_auth_token($type, $value)
|
|
|
|
{
|
2014-02-08 20:13:14 +01:00
|
|
|
return Database::get('db')
|
2013-12-23 03:25:54 +01:00
|
|
|
->table('config')
|
|
|
|
->update(array(
|
|
|
|
'auth_'.$type.'_token' => $value
|
|
|
|
));
|
|
|
|
}
|
|
|
|
|
|
|
|
// Clear authentication tokens
|
|
|
|
function remove_auth_token($type)
|
|
|
|
{
|
2014-02-08 20:13:14 +01:00
|
|
|
Database::get('db')
|
2013-12-23 03:25:54 +01:00
|
|
|
->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)) {
|
2014-02-08 20:13:14 +01:00
|
|
|
return Database::get('db')->table('config')->findOneColumn($name);
|
2013-12-23 03:25:54 +01:00
|
|
|
}
|
|
|
|
else {
|
|
|
|
|
2014-02-26 01:03:46 +01:00
|
|
|
if (! isset($_SESSION['config'][$name])) {
|
2013-12-23 03:25:54 +01:00
|
|
|
$_SESSION['config'] = get_all();
|
|
|
|
}
|
|
|
|
|
|
|
|
if (isset($_SESSION['config'][$name])) {
|
|
|
|
return $_SESSION['config'][$name];
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return null;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Get all config parameters
|
|
|
|
function get_all()
|
|
|
|
{
|
2014-02-08 20:13:14 +01:00
|
|
|
return Database::get('db')
|
2013-12-23 03:25:54 +01:00
|
|
|
->table('config')
|
|
|
|
->columns(
|
|
|
|
'username',
|
|
|
|
'language',
|
2014-02-26 01:03:46 +01:00
|
|
|
'timezone',
|
2013-12-23 03:25:54 +01:00
|
|
|
'autoflush',
|
2014-12-16 02:38:35 +01:00
|
|
|
'autoflush_unread',
|
2013-12-23 03:25:54 +01:00
|
|
|
'nocontent',
|
|
|
|
'items_per_page',
|
|
|
|
'theme',
|
|
|
|
'api_token',
|
|
|
|
'feed_token',
|
2014-10-30 02:28:23 +01:00
|
|
|
'fever_token',
|
2014-05-28 22:44:25 +02:00
|
|
|
'bookmarklet_token',
|
2013-12-23 19:33:16 +01:00
|
|
|
'items_sorting_direction',
|
2014-05-29 16:57:23 +02:00
|
|
|
'items_display_mode',
|
2014-03-30 21:59:26 +02:00
|
|
|
'redirect_nothing_to_read',
|
2014-12-24 16:47:24 +01:00
|
|
|
'auto_update_url',
|
|
|
|
'pinboard_enabled',
|
|
|
|
'pinboard_token',
|
2014-12-24 19:50:20 +01:00
|
|
|
'pinboard_tags',
|
|
|
|
'instapaper_enabled',
|
|
|
|
'instapaper_username',
|
2014-12-24 21:58:24 +01:00
|
|
|
'instapaper_password',
|
|
|
|
'image_proxy'
|
2013-12-23 03:25:54 +01:00
|
|
|
)
|
|
|
|
->findOne();
|
|
|
|
}
|
|
|
|
|
|
|
|
// Validation for edit action
|
|
|
|
function validate_modification(array $values)
|
|
|
|
{
|
2014-03-30 21:59:26 +02:00
|
|
|
$rules = 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')),
|
2014-12-16 02:38:35 +01:00
|
|
|
new Validators\Required('autoflush_unread', t('Value required')),
|
2014-03-30 21:59:26 +02:00
|
|
|
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')),
|
|
|
|
);
|
2013-12-23 03:25:54 +01:00
|
|
|
|
2014-03-30 21:59:26 +02:00
|
|
|
if (ENABLE_AUTO_UPDATE) {
|
|
|
|
$rules[] = new Validators\Required('auto_update_url', t('Value required'));
|
2013-12-23 03:25:54 +01:00
|
|
|
}
|
|
|
|
|
2014-03-30 21:59:26 +02:00
|
|
|
if (! empty($values['password'])) {
|
|
|
|
$rules[] = new Validators\Required('password', t('The password is required'));
|
|
|
|
$rules[] = new Validators\MinLength('password', t('The minimum length is 6 characters'), 6);
|
|
|
|
$rules[] = new Validators\Required('confirmation', t('The confirmation is required'));
|
2014-05-28 22:10:41 +02:00
|
|
|
$rules[] = new Validators\Equals('password', 'confirmation', t('Passwords don\'t match'));
|
2013-12-23 03:25:54 +01:00
|
|
|
}
|
|
|
|
|
2014-03-30 21:59:26 +02:00
|
|
|
$v = new Validator($values, $rules);
|
|
|
|
|
2013-12-23 03:25:54 +01:00
|
|
|
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']);
|
|
|
|
|
|
|
|
// If the user does not want content of feeds, remove it in previous ones
|
|
|
|
if (isset($values['nocontent']) && (bool) $values['nocontent']) {
|
2014-02-08 20:13:14 +01:00
|
|
|
Database::get('db')->table('items')->update(array('content' => ''));
|
2013-12-23 03:25:54 +01:00
|
|
|
}
|
|
|
|
|
2014-12-24 16:47:24 +01:00
|
|
|
if (Database::get('db')->table('config')->update($values)) {
|
|
|
|
reload();
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Reload the cache in session
|
|
|
|
function reload()
|
|
|
|
{
|
|
|
|
$_SESSION['config'] = get_all();
|
|
|
|
\Translator\load(get('language'));
|
2013-12-23 03:25:54 +01:00
|
|
|
}
|
2014-05-27 02:47:40 +02:00
|
|
|
|
|
|
|
// Get the user agent of the connected user
|
|
|
|
function get_user_agent()
|
|
|
|
{
|
|
|
|
return empty($_SERVER['HTTP_USER_AGENT']) ? t('Unknown') : $_SERVER['HTTP_USER_AGENT'];
|
|
|
|
}
|
|
|
|
|
|
|
|
// Get the real IP address of the connected user
|
|
|
|
function get_ip_address($only_public = false)
|
|
|
|
{
|
|
|
|
$keys = array(
|
|
|
|
'HTTP_CLIENT_IP',
|
|
|
|
'HTTP_X_FORWARDED_FOR',
|
|
|
|
'HTTP_X_FORWARDED',
|
|
|
|
'HTTP_X_CLUSTER_CLIENT_IP',
|
|
|
|
'HTTP_FORWARDED_FOR',
|
|
|
|
'HTTP_FORWARDED',
|
|
|
|
'REMOTE_ADDR'
|
|
|
|
);
|
|
|
|
|
|
|
|
foreach ($keys as $key) {
|
|
|
|
|
|
|
|
if (isset($_SERVER[$key])) {
|
|
|
|
|
|
|
|
foreach (explode(',', $_SERVER[$key]) as $ip_address) {
|
|
|
|
|
|
|
|
$ip_address = trim($ip_address);
|
|
|
|
|
|
|
|
if ($only_public) {
|
|
|
|
|
|
|
|
// Return only public IP address
|
|
|
|
if (filter_var($ip_address, FILTER_VALIDATE_IP, FILTER_FLAG_NO_PRIV_RANGE | FILTER_FLAG_NO_RES_RANGE) !== false) {
|
|
|
|
return $ip_address;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
else {
|
|
|
|
|
|
|
|
return $ip_address;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return t('Unknown');
|
|
|
|
}
|