aboutsummaryrefslogtreecommitdiff
path: root/app
diff options
context:
space:
mode:
Diffstat (limited to 'app')
-rw-r--r--app/Controllers/authController.php9
-rwxr-xr-xapp/Controllers/configureController.php1
-rwxr-xr-xapp/Controllers/feedController.php41
-rw-r--r--app/Controllers/importExportController.php14
-rw-r--r--app/Controllers/updateController.php31
-rw-r--r--app/Controllers/userController.php7
-rw-r--r--app/FreshRSS.php14
-rw-r--r--app/Models/Auth.php27
-rw-r--r--app/Models/ConfigurationSetter.php4
-rw-r--r--app/Models/Entry.php8
-rw-r--r--app/Models/EntryDAO.php189
-rw-r--r--app/Models/EntryDAOPGSQL.php26
-rw-r--r--app/Models/EntryDAOSQLite.php39
-rw-r--r--app/Models/Factory.php9
-rw-r--r--app/Models/Feed.php21
-rw-r--r--app/Models/FeedDAO.php56
-rw-r--r--app/Models/FeedDAOSQLite.php19
-rw-r--r--app/Models/Search.php168
-rw-r--r--app/Models/UserDAO.php10
-rw-r--r--app/SQL/install.sql.mysql.php28
-rw-r--r--app/SQL/install.sql.pgsql.php28
-rw-r--r--app/SQL/install.sql.sqlite.php29
-rw-r--r--app/i18n/cz/conf.php1
-rw-r--r--app/i18n/cz/feedback.php2
-rw-r--r--app/i18n/de/conf.php1
-rw-r--r--app/i18n/de/feedback.php2
-rw-r--r--app/i18n/en/admin.php20
-rw-r--r--app/i18n/en/conf.php37
-rw-r--r--app/i18n/en/feedback.php12
-rw-r--r--app/i18n/en/gen.php5
-rw-r--r--app/i18n/en/index.php4
-rw-r--r--app/i18n/en/sub.php6
-rw-r--r--app/i18n/fr/conf.php1
-rw-r--r--app/i18n/fr/feedback.php2
-rw-r--r--app/i18n/it/conf.php1
-rw-r--r--app/i18n/it/feedback.php2
-rw-r--r--app/i18n/nl/admin.php32
-rw-r--r--app/i18n/nl/conf.php3
-rw-r--r--app/i18n/nl/feedback.php8
-rw-r--r--app/i18n/nl/gen.php16
-rw-r--r--app/i18n/nl/index.php4
-rw-r--r--app/i18n/nl/install.php8
-rw-r--r--app/i18n/nl/sub.php6
-rw-r--r--app/i18n/ru/conf.php1
-rw-r--r--app/i18n/ru/feedback.php134
-rw-r--r--app/i18n/tr/conf.php1
-rw-r--r--app/i18n/tr/feedback.php2
-rw-r--r--app/i18n/zh-cn/admin.php181
-rw-r--r--app/i18n/zh-cn/conf.php174
-rw-r--r--app/i18n/zh-cn/feedback.php109
-rw-r--r--app/i18n/zh-cn/gen.php185
-rw-r--r--app/i18n/zh-cn/index.php61
-rw-r--r--app/i18n/zh-cn/install.php119
-rw-r--r--app/i18n/zh-cn/sub.php62
-rw-r--r--app/install.php44
-rw-r--r--app/layout/nav_menu.phtml1
-rw-r--r--app/views/auth/index.phtml13
-rw-r--r--app/views/configure/reading.phtml10
-rw-r--r--app/views/helpers/javascript_vars.phtml1
-rw-r--r--app/views/index/normal.phtml2
-rw-r--r--app/views/index/reader.phtml2
-rw-r--r--app/views/user/profile.phtml13
62 files changed, 1662 insertions, 404 deletions
diff --git a/app/Controllers/authController.php b/app/Controllers/authController.php
index 1398e4e49..5ad1a51d9 100644
--- a/app/Controllers/authController.php
+++ b/app/Controllers/authController.php
@@ -27,11 +27,6 @@ class FreshRSS_auth_Controller extends Minz_ActionController {
if (Minz_Request::isPost()) {
$ok = true;
- $current_token = FreshRSS_Context::$user_conf->token;
- $token = Minz_Request::param('token', $current_token);
- FreshRSS_Context::$user_conf->token = $token;
- $ok &= FreshRSS_Context::$user_conf->save();
-
$anon = Minz_Request::param('anon_access', false);
$anon = ((bool)$anon) && ($anon !== 'no');
$anon_refresh = Minz_Request::param('anon_refresh', false);
@@ -123,7 +118,7 @@ class FreshRSS_auth_Controller extends Minz_ActionController {
$challenge = Minz_Request::param('challenge', '');
$conf = get_user_configuration($username);
- if (is_null($conf)) {
+ if ($conf == null) {
Minz_Error::error(403, array(_t('feedback.auth.login.invalid')), false);
return;
}
@@ -164,7 +159,7 @@ class FreshRSS_auth_Controller extends Minz_ActionController {
}
$conf = get_user_configuration($username);
- if (is_null($conf)) {
+ if ($conf == null) {
return;
}
diff --git a/app/Controllers/configureController.php b/app/Controllers/configureController.php
index e73f106a6..155221d19 100755
--- a/app/Controllers/configureController.php
+++ b/app/Controllers/configureController.php
@@ -109,6 +109,7 @@ class FreshRSS_configure_Controller extends Minz_ActionController {
FreshRSS_Context::$user_conf->hide_read_feeds = Minz_Request::param('hide_read_feeds', false);
FreshRSS_Context::$user_conf->onread_jump_next = Minz_Request::param('onread_jump_next', false);
FreshRSS_Context::$user_conf->lazyload = Minz_Request::param('lazyload', false);
+ FreshRSS_Context::$user_conf->sides_close_article = Minz_Request::param('sides_close_article', false);
FreshRSS_Context::$user_conf->sticky_post = Minz_Request::param('sticky_post', false);
FreshRSS_Context::$user_conf->reading_confirm = Minz_Request::param('reading_confirm', false);
FreshRSS_Context::$user_conf->auto_remove_article = Minz_Request::param('auto_remove_article', false);
diff --git a/app/Controllers/feedController.php b/app/Controllers/feedController.php
index f71f26a4e..c9b6deaa7 100755
--- a/app/Controllers/feedController.php
+++ b/app/Controllers/feedController.php
@@ -226,7 +226,7 @@ class FreshRSS_feed_Controller extends Minz_ActionController {
}
}
- public static function actualizeFeed($feed_id, $feed_url, $force, $simplePiePush = null, $isNewFeed = false) {
+ public static function actualizeFeed($feed_id, $feed_url, $force, $simplePiePush = null, $isNewFeed = false, $noCommit = false) {
@set_time_limit(300);
$feedDAO = FreshRSS_Factory::createFeedDao();
@@ -254,6 +254,7 @@ class FreshRSS_feed_Controller extends Minz_ActionController {
$pshbMinAge = time() - (3600 * 24); //TODO: Make a configuration.
$updated_feeds = 0;
+ $nb_new_articles = 0;
$is_read = FreshRSS_Context::$user_conf->mark_when['reception'] ? 1 : 0;
foreach ($feeds as $feed) {
$url = $feed->url(); //For detection of HTTP 301
@@ -308,6 +309,7 @@ class FreshRSS_feed_Controller extends Minz_ActionController {
// -2 means we take the default value from configuration
$feed_history = FreshRSS_Context::$user_conf->keep_history_default;
}
+ $needFeedCacheRefresh = false;
// We want chronological order and SimplePie uses reverse order.
$entries = array_reverse($feed->entries());
@@ -333,6 +335,7 @@ class FreshRSS_feed_Controller extends Minz_ActionController {
//Minz_Log::debug('Entry with GUID `' . $entry->guid() . '` updated in feed ' . $feed->id() .
//', old hash ' . $existingHash . ', new hash ' . $entry->hash());
//TODO: Make an updated/is_read policy by feed, in addition to the global one.
+ $needFeedCacheRefresh = FreshRSS_Context::$user_conf->mark_updated_article_unread;
$entry->_isRead(FreshRSS_Context::$user_conf->mark_updated_article_unread ? false : null); //Change is_read according to policy.
if (!$entryDAO->inTransaction()) {
$entryDAO->beginTransaction();
@@ -345,6 +348,7 @@ class FreshRSS_feed_Controller extends Minz_ActionController {
} else {
if ($isNewFeed) {
$id = min(time(), $entry_date) . uSecString();
+ $entry->_isRead($is_read);
} elseif ($entry_date < $date_min) {
$id = min(time(), $entry_date) . uSecString();
$entry->_isRead(true); //Old article that was not in database. Probably an error, so mark as read
@@ -372,6 +376,7 @@ class FreshRSS_feed_Controller extends Minz_ActionController {
$entryDAO->beginTransaction();
}
$entryDAO->addEntry($entry->toArray());
+ $nb_new_articles++;
}
}
$entryDAO->updateLastSeen($feed->id(), $oldGuids, $mtime);
@@ -388,12 +393,16 @@ class FreshRSS_feed_Controller extends Minz_ActionController {
$date_min,
max($feed_history, count($entries) + 10));
if ($nb > 0) {
+ $needFeedCacheRefresh = true;
Minz_Log::debug($nb . ' old entries cleaned in feed [' .
$feed->url() . ']');
}
}
- $feedDAO->updateLastUpdate($feed->id(), false, $entryDAO->inTransaction(), $mtime);
+ $feedDAO->updateLastUpdate($feed->id(), false, $mtime);
+ if ($needFeedCacheRefresh) {
+ $feedDAO->updateCachedValue($feed->id());
+ }
if ($entryDAO->inTransaction()) {
$entryDAO->commit();
}
@@ -434,7 +443,17 @@ class FreshRSS_feed_Controller extends Minz_ActionController {
break;
}
}
- return array($updated_feeds, reset($feeds));
+ if (!$noCommit) {
+ if (!$entryDAO->inTransaction()) {
+ $entryDAO->beginTransaction();
+ }
+ $entryDAO->commitNewEntries();
+ $feedDAO->updateCachedValues();
+ if ($entryDAO->inTransaction()) {
+ $entryDAO->commit();
+ }
+ }
+ return array($updated_feeds, reset($feeds), $nb_new_articles);
}
/**
@@ -444,6 +463,7 @@ class FreshRSS_feed_Controller extends Minz_ActionController {
* - id (default: false): Feed ID
* - url (default: false): Feed URL
* - force (default: false)
+ * - noCommit (default: 0): Set to 1 to prevent committing the new articles to the main database
* If id and url are not specified, all the feeds are actualized. But if force is
* false, process stops at 10 feeds to avoid time execution problem.
*/
@@ -452,8 +472,19 @@ class FreshRSS_feed_Controller extends Minz_ActionController {
$id = Minz_Request::param('id');
$url = Minz_Request::param('url');
$force = Minz_Request::param('force');
-
- list($updated_feeds, $feed) = self::actualizeFeed($id, $url, $force);
+ $noCommit = Minz_Request::fetchPOST('noCommit', 0) == 1;
+
+ if ($id == -1 && !$noCommit) { //Special request only to commit & refresh DB cache
+ $updated_feeds = 0;
+ $entryDAO = FreshRSS_Factory::createEntryDao();
+ $feedDAO = FreshRSS_Factory::createFeedDao();
+ $entryDAO->beginTransaction();
+ $entryDAO->commitNewEntries();
+ $feedDAO->updateCachedValues();
+ $entryDAO->commit();
+ } else {
+ list($updated_feeds, $feed, $nb_new_articles) = self::actualizeFeed($id, $url, $force, null, false, $noCommit);
+ }
if (Minz_Request::param('ajax')) {
// Most of the time, ajax request is for only one feed. But since
diff --git a/app/Controllers/importExportController.php b/app/Controllers/importExportController.php
index 6ae89defb..2bc68848c 100644
--- a/app/Controllers/importExportController.php
+++ b/app/Controllers/importExportController.php
@@ -464,18 +464,22 @@ class FreshRSS_importExport_Controller extends Minz_ActionController {
}
$values = $entry->toArray();
+ $ok = false;
if (isset($existingHashForGuids[$entry->guid()])) {
- $id = $this->entryDAO->updateEntry($values);
+ $ok = $this->entryDAO->updateEntry($values);
} else {
- $id = $this->entryDAO->addEntry($values);
+ $ok = $this->entryDAO->addEntry($values);
}
+ $error |= ($ok === false);
- if (!$error && ($id === false)) {
- $error = true;
- }
}
$this->entryDAO->commit();
+ $this->entryDAO->beginTransaction();
+ $this->entryDAO->commitNewEntries();
+ $this->feedDAO->updateCachedValues();
+ $this->entryDAO->commit();
+
return !$error;
}
diff --git a/app/Controllers/updateController.php b/app/Controllers/updateController.php
index b4e8a0bff..7a8a3d6c0 100644
--- a/app/Controllers/updateController.php
+++ b/app/Controllers/updateController.php
@@ -59,24 +59,26 @@ class FreshRSS_update_Controller extends Minz_ActionController {
public function indexAction() {
Minz_View::prependTitle(_t('admin.update.title') . ' · ');
- if (!is_writable(FRESHRSS_PATH)) {
- $this->view->message = array(
- 'status' => 'bad',
- 'title' => _t('gen.short.damn'),
- 'body' => _t('feedback.update.file_is_nok', FRESHRSS_PATH)
- );
- } elseif (file_exists(UPDATE_FILENAME)) {
+ if (file_exists(UPDATE_FILENAME)) {
// There is an update file to apply!
$version = @file_get_contents(join_path(DATA_PATH, 'last_update.txt'));
- if (empty($version)) {
+ if ($version == '') {
$version = 'unknown';
}
- $this->view->update_to_apply = true;
- $this->view->message = array(
- 'status' => 'good',
- 'title' => _t('gen.short.ok'),
- 'body' => _t('feedback.update.can_apply', $version)
- );
+ if (is_writable(FRESHRSS_PATH)) {
+ $this->view->update_to_apply = true;
+ $this->view->message = array(
+ 'status' => 'good',
+ 'title' => _t('gen.short.ok'),
+ 'body' => _t('feedback.update.can_apply', $version),
+ );
+ } else {
+ $this->view->message = array(
+ 'status' => 'bad',
+ 'title' => _t('gen.short.damn'),
+ 'body' => _t('feedback.update.file_is_nok', $version, FRESHRSS_PATH),
+ );
+ }
}
}
@@ -190,6 +192,7 @@ class FreshRSS_update_Controller extends Minz_ActionController {
if (self::isGit()) {
$res = self::gitPull();
} else {
+ require(UPDATE_FILENAME);
if (Minz_Request::isPost()) {
save_info_update();
}
diff --git a/app/Controllers/userController.php b/app/Controllers/userController.php
index f910cecd9..3cbbd8633 100644
--- a/app/Controllers/userController.php
+++ b/app/Controllers/userController.php
@@ -38,7 +38,7 @@ class FreshRSS_user_Controller extends Minz_ActionController {
* The username is also used as folder name, file name, and part of SQL table name.
* '_' is a reserved internal username.
*/
- const USERNAME_PATTERN = '[0-9a-zA-Z]|[0-9a-zA-Z_]{2,38}';
+ const USERNAME_PATTERN = '[0-9a-zA-Z_]{2,38}|[0-9a-zA-Z]';
public static function checkUsername($username) {
return preg_match('/^' . self::USERNAME_PATTERN . '$/', $username) === 1;
@@ -74,6 +74,10 @@ class FreshRSS_user_Controller extends Minz_ActionController {
FreshRSS_Context::$user_conf->apiPasswordHash = $passwordHash;
}
+ $current_token = FreshRSS_Context::$user_conf->token;
+ $token = Minz_Request::param('token', $current_token);
+ FreshRSS_Context::$user_conf->token = $token;
+
$ok &= FreshRSS_Context::$user_conf->save();
if ($ok) {
@@ -213,6 +217,7 @@ class FreshRSS_user_Controller extends Minz_ActionController {
$userDAO = new FreshRSS_UserDAO();
$ok &= $userDAO->deleteUser($username);
$ok &= recursive_unlink($user_data);
+ array_map('unlink', glob(PSHB_PATH . '/feeds/*/' . $username . '.txt'));
}
return $ok;
}
diff --git a/app/FreshRSS.php b/app/FreshRSS.php
index e4caf23d1..90d6fae06 100644
--- a/app/FreshRSS.php
+++ b/app/FreshRSS.php
@@ -41,7 +41,7 @@ class FreshRSS extends Minz_FrontController {
$current_user = Minz_Session::param('currentUser', '_');
Minz_Configuration::register('user',
join_path(USERS_PATH, $current_user, 'config.php'),
- join_path(USERS_PATH, '_', 'config.default.php'),
+ join_path(FRESHRSS_PATH, 'config-user.default.php'),
$configuration_setter);
// Finish to initialize the other FreshRSS / Minz components.
@@ -80,7 +80,7 @@ class FreshRSS extends Minz_FrontController {
public static function loadStylesAndScripts() {
$theme = FreshRSS_Themes::load(FreshRSS_Context::$user_conf->theme);
if ($theme) {
- foreach($theme['files'] as $file) {
+ foreach(array_reverse($theme['files']) as $file) {
if ($file[0] === '_') {
$theme_id = 'base-theme';
$filename = substr($file, 1);
@@ -91,13 +91,13 @@ class FreshRSS extends Minz_FrontController {
$filetime = @filemtime(PUBLIC_PATH . '/themes/' . $theme_id . '/' . $filename);
$url = '/themes/' . $theme_id . '/' . $filename . '?' . $filetime;
header('Link: <' . Minz_Url::display($url, '', 'root') . '>;rel=preload', false); //HTTP2
- Minz_View::appendStyle(Minz_Url::display($url));
+ Minz_View::prependStyle(Minz_Url::display($url));
}
}
-
- Minz_View::appendScript(Minz_Url::display('/scripts/jquery.min.js?' . @filemtime(PUBLIC_PATH . '/scripts/jquery.min.js')));
- Minz_View::appendScript(Minz_Url::display('/scripts/shortcut.js?' . @filemtime(PUBLIC_PATH . '/scripts/shortcut.js')));
- Minz_View::appendScript(Minz_Url::display('/scripts/main.js?' . @filemtime(PUBLIC_PATH . '/scripts/main.js')));
+ //Use prepend to insert before extensions. Added in reverse order.
+ Minz_View::prependScript(Minz_Url::display('/scripts/main.js?' . @filemtime(PUBLIC_PATH . '/scripts/main.js')));
+ Minz_View::prependScript(Minz_Url::display('/scripts/shortcut.js?' . @filemtime(PUBLIC_PATH . '/scripts/shortcut.js')));
+ Minz_View::prependScript(Minz_Url::display('/scripts/jquery.min.js?' . @filemtime(PUBLIC_PATH . '/scripts/jquery.min.js')));
}
private static function loadNotifications() {
diff --git a/app/Models/Auth.php b/app/Models/Auth.php
index 476627e10..4de058999 100644
--- a/app/Models/Auth.php
+++ b/app/Models/Auth.php
@@ -74,6 +74,10 @@ class FreshRSS_Auth {
public static function giveAccess() {
$current_user = Minz_Session::param('currentUser');
$user_conf = get_user_configuration($current_user);
+ if ($user_conf == null) {
+ self::$login_ok = false;
+ return;
+ }
$system_conf = Minz_Configuration::get('system');
switch ($system_conf->auth_type) {
@@ -120,13 +124,28 @@ class FreshRSS_Auth {
* Removes all accesses for the current user.
*/
public static function removeAccess() {
- Minz_Session::_param('loginOk');
self::$login_ok = false;
- $conf = Minz_Configuration::get('system');
- Minz_Session::_param('currentUser', $conf->default_user);
+ Minz_Session::_param('loginOk');
Minz_Session::_param('csrf');
+ $system_conf = Minz_Configuration::get('system');
- switch ($conf->auth_type) {
+ $username = '';
+ $token_param = Minz_Request::param('token', '');
+ if ($token_param != '') {
+ $username = trim(Minz_Request::param('user', ''));
+ if ($username != '') {
+ $conf = get_user_configuration($username);
+ if ($conf == null) {
+ $username = '';
+ }
+ }
+ }
+ if ($username == '') {
+ $username = $system_conf->default_user;
+ }
+ Minz_Session::_param('currentUser', $username);
+
+ switch ($system_conf->auth_type) {
case 'form':
Minz_Session::_param('passwordHash');
FreshRSS_FormAuth::deleteCookie();
diff --git a/app/Models/ConfigurationSetter.php b/app/Models/ConfigurationSetter.php
index 046f54955..70e1dea2e 100644
--- a/app/Models/ConfigurationSetter.php
+++ b/app/Models/ConfigurationSetter.php
@@ -197,6 +197,10 @@ class FreshRSS_ConfigurationSetter {
$data['hide_read_feeds'] = $this->handleBool($value);
}
+ private function _sides_close_article(&$data, $value) {
+ $data['sides_close_article'] = $this->handleBool($value);
+ }
+
private function _lazyload(&$data, $value) {
$data['lazyload'] = $this->handleBool($value);
}
diff --git a/app/Models/Entry.php b/app/Models/Entry.php
index a562a963a..26cd24797 100644
--- a/app/Models/Entry.php
+++ b/app/Models/Entry.php
@@ -22,7 +22,6 @@ class FreshRSS_Entry extends Minz_Model {
public function __construct($feed = '', $guid = '', $title = '', $author = '', $content = '',
$link = '', $pubdate = 0, $is_read = false, $is_favorite = false, $tags = '') {
- $this->_guid($guid);
$this->_title($title);
$this->_author($author);
$this->_content($content);
@@ -32,6 +31,7 @@ class FreshRSS_Entry extends Minz_Model {
$this->_isFavorite($is_favorite);
$this->_feed($feed);
$this->_tags(preg_split('/[\s#]/', $tags));
+ $this->_guid($guid);
}
public function id() {
@@ -101,6 +101,12 @@ class FreshRSS_Entry extends Minz_Model {
$this->id = $value;
}
public function _guid($value) {
+ if ($value == '') {
+ $value = $this->link;
+ if ($value == '') {
+ $value = $this->hash();
+ }
+ }
$this->guid = $value;
}
public function _title($value) {
diff --git a/app/Models/EntryDAO.php b/app/Models/EntryDAO.php
index afcde3d7f..7e836097a 100644
--- a/app/Models/EntryDAO.php
+++ b/app/Models/EntryDAO.php
@@ -88,6 +88,38 @@ class FreshRSS_EntryDAO extends Minz_ModelPdo implements FreshRSS_Searchable {
return false;
}
+ protected function createEntryTempTable() {
+ $ok = false;
+ $hadTransaction = $this->bd->inTransaction();
+ if ($hadTransaction) {
+ $this->bd->commit();
+ }
+ try {
+ $db = FreshRSS_Context::$system_conf->db;
+ require_once(APP_PATH . '/SQL/install.sql.' . $db['type'] . '.php');
+ Minz_Log::warning('SQL CREATE TABLE entrytmp...');
+ if (defined('SQL_CREATE_TABLE_ENTRYTMP')) {
+ $sql = sprintf(SQL_CREATE_TABLE_ENTRYTMP, $this->prefix);
+ $stm = $this->bd->prepare($sql);
+ $ok = $stm && $stm->execute();
+ } else {
+ global $SQL_CREATE_TABLE_ENTRYTMP;
+ $ok = !empty($SQL_CREATE_TABLE_ENTRYTMP);
+ foreach ($SQL_CREATE_TABLE_ENTRYTMP as $instruction) {
+ $sql = sprintf($instruction, $this->prefix);
+ $stm = $this->bd->prepare($sql);
+ $ok &= $stm && $stm->execute();
+ }
+ }
+ } catch (Exception $e) {
+ Minz_Log::error('FreshRSS_EntryDAO::createEntryTempTable error: ' . $e->getMessage());
+ }
+ if ($hadTransaction) {
+ $this->bd->beginTransaction();
+ }
+ return $ok;
+ }
+
protected function autoUpdateDb($errorInfo) {
if (isset($errorInfo[0])) {
if ($errorInfo[0] === '42S22') { //ER_BAD_FIELD_ERROR
@@ -97,6 +129,8 @@ class FreshRSS_EntryDAO extends Minz_ModelPdo implements FreshRSS_Searchable {
return $this->addColumn($column);
}
}
+ } elseif ($errorInfo[0] === '42S02' && stripos($errorInfo[2], 'entrytmp') !== false) { //ER_BAD_TABLE_ERROR
+ return $this->createEntryTempTable(); //v1.7
}
}
if (isset($errorInfo[1])) {
@@ -110,8 +144,8 @@ class FreshRSS_EntryDAO extends Minz_ModelPdo implements FreshRSS_Searchable {
private $addEntryPrepared = null;
public function addEntry($valuesTmp) {
- if ($this->addEntryPrepared === null) {
- $sql = 'INSERT INTO `' . $this->prefix . 'entry` (id, guid, title, author, '
+ if ($this->addEntryPrepared == null) {
+ $sql = 'INSERT INTO `' . $this->prefix . 'entrytmp` (id, guid, title, author, '
. ($this->isCompressed() ? 'content_bin' : 'content')
. ', link, date, `lastSeen`, hash, is_read, is_favorite, id_feed, tags) '
. 'VALUES(:id, :guid, :title, :author, '
@@ -121,43 +155,45 @@ class FreshRSS_EntryDAO extends Minz_ModelPdo implements FreshRSS_Searchable {
. ', :is_read, :is_favorite, :id_feed, :tags)';
$this->addEntryPrepared = $this->bd->prepare($sql);
}
- $this->addEntryPrepared->bindParam(':id', $valuesTmp['id']);
- $valuesTmp['guid'] = substr($valuesTmp['guid'], 0, 760);
- $valuesTmp['guid'] = safe_ascii($valuesTmp['guid']);
- $this->addEntryPrepared->bindParam(':guid', $valuesTmp['guid']);
- $valuesTmp['title'] = substr($valuesTmp['title'], 0, 255);
- $this->addEntryPrepared->bindParam(':title', $valuesTmp['title']);
- $valuesTmp['author'] = substr($valuesTmp['author'], 0, 255);
- $this->addEntryPrepared->bindParam(':author', $valuesTmp['author']);
- $this->addEntryPrepared->bindParam(':content', $valuesTmp['content']);
- $valuesTmp['link'] = substr($valuesTmp['link'], 0, 1023);
- $valuesTmp['link'] = safe_ascii($valuesTmp['link']);
- $this->addEntryPrepared->bindParam(':link', $valuesTmp['link']);
- $this->addEntryPrepared->bindParam(':date', $valuesTmp['date'], PDO::PARAM_INT);
- $valuesTmp['lastSeen'] = time();
- $this->addEntryPrepared->bindParam(':last_seen', $valuesTmp['lastSeen'], PDO::PARAM_INT);
- $valuesTmp['is_read'] = $valuesTmp['is_read'] ? 1 : 0;
- $this->addEntryPrepared->bindParam(':is_read', $valuesTmp['is_read'], PDO::PARAM_INT);
- $valuesTmp['is_favorite'] = $valuesTmp['is_favorite'] ? 1 : 0;
- $this->addEntryPrepared->bindParam(':is_favorite', $valuesTmp['is_favorite'], PDO::PARAM_INT);
- $this->addEntryPrepared->bindParam(':id_feed', $valuesTmp['id_feed'], PDO::PARAM_INT);
- $valuesTmp['tags'] = substr($valuesTmp['tags'], 0, 1023);
- $this->addEntryPrepared->bindParam(':tags', $valuesTmp['tags']);
-
- if ($this->hasNativeHex()) {
- $this->addEntryPrepared->bindParam(':hash', $valuesTmp['hash']);
- } else {
- $valuesTmp['hashBin'] = pack('H*', $valuesTmp['hash']); //hex2bin() is PHP5.4+
- $this->addEntryPrepared->bindParam(':hash', $valuesTmp['hashBin']);
+ if ($this->addEntryPrepared) {
+ $this->addEntryPrepared->bindParam(':id', $valuesTmp['id']);
+ $valuesTmp['guid'] = substr($valuesTmp['guid'], 0, 760);
+ $valuesTmp['guid'] = safe_ascii($valuesTmp['guid']);
+ $this->addEntryPrepared->bindParam(':guid', $valuesTmp['guid']);
+ $valuesTmp['title'] = substr($valuesTmp['title'], 0, 255);
+ $this->addEntryPrepared->bindParam(':title', $valuesTmp['title']);
+ $valuesTmp['author'] = substr($valuesTmp['author'], 0, 255);
+ $this->addEntryPrepared->bindParam(':author', $valuesTmp['author']);
+ $this->addEntryPrepared->bindParam(':content', $valuesTmp['content']);
+ $valuesTmp['link'] = substr($valuesTmp['link'], 0, 1023);
+ $valuesTmp['link'] = safe_ascii($valuesTmp['link']);
+ $this->addEntryPrepared->bindParam(':link', $valuesTmp['link']);
+ $this->addEntryPrepared->bindParam(':date', $valuesTmp['date'], PDO::PARAM_INT);
+ $valuesTmp['lastSeen'] = time();
+ $this->addEntryPrepared->bindParam(':last_seen', $valuesTmp['lastSeen'], PDO::PARAM_INT);
+ $valuesTmp['is_read'] = $valuesTmp['is_read'] ? 1 : 0;
+ $this->addEntryPrepared->bindParam(':is_read', $valuesTmp['is_read'], PDO::PARAM_INT);
+ $valuesTmp['is_favorite'] = $valuesTmp['is_favorite'] ? 1 : 0;
+ $this->addEntryPrepared->bindParam(':is_favorite', $valuesTmp['is_favorite'], PDO::PARAM_INT);
+ $this->addEntryPrepared->bindParam(':id_feed', $valuesTmp['id_feed'], PDO::PARAM_INT);
+ $valuesTmp['tags'] = substr($valuesTmp['tags'], 0, 1023);
+ $this->addEntryPrepared->bindParam(':tags', $valuesTmp['tags']);
+
+ if ($this->hasNativeHex()) {
+ $this->addEntryPrepared->bindParam(':hash', $valuesTmp['hash']);
+ } else {
+ $valuesTmp['hashBin'] = pack('H*', $valuesTmp['hash']); //hex2bin() is PHP5.4+
+ $this->addEntryPrepared->bindParam(':hash', $valuesTmp['hashBin']);
+ }
}
-
if ($this->addEntryPrepared && $this->addEntryPrepared->execute()) {
- return $this->bd->lastInsertId();
+ return true;
} else {
$info = $this->addEntryPrepared == null ? array(0 => '', 1 => '', 2 => 'syntax error') : $this->addEntryPrepared->errorInfo();
if ($this->autoUpdateDb($info)) {
+ $this->addEntryPrepared = null;
return $this->addEntry($valuesTmp);
- } elseif ((int)($info[0] / 1000) !== 23) { //Filter out "SQLSTATE Class code 23: Constraint Violation" because of expected duplicate entries
+ } elseif ((int)((int)$info[0] / 1000) !== 23) { //Filter out "SQLSTATE Class code 23: Constraint Violation" because of expected duplicate entries
Minz_Log::error('SQL error addEntry: ' . $info[0] . ': ' . $info[1] . ' ' . $info[2]
. ' while adding entry in feed ' . $valuesTmp['id_feed'] . ' with title: ' . $valuesTmp['title']);
}
@@ -165,6 +201,22 @@ class FreshRSS_EntryDAO extends Minz_ModelPdo implements FreshRSS_Searchable {
}
}
+ public function commitNewEntries() {
+ $sql = 'SET @rank=(SELECT MAX(id) - COUNT(*) FROM `' . $this->prefix . 'entrytmp`); ' . //MySQL-specific
+ 'INSERT IGNORE INTO `' . $this->prefix . 'entry` (id, guid, title, author, content_bin, link, date, `lastSeen`, hash, is_read, is_favorite, id_feed, tags) ' .
+ 'SELECT @rank:=@rank+1 AS id, guid, title, author, content_bin, link, date, `lastSeen`, hash, is_read, is_favorite, id_feed, tags FROM `' . $this->prefix . 'entrytmp` ORDER BY date; ' .
+ 'DELETE FROM `' . $this->prefix . 'entrytmp` WHERE id <= @rank;';
+ $hadTransaction = $this->bd->inTransaction();
+ if (!$hadTransaction) {
+ $this->bd->beginTransaction();
+ }
+ $result = $this->bd->exec($sql) !== false;
+ if (!$hadTransaction) {
+ $this->bd->commit();
+ }
+ return $result;
+ }
+
private $updateEntryPrepared = null;
public function updateEntry($valuesTmp) {
@@ -212,7 +264,7 @@ class FreshRSS_EntryDAO extends Minz_ModelPdo implements FreshRSS_Searchable {
}
if ($this->updateEntryPrepared && $this->updateEntryPrepared->execute()) {
- return $this->bd->lastInsertId();
+ return true;
} else {
$info = $this->updateEntryPrepared == null ? array(0 => '', 1 => '', 2 => 'syntax error') : $this->updateEntryPrepared->errorInfo();
if ($this->autoUpdateDb($info)) {
@@ -578,18 +630,6 @@ class FreshRSS_EntryDAO extends Minz_ModelPdo implements FreshRSS_Searchable {
$search .= 'AND ' . $alias . 'id >= ' . $date_min . '000000 ';
}
if ($filter) {
- if ($filter->getIntitle()) {
- $search .= 'AND ' . $alias . 'title LIKE ? ';
- $values[] = "%{$filter->getIntitle()}%";
- }
- if ($filter->getInurl()) {
- $search .= 'AND CONCAT(' . $alias . 'link, ' . $alias . 'guid) LIKE ? ';
- $values[] = "%{$filter->getInurl()}%";
- }
- if ($filter->getAuthor()) {
- $search .= 'AND ' . $alias . 'author LIKE ? ';
- $values[] = "%{$filter->getAuthor()}%";
- }
if ($filter->getMinDate()) {
$search .= 'AND ' . $alias . 'id >= ? ';
$values[] = "{$filter->getMinDate()}000000";
@@ -606,20 +646,69 @@ class FreshRSS_EntryDAO extends Minz_ModelPdo implements FreshRSS_Searchable {
$search .= 'AND ' . $alias . 'date <= ? ';
$values[] = $filter->getMaxPubdate();
}
+
+ if ($filter->getAuthor()) {
+ foreach ($filter->getAuthor() as $author) {
+ $search .= 'AND ' . $alias . 'author LIKE ? ';
+ $values[] = "%{$author}%";
+ }
+ }
+ if ($filter->getIntitle()) {
+ foreach ($filter->getIntitle() as $title) {
+ $search .= 'AND ' . $alias . 'title LIKE ? ';
+ $values[] = "%{$title}%";
+ }
+ }
if ($filter->getTags()) {
- $tags = $filter->getTags();
- foreach ($tags as $tag) {
+ foreach ($filter->getTags() as $tag) {
$search .= 'AND ' . $alias . 'tags LIKE ? ';
$values[] = "%{$tag}%";
}
}
+ if ($filter->getInurl()) {
+ foreach ($filter->getInurl() as $url) {
+ $search .= 'AND CONCAT(' . $alias . 'link, ' . $alias . 'guid) LIKE ? ';
+ $values[] = "%{$url}%";
+ }
+ }
+
+ if ($filter->getNotAuthor()) {
+ foreach ($filter->getNotAuthor() as $author) {
+ $search .= 'AND (NOT ' . $alias . 'author LIKE ?) ';
+ $values[] = "%{$author}%";
+ }
+ }
+ if ($filter->getNotIntitle()) {
+ foreach ($filter->getNotIntitle() as $title) {
+ $search .= 'AND (NOT ' . $alias . 'title LIKE ?) ';
+ $values[] = "%{$title}%";
+ }
+ }
+ if ($filter->getNotTags()) {
+ foreach ($filter->getNotTags() as $tag) {
+ $search .= 'AND (NOT ' . $alias . 'tags LIKE ?) ';
+ $values[] = "%{$tag}%";
+ }
+ }
+ if ($filter->getNotInurl()) {
+ foreach ($filter->getNotInurl() as $url) {
+ $search .= 'AND (NOT CONCAT(' . $alias . 'link, ' . $alias . 'guid) LIKE ?) ';
+ $values[] = "%{$url}%";
+ }
+ }
+
if ($filter->getSearch()) {
- $search_values = $filter->getSearch();
- foreach ($search_values as $search_value) {
+ foreach ($filter->getSearch() as $search_value) {
$search .= 'AND ' . $this->sqlconcat($alias . 'title', $this->isCompressed() ? 'UNCOMPRESS(' . $alias . 'content_bin)' : '' . $alias . 'content') . ' LIKE ? ';
$values[] = "%{$search_value}%";
}
}
+ if ($filter->getNotSearch()) {
+ foreach ($filter->getNotSearch() as $search_value) {
+ $search .= 'AND (NOT ' . $this->sqlconcat($alias . 'title', $this->isCompressed() ? 'UNCOMPRESS(' . $alias . 'content_bin)' : '' . $alias . 'content') . ' LIKE ?) ';
+ $values[] = "%{$search_value}%";
+ }
+ }
}
return array($values, $search);
}
diff --git a/app/Models/EntryDAOPGSQL.php b/app/Models/EntryDAOPGSQL.php
index b96a62ebc..b25993c47 100644
--- a/app/Models/EntryDAOPGSQL.php
+++ b/app/Models/EntryDAOPGSQL.php
@@ -11,6 +11,11 @@ class FreshRSS_EntryDAOPGSQL extends FreshRSS_EntryDAOSQLite {
}
protected function autoUpdateDb($errorInfo) {
+ if (isset($errorInfo[0])) {
+ if ($errorInfo[0] === '42P01' && stripos($errorInfo[2], 'entrytmp') !== false) { //undefined_table
+ return $this->createEntryTempTable();
+ }
+ }
return false;
}
@@ -18,6 +23,27 @@ class FreshRSS_EntryDAOPGSQL extends FreshRSS_EntryDAOSQLite {
return false;
}
+ public function commitNewEntries() {
+ $sql = 'DO $$
+DECLARE
+maxrank bigint := (SELECT MAX(id) FROM `' . $this->prefix . 'entrytmp`);
+rank bigint := (SELECT maxrank - COUNT(*) FROM `' . $this->prefix . 'entrytmp`);
+BEGIN
+ INSERT INTO `' . $this->prefix . 'entry` (id, guid, title, author, content, link, date, `lastSeen`, hash, is_read, is_favorite, id_feed, tags)
+ (SELECT rank + row_number() OVER(ORDER BY date) AS id, guid, title, author, content, link, date, `lastSeen`, hash, is_read, is_favorite, id_feed, tags FROM `' . $this->prefix . 'entrytmp` ORDER BY date);
+ DELETE FROM `' . $this->prefix . 'entrytmp` WHERE id <= maxrank;
+END $$;';
+ $hadTransaction = $this->bd->inTransaction();
+ if (!$hadTransaction) {
+ $this->bd->beginTransaction();
+ }
+ $result = $this->bd->exec($sql) !== false;
+ if (!$hadTransaction) {
+ $this->bd->commit();
+ }
+ return $result;
+ }
+
public function size($all = true) {
$db = FreshRSS_Context::$system_conf->db;
$sql = 'SELECT pg_size_pretty(pg_database_size(?))';
diff --git a/app/Models/EntryDAOSQLite.php b/app/Models/EntryDAOSQLite.php
index 34e854608..ad7bcd865 100644
--- a/app/Models/EntryDAOSQLite.php
+++ b/app/Models/EntryDAOSQLite.php
@@ -7,21 +7,42 @@ class FreshRSS_EntryDAOSQLite extends FreshRSS_EntryDAO {
}
protected function autoUpdateDb($errorInfo) {
- if (empty($errorInfo[0]) || $errorInfo[0] == '42S22') { //ER_BAD_FIELD_ERROR
- //autoAddColumn
- if ($tableInfo = $this->bd->query("SELECT sql FROM sqlite_master where name='entry'")) {
- $showCreate = $tableInfo->fetchColumn();
- Minz_Log::debug('FreshRSS_EntryDAOSQLite::autoUpdateDb: ' . $showCreate);
- foreach (array('lastSeen', 'hash') as $column) {
- if (stripos($showCreate, $column) === false) {
- return $this->addColumn($column);
- }
+ Minz_Log::error('FreshRSS_EntryDAO::autoUpdateDb error: ' . print_r($errorInfo, true));
+ if ($tableInfo = $this->bd->query("SELECT sql FROM sqlite_master where name='entrytmp'")) {
+ $showCreate = $tableInfo->fetchColumn();
+ if (stripos($showCreate, 'entrytmp') === false) {
+ return $this->createEntryTempTable();
+ }
+ }
+ if ($tableInfo = $this->bd->query("SELECT sql FROM sqlite_master where name='entry'")) {
+ $showCreate = $tableInfo->fetchColumn();
+ foreach (array('lastSeen', 'hash') as $column) {
+ if (stripos($showCreate, $column) === false) {
+ return $this->addColumn($column);
}
}
}
return false;
}
+ public function commitNewEntries() {
+ $sql = '
+CREATE TEMP TABLE `tmp` AS SELECT id, guid, title, author, content, link, date, `lastSeen`, hash, is_read, is_favorite, id_feed, tags FROM `' . $this->prefix . 'entrytmp` ORDER BY date;
+INSERT OR IGNORE INTO `' . $this->prefix . 'entry` (id, guid, title, author, content, link, date, `lastSeen`, hash, is_read, is_favorite, id_feed, tags)
+ SELECT rowid + (SELECT MAX(id) - COUNT(*) FROM `tmp`) AS id, guid, title, author, content, link, date, `lastSeen`, hash, is_read, is_favorite, id_feed, tags FROM `tmp` ORDER BY date;
+DELETE FROM `' . $this->prefix . 'entrytmp` WHERE id <= (SELECT MAX(id) FROM `tmp`);
+DROP TABLE `tmp`;';
+ $hadTransaction = $this->bd->inTransaction();
+ if (!$hadTransaction) {
+ $this->bd->beginTransaction();
+ }
+ $result = $this->bd->exec($sql) !== false;
+ if (!$hadTransaction) {
+ $this->bd->commit();
+ }
+ return $result;
+ }
+
protected function sqlConcat($s1, $s2) {
return $s1 . '||' . $s2;
}
diff --git a/app/Models/Factory.php b/app/Models/Factory.php
index 6502c38b7..dfccc883e 100644
--- a/app/Models/Factory.php
+++ b/app/Models/Factory.php
@@ -3,14 +3,7 @@
class FreshRSS_Factory {
public static function createFeedDao($username = null) {
- $conf = Minz_Configuration::get('system');
- switch ($conf->db['type']) {
- case 'sqlite':
- case 'pgsql':
- return new FreshRSS_FeedDAOSQLite($username);
- default:
- return new FreshRSS_FeedDAO($username);
- }
+ return new FreshRSS_FeedDAO($username);
}
public static function createEntryDao($username = null) {
diff --git a/app/Models/Feed.php b/app/Models/Feed.php
index 7a9cf8612..52d49db6e 100644
--- a/app/Models/Feed.php
+++ b/app/Models/Feed.php
@@ -318,7 +318,7 @@ class FreshRSS_Feed extends Minz_Model {
$elinks = array();
foreach ($item->get_enclosures() as $enclosure) {
$elink = $enclosure->get_link();
- if (empty($elinks[$elink])) {
+ if ($elink != '' && empty($elinks[$elink])) {
$elinks[$elink] = '1';
$mime = strtolower($enclosure->get_type());
if (strpos($mime, 'image/') === 0) {
@@ -327,6 +327,8 @@ class FreshRSS_Feed extends Minz_Model {
$content .= '<p class="enclosure"><audio preload="none" src="' . $elink . '" controls="controls"></audio> <a download="" href="' . $elink . '">💾</a></p>';
} elseif (strpos($mime, 'video/') === 0) {
$content .= '<p class="enclosure"><video preload="none" src="' . $elink . '" controls="controls"></video> <a download="" href="' . $elink . '">💾</a></p>';
+ } elseif (strpos($mime, 'application/') === 0 || strpos($mime, 'text/') === 0) {
+ $content .= '<p class="enclosure"><a download="" href="' . $elink . '">💾</a></p>';
} else {
unset($elinks[$elink]);
}
@@ -335,7 +337,7 @@ class FreshRSS_Feed extends Minz_Model {
$entry = new FreshRSS_Entry(
$this->id(),
- $item->get_id(),
+ $item->get_id(false, false),
$title === null ? '' : $title,
$author === null ? '' : html_only_entity_decode($author->name),
$content === null ? '' : $content,
@@ -429,7 +431,7 @@ class FreshRSS_Feed extends Minz_Model {
}
} else {
@mkdir($path, 0777, true);
- $key = sha1($path . FreshRSS_Context::$system_conf->salt . uniqid(mt_rand(), true));
+ $key = sha1($path . FreshRSS_Context::$system_conf->salt);
$hubJson = array(
'hub' => $this->hubUrl,
'key' => $key,
@@ -451,15 +453,16 @@ class FreshRSS_Feed extends Minz_Model {
//Parameter true to subscribe, false to unsubscribe.
function pubSubHubbubSubscribe($state) {
- if (FreshRSS_Context::$system_conf->base_url && $this->hubUrl && $this->selfUrl) {
- $hubFilename = PSHB_PATH . '/feeds/' . base64url_encode($this->selfUrl) . '/!hub.json';
+ $url = $this->selfUrl ? $this->selfUrl : $this->url;
+ if (FreshRSS_Context::$system_conf->base_url && $url) {
+ $hubFilename = PSHB_PATH . '/feeds/' . base64url_encode($url) . '/!hub.json';
$hubFile = @file_get_contents($hubFilename);
if ($hubFile === false) {
Minz_Log::warning('JSON not found for PubSubHubbub: ' . $this->url);
return false;
}
$hubJson = json_decode($hubFile, true);
- if (!$hubJson || empty($hubJson['key']) || !ctype_xdigit($hubJson['key'])) {
+ if (!$hubJson || empty($hubJson['key']) || !ctype_xdigit($hubJson['key']) || empty($hubJson['hub'])) {
Minz_Log::warning('Invalid JSON for PubSubHubbub: ' . $this->url);
return false;
}
@@ -474,13 +477,13 @@ class FreshRSS_Feed extends Minz_Model {
}
$ch = curl_init();
curl_setopt_array($ch, array(
- CURLOPT_URL => $this->hubUrl,
+ CURLOPT_URL => $hubJson['hub'],
CURLOPT_FOLLOWLOCATION => true,
CURLOPT_RETURNTRANSFER => true,
CURLOPT_USERAGENT => 'FreshRSS/' . FRESHRSS_VERSION . ' (' . PHP_OS . '; ' . FRESHRSS_WEBSITE . ')',
CURLOPT_POSTFIELDS => 'hub.verify=sync'
. '&hub.mode=' . ($state ? 'subscribe' : 'unsubscribe')
- . '&hub.topic=' . urlencode($this->selfUrl)
+ . '&hub.topic=' . urlencode($url)
. '&hub.callback=' . urlencode($callbackUrl)
)
);
@@ -488,7 +491,7 @@ class FreshRSS_Feed extends Minz_Model {
$info = curl_getinfo($ch);
file_put_contents(USERS_PATH . '/_/log_pshb.txt', date('c') . "\t" .
- 'PubSubHubbub ' . ($state ? 'subscribe' : 'unsubscribe') . ' to ' . $this->selfUrl .
+ 'PubSubHubbub ' . ($state ? 'subscribe' : 'unsubscribe') . ' to ' . $url .
' with callback ' . $callbackUrl . ': ' . $info['http_code'] . ' ' . $response . "\n", FILE_APPEND);
if (substr($info['http_code'], 0, 1) == '2') {
diff --git a/app/Models/FeedDAO.php b/app/Models/FeedDAO.php
index 0168aebd9..d278122e3 100644
--- a/app/Models/FeedDAO.php
+++ b/app/Models/FeedDAO.php
@@ -92,29 +92,15 @@ class FreshRSS_FeedDAO extends Minz_ModelPdo implements FreshRSS_Searchable {
}
}
- public function updateLastUpdate($id, $inError = false, $updateCache = true, $mtime = 0) {
- if ($updateCache) {
- $sql = 'UPDATE `' . $this->prefix . 'feed` ' //2 sub-requests with FOREIGN KEY(e.id_feed), INDEX(e.is_read) faster than 1 request with GROUP BY or CASE
- . 'SET `cache_nbEntries`=(SELECT COUNT(e1.id) FROM `' . $this->prefix . 'entry` e1 WHERE e1.id_feed=`' . $this->prefix . 'feed`.id),'
- . '`cache_nbUnreads`=(SELECT COUNT(e2.id) FROM `' . $this->prefix . 'entry` e2 WHERE e2.id_feed=`' . $this->prefix . 'feed`.id AND e2.is_read=0),'
- . '`lastUpdate`=?, error=? '
- . 'WHERE id=?';
- } else {
- $sql = 'UPDATE `' . $this->prefix . 'feed` '
- . 'SET `lastUpdate`=?, error=? '
- . 'WHERE id=?';
- }
-
- if ($mtime <= 0) {
- $mtime = time();
- }
-
+ public function updateLastUpdate($id, $inError = false, $mtime = 0) { //See also updateCachedValue()
+ $sql = 'UPDATE `' . $this->prefix . 'feed` '
+ . 'SET `lastUpdate`=?, error=? '
+ . 'WHERE id=?';
$values = array(
- $mtime,
+ $mtime <= 0 ? time() : $mtime,
$inError ? 1 : 0,
$id,
);
-
$stm = $this->bd->prepare($sql);
if ($stm && $stm->execute($values)) {
@@ -294,18 +280,28 @@ class FreshRSS_FeedDAO extends Minz_ModelPdo implements FreshRSS_Searchable {
return $res[0]['count'];
}
- public function updateCachedValues() { //For one single feed, call updateLastUpdate($id)
- $sql = 'UPDATE `' . $this->prefix . 'feed` f '
- . 'INNER JOIN ('
- . 'SELECT e.id_feed, '
- . 'COUNT(CASE WHEN e.is_read = 0 THEN 1 END) AS nbUnreads, '
- . 'COUNT(e.id) AS nbEntries '
- . 'FROM `' . $this->prefix . 'entry` e '
- . 'GROUP BY e.id_feed'
- . ') x ON x.id_feed=f.id '
- . 'SET f.`cache_nbEntries`=x.nbEntries, f.`cache_nbUnreads`=x.nbUnreads';
+ public function updateCachedValue($id) { //For multiple feeds, call updateCachedValues()
+ $sql = 'UPDATE `' . $this->prefix . 'feed` ' //2 sub-requests with FOREIGN KEY(e.id_feed), INDEX(e.is_read) faster than 1 request with GROUP BY or CASE
+ . 'SET `cache_nbEntries`=(SELECT COUNT(e1.id) FROM `' . $this->prefix . 'entry` e1 WHERE e1.id_feed=`' . $this->prefix . 'feed`.id),'
+ . '`cache_nbUnreads`=(SELECT COUNT(e2.id) FROM `' . $this->prefix . 'entry` e2 WHERE e2.id_feed=`' . $this->prefix . 'feed`.id AND e2.is_read=0) '
+ . 'WHERE id=?';
+ $values = array($id);
$stm = $this->bd->prepare($sql);
+ if ($stm && $stm->execute($values)) {
+ return $stm->rowCount();
+ } else {
+ $info = $stm == null ? array(2 => 'syntax error') : $stm->errorInfo();
+ Minz_Log::error('SQL error updateCachedValue: ' . $info[2]);
+ return false;
+ }
+ }
+
+ public function updateCachedValues() { //For one single feed, call updateCachedValue($id)
+ $sql = 'UPDATE `' . $this->prefix . 'feed` '
+ . 'SET `cache_nbEntries`=(SELECT COUNT(e1.id) FROM `' . $this->prefix . 'entry` e1 WHERE e1.id_feed=`' . $this->prefix . 'feed`.id),'
+ . '`cache_nbUnreads`=(SELECT COUNT(e2.id) FROM `' . $this->prefix . 'entry` e2 WHERE e2.id_feed=`' . $this->prefix . 'feed`.id AND e2.is_read=0)';
+ $stm = $this->bd->prepare($sql);
if ($stm && $stm->execute()) {
return $stm->rowCount();
} else {
@@ -343,7 +339,7 @@ class FreshRSS_FeedDAO extends Minz_ModelPdo implements FreshRSS_Searchable {
return $affected;
}
- public function cleanOldEntries($id, $date_min, $keep = 15) { //Remember to call updateLastUpdate($id) or updateCachedValues() just after
+ public function cleanOldEntries($id, $date_min, $keep = 15) { //Remember to call updateCachedValue($id) or updateCachedValues() just after
$sql = 'DELETE FROM `' . $this->prefix . 'entry` '
. 'WHERE id_feed=:id_feed AND id<=:id_max '
. 'AND is_favorite=0 ' //Do not remove favourites
diff --git a/app/Models/FeedDAOSQLite.php b/app/Models/FeedDAOSQLite.php
deleted file mode 100644
index 440ae74da..000000000
--- a/app/Models/FeedDAOSQLite.php
+++ /dev/null
@@ -1,19 +0,0 @@
-<?php
-
-class FreshRSS_FeedDAOSQLite extends FreshRSS_FeedDAO {
-
- public function updateCachedValues() { //For one single feed, call updateLastUpdate($id)
- $sql = 'UPDATE `' . $this->prefix . 'feed` '
- . 'SET `cache_nbEntries`=(SELECT COUNT(e1.id) FROM `' . $this->prefix . 'entry` e1 WHERE e1.id_feed=`' . $this->prefix . 'feed`.id),'
- . '`cache_nbUnreads`=(SELECT COUNT(e2.id) FROM `' . $this->prefix . 'entry` e2 WHERE e2.id_feed=`' . $this->prefix . 'feed`.id AND e2.is_read=0)';
- $stm = $this->bd->prepare($sql);
- if ($stm && $stm->execute()) {
- return $stm->rowCount();
- } else {
- $info = $stm == null ? array(2 => 'syntax error') : $stm->errorInfo();
- Minz_Log::error('SQL error updateCachedValues: ' . $info[2]);
- return false;
- }
- }
-
-}
diff --git a/app/Models/Search.php b/app/Models/Search.php
index 575a9a2cb..5cc7f8e8d 100644
--- a/app/Models/Search.php
+++ b/app/Models/Search.php
@@ -23,18 +23,35 @@ class FreshRSS_Search {
private $tags;
private $search;
+ private $not_intitle;
+ private $not_inurl;
+ private $not_author;
+ private $not_tags;
+ private $not_search;
+
public function __construct($input) {
- if (strcmp($input, '') == 0) {
+ if ($input == '') {
return;
}
$this->raw_input = $input;
+
+ $input = preg_replace('/:&quot;(.*?)&quot;/', ':"\1"', $input);
+
+ $input = $this->parseNotIntitleSearch($input);
+ $input = $this->parseNotAuthorSearch($input);
+ $input = $this->parseNotInurlSearch($input);
+ $input = $this->parseNotTagsSeach($input);
+
+ $input = $this->parsePubdateSearch($input);
+ $input = $this->parseDateSearch($input);
+
$input = $this->parseIntitleSearch($input);
$input = $this->parseAuthorSearch($input);
$input = $this->parseInurlSearch($input);
- $input = $this->parsePubdateSearch($input);
- $input = $this->parseDateSearch($input);
$input = $this->parseTagsSeach($input);
- $this->parseSearch($input);
+
+ $input = $this->parseNotSearch($input);
+ $input = $this->parseSearch($input);
}
public function __toString() {
@@ -48,6 +65,9 @@ class FreshRSS_Search {
public function getIntitle() {
return $this->intitle;
}
+ public function getNotIntitle() {
+ return $this->not_intitle;
+ }
public function getMinDate() {
return $this->min_date;
@@ -68,18 +88,34 @@ class FreshRSS_Search {
public function getInurl() {
return $this->inurl;
}
+ public function getNotInurl() {
+ return $this->not_inurl;
+ }
public function getAuthor() {
return $this->author;
}
+ public function getNotAuthor() {
+ return $this->not_author;
+ }
public function getTags() {
return $this->tags;
}
+ public function getNotTags() {
+ return $this->not_tags;
+ }
public function getSearch() {
return $this->search;
}
+ public function getNotSearch() {
+ return $this->not_search;
+ }
+
+ private static function removeEmptyValues($anArray) {
+ return is_array($anArray) ? array_filter($anArray, function($value) { return $value !== ''; }) : array();
+ }
/**
* Parse the search string to find intitle keyword and the search related
@@ -90,14 +126,28 @@ class FreshRSS_Search {
* @return string
*/
private function parseIntitleSearch($input) {
- if (preg_match('/intitle:(?P<delim>[\'"])(?P<search>.*)(?P=delim)/U', $input, $matches)) {
+ if (preg_match_all('/\bintitle:(?P<delim>[\'"])(?P<search>.*)(?P=delim)/U', $input, $matches)) {
$this->intitle = $matches['search'];
- return str_replace($matches[0], '', $input);
+ $input = str_replace($matches[0], '', $input);
}
- if (preg_match('/intitle:(?P<search>\w*)/', $input, $matches)) {
- $this->intitle = $matches['search'];
- return str_replace($matches[0], '', $input);
+ if (preg_match_all('/\bintitle:(?P<search>\w*)/', $input, $matches)) {
+ $this->intitle = array_merge($this->intitle ? $this->intitle : array(), $matches['search']);
+ $input = str_replace($matches[0], '', $input);
+ }
+ $this->intitle = self::removeEmptyValues($this->intitle);
+ return $input;
+ }
+
+ private function parseNotIntitleSearch($input) {
+ if (preg_match_all('/[!-]intitle:(?P<delim>[\'"])(?P<search>.*)(?P=delim)/U', $input, $matches)) {
+ $this->not_intitle = $matches['search'];
+ $input = str_replace($matches[0], '', $input);
+ }
+ if (preg_match_all('/[!-]intitle:(?P<search>\w*)/', $input, $matches)) {
+ $this->not_intitle = array_merge($this->not_intitle ? $this->not_intitle : array(), $matches['search']);
+ $input = str_replace($matches[0], '', $input);
}
+ $this->not_intitle = self::removeEmptyValues($this->not_intitle);
return $input;
}
@@ -112,30 +162,54 @@ class FreshRSS_Search {
* @return string
*/
private function parseAuthorSearch($input) {
- if (preg_match('/author:(?P<delim>[\'"])(?P<search>.*)(?P=delim)/U', $input, $matches)) {
+ if (preg_match_all('/\bauthor:(?P<delim>[\'"])(?P<search>.*)(?P=delim)/U', $input, $matches)) {
$this->author = $matches['search'];
- return str_replace($matches[0], '', $input);
+ $input = str_replace($matches[0], '', $input);
}
- if (preg_match('/author:(?P<search>\w*)/', $input, $matches)) {
- $this->author = $matches['search'];
- return str_replace($matches[0], '', $input);
+ if (preg_match_all('/\bauthor:(?P<search>\w*)/', $input, $matches)) {
+ $this->author = array_merge($this->author ? $this->author : array(), $matches['search']);
+ $input = str_replace($matches[0], '', $input);
}
+ $this->author = self::removeEmptyValues($this->author);
+ return $input;
+ }
+
+ private function parseNotAuthorSearch($input) {
+ if (preg_match_all('/[!-]author:(?P<delim>[\'"])(?P<search>.*)(?P=delim)/U', $input, $matches)) {
+ $this->not_author = $matches['search'];
+ $input = str_replace($matches[0], '', $input);
+ }
+ if (preg_match_all('/[!-]author:(?P<search>\w*)/', $input, $matches)) {
+ $this->not_author = array_merge($this->not_author ? $this->not_author : array(), $matches['search']);
+ $input = str_replace($matches[0], '', $input);
+ }
+ $this->not_author = self::removeEmptyValues($this->not_author);
return $input;
}
/**
* Parse the search string to find inurl keyword and the search related
* to it.
- * The search is the first word following the keyword except.
+ * The search is the first word following the keyword.
*
* @param string $input
* @return string
*/
private function parseInurlSearch($input) {
- if (preg_match('/inurl:(?P<search>[^\s]*)/', $input, $matches)) {
+ if (preg_match_all('/\binurl:(?P<search>[^\s]*)/', $input, $matches)) {
$this->inurl = $matches['search'];
- return str_replace($matches[0], '', $input);
+ $input = str_replace($matches[0], '', $input);
+ }
+ $this->inurl = self::removeEmptyValues($this->inurl);
+ return $input;
+ }
+
+ private function parseNotInurlSearch($input) {
+ if (preg_match_all('/[!-]inurl:(?P<search>[^\s]*)/', $input, $matches)) {
+ $this->not_inurl = $matches['search'];
+ $input = str_replace($matches[0], '', $input);
}
+ $this->not_inurl = self::removeEmptyValues($this->not_inurl);
return $input;
}
@@ -148,9 +222,12 @@ class FreshRSS_Search {
* @return string
*/
private function parseDateSearch($input) {
- if (preg_match('/date:(?P<search>[^\s]*)/', $input, $matches)) {
- list($this->min_date, $this->max_date) = parseDateInterval($matches['search']);
- return str_replace($matches[0], '', $input);
+ if (preg_match_all('/\bdate:(?P<search>[^\s]*)/', $input, $matches)) {
+ $input = str_replace($matches[0], '', $input);
+ $dates = self::removeEmptyValues($matches['search']);
+ if (!empty($dates[0])) {
+ list($this->min_date, $this->max_date) = parseDateInterval($dates[0]);
+ }
}
return $input;
}
@@ -164,9 +241,12 @@ class FreshRSS_Search {
* @return string
*/
private function parsePubdateSearch($input) {
- if (preg_match('/pubdate:(?P<search>[^\s]*)/', $input, $matches)) {
- list($this->min_pubdate, $this->max_pubdate) = parseDateInterval($matches['search']);
- return str_replace($matches[0], '', $input);
+ if (preg_match_all('/\bpubdate:(?P<search>[^\s]*)/', $input, $matches)) {
+ $input = str_replace($matches[0], '', $input);
+ $dates = self::removeEmptyValues($matches['search']);
+ if (!empty($dates[0])) {
+ list($this->min_pubdate, $this->max_pubdate) = parseDateInterval($dates[0]);
+ }
}
return $input;
}
@@ -182,8 +262,18 @@ class FreshRSS_Search {
private function parseTagsSeach($input) {
if (preg_match_all('/#(?P<search>[^\s]+)/', $input, $matches)) {
$this->tags = $matches['search'];
- return str_replace($matches[0], '', $input);
+ $input = str_replace($matches[0], '', $input);
}
+ $this->tags = self::removeEmptyValues($this->tags);
+ return $input;
+ }
+
+ private function parseNotTagsSeach($input) {
+ if (preg_match_all('/[!-]#(?P<search>[^\s]+)/', $input, $matches)) {
+ $this->not_tags = $matches['search'];
+ $input = str_replace($matches[0], '', $input);
+ }
+ $this->not_tags = self::removeEmptyValues($this->not_tags);
return $input;
}
@@ -196,16 +286,16 @@ class FreshRSS_Search {
* @return string
*/
private function parseSearch($input) {
- $input = $this->cleanSearch($input);
- if (strcmp($input, '') == 0) {
+ $input = self::cleanSearch($input);
+ if ($input == '') {
return;
}
if (preg_match_all('/(?P<delim>[\'"])(?P<search>.*)(?P=delim)/U', $input, $matches)) {
$this->search = $matches['search'];
$input = str_replace($matches[0], '', $input);
}
- $input = $this->cleanSearch($input);
- if (strcmp($input, '') == 0) {
+ $input = self::cleanSearch($input);
+ if ($input == '') {
return;
}
if (is_array($this->search)) {
@@ -215,13 +305,33 @@ class FreshRSS_Search {
}
}
+ private function parseNotSearch($input) {
+ $input = self::cleanSearch($input);
+ if ($input == '') {
+ return;
+ }
+ if (preg_match_all('/[!-](?P<delim>[\'"])(?P<search>.*)(?P=delim)/U', $input, $matches)) {
+ $this->not_search = $matches['search'];
+ $input = str_replace($matches[0], '', $input);
+ }
+ if ($input == '') {
+ return;
+ }
+ if (preg_match_all('/[!-](?P<search>[^\s]+)/', $input, $matches)) {
+ $this->not_search = array_merge(is_array($this->not_search) ? $this->not_search : array(), $matches['search']);
+ $input = str_replace($matches[0], '', $input);
+ }
+ $this->not_search = self::removeEmptyValues($this->not_search);
+ return $input;
+ }
+
/**
* Remove all unnecessary spaces in the search
*
* @param string $input
* @return string
*/
- private function cleanSearch($input) {
+ private static function cleanSearch($input) {
$input = preg_replace('/\s+/', ' ', $input);
return trim($input);
}
diff --git a/app/Models/UserDAO.php b/app/Models/UserDAO.php
index a60caf395..310c7c096 100644
--- a/app/Models/UserDAO.php
+++ b/app/Models/UserDAO.php
@@ -14,21 +14,23 @@ class FreshRSS_UserDAO extends Minz_ModelPdo {
$ok = false;
$bd_prefix_user = $db['prefix'] . $username . '_';
if (defined('SQL_CREATE_TABLES')) { //E.g. MySQL
- $sql = sprintf(SQL_CREATE_TABLES, $bd_prefix_user, _t('gen.short.default_category'));
+ $sql = sprintf(SQL_CREATE_TABLES . SQL_CREATE_TABLE_ENTRYTMP, $bd_prefix_user, _t('gen.short.default_category'));
$stm = $userPDO->bd->prepare($sql);
$ok = $stm && $stm->execute();
} else { //E.g. SQLite
global $SQL_CREATE_TABLES;
+ global $SQL_CREATE_TABLE_ENTRYTMP;
if (is_array($SQL_CREATE_TABLES)) {
- $ok = true;
- foreach ($SQL_CREATE_TABLES as $instruction) {
+ $instructions = array_merge($SQL_CREATE_TABLES, $SQL_CREATE_TABLE_ENTRYTMP);
+ $ok = !empty($instructions);
+ foreach ($instructions as $instruction) {
$sql = sprintf($instruction, $bd_prefix_user, _t('gen.short.default_category'));
$stm = $userPDO->bd->prepare($sql);
$ok &= ($stm && $stm->execute());
}
}
}
- if ($insertDefaultFeeds) {
+ if ($ok && $insertDefaultFeeds) {
if (defined('SQL_INSERT_FEEDS')) { //E.g. MySQL
$sql = sprintf(SQL_INSERT_FEEDS, $bd_prefix_user);
$stm = $userPDO->bd->prepare($sql);
diff --git a/app/SQL/install.sql.mysql.php b/app/SQL/install.sql.mysql.php
index a454829d5..09defd452 100644
--- a/app/SQL/install.sql.mysql.php
+++ b/app/SQL/install.sql.mysql.php
@@ -55,18 +55,44 @@ CREATE TABLE IF NOT EXISTS `%1$sentry` (
INDEX (`is_favorite`), -- v0.7
INDEX (`is_read`), -- v0.7
INDEX `entry_lastSeen_index` (`lastSeen`) -- v1.1.1
+ -- INDEX `entry_feed_read_index` (`id_feed`,`is_read`) -- v1.7 Located futher down
) DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci
ENGINE = INNODB;
INSERT IGNORE INTO `%1$scategory` (id, name) VALUES(1, "%2$s");
');
+define('SQL_CREATE_TABLE_ENTRYTMP', '
+CREATE TABLE IF NOT EXISTS `%1$sentrytmp` ( -- v1.7
+ `id` bigint NOT NULL,
+ `guid` varchar(760) CHARACTER SET latin1 NOT NULL,
+ `title` varchar(255) NOT NULL,
+ `author` varchar(255),
+ `content_bin` blob,
+ `link` varchar(1023) CHARACTER SET latin1 NOT NULL,
+ `date` int(11),
+ `lastSeen` INT(11) DEFAULT 0,
+ `hash` BINARY(16),
+ `is_read` boolean NOT NULL DEFAULT 0,
+ `is_favorite` boolean NOT NULL DEFAULT 0,
+ `id_feed` SMALLINT,
+ `tags` varchar(1023),
+ PRIMARY KEY (`id`),
+ FOREIGN KEY (`id_feed`) REFERENCES `%1$sfeed`(`id`) ON DELETE CASCADE ON UPDATE CASCADE,
+ UNIQUE KEY (`id_feed`,`guid`),
+ INDEX (`date`)
+) DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci
+ENGINE = INNODB;
+
+CREATE INDEX `entry_feed_read_index` ON `%1$sentry`(`id_feed`,`is_read`); -- v1.7 Located here to be auto-added
+');
+
define('SQL_INSERT_FEEDS', '
INSERT IGNORE INTO `%1$sfeed` (url, category, name, website, description, ttl) VALUES("http://freshrss.org/feeds/all.atom.xml", 1, "FreshRSS.org", "http://freshrss.org/", "FreshRSS, a free, self-hostable aggregator…", 86400);
INSERT IGNORE INTO `%1$sfeed` (url, category, name, website, description, ttl) VALUES("https://github.com/FreshRSS/FreshRSS/releases.atom", 1, "FreshRSS @ GitHub", "https://github.com/FreshRSS/FreshRSS/", "FreshRSS releases @ GitHub", 86400);
');
-define('SQL_DROP_TABLES', 'DROP TABLE IF EXISTS `%1$sentry`, `%1$sfeed`, `%1$scategory`');
+define('SQL_DROP_TABLES', 'DROP TABLE IF EXISTS `%1$sentrytmp`, `%1$sentry`, `%1$sfeed`, `%1$scategory`');
define('SQL_UPDATE_UTF8MB4', '
ALTER DATABASE `%2$s` CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
diff --git a/app/SQL/install.sql.pgsql.php b/app/SQL/install.sql.pgsql.php
index 9f4240b98..4cfeb2517 100644
--- a/app/SQL/install.sql.pgsql.php
+++ b/app/SQL/install.sql.pgsql.php
@@ -32,7 +32,7 @@ $SQL_CREATE_TABLES = array(
'CREATE TABLE IF NOT EXISTS "%1$sentry" (
"id" BIGINT NOT NULL PRIMARY KEY,
- "guid" VARCHAR(760) UNIQUE NOT NULL,
+ "guid" VARCHAR(760) NOT NULL,
"title" VARCHAR(255) NOT NULL,
"author" VARCHAR(255),
"content" TEXT,
@@ -54,10 +54,34 @@ $SQL_CREATE_TABLES = array(
'INSERT INTO "%1$scategory" (name) SELECT \'%2$s\' WHERE NOT EXISTS (SELECT id FROM "%1$scategory" WHERE id = 1);',
);
+global $SQL_CREATE_TABLE_ENTRYTMP;
+$SQL_CREATE_TABLE_ENTRYTMP = array(
+'CREATE TABLE IF NOT EXISTS "%1$sentrytmp" ( -- v1.7
+ "id" BIGINT NOT NULL PRIMARY KEY,
+ "guid" VARCHAR(760) NOT NULL,
+ "title" VARCHAR(255) NOT NULL,
+ "author" VARCHAR(255),
+ "content" TEXT,
+ "link" VARCHAR(1023) NOT NULL,
+ "date" INT,
+ "lastSeen" INT DEFAULT 0,
+ "hash" BYTEA,
+ "is_read" SMALLINT NOT NULL DEFAULT 0,
+ "is_favorite" SMALLINT NOT NULL DEFAULT 0,
+ "id_feed" SMALLINT,
+ "tags" VARCHAR(1023),
+ FOREIGN KEY ("id_feed") REFERENCES "%1$sfeed" ("id") ON DELETE CASCADE ON UPDATE CASCADE,
+ UNIQUE ("id_feed","guid")
+);',
+'CREATE INDEX %1$sentrytmp_date_index ON "%1$sentrytmp" ("date");',
+
+'CREATE INDEX %1$sentry_feed_read_index ON "%1$sentry" ("id_feed","is_read");', //v1.7
+);
+
global $SQL_INSERT_FEEDS;
$SQL_INSERT_FEEDS = array(
'INSERT INTO "%1$sfeed" (url, category, name, website, description, ttl) SELECT \'http://freshrss.org/feeds/all.atom.xml\', 1, \'FreshRSS.org\', \'http://freshrss.org/\', \'FreshRSS, a free, self-hostable aggregator…\', 86400 WHERE NOT EXISTS (SELECT id FROM "%1$sfeed" WHERE url = \'http://freshrss.org/feeds/all.atom.xml\');',
'INSERT INTO "%1$sfeed" (url, category, name, website, description, ttl) SELECT \'https://github.com/FreshRSS/FreshRSS/releases.atom\', 1, \'FreshRSS @ GitHub\', \'https://github.com/FreshRSS/FreshRSS/\', \'FreshRSS releases @ GitHub\', 86400 WHERE NOT EXISTS (SELECT id FROM "%1$sfeed" WHERE url = \'https://github.com/FreshRSS/FreshRSS/releases.atom\');',
);
-define('SQL_DROP_TABLES', 'DROP TABLE IF EXISTS "%1$sentry", "%1$sfeed", "%1$scategory"');
+define('SQL_DROP_TABLES', 'DROP TABLE IF EXISTS "%1$sentrytmp", "%1$sentry", "%1$sfeed", "%1$scategory"');
diff --git a/app/SQL/install.sql.sqlite.php b/app/SQL/install.sql.sqlite.php
index 68d93ba92..c4e4af006 100644
--- a/app/SQL/install.sql.sqlite.php
+++ b/app/SQL/install.sql.sqlite.php
@@ -26,7 +26,6 @@ $SQL_CREATE_TABLES = array(
FOREIGN KEY (`category`) REFERENCES `category`(`id`) ON DELETE SET NULL ON UPDATE CASCADE,
UNIQUE (`url`)
);',
-
'CREATE INDEX IF NOT EXISTS feed_name_index ON `feed`(`name`);',
'CREATE INDEX IF NOT EXISTS feed_priority_index ON `feed`(`priority`);',
'CREATE INDEX IF NOT EXISTS feed_keep_history_index ON `feed`(`keep_history`);',
@@ -49,7 +48,6 @@ $SQL_CREATE_TABLES = array(
FOREIGN KEY (`id_feed`) REFERENCES `feed`(`id`) ON DELETE CASCADE ON UPDATE CASCADE,
UNIQUE (`id_feed`,`guid`)
);',
-
'CREATE INDEX IF NOT EXISTS entry_is_favorite_index ON `entry`(`is_favorite`);',
'CREATE INDEX IF NOT EXISTS entry_is_read_index ON `entry`(`is_read`);',
'CREATE INDEX IF NOT EXISTS entry_lastSeen_index ON `entry`(`lastSeen`);', //v1.1.1
@@ -57,10 +55,35 @@ $SQL_CREATE_TABLES = array(
'INSERT OR IGNORE INTO `category` (id, name) VALUES(1, "%2$s");',
);
+global $SQL_CREATE_TABLE_ENTRYTMP;
+$SQL_CREATE_TABLE_ENTRYTMP = array(
+'CREATE TABLE IF NOT EXISTS `entrytmp` ( -- v1.7
+ `id` bigint NOT NULL,
+ `guid` varchar(760) NOT NULL,
+ `title` varchar(255) NOT NULL,
+ `author` varchar(255),
+ `content` text,
+ `link` varchar(1023) NOT NULL,
+ `date` int(11),
+ `lastSeen` INT(11) DEFAULT 0,
+ `hash` BINARY(16),
+ `is_read` boolean NOT NULL DEFAULT 0,
+ `is_favorite` boolean NOT NULL DEFAULT 0,
+ `id_feed` SMALLINT,
+ `tags` varchar(1023),
+ PRIMARY KEY (`id`),
+ FOREIGN KEY (`id_feed`) REFERENCES `feed`(`id`) ON DELETE CASCADE ON UPDATE CASCADE,
+ UNIQUE (`id_feed`,`guid`)
+);',
+'CREATE INDEX IF NOT EXISTS entrytmp_date_index ON `entrytmp`(`date`);',
+
+'CREATE INDEX IF NOT EXISTS `entry_feed_read_index` ON `entry`(`id_feed`,`is_read`);', //v1.7
+);
+
global $SQL_INSERT_FEEDS;
$SQL_INSERT_FEEDS = array(
'INSERT OR IGNORE INTO `feed` (url, category, name, website, description, ttl) VALUES("http://freshrss.org/feeds/all.atom.xml", 1, "FreshRSS.org", "http://freshrss.org/", "FreshRSS, a free, self-hostable aggregator…", 86400);',
'INSERT OR IGNORE INTO `feed` (url, category, name, website, description, ttl) VALUES("https://github.com/FreshRSS/FreshRSS/releases.atom", 1, "FreshRSS releases", "https://github.com/FreshRSS/FreshRSS/", "FreshRSS releases @ GitHub", 86400);',
);
-define('SQL_DROP_TABLES', 'DROP TABLE IF EXISTS entry, feed, category');
+define('SQL_DROP_TABLES', 'DROP TABLE IF EXISTS `entrytmp`, `entry`, `feed`, `category`');
diff --git a/app/i18n/cz/conf.php b/app/i18n/cz/conf.php
index ec25f988c..fd414e246 100644
--- a/app/i18n/cz/conf.php
+++ b/app/i18n/cz/conf.php
@@ -93,6 +93,7 @@ return array(
'display_categories_unfolded' => 'Ve výchozím stavu zobrazovat kategorie zavřené',
'hide_read_feeds' => 'Schovat kategorie a kanály s nulovým počtem nepřečtených článků (nefunguje s nastavením “Zobrazit všechny články”)',
'img_with_lazyload' => 'Použít "lazy load" mód pro načítaní obrázků',
+ 'sides_close_article' => 'Clicking outside of article text area closes the article', //TODO
'jump_next' => 'skočit na další nepřečtený (kanál nebo kategorii)',
'number_divided_when_reader' => 'V režimu “Čtení” děleno dvěma.',
'read' => array(
diff --git a/app/i18n/cz/feedback.php b/app/i18n/cz/feedback.php
index f2bd87c77..f7b8d8c73 100644
--- a/app/i18n/cz/feedback.php
+++ b/app/i18n/cz/feedback.php
@@ -87,7 +87,7 @@ return array(
'update' => array(
'can_apply' => 'FreshRSS bude nyní upgradováno na <strong>verzi %s</strong>.',
'error' => 'Během upgrade došlo k chybě: %s',
- 'file_is_nok' => 'Zkontrolujte oprávnění adresáře <em>%s</em>. HTTP server musí mít do tohoto adresáře práva zápisu',
+ 'file_is_nok' => '<strong>Verzi %s</strong>. Zkontrolujte oprávnění adresáře <em>%s</em>. HTTP server musí mít do tohoto adresáře práva zápisu',
'finished' => 'Upgrade hotov!',
'none' => 'Novější verze není k dispozici',
'server_not_found' => 'Nelze nalézt server s instalačním souborem. [%s]',
diff --git a/app/i18n/de/conf.php b/app/i18n/de/conf.php
index 7c57d5655..bfc24d2d2 100644
--- a/app/i18n/de/conf.php
+++ b/app/i18n/de/conf.php
@@ -93,6 +93,7 @@ return array(
'display_categories_unfolded' => 'Kategorien standardmäßig eingeklappt zeigen',
'hide_read_feeds' => 'Kategorien & Feeds ohne ungelesene Artikel verstecken (funktioniert nicht mit der Einstellung „Alle Artikel zeigen“)',
'img_with_lazyload' => 'Verwende die "träges Laden"-Methode zum Laden von Bildern',
+ 'sides_close_article' => 'Clicking outside of article text area closes the article', //TODO
'jump_next' => 'springe zum nächsten ungelesenen Geschwisterelement (Feed oder Kategorie)',
'number_divided_when_reader' => 'Geteilt durch 2 in der Lese-Ansicht.',
'read' => array(
diff --git a/app/i18n/de/feedback.php b/app/i18n/de/feedback.php
index 195083b36..e2e9a71ba 100644
--- a/app/i18n/de/feedback.php
+++ b/app/i18n/de/feedback.php
@@ -87,7 +87,7 @@ return array(
'update' => array(
'can_apply' => 'FreshRSS wird nun auf die <strong>Version %s</strong> aktualisiert.',
'error' => 'Der Aktualisierungsvorgang stieß auf einen Fehler: %s',
- 'file_is_nok' => 'Überprüfen Sie die Berechtigungen des Verzeichnisses <em>%s</em>. Der HTTP-Server muss Schreibrechte besitzen',
+ 'file_is_nok' => '<strong>Version %s</strong>. Überprüfen Sie die Berechtigungen des Verzeichnisses <em>%s</em>. Der HTTP-Server muss Schreibrechte besitzen',
'finished' => 'Aktualisierung abgeschlossen!',
'none' => 'Keine Aktualisierung zum Anwenden',
'server_not_found' => 'Der Aktualisierungs-Server kann nicht gefunden werden. [%s]',
diff --git a/app/i18n/en/admin.php b/app/i18n/en/admin.php
index e94d9fa80..707627782 100644
--- a/app/i18n/en/admin.php
+++ b/app/i18n/en/admin.php
@@ -21,11 +21,11 @@ return array(
'ok' => 'Permissions on cache directory are good.',
),
'categories' => array(
- 'nok' => 'Category table is bad configured.',
+ 'nok' => 'Category table is improperly configured.',
'ok' => 'Category table is ok.',
),
'connection' => array(
- 'nok' => 'Connection to the database cannot being established.',
+ 'nok' => 'Connection to the database cannot be established.',
'ok' => 'Connection to the database is ok.',
),
'ctype' => array(
@@ -46,7 +46,7 @@ return array(
'ok' => 'You have the required library to browse the DOM.',
),
'entries' => array(
- 'nok' => 'Entry table is bad configured.',
+ 'nok' => 'Entry table is improperly configured.',
'ok' => 'Entry table is ok.',
),
'favicons' => array(
@@ -54,7 +54,7 @@ return array(
'ok' => 'Permissions on favicons directory are good.',
),
'feeds' => array(
- 'nok' => 'Feed table is bad configured.',
+ 'nok' => 'Feed table is improperly configured.',
'ok' => 'Feed table is ok.',
),
'fileinfo' => array(
@@ -84,8 +84,8 @@ return array(
'ok' => 'Your PHP version is %s, which is compatible with FreshRSS.',
),
'tables' => array(
- 'nok' => 'There is one or more lacking tables in the database.',
- 'ok' => 'Tables are existing in the database.',
+ 'nok' => 'There are one or more missing tables in the database.',
+ 'ok' => 'The appropriate tables exist in the database.',
),
'title' => 'Installation checking',
'tokens' => array(
@@ -103,7 +103,7 @@ return array(
),
'extensions' => array(
'disabled' => 'Disabled',
- 'empty_list' => 'There is no installed extension',
+ 'empty_list' => 'There are no installed extensions',
'enabled' => 'Enabled',
'no_configure_view' => 'This extension cannot be configured.',
'system' => array(
@@ -160,7 +160,7 @@ return array(
'_' => 'Update system',
'apply' => 'Apply',
'check' => 'Check for new updates',
- 'current_version' => 'Your current version of FreshRSS is the %s.',
+ 'current_version' => 'Your current version of FreshRSS is %s.',
'last' => 'Last verification: %s',
'none' => 'No update to apply',
'title' => 'Update system',
@@ -169,8 +169,8 @@ return array(
'articles_and_size' => '%s articles (%s)',
'create' => 'Create new user',
'language' => 'Language',
- 'number' => 'There is %d account created yet',
- 'numbers' => 'There are %d accounts created yet',
+ 'number' => 'There is %d account created',
+ 'numbers' => 'There are %d accounts created',
'password_form' => 'Password<br /><small>(for the Web-form login method)</small>',
'password_format' => 'At least 7 characters',
'title' => 'Manage users',
diff --git a/app/i18n/en/conf.php b/app/i18n/en/conf.php
index b5ab73510..f4618a1ff 100644
--- a/app/i18n/en/conf.php
+++ b/app/i18n/en/conf.php
@@ -8,7 +8,7 @@ return array(
'help' => 'More options are available in the individual feed settings',
'keep_history_by_feed' => 'Minimum number of articles to keep by feed',
'optimize' => 'Optimise database',
- 'optimize_help' => 'To do occasionally to reduce the size of the database',
+ 'optimize_help' => 'Do occasionally to reduce the size of the database',
'purge_now' => 'Purge now',
'title' => 'Archiving',
'ttl' => 'Do not automatically refresh more often than',
@@ -44,10 +44,10 @@ return array(
'filter' => 'Filter applied:',
'get_all' => 'Display all articles',
'get_category' => 'Display "%s" category',
- 'get_favorite' => 'Display favorite articles',
+ 'get_favorite' => 'Display favourite articles',
'get_feed' => 'Display "%s" feed',
'no_filter' => 'No filter',
- 'none' => 'You haven’t created any user query yet.',
+ 'none' => 'You haven’t created any user queries yet.',
'number' => 'Query n°%d',
'order_asc' => 'Display oldest articles first',
'order_desc' => 'Display newest articles first',
@@ -56,14 +56,14 @@ return array(
'state_1' => 'Display read articles',
'state_2' => 'Display unread articles',
'state_3' => 'Display all articles',
- 'state_4' => 'Display favorite articles',
- 'state_5' => 'Display read favorite articles',
- 'state_6' => 'Display unread favorite articles',
- 'state_7' => 'Display favorite articles',
- 'state_8' => 'Display not favorite articles',
- 'state_9' => 'Display read not favorite articles',
- 'state_10' => 'Display unread not favorite articles',
- 'state_11' => 'Display not favorite articles',
+ 'state_4' => 'Display favourite articles',
+ 'state_5' => 'Display read favourite articles',
+ 'state_6' => 'Display unread favourite articles',
+ 'state_7' => 'Display favourite articles',
+ 'state_8' => 'Display not favourite articles',
+ 'state_9' => 'Display read not favourite articles',
+ 'state_10' => 'Display unread not favourite articles',
+ 'state_11' => 'Display not favourite articles',
'state_12' => 'Display all articles',
'state_13' => 'Display read articles',
'state_14' => 'Display unread articles',
@@ -74,7 +74,7 @@ return array(
'_' => 'Profile management',
'delete' => array(
'_' => 'Account deletion',
- 'warn' => 'Your account and all the related data will be deleted.',
+ 'warn' => 'Your account and all related data will be deleted.',
),
'password_api' => 'API password<br /><small>(e.g., for mobile apps)</small>',
'password_form' => 'Password<br /><small>(for the Web-form login method)</small>',
@@ -85,21 +85,22 @@ return array(
'_' => 'Reading',
'after_onread' => 'After “mark all as read”,',
'articles_per_page' => 'Number of articles per page',
- 'auto_load_more' => 'Load next articles at the page bottom',
+ 'auto_load_more' => 'Load more articles at the page bottom',
'auto_remove_article' => 'Hide articles after reading',
'mark_updated_article_unread' => 'Mark updated articles as unread',
'confirm_enabled' => 'Display a confirmation dialog on “mark all as read” actions',
'display_articles_unfolded' => 'Show articles unfolded by default',
'display_categories_unfolded' => 'Show categories folded by default',
- 'hide_read_feeds' => 'Hide categories & feeds with no unread article (does not work with “Show all articles” configuration)',
+ 'hide_read_feeds' => 'Hide categories & feeds with no unread articles (does not work with “Show all articles” configuration)',
'img_with_lazyload' => 'Use "lazy load" mode to load pictures',
+ 'sides_close_article' => 'Clicking outside of article text area closes the article',
'jump_next' => 'jump to next unread sibling (feed or category)',
'number_divided_when_reader' => 'Divided by 2 in the reading view.',
'read' => array(
'article_open_on_website' => 'when article is opened on its original website',
'article_viewed' => 'when article is viewed',
'scroll' => 'while scrolling',
- 'upon_reception' => 'upon reception of the article',
+ 'upon_reception' => 'upon receiving the article',
'when' => 'Mark article as read…',
),
'show' => array(
@@ -110,7 +111,7 @@ return array(
),
'sort' => array(
'_' => 'Sort order',
- 'newer_first' => 'Newer first',
+ 'newer_first' => 'Newest first',
'older_first' => 'Oldest first',
),
'sticky_post' => 'Stick the article to the top when opened',
@@ -142,7 +143,7 @@ return array(
'_' => 'Shortcuts',
'article_action' => 'Article actions',
'auto_share' => 'Share',
- 'auto_share_help' => 'If there is only one sharing mode, it is used. Else modes are accessible by their number.',
+ 'auto_share_help' => 'If there is only one sharing mode, it is used. Otherwise, modes are accessible by their number.',
'close_dropdown' => 'Close menus',
'collapse_article' => 'Collapse',
'first_article' => 'Skip to the first article',
@@ -162,7 +163,7 @@ return array(
'shift_for_all_read' => '+ <code>shift</code> to mark all articles as read',
'title' => 'Shortcuts',
'user_filter' => 'Access user filters',
- 'user_filter_help' => 'If there is only one user filter, it is used. Else filters are accessible by their number.',
+ 'user_filter_help' => 'If there is only one user filter, it is used. Otherwise, filters are accessible by their number.',
),
'user' => array(
'articles_and_size' => '%s articles (%s)',
diff --git a/app/i18n/en/feedback.php b/app/i18n/en/feedback.php
index e7f6b9f85..334d9a8f5 100644
--- a/app/i18n/en/feedback.php
+++ b/app/i18n/en/feedback.php
@@ -2,7 +2,7 @@
return array(
'admin' => array(
- 'optimization_complete' => 'Optimisation complete',
+ 'optimization_complete' => 'Optimization complete',
),
'access' => array(
'denied' => 'You don’t have permission to access this page',
@@ -39,26 +39,26 @@ return array(
'ok' => '%s is now enabled',
),
'no_access' => 'You have no access on %s',
- 'not_enabled' => '%s is not enabled yet',
+ 'not_enabled' => '%s is not enabled',
'not_found' => '%s does not exist',
),
'import_export' => array(
'export_no_zip_extension' => 'ZIP extension is not present on your server. Please try to export files one by one.',
'feeds_imported' => 'Your feeds have been imported and will now be updated',
- 'feeds_imported_with_errors' => 'Your feeds have been imported but some errors occurred',
+ 'feeds_imported_with_errors' => 'Your feeds have been imported, but some errors occurred',
'file_cannot_be_uploaded' => 'File cannot be uploaded!',
'no_zip_extension' => 'ZIP extension is not present on your server.',
'zip_error' => 'An error occured during ZIP import.',
),
'sub' => array(
- 'actualize' => 'Actualise',
+ 'actualize' => 'Updating',
'category' => array(
'created' => 'Category %s has been created.',
'deleted' => 'Category has been deleted.',
'emptied' => 'Category has been emptied',
'error' => 'Category cannot be updated',
'name_exists' => 'Category name already exists.',
- 'no_id' => 'You must precise the id of the category.',
+ 'no_id' => 'You must specify the id of the category.',
'no_name' => 'Category name cannot be empty.',
'not_delete_default' => 'You cannot delete the default category!',
'not_exist' => 'The category does not exist!',
@@ -87,7 +87,7 @@ return array(
'update' => array(
'can_apply' => 'FreshRSS will now be updated to the <strong>version %s</strong>.',
'error' => 'The update process has encountered an error: %s',
- 'file_is_nok' => 'Check permissions on <em>%s</em> directory. HTTP server must have rights to write into',
+ 'file_is_nok' => 'New <strong>version %s</strong> available, but check permissions on <em>%s</em> directory. HTTP server must have rights to write into',
'finished' => 'Update completed!',
'none' => 'No update to apply',
'server_not_found' => 'Update server cannot be found. [%s]',
diff --git a/app/i18n/en/gen.php b/app/i18n/en/gen.php
index 1ee5336bd..05281769f 100644
--- a/app/i18n/en/gen.php
+++ b/app/i18n/en/gen.php
@@ -103,7 +103,7 @@ return array(
'js' => array(
'category_empty' => 'Empty category',
'confirm_action' => 'Are you sure you want to perform this action? It cannot be cancelled!',
- 'confirm_action_feed_cat' => 'Are you sure you want to perform this action? You will lose related favorites and user queries. It cannot be cancelled!',
+ 'confirm_action_feed_cat' => 'Are you sure you want to perform this action? You will lose related favourites and user queries. It cannot be cancelled!',
'feedback' => array(
'body_new_articles' => 'There are %%d new articles to read on FreshRSS.',
'request_failed' => 'A request has failed, it may have been caused by Internet connection problems.',
@@ -121,6 +121,7 @@ return array(
'nl' => 'Nederlands',
'ru' => 'Русский',
'tr' => 'Türkçe',
+ 'zh-cn' => '简体中文'
),
'menu' => array(
'about' => 'About',
@@ -173,7 +174,7 @@ return array(
'blank_to_disable' => 'Leave blank to disable',
'by_author' => 'By <em>%s</em>',
'by_default' => 'By default',
- 'damn' => 'Damn!',
+ 'damn' => 'Blast!',
'default_category' => 'Uncategorized',
'no' => 'No',
'not_applicable' => 'Not available',
diff --git a/app/i18n/en/index.php b/app/i18n/en/index.php
index eb6413e3c..a4686de4e 100644
--- a/app/i18n/en/index.php
+++ b/app/i18n/en/index.php
@@ -41,7 +41,7 @@ return array(
'mark_cat_read' => 'Mark category as read',
'mark_feed_read' => 'Mark feed as read',
'newer_first' => 'Newer first',
- 'non-starred' => 'Show all but favorites',
+ 'non-starred' => 'Show all but favourites',
'normal_view' => 'Normal view',
'older_first' => 'Oldest first',
'queries' => 'User queries',
@@ -49,7 +49,7 @@ return array(
'reader_view' => 'Reading view',
'rss_view' => 'RSS feed',
'search_short' => 'Search',
- 'starred' => 'Show only favorites',
+ 'starred' => 'Show only favourites',
'stats' => 'Statistics',
'subscription' => 'Subscriptions management',
'unread' => 'Show only unread',
diff --git a/app/i18n/en/sub.php b/app/i18n/en/sub.php
index 789433ee6..86600e882 100644
--- a/app/i18n/en/sub.php
+++ b/app/i18n/en/sub.php
@@ -10,10 +10,10 @@ return array(
'feed' => array(
'add' => 'Add a RSS feed',
'advanced' => 'Advanced',
- 'archiving' => 'Archivage',
+ 'archiving' => 'Archiving',
'auth' => array(
'configuration' => 'Login',
- 'help' => 'Connection allows to access HTTP protected RSS feeds',
+ 'help' => 'Allows access to HTTP protected RSS feeds',
'http' => 'HTTP Authentication',
'password' => 'HTTP password',
'username' => 'HTTP username',
@@ -22,7 +22,7 @@ return array(
'css_path' => 'Articles CSS path on original website',
'description' => 'Description',
'empty' => 'This feed is empty. Please verify that it is still maintained.',
- 'error' => 'This feed has encountered a problem. Please verify that it is always reachable then actualize it.',
+ 'error' => 'This feed has encountered a problem. Please verify that it is always reachable then update it.',
'in_main_stream' => 'Show in main stream',
'informations' => 'Information',
'keep_history' => 'Minimum number of articles to keep',
diff --git a/app/i18n/fr/conf.php b/app/i18n/fr/conf.php
index 7a6d12e17..0c8188623 100644
--- a/app/i18n/fr/conf.php
+++ b/app/i18n/fr/conf.php
@@ -93,6 +93,7 @@ return array(
'display_categories_unfolded' => 'Afficher les catégories pliées par défaut',
'hide_read_feeds' => 'Cacher les catégories & flux sans article non-lu (ne fonctionne pas avec la configuration “Afficher tous les articles”)',
'img_with_lazyload' => 'Utiliser le mode “chargement différé” pour les images',
+ 'sides_close_article' => 'Cliquer hors de la zone de texte ferme l’article',
'jump_next' => 'sauter au prochain voisin non lu (flux ou catégorie)',
'number_divided_when_reader' => 'Divisé par 2 dans la vue de lecture.',
'read' => array(
diff --git a/app/i18n/fr/feedback.php b/app/i18n/fr/feedback.php
index 5966fc3a7..aa19cd02b 100644
--- a/app/i18n/fr/feedback.php
+++ b/app/i18n/fr/feedback.php
@@ -87,7 +87,7 @@ return array(
'update' => array(
'can_apply' => 'FreshRSS va maintenant être mis à jour vers la <strong>version %s</strong>.',
'error' => 'La mise à jour a rencontré un problème : %s',
- 'file_is_nok' => 'Veuillez vérifier les droits sur le répertoire <em>%s</em>. Le serveur HTTP doit être capable d’écrire dedans',
+ 'file_is_nok' => 'Nouvelle <strong>version %s</strong> disponible, mais veuillez vérifier les droits sur le répertoire <em>%s</em>. Le serveur HTTP doit être capable d’écrire dedans',
'finished' => 'La mise à jour est terminée !',
'none' => 'Aucune mise à jour à appliquer',
'server_not_found' => 'Le serveur de mise à jour n’a pas été trouvé. [%s]',
diff --git a/app/i18n/it/conf.php b/app/i18n/it/conf.php
index 19b62c9a7..e6ce86ef9 100644
--- a/app/i18n/it/conf.php
+++ b/app/i18n/it/conf.php
@@ -93,6 +93,7 @@ return array(
'display_categories_unfolded' => 'Mostra categorie aperte di predefinito',
'hide_read_feeds' => 'Nascondi categorie e feeds con articoli già letti (non funziona se “Mostra tutti gli articoli” è selezionato)',
'img_with_lazyload' => 'Usa la modalità "caricamento ritardato" per le immagini',
+ 'sides_close_article' => 'Clicking outside of article text area closes the article', //TODO
'jump_next' => 'Salta al successivo feed o categoria non letto',
'number_divided_when_reader' => 'Diviso 2 nella modalità di lettura.',
'read' => array(
diff --git a/app/i18n/it/feedback.php b/app/i18n/it/feedback.php
index 5851cb2e6..8f3cf3ed6 100644
--- a/app/i18n/it/feedback.php
+++ b/app/i18n/it/feedback.php
@@ -87,7 +87,7 @@ return array(
'update' => array(
'can_apply' => 'FreshRSS verrà aggiornato alla <strong>versione %s</strong>.',
'error' => 'Il processo di aggiornamento ha riscontrato il seguente errore: %s',
- 'file_is_nok' => 'Verifica i permessi della cartella <em>%s</em>. Il server HTTP deve avere i permessi per la scrittura ',
+ 'file_is_nok' => 'Nuova <strong>versione %s</strong>, ma verifica i permessi della cartella <em>%s</em>. Il server HTTP deve avere i permessi per la scrittura ',
'finished' => 'Aggiornamento completato con successo!',
'none' => 'Nessun aggiornamento disponibile',
'server_not_found' => 'Server per aggiornamento non disponibile. [%s]',
diff --git a/app/i18n/nl/admin.php b/app/i18n/nl/admin.php
index 607fb1892..fdfe6e3bc 100644
--- a/app/i18n/nl/admin.php
+++ b/app/i18n/nl/admin.php
@@ -5,8 +5,8 @@ return array(
'allow_anonymous' => 'Sta bezoekers toe om artikelen te lezen van de standaard gebruiker (%s)',
'allow_anonymous_refresh' => 'Sta bezoekers toe om de artikelen te vernieuwen',
'api_enabled' => 'Sta <abbr>API</abbr> toegang toe <small>(nodig voor mobiele apps)</small>',
- 'form' => 'Web formulier (traditioneel, benodigd JavaScript)',
- 'http' => 'HTTP (voor geavanceerde gebruikers met HTTPS)',
+ 'form' => 'Web formulier (traditioneel, JavaScript vereist)',
+ 'http' => 'HTTP (voor gevorderde gebruikers met HTTPS)',
'none' => 'Geen (gevaarlijk)',
'title' => 'Authenticatie',
'title_reset' => 'Authenticatie terugzetten',
@@ -37,8 +37,8 @@ return array(
'ok' => 'U hebt de cURL uitbreiding.',
),
'data' => array(
- 'nok' => 'Controleer de permissies op de <em>./data</em> map. HTTP server moet rechten hebben om hierin te schrijven',
- 'ok' => 'Permissies op de data map zijn goed.',
+ 'nok' => 'Controleer de permissies op de <em>./data</em> map. De HTTP server moet rechten hebben om hierin te schrijven',
+ 'ok' => 'Permissies op de data map zijn in orde.',
),
'database' => 'Database installatie',
'dom' => array(
@@ -46,16 +46,16 @@ return array(
'ok' => 'U hebt de benodigde bibliotheek voor het bladeren van DOM.',
),
'entries' => array(
- 'nok' => 'Invoer tabel is slecht geconfigureerd.',
- 'ok' => 'Invoer tabel is ok.',
+ 'nok' => 'Invoertabel is slecht geconfigureerd.',
+ 'ok' => 'Invoertabel is ok.',
),
'favicons' => array(
'nok' => 'Controleer de permissies op de <em>./data/favicons</em> map. HTTP server moet rechten hebben om hierin te schrijven',
'ok' => 'Permissies op de favicons map zijn goed.',
),
'feeds' => array(
- 'nok' => 'Feed tabel is slecht geconfigureerd.',
- 'ok' => 'Feed tabel is ok.',
+ 'nok' => 'Feedtabel is slecht geconfigureerd.',
+ 'ok' => 'Feedtabel is ok.',
),
'fileinfo' => array(
'nok' => 'U mist de PHP fileinfo (fileinfo package).',
@@ -107,11 +107,11 @@ return array(
'enabled' => 'Ingeschakeld',
'no_configure_view' => 'Deze uitbreiding kan niet worden geconfigureerd.',
'system' => array(
- '_' => 'Systeem uitbreidingen',
- 'no_rights' => 'Systeem uitbreidingen (U hebt hier geen rechten op)',
+ '_' => 'Systeemuitbreidingen',
+ 'no_rights' => 'Systeemuitbreidingen (U hebt hier geen rechten op)',
),
'title' => 'Uitbreidingen',
- 'user' => 'Gebruikers uitbreidingen',
+ 'user' => 'Gebruikersuitbreidingen',
),
'stats' => array(
'_' => 'Statistieken',
@@ -137,7 +137,7 @@ return array(
'no_idle' => 'Er is geen gepauzeerde feed!',
'number_entries' => '%d artikelen',
'percent_of_total' => '%% van totaal',
- 'repartition' => 'Artikelen verdeling',
+ 'repartition' => 'Artikelverdeling',
'status_favorites' => 'Favorieten',
'status_read' => 'Gelezen',
'status_total' => 'Totaal',
@@ -167,20 +167,20 @@ return array(
),
'user' => array(
'articles_and_size' => '%s artikelen (%s)',
- 'create' => 'Creëer nieuwe gebruiker',
+ 'create' => 'Creëer nieuwe gebruiker',
'language' => 'Taal',
'number' => 'Er is %d accounts gemaakt',
'numbers' => 'Er zijn %d accounts gemaakt',
- 'password_form' => 'Wachtwoord<br /><small>(voor de Web-formulier log in methode)</small>',
+ 'password_form' => 'Wachtwoord<br /><small>(voor de Web-formulier loginmethode)</small>',
'password_format' => 'Ten minste 7 tekens',
'registration' => array(
'allow' => 'Sta het maken van nieuwe accounts toe',
- 'help' => '0 betekent dat er geen account limiet is',
+ 'help' => '0 betekent dat er geen accountlimiet is',
'number' => 'Max aantal accounts',
),
'title' => 'Beheer gebruikers',
'user_list' => 'Lijst van gebruikers ',
- 'username' => 'Gebruikers naam',
+ 'username' => 'Gebruikersnaam',
'users' => 'Gebruikers',
),
);
diff --git a/app/i18n/nl/conf.php b/app/i18n/nl/conf.php
index 573dabf45..77f6307ed 100644
--- a/app/i18n/nl/conf.php
+++ b/app/i18n/nl/conf.php
@@ -92,6 +92,7 @@ return array(
'display_categories_unfolded' => 'Toon categoriën ingeklapt als standaard',
'hide_read_feeds' => 'Verberg categoriën en feeds zonder ongelezen artikelen (werkt niet met “Toon alle artikelen” configuratie)',
'img_with_lazyload' => 'Gebruik "lazy load" methode om afbeeldingen te laden',
+ 'sides_close_article' => 'Sluit het artikel door buiten de artikeltekst te klikken',
'jump_next' => 'Ga naar volgende ongelezen (feed of categorie)',
'mark_updated_article_unread' => 'Markeer vernieuwd artikel als ongelezen',
'number_divided_when_reader' => 'Gedeeld door 2 in de lees modus.',
@@ -167,7 +168,7 @@ return array(
'user' => array(
'articles_and_size' => '%s artikelen (%s)',
'current' => 'Huidige gebruiker',
- 'is_admin' => 'is administrateur',
+ 'is_admin' => 'is beheerder',
'users' => 'Gebruikers',
),
);
diff --git a/app/i18n/nl/feedback.php b/app/i18n/nl/feedback.php
index 386b8d415..cf1274767 100644
--- a/app/i18n/nl/feedback.php
+++ b/app/i18n/nl/feedback.php
@@ -10,17 +10,17 @@ return array(
),
'auth' => array(
'form' => array(
- 'not_set' => 'Een probleem is opgetreden tijdens de controle van de systeem configuratie. Probeer het later nog eens.',
+ 'not_set' => 'Er is een probleem opgetreden tijdens de controle van de systeemconfiguratie. Probeer het later nog eens.',
'set' => 'Formulier is nu uw standaard authenticatie systeem.',
),
'login' => array(
- 'invalid' => 'Log in is ongeldig',
+ 'invalid' => 'Login is ongeldig',
'success' => 'U bent ingelogd',
),
'logout' => array(
'success' => 'U bent uitgelogd',
),
- 'no_password_set' => 'Administrateur wachtwoord is niet ingesteld. Deze mogelijkheid is niet beschikbaar.',
+ 'no_password_set' => 'Beheerderswachtwoord is niet ingesteld. Deze mogelijkheid is niet beschikbaar.',
),
'conf' => array(
'error' => 'Er is een fout opgetreden tijdens het opslaan van de configuratie',
@@ -87,7 +87,7 @@ return array(
'update' => array(
'can_apply' => 'FreshRSS word nu vernieud naar <strong>versie %s</strong>.',
'error' => 'Het vernieuwingsproces kwam een fout tegen: %s',
- 'file_is_nok' => 'Controleer permissies op <em>%s</em> map. HTTP server moet rechten hebben om er in te schrijven',
+ 'file_is_nok' => '<strong>Versie %s</strong>. Controleer permissies op <em>%s</em> map. HTTP server moet rechten hebben om er in te schrijven',
'finished' => 'Vernieuwing compleet!',
'none' => 'Geen vernieuwing om toe te passen',
'server_not_found' => 'Vernieuwings server kan niet worden gevonden. [%s]',
diff --git a/app/i18n/nl/gen.php b/app/i18n/nl/gen.php
index 11e82cb4d..07d444ec3 100644
--- a/app/i18n/nl/gen.php
+++ b/app/i18n/nl/gen.php
@@ -37,8 +37,8 @@ return array(
'reset' => 'Authenticatie reset',
'username' => array(
'_' => 'Gebruikersnaam',
- 'admin' => 'Administrator gebruikersnaam',
- 'format' => '<small>maximaal 16 alphanumerieke tekens</small>',
+ 'admin' => 'Beheerdersgebruikersnaam',
+ 'format' => '<small>maximaal 16 alfanumerieke tekens</small>',
),
),
'date' => array(
@@ -109,8 +109,8 @@ return array(
'request_failed' => 'Een opdracht is mislukt, mogelijk door Internet verbindings problemen.',
'title_new_articles' => 'FreshRSS: nieuwe artikelen!',
),
- 'new_article' => 'Er zijn nieuwe artikelen beschikbaar, klik om de pagina te vernieuwen.',
- 'should_be_activated' => 'JavaScript moet aan staan',
+ 'new_article' => 'Er zijn nieuwe artikelen beschikbaar. Klik om de pagina te vernieuwen.',
+ 'should_be_activated' => 'JavaScript moet aanstaan',
),
'lang' => array(
'cz' => 'Čeština',
@@ -127,7 +127,7 @@ return array(
'admin' => 'Administratie',
'archiving' => 'Archiveren',
'authentication' => 'Authenticatie',
- 'check_install' => 'Installatie controle',
+ 'check_install' => 'Installatiecontrole',
'configuration' => 'Configuratie',
'display' => 'Opmaak',
'extensions' => 'Uitbreidingen',
@@ -138,9 +138,9 @@ return array(
'sharing' => 'Delen',
'shortcuts' => 'Snelle toegang',
'stats' => 'Statistieken',
- 'system' => 'Systeem configuratie',
- 'update' => 'Versie controle',
- 'user_management' => 'Beheer gebruikers',
+ 'system' => 'Systeemconfiguratie',
+ 'update' => 'Versiecontrole',
+ 'user_management' => 'Gebruikersbeheer',
'user_profile' => 'Profiel',
),
'pagination' => array(
diff --git a/app/i18n/nl/index.php b/app/i18n/nl/index.php
index 751806bfa..e0184a0d0 100644
--- a/app/i18n/nl/index.php
+++ b/app/i18n/nl/index.php
@@ -32,8 +32,8 @@ return array(
'menu' => array(
'about' => 'Over FreshRSS',
'add_query' => 'Voeg een query toe',
- 'before_one_day' => 'Ouder als een dag',
- 'before_one_week' => 'Ouder als een week',
+ 'before_one_day' => 'Ouder dan een dag',
+ 'before_one_week' => 'Ouder dan een week',
'favorites' => 'Favorieten (%s)',
'global_view' => 'Globale weergave',
'main_stream' => 'Overzicht',
diff --git a/app/i18n/nl/install.php b/app/i18n/nl/install.php
index 79db9c794..419ee4c9b 100644
--- a/app/i18n/nl/install.php
+++ b/app/i18n/nl/install.php
@@ -14,7 +14,7 @@ return array(
'none' => 'Geen (gevaarlijk)',
'password_form' => 'Wachtwoord<br /><small>(voor de Web-formulier log in methode)</small>',
'password_format' => 'Tenminste 7 tekens',
- 'type' => 'Authenticatie methode',
+ 'type' => 'Authenticatiemethode',
),
'bdd' => array(
'_' => 'Database',
@@ -98,12 +98,12 @@ return array(
'ok' => 'Algemene configuratie is opgeslagen.',
),
'congratulations' => 'Gefeliciteerd!',
- 'default_user' => 'Gebruikersnaam van de standaard gebruiker <small>(maximaal 16 alphanumerieke tekens)</small>',
+ 'default_user' => 'Gebruikersnaam van de standaardgebruiker <small>(maximaal 16 alfanumerieke tekens)</small>',
'delete_articles_after' => 'Verwijder artikelen na',
'fix_errors_before' => 'Repareer fouten alvorens U naar de volgende stap gaat.',
'javascript_is_better' => 'FreshRSS werkt beter JavaScript ingeschakeld',
'js' => array(
- 'confirm_reinstall' => 'U verliest uw vorige configuratie door FreshRSS opnieuw te installeren. Weet u zeker dat u verder wilt gaan?',
+ 'confirm_reinstall' => 'U zal uw vorige configuratie kwijtraken door FreshRSS opnieuw te installeren. Weet u zeker dat u verder wilt gaan?',
),
'language' => array(
'_' => 'Taal',
@@ -111,7 +111,7 @@ return array(
'defined' => 'Taal is bepaald.',
),
'not_deleted' => 'Er ging iets fout! U moet het bestand <em>%s</em> handmatig verwijderen.',
- 'ok' => 'De installatie procedure is geslaagd.',
+ 'ok' => 'De installatieprocedure is geslaagd.',
'step' => 'stap %d',
'steps' => 'Stappen',
'title' => 'Installatie · FreshRSS',
diff --git a/app/i18n/nl/sub.php b/app/i18n/nl/sub.php
index a1ba3f03d..e0cebbb4e 100644
--- a/app/i18n/nl/sub.php
+++ b/app/i18n/nl/sub.php
@@ -53,10 +53,10 @@ return array(
'menu' => array(
'bookmark' => 'Abonneer (FreshRSS bladwijzer)',
'import_export' => 'Importeer / exporteer',
- 'subscription_management' => 'Abonnementen beheer',
+ 'subscription_management' => 'Abonnementenbeheer',
),
'title' => array(
- '_' => 'Abonnementen beheer',
- 'feed_management' => 'RSS feed beheer',
+ '_' => 'Abonnementenbeheer',
+ 'feed_management' => 'RSS-feedbeheer',
),
);
diff --git a/app/i18n/ru/conf.php b/app/i18n/ru/conf.php
index 557fbe369..3cf0a5ea9 100644
--- a/app/i18n/ru/conf.php
+++ b/app/i18n/ru/conf.php
@@ -93,6 +93,7 @@ return array(
'display_categories_unfolded' => 'Show categories folded by default',
'hide_read_feeds' => 'Hide categories & feeds with no unread article (does not work with “Show all articles” configuration)',
'img_with_lazyload' => 'Use "lazy load" mode to load pictures',
+ 'sides_close_article' => 'Clicking outside of article text area closes the article', //TODO
'jump_next' => 'jump to next unread sibling (feed or category)',
'number_divided_when_reader' => 'Divided by 2 in the reading view.',
'read' => array(
diff --git a/app/i18n/ru/feedback.php b/app/i18n/ru/feedback.php
index e7f6b9f85..ffebd6dc9 100644
--- a/app/i18n/ru/feedback.php
+++ b/app/i18n/ru/feedback.php
@@ -2,108 +2,108 @@
return array(
'admin' => array(
- 'optimization_complete' => 'Optimisation complete',
+ 'optimization_complete' => 'Optimisation complete', //TODO
),
'access' => array(
- 'denied' => 'You don’t have permission to access this page',
- 'not_found' => 'You are looking for a page which doesn’t exist',
+ 'denied' => 'You don’t have permission to access this page', //TODO
+ 'not_found' => 'You are looking for a page which doesn’t exist', //TODO
),
'auth' => array(
'form' => array(
- 'not_set' => 'A problem occured during authentication system configuration. Please retry later.',
- 'set' => 'Form is now your default authentication system.',
+ 'not_set' => 'A problem occured during authentication system configuration. Please retry later.', //TODO
+ 'set' => 'Form is now your default authentication system.', //TODO
),
'login' => array(
- 'invalid' => 'Login is invalid',
- 'success' => 'You are connected',
+ 'invalid' => 'Login is invalid', //TODO
+ 'success' => 'You are connected', //TODO
),
'logout' => array(
- 'success' => 'You are disconnected',
+ 'success' => 'You are disconnected', //TODO
),
- 'no_password_set' => 'Administrator password hasn’t been set. This feature isn’t available.',
+ 'no_password_set' => 'Administrator password hasn’t been set. This feature isn’t available.', //TODO
),
'conf' => array(
- 'error' => 'An error occurred during configuration saving',
- 'query_created' => 'Query "%s" has been created.',
- 'shortcuts_updated' => 'Shortcuts have been updated',
- 'updated' => 'Configuration has been updated',
+ 'error' => 'An error occurred during configuration saving', //TODO
+ 'query_created' => 'Query "%s" has been created.', //TODO
+ 'shortcuts_updated' => 'Shortcuts have been updated', //TODO
+ 'updated' => 'Configuration has been updated', //TODO
),
'extensions' => array(
- 'already_enabled' => '%s is already enabled',
+ 'already_enabled' => '%s is already enabled', //TODO
'disable' => array(
- 'ko' => '%s cannot be disabled. <a href="%s">Check FressRSS logs</a> for details.',
- 'ok' => '%s is now disabled',
+ 'ko' => '%s cannot be disabled. <a href="%s">Check FressRSS logs</a> for details.', //TODO
+ 'ok' => '%s is now disabled', //TODO
),
'enable' => array(
- 'ko' => '%s cannot be enabled. <a href="%s">Check FressRSS logs</a> for details.',
- 'ok' => '%s is now enabled',
+ 'ko' => '%s cannot be enabled. <a href="%s">Check FressRSS logs</a> for details.', //TODO
+ 'ok' => '%s is now enabled', //TODO
),
- 'no_access' => 'You have no access on %s',
- 'not_enabled' => '%s is not enabled yet',
- 'not_found' => '%s does not exist',
+ 'no_access' => 'You have no access on %s', //TODO
+ 'not_enabled' => '%s is not enabled yet', //TODO
+ 'not_found' => '%s does not exist', //TODO
),
'import_export' => array(
- 'export_no_zip_extension' => 'ZIP extension is not present on your server. Please try to export files one by one.',
- 'feeds_imported' => 'Your feeds have been imported and will now be updated',
- 'feeds_imported_with_errors' => 'Your feeds have been imported but some errors occurred',
- 'file_cannot_be_uploaded' => 'File cannot be uploaded!',
- 'no_zip_extension' => 'ZIP extension is not present on your server.',
- 'zip_error' => 'An error occured during ZIP import.',
+ 'export_no_zip_extension' => 'ZIP extension is not present on your server. Please try to export files one by one.', //TODO
+ 'feeds_imported' => 'Your feeds have been imported and will now be updated', //TODO
+ 'feeds_imported_with_errors' => 'Your feeds have been imported but some errors occurred', //TODO
+ 'file_cannot_be_uploaded' => 'File cannot be uploaded!', //TODO
+ 'no_zip_extension' => 'ZIP extension is not present on your server.', //TODO
+ 'zip_error' => 'An error occured during ZIP import.', //TODO
),
'sub' => array(
- 'actualize' => 'Actualise',
+ 'actualize' => 'Actualise', //TODO
'category' => array(
- 'created' => 'Category %s has been created.',
- 'deleted' => 'Category has been deleted.',
- 'emptied' => 'Category has been emptied',
- 'error' => 'Category cannot be updated',
- 'name_exists' => 'Category name already exists.',
- 'no_id' => 'You must precise the id of the category.',
- 'no_name' => 'Category name cannot be empty.',
- 'not_delete_default' => 'You cannot delete the default category!',
- 'not_exist' => 'The category does not exist!',
- 'over_max' => 'You have reached your limit of categories (%d)',
- 'updated' => 'Category has been updated.',
+ 'created' => 'Category %s has been created.', //TODO
+ 'deleted' => 'Category has been deleted.', //TODO
+ 'emptied' => 'Category has been emptied', //TODO
+ 'error' => 'Category cannot be updated', //TODO
+ 'name_exists' => 'Category name already exists.', //TODO
+ 'no_id' => 'You must precise the id of the category.', //TODO
+ 'no_name' => 'Category name cannot be empty.', //TODO
+ 'not_delete_default' => 'You cannot delete the default category!', //TODO
+ 'not_exist' => 'The category does not exist!', //TODO
+ 'over_max' => 'You have reached your limit of categories (%d)', //TODO
+ 'updated' => 'Category has been updated.', //TODO
),
'feed' => array(
- 'actualized' => '<em>%s</em> has been updated',
- 'actualizeds' => 'RSS feeds have been updated',
- 'added' => 'RSS feed <em>%s</em> has been added',
- 'already_subscribed' => 'You have already subscribed to <em>%s</em>',
- 'deleted' => 'Feed has been deleted',
- 'error' => 'Feed cannot be updated',
- 'internal_problem' => 'The RSS feed could not be added. <a href="%s">Check FressRSS logs</a> for details.',
- 'invalid_url' => 'URL <em>%s</em> is invalid',
- 'marked_read' => 'Feeds have been marked as read',
- 'n_actualized' => '%d feeds have been updated',
- 'n_entries_deleted' => '%d articles have been deleted',
- 'no_refresh' => 'There is no feed to refresh…',
- 'not_added' => '<em>%s</em> could not be added',
- 'over_max' => 'You have reached your limit of feeds (%d)',
- 'updated' => 'Feed has been updated',
+ 'actualized' => '<em>%s</em> has been updated', //TODO
+ 'actualizeds' => 'RSS feeds have been updated', //TODO
+ 'added' => 'RSS feed <em>%s</em> has been added', //TODO
+ 'already_subscribed' => 'You have already subscribed to <em>%s</em>', //TODO
+ 'deleted' => 'Feed has been deleted', //TODO
+ 'error' => 'Feed cannot be updated', //TODO
+ 'internal_problem' => 'The RSS feed could not be added. <a href="%s">Check FressRSS logs</a> for details.', //TODO
+ 'invalid_url' => 'URL <em>%s</em> is invalid', //TODO
+ 'marked_read' => 'Feeds have been marked as read', //TODO
+ 'n_actualized' => '%d feeds have been updated', //TODO
+ 'n_entries_deleted' => '%d articles have been deleted', //TODO
+ 'no_refresh' => 'There is no feed to refresh…', //TODO
+ 'not_added' => '<em>%s</em> could not be added', //TODO
+ 'over_max' => 'You have reached your limit of feeds (%d)', //TODO
+ 'updated' => 'Feed has been updated', //TODO
),
- 'purge_completed' => 'Purge completed (%d articles deleted)',
+ 'purge_completed' => 'Purge completed (%d articles deleted)', //TODO
),
'update' => array(
- 'can_apply' => 'FreshRSS will now be updated to the <strong>version %s</strong>.',
- 'error' => 'The update process has encountered an error: %s',
- 'file_is_nok' => 'Check permissions on <em>%s</em> directory. HTTP server must have rights to write into',
- 'finished' => 'Update completed!',
- 'none' => 'No update to apply',
- 'server_not_found' => 'Update server cannot be found. [%s]',
+ 'can_apply' => 'FreshRSS will now be updated to the <strong>version %s</strong>.', //TODO
+ 'error' => 'The update process has encountered an error: %s', //TODO
+ 'file_is_nok' => 'New <strong>version %s</strong> available, but check permissions on <em>%s</em> directory. HTTP server must have rights to write into', //TODO
+ 'finished' => 'Update completed!', //TODO
+ 'none' => 'No update to apply', //TODO
+ 'server_not_found' => 'Update server cannot be found. [%s]', //TODO
),
'user' => array(
'created' => array(
- '_' => 'User %s has been created',
- 'error' => 'User %s cannot be created',
+ '_' => 'User %s has been created', //TODO
+ 'error' => 'User %s cannot be created', //TODO
),
'deleted' => array(
- '_' => 'User %s has been deleted',
- 'error' => 'User %s cannot be deleted',
+ '_' => 'User %s has been deleted', //TODO
+ 'error' => 'User %s cannot be deleted', //TODO
),
),
'profile' => array(
- 'error' => 'Your profile cannot be modified',
- 'updated' => 'Your profile has been modified',
+ 'error' => 'Your profile cannot be modified', //TODO
+ 'updated' => 'Your profile has been modified', //TODO
),
);
diff --git a/app/i18n/tr/conf.php b/app/i18n/tr/conf.php
index 2fdc248e4..9930c3637 100644
--- a/app/i18n/tr/conf.php
+++ b/app/i18n/tr/conf.php
@@ -93,6 +93,7 @@ return array(
'display_categories_unfolded' => 'Show categories folded by default',
'hide_read_feeds' => 'Okunmamış makalesi olmayan kategori veya akışı gizle ("Tüm makaleleri göster" komutunda çalışmaz)',
'img_with_lazyload' => 'Resimleri yüklemek için "tembel modu" kullan',
+ 'sides_close_article' => 'Clicking outside of article text area closes the article', //TODO
'jump_next' => 'Bir sonraki benzer okunmamışa geç (akış veya kategori)',
'number_divided_when_reader' => 'Okuma modunda ikiye bölünecek.',
'read' => array(
diff --git a/app/i18n/tr/feedback.php b/app/i18n/tr/feedback.php
index 87361ff51..be79630be 100644
--- a/app/i18n/tr/feedback.php
+++ b/app/i18n/tr/feedback.php
@@ -87,7 +87,7 @@ return array(
'update' => array(
'can_apply' => 'FreshRSS <strong>%s versiyonuna</strong> güncellenecek.',
'error' => 'Güncelleme işlemi sırasında hata: %s',
- 'file_is_nok' => '<em>%s</em> klasör yetkisini kontrol edin. HTTP yazma yetkisi olmalı',
+ 'file_is_nok' => '<strong>%s versiyonuna</strong>. <em>%s</em> klasör yetkisini kontrol edin. HTTP yazma yetkisi olmalı',
'finished' => 'Güncelleme tamamlandı!',
'none' => 'Güncelleme yok',
'server_not_found' => 'Güncelleme sunucusu bulunamadı. [%s]',
diff --git a/app/i18n/zh-cn/admin.php b/app/i18n/zh-cn/admin.php
new file mode 100644
index 000000000..202114c75
--- /dev/null
+++ b/app/i18n/zh-cn/admin.php
@@ -0,0 +1,181 @@
+<?php
+
+return array(
+ 'auth' => array(
+ 'allow_anonymous' => '允许匿名阅读默认用户 (%s) 的文章',
+ 'allow_anonymous_refresh' => '允许匿名刷新文章',
+ 'api_enabled' => '允许 <abbr>API</abbr> 访问 <small>(用于手机 APP)</small>',
+ 'form' => 'Web form (传统方式, 需要 JavaScript)',
+ 'http' => 'HTTP (面向启用 HTTPS 的高级用户)',
+ 'none' => '无 (危险)',
+ 'title' => '认证',
+ 'title_reset' => '认证重置',
+ 'token' => '认证口令',
+ 'token_help' => '允许不经认证访问默认用户的 RSS 输出:',
+ 'type' => '认证方式',
+ 'unsafe_autologin' => '允许不安全的自动登陆方式:',
+ ),
+ 'check_install' => array(
+ 'cache' => array(
+ 'nok' => '请检查 <em>./data/cache</em> 目录权限。HTTP 服务器必须有其写入权限。',
+ 'ok' => 'cache 目录权限正常。',
+ ),
+ 'categories' => array(
+ 'nok' => '分类表配置错误。',
+ 'ok' => '分类表正常。',
+ ),
+ 'connection' => array(
+ 'nok' => '数据库连接失败。',
+ 'ok' => '数据库连接正常。',
+ ),
+ 'ctype' => array(
+ 'nok' => '找不到字符类型检测库 (php-ctype) 。',
+ 'ok' => '你已有字符类型检测库 (ctype) 。',
+ ),
+ 'curl' => array(
+ 'nok' => '找不到 cURL 库 (php-curl package) 。',
+ 'ok' => '你已有 cURL 库。',
+ ),
+ 'data' => array(
+ 'nok' => '请检查 <em>./data</em> 目录权限。HTTP 服务器必须有其写入权限。',
+ 'ok' => 'data 目录权限正常。',
+ ),
+ 'database' => '数据库相关',
+ 'dom' => array(
+ 'nok' => '找不到用于浏览 DOM 的库 (php-xml) 。',
+ 'ok' => '你已有用于浏览 DOM 的库。',
+ ),
+ 'entries' => array(
+ 'nok' => '条目表配置错误。',
+ 'ok' => '条目表正常。',
+ ),
+ 'favicons' => array(
+ 'nok' => '请检查 <em>./data/favicons</em> 目录权限。HTTP 服务器必须有其写入权限。',
+ 'ok' => 'favicons 目录权限正常。',
+ ),
+ 'feeds' => array(
+ 'nok' => 'RSS 源表配置错误。',
+ 'ok' => 'RSS 源表正常。',
+ ),
+ 'fileinfo' => array(
+ 'nok' => '找不到 PHP fileinfo 库 (fileinfo) 。',
+ 'ok' => '你已有 fileinfo 库。',
+ ),
+ 'files' => '文件相关',
+ 'json' => array(
+ 'nok' => '找不到 JSON 扩展 (php5-json ) 。',
+ 'ok' => '你已有 JSON 扩展',
+ ),
+ 'minz' => array(
+ 'nok' => '找不到 Minz 框架。',
+ 'ok' => '你已有 Minz 框架。',
+ ),
+ 'pcre' => array(
+ 'nok' => '找不到正则表达式解析库 (php-pcre) 。',
+ 'ok' => '你已有正则表达式解析库 (PCRE) 。',
+ ),
+ 'pdo' => array(
+ 'nok' => '找不到 PDO 或支持的驱动 (pdo_mysql, pdo_sqlite, pdo_pgsql) 。',
+ 'ok' => '你已有 PDO 和支持的至少一种驱动 (pdo_mysql, pdo_sqlite, pdo_pgsql) 。',
+ ),
+ 'php' => array(
+ '_' => 'PHP 相关',
+ 'nok' => '你的 PHP 版本为 %s,但 FreshRSS 最低需要 %s。',
+ 'ok' => '你的 PHP 版本为 %s,与 FreshRSS 兼容。',
+ ),
+ 'tables' => array(
+ 'nok' => '数据库中缺少一个或多个表。',
+ 'ok' => '数据库中相关表存在。',
+ ),
+ 'title' => '环境检查',
+ 'tokens' => array(
+ 'nok' => '请检查 <em>./data/tokens</em> 目录权限。HTTP 服务器必须有其写入权限。',
+ 'ok' => 'tokens 目录权限正常。',
+ ),
+ 'users' => array(
+ 'nok' => '请检查 <em>./data/users</em> 目录权限。HTTP 服务器必须有其写入权限。',
+ 'ok' => 'users 目录权限正常。',
+ ),
+ 'zip' => array(
+ 'nok' => '找不到 ZIP 扩展 (php-zip) 。',
+ 'ok' => '你已有 ZIP 扩展。',
+ ),
+ ),
+ 'extensions' => array(
+ 'disabled' => '已禁用',
+ 'empty_list' => '没有已安装的扩展',
+ 'enabled' => '已启用',
+ 'no_configure_view' => '此扩展不能配置。',
+ 'system' => array(
+ '_' => '系统扩展',
+ 'no_rights' => '系统扩展 (你不能修改它)',
+ ),
+ 'title' => '扩展',
+ 'user' => '用户扩展',
+ ),
+ 'stats' => array(
+ '_' => '统计',
+ 'all_feeds' => '所有 RSS 源',
+ 'category' => '分类',
+ 'entry_count' => '条目数',
+ 'entry_per_category' => '每分类条目数',
+ 'entry_per_day' => '每天条目数 (最近 30 天)',
+ 'entry_per_day_of_week' => '周内每天 (平均: %.2f 条消息)',
+ 'entry_per_hour' => '每小时 (平均: %.2f 条消息)',
+ 'entry_per_month' => '每月 (平均: %.2f 条消息)',
+ 'entry_repartition' => '条目分布',
+ 'feed' => 'RSS 源',
+ 'feed_per_category' => '每分类 RSS 源',
+ 'idle' => '闲置 RSS 源',
+ 'main' => '主要统计',
+ 'main_stream' => '首页',
+ 'menu' => array(
+ 'idle' => '闲置 RSS 源',
+ 'main' => '主要统计',
+ 'repartition' => '文章分布',
+ ),
+ 'no_idle' => '无闲置 RSS 源!',
+ 'number_entries' => '%d 篇文章',
+ 'percent_of_total' => '%%',
+ 'repartition' => '文章分布',
+ 'status_favorites' => '收藏',
+ 'status_read' => '已读',
+ 'status_total' => '总计',
+ 'status_unread' => '未读',
+ 'title' => '统计',
+ 'top_feed' => '前十 RSS 源',
+ ),
+ 'system' => array(
+ '_' => '系统配置',
+ 'auto-update-url' => '自动升级服务器 URL',
+ 'instance-name' => '实例名称',
+ 'max-categories' => '每用户分类限制',
+ 'max-feeds' => '每用户 RSS 源限制',
+ 'registration' => array(
+ 'help' => '0 表示无账户数限制',
+ 'number' => '最大账户数',
+ ),
+ ),
+ 'update' => array(
+ '_' => '更新系统',
+ 'apply' => '应用',
+ 'check' => '检查更新',
+ 'current_version' => '当前 FreshRSS 版本为 %s.',
+ 'last' => '上一次检查: %s',
+ 'none' => '没有可用更新',
+ 'title' => '更新系统',
+ ),
+ 'user' => array(
+ 'articles_and_size' => '%s 篇文章 (%s)',
+ 'create' => '创建新用户',
+ 'language' => '语言',
+ 'number' => '已有 %d 个帐户',
+ 'numbers' => '已有 %d 个帐户',
+ 'password_form' => '密码<br /><small>(用于 Web-form 登录方式)</small>',
+ 'password_format' => '至少 7 个字符',
+ 'title' => '用户管理',
+ 'user_list' => '用户列表',
+ 'username' => '用户名',
+ 'users' => '用户',
+ ),
+);
diff --git a/app/i18n/zh-cn/conf.php b/app/i18n/zh-cn/conf.php
new file mode 100644
index 000000000..6ac0bb5e7
--- /dev/null
+++ b/app/i18n/zh-cn/conf.php
@@ -0,0 +1,174 @@
+<?php
+
+return array(
+ 'archiving' => array(
+ '_' => '存档',
+ 'advanced' => '高级',
+ 'delete_after' => '文章保留',
+ 'help' => '详细选项位于单独的 RSS 源设置',
+ 'keep_history_by_feed' => '至少保存的文章数',
+ 'optimize' => '优化数据库',
+ 'optimize_help' => '不时地执行优化可以减少数据库大小',
+ 'purge_now' => '立即清除',
+ 'title' => '存档',
+ 'ttl' => '最小自动更新时间',
+ ),
+ 'display' => array(
+ '_' => '显示',
+ 'icon' => array(
+ 'bottom_line' => '底栏',
+ 'entry' => '文章图标',
+ 'publication_date' => '更新日期',
+ 'related_tags' => '相关标签',
+ 'sharing' => '分享',
+ 'top_line' => '顶栏',
+ ),
+ 'language' => '语言',
+ 'notif_html5' => array(
+ 'seconds' => '秒 (0 表示不超时)',
+ 'timeout' => 'HTML5 通知超时时间',
+ ),
+ 'theme' => '主题',
+ 'title' => '显示',
+ 'width' => array(
+ 'content' => '内容宽度',
+ 'large' => '大',
+ 'medium' => '中',
+ 'no_limit' => '无限制',
+ 'thin' => '小',
+ ),
+ ),
+ 'query' => array(
+ '_' => '自定义查询',
+ 'deprecated' => '此查询不再有效。相关的分类或 RSS 源已被删除。',
+ 'filter' => '生效的过滤器:',
+ 'get_all' => '显示所有文章',
+ 'get_category' => '显示分类 "%s"',
+ 'get_favorite' => '显示收藏文章',
+ 'get_feed' => '显示RSS 源 "%s"',
+ 'no_filter' => '无过滤器',
+ 'none' => '你未创建任何自定义查询。',
+ 'number' => '查询 n°%d',
+ 'order_asc' => '由旧到新显示文章',
+ 'order_desc' => '由新到旧显示文章',
+ 'search' => '搜索 "%s"',
+ 'state_0' => '显示所有文章',
+ 'state_1' => '显示已读文章',
+ 'state_2' => '显示未读文章',
+ 'state_3' => '显示所有文章',
+ 'state_4' => '显示收藏文章',
+ 'state_5' => '显示已读的收藏文章',
+ 'state_6' => '显示未读的收藏文章',
+ 'state_7' => '显示收藏文章',
+ 'state_8' => '显示未收藏文章',
+ 'state_9' => '显示已读的未收藏文章',
+ 'state_10' => '显示未读的未收藏文章',
+ 'state_11' => '显示未收藏文章',
+ 'state_12' => '显示所有文章',
+ 'state_13' => '显示已读文章',
+ 'state_14' => '显示未读文章',
+ 'state_15' => '显示所有文章',
+ 'title' => '自定义查询',
+ ),
+ 'profile' => array(
+ '_' => '帐户管理',
+ 'delete' => array(
+ '_' => '账户删除',
+ 'warn' => '你的帐户和所有相关数据都将被删除。',
+ ),
+ 'password_api' => 'API 密码<br /><small>(例如,用于手机 APP)</small>',
+ 'password_form' => '密码<br /><small>(用于 Web-form 登录方式)</small>',
+ 'password_format' => '至少 7 个字符',
+ 'title' => '用户帐户',
+ ),
+ 'reading' => array(
+ '_' => '阅读',
+ 'after_onread' => '“全部设为已读”后,',
+ 'articles_per_page' => '每页文章数',
+ 'auto_load_more' => '在页面底部载入下一篇文章',
+ 'auto_remove_article' => '阅读后隐藏文章',
+ 'mark_updated_article_unread' => '将有更新的文章设为未读',
+ 'confirm_enabled' => '“全部设为已读”时显示确认对话框',
+ 'display_articles_unfolded' => '默认展开文章',
+ 'display_categories_unfolded' => '默认展开分类',
+ 'hide_read_feeds' => '隐藏没有未读文章的分类或 RSS 源 (启用“显示所有文章”时不生效))',
+ 'img_with_lazyload' => '使用 "lazy load" 模式加载图片',
+ 'sides_close_article' => '点击文章外区域以关闭文章',
+ 'jump_next' => '跳转到下一未读项 (RSS 源或分类)',
+ 'number_divided_when_reader' => '阅读视图除以 2',
+ 'read' => array(
+ 'article_open_on_website' => '在打开原文章后',
+ 'article_viewed' => '在文章被浏览后',
+ 'scroll' => '在滚动浏览后',
+ 'upon_reception' => '在接收文章后',
+ 'when' => '将文章设为已读…',
+ ),
+ 'show' => array(
+ '_' => '文章显示',
+ 'adaptive' => '智能显示',
+ 'all_articles' => '显示所有文章',
+ 'unread' => '只显示未读',
+ ),
+ 'sort' => array(
+ '_' => '排列顺序',
+ 'newer_first' => '由新到旧',
+ 'older_first' => '由旧到新',
+ ),
+ 'sticky_post' => '打开文章时将其置顶',
+ 'title' => '阅读',
+ 'view' => array(
+ 'default' => '默认视图',
+ 'global' => '全屏视图',
+ 'normal' => '普通视图',
+ 'reader' => '阅读视图',
+ ),
+ ),
+ 'sharing' => array(
+ '_' => '分享',
+ 'blogotext' => 'Blogotext',
+ 'diaspora' => 'Diaspora*',
+ 'email' => 'Email',
+ 'facebook' => 'Facebook',
+ 'g+' => 'Google+',
+ 'more_information' => '更多信息',
+ 'print' => '打印',
+ 'shaarli' => 'Shaarli',
+ 'share_name' => '名称',
+ 'share_url' => 'URL',
+ 'title' => '分享',
+ 'twitter' => 'Twitter',
+ 'wallabag' => 'wallabag',
+ ),
+ 'shortcut' => array(
+ '_' => '快捷键',
+ 'article_action' => '文章操作',
+ 'auto_share' => '分享',
+ 'auto_share_help' => '如果有多种分享模式,则会按照它们的编号访问。',
+ 'close_dropdown' => '关闭菜单',
+ 'collapse_article' => '收起文章',
+ 'first_article' => '跳转到第一篇文章',
+ 'focus_search' => '聚焦到搜索框',
+ 'help' => '显示帮助文档',
+ 'javascript' => '若要使用快捷键,必须启用 JavaScript',
+ 'last_article' => '跳转到最后一篇文章',
+ 'load_more' => '载入更多文章',
+ 'mark_read' => '设为已读',
+ 'mark_favorite' => '加入收藏',
+ 'navigation' => '浏览',
+ 'navigation_help' => '搭配 "Shift" 键,浏览快捷键将生效于 RSS 源。<br/>搭配 "Alt" 键,浏览快捷键将生效于分类。',
+ 'next_article' => '跳转到下一篇文章',
+ 'other_action' => '其他操作',
+ 'previous_article' => '跳转到上一篇文章',
+ 'see_on_website' => '在原网站上查看',
+ 'shift_for_all_read' => '+ <code>shift</code> 可以将全部文章设为已读',
+ 'title' => '快捷键',
+ 'user_filter' => '显示自定义查询',
+ 'user_filter_help' => '如果有多个自定义过滤器,则会按照它们的编号访问。',
+ ),
+ 'user' => array(
+ 'articles_and_size' => '%s 篇文章 (%s)',
+ 'current' => '当前用户',
+ 'is_admin' => '此用户为管理员',
+ 'users' => '用户',
+ ),
+);
diff --git a/app/i18n/zh-cn/feedback.php b/app/i18n/zh-cn/feedback.php
new file mode 100644
index 000000000..bee5914e9
--- /dev/null
+++ b/app/i18n/zh-cn/feedback.php
@@ -0,0 +1,109 @@
+<?php
+
+return array(
+ 'admin' => array(
+ 'optimization_complete' => '优化完成',
+ ),
+ 'access' => array(
+ 'denied' => '你无权访问此页面',
+ 'not_found' => '你寻找的页面不存在',
+ ),
+ 'auth' => array(
+ 'form' => array(
+ 'not_set' => '配置认证方式时出错。请稍后重试。',
+ 'set' => 'Form 是你当前默认的认证方式。',
+ ),
+ 'login' => array(
+ 'invalid' => '用户名或密码无效',
+ 'success' => '你已成功登录',
+ ),
+ 'logout' => array(
+ 'success' => '你已登出',
+ ),
+ 'no_password_set' => '管理员密码尚未设置。此特性不可用。',
+ ),
+ 'conf' => array(
+ 'error' => '保存配置时出错',
+ 'query_created' => '查询 "%s" 已创建。',
+ 'shortcuts_updated' => '快捷键已更新',
+ 'updated' => '配置已更新',
+ ),
+ 'extensions' => array(
+ 'already_enabled' => '%s 已启用',
+ 'disable' => array(
+ 'ko' => '%s 禁用失败。<a href="%s">检查 FressRSS 日志</a> 查看详情。',
+ 'ok' => '%s 现已禁用',
+ ),
+ 'enable' => array(
+ 'ko' => '%s 启用失败。<a href="%s">检查 FressRSS 日志</a> 查看详情。',
+ 'ok' => '%s 现已禁用',
+ ),
+ 'no_access' => '你无权访问 %s',
+ 'not_enabled' => '%s 未启用',
+ 'not_found' => '%s 不存在',
+ ),
+ 'import_export' => array(
+ 'export_no_zip_extension' => '服务器未启用 ZIP 扩展。请尝试一个一个导出文件。',
+ 'feeds_imported' => '你的 RSS 源已导入,即将更新',
+ 'feeds_imported_with_errors' => '你的 RSS 源已导入,但发生错误',
+ 'file_cannot_be_uploaded' => '文件未能上传!',
+ 'no_zip_extension' => '服务器未启用 ZIP 扩展。',
+ 'zip_error' => '导入 ZIP 文件时出错',
+ ),
+ 'sub' => array(
+ 'actualize' => '获取',
+ 'category' => array(
+ 'created' => '分类 %s 已创建。',
+ 'deleted' => '分类已删除。',
+ 'emptied' => '分类已清空。',
+ 'error' => '分类更新失败。',
+ 'name_exists' => '分类名已存在。',
+ 'no_id' => '你必须明确分类 ID',
+ 'no_name' => '分类名不能为空。',
+ 'not_delete_default' => '你不能删除默认分类!',
+ 'not_exist' => '分类不存在!',
+ 'over_max' => '你已达到分类数限制 (%d)',
+ 'updated' => '分类已更新。',
+ ),
+ 'feed' => array(
+ 'actualized' => '<em>%s</em> 已更新',
+ 'actualizeds' => 'RSS 源已更新',
+ 'added' => 'RSS 源 <em>%s</em> 已添加',
+ 'already_subscribed' => '你已订阅 <em>%s</em>',
+ 'deleted' => 'RSS 源已删除',
+ 'error' => 'RSS 源更新失败',
+ 'internal_problem' => 'RSS 源添加失败。<a href="%s">检查 FressRSS 日志</a> 查看详情。',
+ 'invalid_url' => 'URL <em>%s</em> 无效',
+ 'marked_read' => 'RSS 源已被设为已读',
+ 'n_actualized' => '%d 个 RSS 源已更新',
+ 'n_entries_deleted' => '%d 篇文章已删除',
+ 'no_refresh' => '没有可刷新的 RSS 源…',
+ 'not_added' => '<em>%s</em> 添加失败',
+ 'over_max' => '你已达到 RSS 源数限制 (%d)',
+ 'updated' => 'RSS 源已更新',
+ ),
+ 'purge_completed' => '清除完成 (%d 篇文章已删除)',
+ ),
+ 'update' => array(
+ 'can_apply' => 'FreshRSS 将更新到 <strong>版本 %s</strong>.',
+ 'error' => '更新出错:%s',
+ 'file_is_nok' => '请检查 <em>%s</em> 目录权限。HTTP 服务器必须有其写入权限。',
+ 'finished' => '更新完成!',
+ 'none' => '没有可用更新',
+ 'server_not_found' => '找不到更新服务器 [%s]',
+ ),
+ 'user' => array(
+ 'created' => array(
+ '_' => '用户 %s 已创建',
+ 'error' => '用户 %s 创建失败',
+ ),
+ 'deleted' => array(
+ '_' => '用户 %s 已删除',
+ 'error' => '用户 %s 删除失败',
+ ),
+ ),
+ 'profile' => array(
+ 'error' => '你的帐户修改失败',
+ 'updated' => '你的帐户已修改',
+ ),
+);
diff --git a/app/i18n/zh-cn/gen.php b/app/i18n/zh-cn/gen.php
new file mode 100644
index 000000000..a4ef03bc9
--- /dev/null
+++ b/app/i18n/zh-cn/gen.php
@@ -0,0 +1,185 @@
+<?php
+
+return array(
+ 'action' => array(
+ 'actualize' => '获取',
+ 'back_to_rss_feeds' => '← 返回',
+ 'cancel' => '取消',
+ 'create' => '创建',
+ 'disable' => '禁用',
+ 'empty' => '清空',
+ 'enable' => '启用',
+ 'export' => '导出',
+ 'filter' => '过滤器',
+ 'import' => '导入',
+ 'manage' => '管理',
+ 'mark_read' => '设为已读',
+ 'mark_favorite' => '加入收藏',
+ 'remove' => '删除',
+ 'see_website' => '查看网站',
+ 'submit' => '提交',
+ 'truncate' => '删除所有文章',
+ ),
+ 'auth' => array(
+ 'email' => 'Email 地址',
+ 'keep_logged_in' => '自动登录<small>(%s 天)</small>',
+ 'login' => '登录',
+ 'logout' => '登出',
+ 'password' => array(
+ '_' => '密码',
+ 'format' => '<small>至少 7 个字符</small>',
+ ),
+ 'registration' => array(
+ '_' => '新账户',
+ 'ask' => '创建新账户?',
+ 'title' => '账户创建',
+ ),
+ 'reset' => '认证重置',
+ 'username' => array(
+ '_' => '用户名',
+ 'admin' => '管理员用户名',
+ 'format' => '<small>最大 16 个数字或字母</small>',
+ ),
+ ),
+ 'date' => array(
+ 'Apr' => '\\A\\p\\r\\i\\l',
+ 'Aug' => '\\A\\u\\g\\u\\s\\t',
+ 'Dec' => '\\D\\e\\c\\e\\m\\b\\e\\r',
+ 'Feb' => '\\F\\e\\b\\r\\u\\a\\r\\y',
+ 'Jan' => '\\J\\a\\n\\u\\a\\r\\y',
+ 'Jul' => '\\J\\u\\l\\y',
+ 'Jun' => '\\J\\u\\n\\e',
+ 'Mar' => '\\M\\a\\r\\c\\h',
+ 'May' => '\\M\\a\\y',
+ 'Nov' => '\\N\\o\\v\\e\\m\\b\\e\\r',
+ 'Oct' => '\\O\\c\\t\\o\\b\\e\\r',
+ 'Sep' => '\\S\\e\\p\\t\\e\\m\\b\\e\\r',
+ 'apr' => '四月',
+ 'april' => '四月',
+ 'aug' => '八月',
+ 'august' => '八月',
+ 'before_yesterday' => '昨天以前',
+ 'dec' => '十二月',
+ 'december' => '十二月',
+ 'feb' => '二月',
+ 'february' => '二月',
+ 'format_date' => 'Y\\年n\\月j\\日',
+ 'format_date_hour' => 'Y\\年n\\月j\\日 H\\:i',
+ 'fri' => '周五',
+ 'jan' => '一月',
+ 'january' => '一月',
+ 'jul' => '七月',
+ 'july' => '七月',
+ 'jun' => '六月',
+ 'june' => '六月',
+ 'last_3_month' => '最近三个月',
+ 'last_6_month' => '最近六个月',
+ 'last_month' => '上月',
+ 'last_week' => '上周',
+ 'last_year' => '去年',
+ 'mar' => '三月',
+ 'march' => '三月',
+ 'may' => '五月',
+ 'mon' => '周一',
+ 'month' => '个月',
+ 'nov' => '十一月',
+ 'november' => '十一月',
+ 'oct' => '十月',
+ 'october' => '十月',
+ 'sat' => '周日',
+ 'sep' => '九月',
+ 'september' => '九月',
+ 'sun' => '周日',
+ 'thu' => '周四',
+ 'today' => '今天',
+ 'tue' => '周二',
+ 'wed' => '周三',
+ 'yesterday' => '昨天',
+ ),
+ 'freshrss' => array(
+ '_' => 'FreshRSS',
+ 'about' => '关于 FreshRSS',
+ ),
+ 'js' => array(
+ 'category_empty' => '清空分类',
+ 'confirm_action' => '你确定要执行此操作吗?这将不可撤销!',
+ 'confirm_action_feed_cat' => '你确定要执行此操作吗?你将丢失相关的收藏和自定义查询。这将不可撤销!',
+ 'feedback' => array(
+ 'body_new_articles' => 'FreshRSS 中有 %%d 篇文章等待阅读。',
+ 'request_failed' => '请求失败,这可能是因为网络连接问题。',
+ 'title_new_articles' => 'FreshRSS: 新文章!',
+ ),
+ 'new_article' => '发现新文章,点击刷新页面。',
+ 'should_be_activated' => 'JavaScript 必须启用',
+ ),
+ 'lang' => array(
+ 'cz' => 'Čeština',
+ 'de' => 'Deutsch',
+ 'en' => 'English1',
+ 'fr' => 'Français',
+ 'it' => 'Italiano1',
+ 'nl' => 'Nederlands',
+ 'ru' => 'Русский',
+ 'tr' => 'Türkçe',
+ 'zh-cn' => '简体中文'
+ ),
+ 'menu' => array(
+ 'about' => '关于',
+ 'admin' => '管理',
+ 'archiving' => '存档',
+ 'authentication' => '认证',
+ 'check_install' => '环境检查',
+ 'configuration' => '配置',
+ 'display' => '显示',
+ 'extensions' => '扩展',
+ 'logs' => '日志',
+ 'queries' => '自定义查询',
+ 'reading' => '阅读',
+ 'search' => '搜索内容或#标签',
+ 'sharing' => '分享',
+ 'shortcuts' => '快捷键',
+ 'stats' => '统计',
+ 'system' => '系统配置',
+ 'update' => '更新',
+ 'user_management' => '用户管理',
+ 'user_profile' => '用户帐户',
+ ),
+ 'pagination' => array(
+ 'first' => '第一页',
+ 'last' => '最后一页',
+ 'load_more' => '载入更多文章',
+ 'mark_all_read' => '全部设为已读',
+ 'next' => '下一页',
+ 'nothing_to_load' => '没有更多文章了',
+ 'previous' => '上一页',
+ ),
+ 'share' => array(
+ 'blogotext' => 'Blogotext',
+ 'diaspora' => 'Diaspora*',
+ 'email' => 'Email',
+ 'facebook' => 'Facebook',
+ 'g+' => 'Google+',
+ 'movim' => 'Movim',
+ 'print' => 'Print',
+ 'shaarli' => 'Shaarli',
+ 'twitter' => 'Twitter',
+ 'wallabag' => 'wallabag v1',
+ 'wallabagv2' => 'wallabag v2',
+ 'jdh' => 'Journal du hacker',
+ 'Known' => 'Known based sites',
+ 'gnusocial' => 'GNU social',
+ ),
+ 'short' => array(
+ 'attention' => '警告!',
+ 'blank_to_disable' => '留空以禁用',
+ 'by_author' => 'By <em>%s</em>',
+ 'by_default' => '默认',
+ 'damn' => '错误!',
+ 'default_category' => '未分类',
+ 'no' => '否',
+ 'not_applicable' => '不可用',
+ 'ok' => '正常!',
+ 'or' => '或',
+ 'yes' => '是',
+ ),
+);
diff --git a/app/i18n/zh-cn/index.php b/app/i18n/zh-cn/index.php
new file mode 100644
index 000000000..0d6e8e82d
--- /dev/null
+++ b/app/i18n/zh-cn/index.php
@@ -0,0 +1,61 @@
+<?php
+
+return array(
+ 'about' => array(
+ '_' => '关于',
+ 'agpl3' => '<a href="https://www.gnu.org/licenses/agpl-3.0.html">AGPL 3</a>',
+ 'bugs_reports' => 'Bug 报告',
+ 'credits' => '致谢',
+ 'credits_content' => '某些设计元素来自于 <a href="http://twitter.github.io/bootstrap/">Bootstrap</a> ,尽管 FreshRSS 并没有使用此框架。<a href="https://git.gnome.org/browse/gnome-icon-theme-symbolic">图标</a> 来自于 <a href="https://www.gnome.org/">GNOME 项目</a>。<em>Open Sans</em> 字体出自 <a href="https://fonts.google.com/specimen/Open+Sans">Steve Matteson</a> 之手。FreshRSS 基于 PHP 框架 <a href="https://github.com/marienfressinaud/MINZ">Minz</a>。',
+ 'freshrss_description' => 'FreshRSS 是一个自托管的 RSS 聚合服务,类似于 <a href="http://tontof.net/kriss/feed/">Kriss Feed</a> 或 <a href="http://projet.idleman.fr/leed/">Leed</a>。 它不仅轻快又易用,而且强大又易于配置。',
+ 'github' => '<a href="https://github.com/FreshRSS/FreshRSS/issues">Github Issues</a>',
+ 'license' => '授权',
+ 'project_website' => '项目网站',
+ 'title' => '关于',
+ 'version' => '版本',
+ 'website' => '网站',
+ ),
+ 'feed' => array(
+ 'add' => '你可以添加一些 RSS 源。',
+ 'empty' => '暂时没有文章可显示。',
+ 'rss_of' => '%s 的 RSS 源',
+ 'title' => '首页',
+ 'title_global' => '全屏视图',
+ 'title_fav' => '收藏',
+ ),
+ 'log' => array(
+ '_' => '日志',
+ 'clear' => '清除日志',
+ 'empty' => '日志文件为空',
+ 'title' => '日志',
+ ),
+ 'menu' => array(
+ 'about' => '关于 FreshRSS',
+ 'add_query' => '添加查询',
+ 'before_one_day' => '一天前',
+ 'before_one_week' => '一周前',
+ 'favorites' => '收藏 (%s)',
+ 'global_view' => '全屏视图',
+ 'main_stream' => '首页',
+ 'mark_all_read' => '全部设为已读',
+ 'mark_cat_read' => '此分类设为已读',
+ 'mark_feed_read' => '此源设为已读',
+ 'newer_first' => '由新到旧',
+ 'non-starred' => '不显示收藏',
+ 'normal_view' => '普通视图',
+ 'older_first' => '由旧到新',
+ 'queries' => '自定义查询',
+ 'read' => '只显示已读',
+ 'reader_view' => '阅读视图',
+ 'rss_view' => 'RSS 源',
+ 'search_short' => '搜索',
+ 'starred' => '只显示收藏',
+ 'stats' => '统计',
+ 'subscription' => '订阅管理',
+ 'unread' => '只显示未读',
+ ),
+ 'share' => '分享',
+ 'tag' => array(
+ 'related' => '相关标签',
+ ),
+);
diff --git a/app/i18n/zh-cn/install.php b/app/i18n/zh-cn/install.php
new file mode 100644
index 000000000..f247a20fd
--- /dev/null
+++ b/app/i18n/zh-cn/install.php
@@ -0,0 +1,119 @@
+<?php
+
+return array(
+ 'action' => array(
+ 'finish' => '完成安装',
+ 'fix_errors_before' => '请在继续下一步前修复错误。',
+ 'keep_install' => '保留以前配置',
+ 'next_step' => '下一步',
+ 'reinstall' => '重新安装 FreshRSS',
+ ),
+ 'auth' => array(
+ 'form' => 'Web form (传统方式, 需要 JavaScript)',
+ 'http' => 'HTTP (面向启用 HTTPS 的高级用户)',
+ 'none' => '无 (危险)',
+ 'password_form' => '密码<br /><small>(用于 Web-form 登录方式)</small>',
+ 'password_format' => '至少 7 个字符',
+ 'type' => '认证方式',
+ ),
+ 'bdd' => array(
+ '_' => '数据库',
+ 'conf' => array(
+ '_' => '数据库配置',
+ 'ko' => '请验证你的数据库信息。',
+ 'ok' => '数据库配置已保存。',
+ ),
+ 'host' => '主机',
+ 'prefix' => '表前缀',
+ 'password' => '密码',
+ 'type' => '数据库类型',
+ 'username' => '用户名',
+ ),
+ 'check' => array(
+ '_' => '检查',
+ 'already_installed' => '我们检测到 FreshRSS 已经安装!',
+ 'cache' => array(
+ 'nok' => '请检查 <em>./data/cache</em> 目录权限。HTTP 服务器必须有其写入权限。',
+ 'ok' => 'cache 目录权限正常。',
+ ),
+ 'ctype' => array(
+ 'nok' => '找不到字符类型检测库 (php-ctype) 。',
+ 'ok' => '你已有字符类型检测库 (ctype) 。',
+ ),
+ 'curl' => array(
+ 'nok' => '找不到 cURL 库 (php-curl package) 。',
+ 'ok' => '你已有 cURL 库。',
+ ),
+ 'data' => array(
+ 'nok' => '请检查 <em>./data</em> 目录权限。HTTP 服务器必须有其写入权限。',
+ 'ok' => 'data 目录权限正常。',
+ ),
+ 'dom' => array(
+ 'nok' => '找不到用于浏览 DOM 的库 (php-xml) 。',
+ 'ok' => '你已有用于浏览 DOM 的库。',
+ ),
+ 'favicons' => array(
+ 'nok' => '请检查 <em>./data/favicons</em> 目录权限。HTTP 服务器必须有其写入权限。',
+ 'ok' => 'favicons 目录权限正常。',
+ ),
+ 'fileinfo' => array(
+ 'nok' => '找不到 PHP fileinfo 库 (fileinfo) 。',
+ 'ok' => '你已有 fileinfo 库。',
+ ),
+ 'http_referer' => array(
+ 'nok' => '请检查你是否修改了 HTTP REFERER。',
+ 'ok' => '你的 HTTP REFERER 已知且与服务器一致。',
+ ),
+ 'json' => array(
+ 'nok' => '找不到推荐的 JSON 解析库。',
+ 'ok' => '你已有推荐的 JSON 解析库。',
+ ),
+ 'minz' => array(
+ 'nok' => '找不到 Minz 框架。',
+ 'ok' => '你已有 Minz 框架。',
+ ),
+ 'pcre' => array(
+ 'nok' => '找不到正则表达式解析库 (php-pcre) 。',
+ 'ok' => '你已有正则表达式解析库 (PCRE) 。',
+ ),
+ 'pdo' => array(
+ 'nok' => '找不到 PDO 或支持的驱动 (pdo_mysql, pdo_sqlite, pdo_pgsql) 。',
+ 'ok' => '你已有 PDO 和支持的至少一种驱动 (pdo_mysql, pdo_sqlite, pdo_pgsql) 。',
+ ),
+ 'php' => array(
+ 'nok' => '你的 PHP 版本为 %s,但 FreshRSS 最低需要 %s。',
+ 'ok' => '你的 PHP 版本为 %s,与 FreshRSS 兼容。',
+ ),
+ 'users' => array(
+ 'nok' => '请检查 <em>./data/users</em> 目录权限。HTTP 服务器必须有其写入权限。',
+ 'ok' => 'users 目录权限正常。',
+ ),
+ 'xml' => array(
+ 'nok' => '找不到用于 XML 解析库。',
+ 'ok' => '你已有 XML 解析库。',
+ ),
+ ),
+ 'conf' => array(
+ '_' => '常规配置',
+ 'ok' => '常规配置已保存。',
+ ),
+ 'congratulations' => '恭喜!',
+ 'default_user' => '默认用户名 <small>(最大 16 个数字或字母)</small>',
+ 'delete_articles_after' => '保留文章',
+ 'fix_errors_before' => '请在继续下一步前修复错误。',
+ 'javascript_is_better' => '启用 JavaScript 会使 FreshRSS 工作得更好',
+ 'js' => array(
+ 'confirm_reinstall' => '重新安装 FreshRSS 将会重置之前的配置。你确定要继续吗?',
+ ),
+ 'language' => array(
+ '_' => '语言',
+ 'choose' => '为 FreshRSS 选择语言',
+ 'defined' => '语言已指定。',
+ ),
+ 'not_deleted' => '出错!你必须手动删除文件 <em>%s</em>。',
+ 'ok' => '安装成功。',
+ 'step' => '步骤 %d',
+ 'steps' => '步骤',
+ 'title' => '安装 FreshRSS',
+ 'this_is_the_end' => '最后一步',
+);
diff --git a/app/i18n/zh-cn/sub.php b/app/i18n/zh-cn/sub.php
new file mode 100644
index 000000000..0c599e986
--- /dev/null
+++ b/app/i18n/zh-cn/sub.php
@@ -0,0 +1,62 @@
+<?php
+
+return array(
+ 'category' => array(
+ '_' => '分类',
+ 'add' => '添加分类',
+ 'empty' => '空分类',
+ 'new' => '新分类',
+ ),
+ 'feed' => array(
+ 'add' => '添加 RSS 源',
+ 'advanced' => '高级',
+ 'archiving' => '存档',
+ 'auth' => array(
+ 'configuration' => '认证',
+ 'help' => '连接启用 HTTP 认证的 RSS 源',
+ 'http' => 'HTTP 认证',
+ 'password' => 'HTTP 密码',
+ 'username' => 'HTTP 用户名',
+ ),
+ 'css_help' => '获取全文(注意,会耗费更多时间!)',
+ 'css_path' => '原网站中文章的 CSS 路径',
+ 'description' => '描述',
+ 'empty' => '此源为空。请确认它是否正常更新。',
+ 'error' => '此源遇到一些问题。请确认它是否可访问后重试。',
+ 'in_main_stream' => '在首页中显示',
+ 'informations' => '信息',
+ 'keep_history' => '至少保存的文章数',
+ 'moved_category_deleted' => '删除分类时,其中的 RSS 源会自动归类到<em>%s</em>。',
+ 'no_selected' => '未选择 RSS 源。',
+ 'number_entries' => '%d 篇文章',
+ 'stats' => '统计',
+ 'think_to_add' => '你可以添加一些 RSS 源。',
+ 'title' => '标题',
+ 'title_add' => '添加 RSS 源',
+ 'ttl' => '最小自动更新时间',
+ 'url' => '源 URL',
+ 'validator' => '检查 RSS 源有效性',
+ 'website' => '网站 URL',
+ 'pubsubhubbub' => 'PubSubHubbub 即时通知',
+ ),
+ 'import_export' => array(
+ 'export' => '导出',
+ 'export_opml' => '导出 RSS 源列表 (OPML)',
+ 'export_starred' => '导出你的收藏',
+ 'feed_list' => '%s 文章列表',
+ 'file_to_import' => '需要导入的文件<br />(OPML, JSON 或 ZIP)',
+ 'file_to_import_no_zip' => '需要导入的文件<br />(OPML 或 JSON)',
+ 'import' => '导入',
+ 'starred_list' => '收藏文章列表',
+ 'title' => '导入/导出',
+ ),
+ 'menu' => array(
+ 'bookmark' => '订阅 (FreshRSS 书签)',
+ 'import_export' => '导入/导出',
+ 'subscription_management' => '订阅管理',
+ ),
+ 'title' => array(
+ '_' => '订阅管理',
+ 'feed_management' => 'RSS 源管理',
+ ),
+);
diff --git a/app/install.php b/app/install.php
index 9a88e0f37..9e474ca73 100644
--- a/app/install.php
+++ b/app/install.php
@@ -88,13 +88,13 @@ function saveStep1() {
// First, we try to get previous configurations
Minz_Configuration::register('system',
join_path(DATA_PATH, 'config.php'),
- join_path(DATA_PATH, 'config.default.php'));
+ join_path(FRESHRSS_PATH, 'config.default.php'));
$system_conf = Minz_Configuration::get('system');
$current_user = $system_conf->default_user;
Minz_Configuration::register('user',
join_path(USERS_PATH, $current_user, 'config.php'),
- join_path(USERS_PATH, '_', 'config.default.php'));
+ join_path(FRESHRSS_PATH, 'config-user.default.php'));
$user_conf = Minz_Configuration::get('user');
// Then, we set $_SESSION vars
@@ -342,35 +342,19 @@ function checkDbUser(&$dbOptions) {
$driver_options = $dbOptions['options'];
try {
$c = new PDO($str, $dbOptions['user'], $dbOptions['password'], $driver_options);
-
if (defined('SQL_CREATE_TABLES')) {
- $sql = sprintf(SQL_CREATE_TABLES, $dbOptions['prefix_user'], _t('gen.short.default_category'));
- $stm = $c->prepare($sql);
- $ok = $stm->execute();
- } else {
- global $SQL_CREATE_TABLES;
- if (is_array($SQL_CREATE_TABLES)) {
- $ok = true;
- foreach ($SQL_CREATE_TABLES as $instruction) {
- $sql = sprintf($instruction, $dbOptions['prefix_user'], _t('gen.short.default_category'));
- $stm = $c->prepare($sql);
- $ok &= $stm->execute();
- }
- }
- }
-
- if (defined('SQL_INSERT_FEEDS')) {
- $sql = sprintf(SQL_INSERT_FEEDS, $dbOptions['prefix_user']);
+ $sql = sprintf(SQL_CREATE_TABLES . SQL_CREATE_TABLE_ENTRYTMP . SQL_INSERT_FEEDS,
+ $dbOptions['prefix_user'], _t('gen.short.default_category'));
$stm = $c->prepare($sql);
- $ok &= $stm->execute();
+ $ok = $stm && $stm->execute();
} else {
- global $SQL_INSERT_FEEDS;
- if (is_array($SQL_INSERT_FEEDS)) {
- foreach ($SQL_INSERT_FEEDS as $instruction) {
- $sql = sprintf($instruction, $dbOptions['prefix_user']);
- $stm = $c->prepare($sql);
- $ok &= $stm->execute();
- }
+ global $SQL_CREATE_TABLES, $SQL_CREATE_TABLE_ENTRYTMP, $SQL_INSERT_FEEDS;
+ $instructions = array_merge($SQL_CREATE_TABLES, $SQL_CREATE_TABLE_ENTRYTMP, $SQL_INSERT_FEEDS);
+ $ok = !empty($instructions);
+ foreach ($instructions as $instruction) {
+ $sql = sprintf($instruction, $dbOptions['prefix_user'], _t('gen.short.default_category'));
+ $stm = $c->prepare($sql);
+ $ok &= $stm && $stm->execute();
}
}
} catch (PDOException $e) {
@@ -481,7 +465,7 @@ function printStep1() {
<?php if ($res['fileinfo'] == 'ok') { ?>
<p class="alert alert-success"><span class="alert-head"><?php echo _t('gen.short.ok'); ?></span> <?php echo _t('install.check.fileinfo.ok'); ?></p>
<?php } else { ?>
- <p class="alert alert-error"><span class="alert-head"><?php echo _t('gen.short.damn'); ?></span> <?php echo _t('install.check.fileinfo.nok'); ?></p>
+ <p class="alert alert-warn"><span class="alert-head"><?php echo _t('gen.short.damn'); ?></span> <?php echo _t('install.check.fileinfo.nok'); ?></p>
<?php } ?>
<?php if ($res['data'] == 'ok') { ?>
@@ -629,7 +613,7 @@ function printStep3() {
<?php if (extension_loaded('pdo_pgsql')) {?>
<option value="pgsql"
<?php echo(isset($_SESSION['bd_type']) && $_SESSION['bd_type'] === 'pgsql') ? 'selected="selected"' : ''; ?>>
- PostgreSQL (⚠️ experimental)
+ PostgreSQL
</option>
<?php }?>
</select>
diff --git a/app/layout/nav_menu.phtml b/app/layout/nav_menu.phtml
index f6d824d55..04ee03cd6 100644
--- a/app/layout/nav_menu.phtml
+++ b/app/layout/nav_menu.phtml
@@ -149,6 +149,7 @@
<?php
$url_output['a'] = 'rss';
if (FreshRSS_Context::$user_conf->token) {
+ $url_output['params']['user'] = Minz_Session::param('currentUser');
$url_output['params']['token'] = FreshRSS_Context::$user_conf->token;
}
if (FreshRSS_Context::$user_conf->since_hours_posts_per_rss) {
diff --git a/app/views/auth/index.phtml b/app/views/auth/index.phtml
index 010eae33f..20966f24e 100644
--- a/app/views/auth/index.phtml
+++ b/app/views/auth/index.phtml
@@ -52,19 +52,6 @@
</div>
</div>
- <?php if (FreshRSS_Auth::accessNeedsAction()) { ?>
- <div class="form-group">
- <label class="group-name" for="token"><?php echo _t('admin.auth.token'); ?></label>
- <?php $token = FreshRSS_Context::$user_conf->token; ?>
- <div class="group-controls">
- <input type="text" id="token" name="token" value="<?php echo $token; ?>" placeholder="<?php echo _t('gen.short.blank_to_disable'); ?>"<?php
- echo FreshRSS_Auth::accessNeedsAction() ? '' : ' disabled="disabled"'; ?> data-leave-validation="<?php echo $token; ?>"/>
- <?php echo _i('help'); ?> <?php echo _t('admin.auth.token_help'); ?>
- <kbd><?php echo Minz_Url::display(array('a' => 'rss', 'params' => array('token' => $token, 'hours' => FreshRSS_Context::$user_conf->since_hours_posts_per_rss)), 'html', true); ?></kbd>
- </div>
- </div>
- <?php } ?>
-
<div class="form-group">
<div class="group-controls">
<label class="checkbox" for="api_enabled">
diff --git a/app/views/configure/reading.phtml b/app/views/configure/reading.phtml
index 07dabf15f..ebb00c97b 100644
--- a/app/views/configure/reading.phtml
+++ b/app/views/configure/reading.phtml
@@ -108,6 +108,16 @@
<div class="form-group">
<div class="group-controls">
+ <label class="checkbox" for="sides_close_article">
+ <input type="checkbox" name="sides_close_article" id="sides_close_article" value="1"<?php echo FreshRSS_Context::$user_conf->sides_close_article ? ' checked="checked"' : ''; ?> data-leave-validation="<?php echo FreshRSS_Context::$user_conf->sides_close_article; ?>"/>
+ <?php echo _t('conf.reading.sides_close_article'); ?>
+ <noscript> — <strong><?php echo _t('gen.js.should_be_activated'); ?></strong></noscript>
+ </label>
+ </div>
+ </div>
+
+ <div class="form-group">
+ <div class="group-controls">
<label class="checkbox" for="reading_confirm">
<input type="checkbox" name="reading_confirm" id="reading_confirm" value="1"<?php echo FreshRSS_Context::$user_conf->reading_confirm ? ' checked="checked"' : ''; ?> data-leave-validation="<?php echo FreshRSS_Context::$user_conf->reading_confirm; ?>"/>
<?php echo _t('conf.reading.confirm_enabled'); ?>
diff --git a/app/views/helpers/javascript_vars.phtml b/app/views/helpers/javascript_vars.phtml
index 059224305..2da53b679 100644
--- a/app/views/helpers/javascript_vars.phtml
+++ b/app/views/helpers/javascript_vars.phtml
@@ -13,6 +13,7 @@ echo htmlspecialchars(json_encode(array(
'auto_load_more' => !!FreshRSS_Context::$user_conf->auto_load_more,
'auto_actualize_feeds' => !!Minz_Session::param('actualize_feeds', false),
'does_lazyload' => !!FreshRSS_Context::$user_conf->lazyload ,
+ 'sides_close_article' => !!FreshRSS_Context::$user_conf->sides_close_article,
'sticky_post' => !!FreshRSS_Context::isStickyPostEnabled(),
'html5_notif_timeout' => FreshRSS_Context::$user_conf->html5_notif_timeout,
'auth_type' => FreshRSS_Context::$system_conf->auth_type,
diff --git a/app/views/index/normal.phtml b/app/views/index/normal.phtml
index 6fda11ed9..ba48b2501 100644
--- a/app/views/index/normal.phtml
+++ b/app/views/index/normal.phtml
@@ -66,7 +66,7 @@ if (!empty($this->entries)) {
?><div class="flux_content">
<div class="content <?php echo $content_width; ?>">
- <h1 class="title"><a target="_blank" rel="noreferrer" href="<?php echo $this->entry->link(); ?>"><?php echo $this->entry->title(); ?></a></h1>
+ <h1 class="title"><a target="_blank" rel="noreferrer" class="go_website" href="<?php echo $this->entry->link(); ?>"><?php echo $this->entry->title(); ?></a></h1>
<?php
$author = $this->entry->author();
echo $author != '' ? '<div class="author">' . _t('gen.short.by_author', $author) . '</div>' : '',
diff --git a/app/views/index/reader.phtml b/app/views/index/reader.phtml
index a19ee322e..f2af75af0 100644
--- a/app/views/index/reader.phtml
+++ b/app/views/index/reader.phtml
@@ -19,7 +19,7 @@ if (!empty($this->entries)) {
$feed = FreshRSS_CategoryDAO::findFeed($this->categories, $item->feed()); //We most likely already have the feed object in cache
if (empty($feed)) $feed = $item->feed(true);
?>
- <a href="<?php echo $item->link(); ?>">
+ <a target="_blank" rel="noreferrer" class="go_website" href="<?php echo $item->link(); ?>">
<img class="favicon" src="<?php echo $feed->favicon(); ?>" alt="✇" /> <span><?php echo $feed->name(); ?></span>
</a>
<h1 class="title"><?php echo $item->title(); ?></h1>
diff --git a/app/views/user/profile.phtml b/app/views/user/profile.phtml
index 429375e44..f09c87765 100644
--- a/app/views/user/profile.phtml
+++ b/app/views/user/profile.phtml
@@ -43,6 +43,19 @@
</div>
<?php } ?>
+ <?php if (FreshRSS_Auth::accessNeedsAction()) { ?>
+ <div class="form-group">
+ <label class="group-name" for="token"><?php echo _t('admin.auth.token'); ?></label>
+ <?php $token = FreshRSS_Context::$user_conf->token; ?>
+ <div class="group-controls">
+ <input type="text" id="token" name="token" value="<?php echo $token; ?>" placeholder="<?php echo _t('gen.short.blank_to_disable'); ?>"<?php
+ echo FreshRSS_Auth::accessNeedsAction() ? '' : ' disabled="disabled"'; ?> data-leave-validation="<?php echo $token; ?>"/>
+ <?php echo _i('help'); ?> <?php echo _t('admin.auth.token_help'); ?>
+ <kbd><?php echo Minz_Url::display(array('a' => 'rss', 'params' => array('user' => Minz_Session::param('currentUser'), 'token' => $token, 'hours' => FreshRSS_Context::$user_conf->since_hours_posts_per_rss)), 'html', true); ?></kbd>
+ </div>
+ </div>
+ <?php } ?>
+
<div class="form-group form-actions">
<div class="group-controls">
<button type="submit" class="btn btn-important"><?php echo _t('gen.action.submit'); ?></button>