aboutsummaryrefslogtreecommitdiff
path: root/app
diff options
context:
space:
mode:
Diffstat (limited to 'app')
-rw-r--r--app/Controllers/authController.php9
-rwxr-xr-xapp/Controllers/entryController.php13
-rwxr-xr-xapp/Controllers/feedController.php39
-rw-r--r--app/Controllers/importExportController.php2
-rwxr-xr-xapp/Controllers/indexController.php1
-rw-r--r--app/Controllers/statsController.php1
-rw-r--r--app/Controllers/subscriptionController.php7
-rw-r--r--app/Controllers/userController.php5
-rw-r--r--app/FreshRSS.php39
-rw-r--r--app/Models/Auth.php10
-rw-r--r--app/Models/Context.php42
-rw-r--r--app/Models/Entry.php113
-rw-r--r--app/Models/EntryDAO.php4
-rw-r--r--app/Models/EntryDAOPGSQL.php4
-rw-r--r--app/Models/Feed.php104
-rw-r--r--app/Models/FilterAction.php45
-rw-r--r--app/Models/TagDAO.php48
-rw-r--r--app/i18n/cz/sub.php4
-rw-r--r--app/i18n/de/sub.php4
-rw-r--r--app/i18n/en/conf.php9
-rw-r--r--app/i18n/en/sub.php4
-rwxr-xr-xapp/i18n/es/sub.php4
-rw-r--r--app/i18n/fr/conf.php10
-rw-r--r--app/i18n/fr/sub.php4
-rw-r--r--app/i18n/he/sub.php4
-rw-r--r--app/i18n/it/sub.php4
-rw-r--r--app/i18n/kr/admin.php8
-rw-r--r--app/i18n/kr/conf.php6
-rw-r--r--app/i18n/kr/feedback.php4
-rw-r--r--app/i18n/kr/gen.php12
-rw-r--r--app/i18n/kr/index.php6
-rw-r--r--app/i18n/kr/install.php4
-rw-r--r--app/i18n/kr/sub.php14
-rw-r--r--app/i18n/nl/sub.php4
-rw-r--r--app/i18n/oc/admin.php2
-rw-r--r--app/i18n/oc/gen.php4
-rw-r--r--app/i18n/oc/index.php2
-rw-r--r--app/i18n/oc/sub.php4
-rw-r--r--app/i18n/pt-br/sub.php4
-rw-r--r--app/i18n/ru/conf.php2
-rw-r--r--app/i18n/ru/sub.php4
-rw-r--r--app/i18n/tr/conf.php2
-rw-r--r--app/i18n/tr/sub.php4
-rw-r--r--app/i18n/zh-cn/admin.php20
-rw-r--r--app/i18n/zh-cn/conf.php26
-rw-r--r--app/i18n/zh-cn/feedback.php10
-rw-r--r--app/i18n/zh-cn/gen.php2
-rw-r--r--app/i18n/zh-cn/index.php6
-rw-r--r--app/i18n/zh-cn/install.php4
-rw-r--r--app/i18n/zh-cn/sub.php18
-rw-r--r--app/layout/aside_feed.phtml6
-rwxr-xr-xapp/views/entry/read.phtml10
-rw-r--r--app/views/helpers/feed/update.phtml13
-rw-r--r--app/views/helpers/index/normal/entry_bottom.phtml2
-rw-r--r--app/views/helpers/javascript_vars.phtml5
-rw-r--r--app/views/index/reader.phtml2
-rw-r--r--app/views/user/manage.phtml2
57 files changed, 538 insertions, 207 deletions
diff --git a/app/Controllers/authController.php b/app/Controllers/authController.php
index 3b2d78b19..ca44b1a96 100644
--- a/app/Controllers/authController.php
+++ b/app/Controllers/authController.php
@@ -69,7 +69,7 @@ class FreshRSS_auth_Controller extends Minz_ActionController {
* the user is already connected.
*/
public function loginAction() {
- if (FreshRSS_Auth::hasAccess()) {
+ if (FreshRSS_Auth::hasAccess() && Minz_Request::param('u', '') == '') {
Minz_Request::forward(array('c' => 'index', 'a' => 'index'), true);
}
@@ -109,8 +109,7 @@ class FreshRSS_auth_Controller extends Minz_ActionController {
public function formLoginAction() {
invalidateHttpCache();
- $file_mtime = @filemtime(PUBLIC_PATH . '/scripts/bcrypt.min.js');
- Minz_View::appendScript(Minz_Url::display('/scripts/bcrypt.min.js?' . $file_mtime));
+ Minz_View::appendScript(Minz_Url::display('/scripts/bcrypt.min.js?' . @filemtime(PUBLIC_PATH . '/scripts/bcrypt.min.js')));
$conf = Minz_Configuration::get('system');
$limits = $conf->limits;
@@ -134,6 +133,7 @@ class FreshRSS_auth_Controller extends Minz_ActionController {
// Set session parameter to give access to the user.
Minz_Session::_param('currentUser', $username);
Minz_Session::_param('passwordHash', $conf->passwordHash);
+ Minz_Session::_param('csrf');
FreshRSS_Auth::giveAccess();
// Set cookie parameter if nedded.
@@ -162,6 +162,8 @@ class FreshRSS_auth_Controller extends Minz_ActionController {
return;
}
+ FreshRSS_FormAuth::deleteCookie();
+
$conf = get_user_configuration($username);
if ($conf == null) {
return;
@@ -177,6 +179,7 @@ class FreshRSS_auth_Controller extends Minz_ActionController {
if ($ok) {
Minz_Session::_param('currentUser', $username);
Minz_Session::_param('passwordHash', $s);
+ Minz_Session::_param('csrf');
FreshRSS_Auth::giveAccess();
Minz_Request::good(_t('feedback.auth.login.success'),
diff --git a/app/Controllers/entryController.php b/app/Controllers/entryController.php
index fc0af0639..9c5ee2616 100755
--- a/app/Controllers/entryController.php
+++ b/app/Controllers/entryController.php
@@ -97,14 +97,15 @@ class FreshRSS_entry_Controller extends Minz_ActionController {
}
}
} else {
- $entryDAO->markRead($id, $is_read);
-
+ $ids = is_array($id) ? $id : array($id);
+ $entryDAO->markRead($ids, $is_read);
$tagDAO = FreshRSS_Factory::createTagDao();
- foreach ($tagDAO->getTagsForEntry($id) as $tag) {
- if (!empty($tag['checked'])) {
- $this->view->tags[] = $tag['id'];
- }
+ $tagsForEntries = $tagDAO->getTagsForEntries($ids);
+ $tags = array();
+ foreach ($tagsForEntries as $line) {
+ $tags['t_' . $line['id_tag']][] = $line['id_entry'];
}
+ $this->view->tags = $tags;
}
if (!$this->ajax) {
diff --git a/app/Controllers/feedController.php b/app/Controllers/feedController.php
index 74c9eacfa..0aed9b0a1 100755
--- a/app/Controllers/feedController.php
+++ b/app/Controllers/feedController.php
@@ -289,7 +289,8 @@ class FreshRSS_feed_Controller extends Minz_ActionController {
}
$ttl = $feed->ttl();
if ((!$simplePiePush) && (!$feed_id) &&
- ($feed->lastUpdate() + 10 >= time() - ($ttl == FreshRSS_Feed::TTL_DEFAULT ? FreshRSS_Context::$user_conf->ttl_default : $ttl))) {
+ ($feed->lastUpdate() + 10 >= time() - (
+ $ttl == FreshRSS_Feed::TTL_DEFAULT ? FreshRSS_Context::$user_conf->ttl_default : $ttl))) {
//Too early to refresh from source, but check whether the feed was updated by another user
$mtime = $feed->cacheModifiedTime();
if ($feed->lastUpdate() + 10 >= $mtime) {
@@ -347,8 +348,8 @@ class FreshRSS_feed_Controller extends Minz_ActionController {
$entry_date = $entry->date(true);
if (isset($existingHashForGuids[$entry->guid()])) {
$existingHash = $existingHashForGuids[$entry->guid()];
- if (strcasecmp($existingHash, $entry->hash()) === 0 || trim($existingHash, '0') == '') {
- //This entry already exists and is unchanged. TODO: Remove the test with the zero'ed hash in FreshRSS v1.3
+ if (strcasecmp($existingHash, $entry->hash()) === 0) {
+ //This entry already exists and is unchanged.
$oldGuids[] = $entry->guid();
} else { //This entry already exists but has been updated
//Minz_Log::debug('Entry with GUID `' . $entry->guid() . '` updated in feed ' . $feed->url(false) .
@@ -357,7 +358,7 @@ class FreshRSS_feed_Controller extends Minz_ActionController {
$feed->attributes('mark_updated_article_unread')
) : FreshRSS_Context::$user_conf->mark_updated_article_unread;
$needFeedCacheRefresh = $mark_updated_article_unread;
- $entry->_isRead(FreshRSS_Context::$user_conf->mark_updated_article_unread ? false : null); //Change is_read according to policy.
+ $entry->_isRead($mark_updated_article_unread ? false : null); //Change is_read according to policy.
$entry = Minz_ExtensionManager::callHook('entry_before_insert', $entry);
if ($entry === null) {
@@ -374,20 +375,13 @@ class FreshRSS_feed_Controller extends Minz_ActionController {
// This entry should not be added considering configuration and date.
$oldGuids[] = $entry->guid();
} else {
- $read_upon_reception = $feed->attributes('read_upon_reception') !== null ? (
- $feed->attributes('read_upon_reception')
- ) : FreshRSS_Context::$user_conf->mark_when['reception'];
- if ($isNewFeed) {
- $id = min(time(), $entry_date) . uSecString();
- $entry->_isRead($read_upon_reception);
- } elseif ($entry_date < $date_min) {
- $id = min(time(), $entry_date) . uSecString();
+ $id = uTimeString();
+ $entry->_id($id);
+ if ($entry_date < $date_min) {
$entry->_isRead(true); //Old article that was not in database. Probably an error, so mark as read
- } else {
- $id = uTimeString();
- $entry->_isRead($read_upon_reception);
}
- $entry->_id($id);
+
+ $entry->applyFilterActions();
$entry = Minz_ExtensionManager::callHook('entry_before_insert', $entry);
if ($entry === null) {
@@ -396,7 +390,8 @@ class FreshRSS_feed_Controller extends Minz_ActionController {
}
if ($pubSubHubbubEnabled && !$simplePiePush) { //We use push, but have discovered an article by pull!
- $text = 'An article was discovered by pull although we use PubSubHubbub!: Feed ' . $url . ' GUID ' . $entry->guid();
+ $text = 'An article was discovered by pull although we use PubSubHubbub!: Feed ' . $url .
+ ' GUID ' . $entry->guid();
Minz_Log::warning($text, PSHB_LOG);
Minz_Log::warning($text);
$pubSubHubbubEnabled = false;
@@ -420,9 +415,7 @@ class FreshRSS_feed_Controller extends Minz_ActionController {
$entryDAO->beginTransaction();
}
- $nb = $entryDAO->cleanOldEntries($feed->id(),
- $date_min,
- max($feed_history, count($entries) + 10));
+ $nb = $entryDAO->cleanOldEntries($feed->id(), $date_min, max($feed_history, count($entries) + 10));
if ($nb > 0) {
$needFeedCacheRefresh = true;
Minz_Log::debug($nb . ' old entries cleaned in feed [' . $feed->url(false) . ']');
@@ -602,11 +595,9 @@ class FreshRSS_feed_Controller extends Minz_ActionController {
if (self::moveFeed($feed_id, $cat_id)) {
// TODO: return something useful
// Log a notice to prevent "Empty IF statement" warning in PHP_CodeSniffer
- Minz_Log::notice('Moved feed `' . $feed_id . '` ' .
- 'in the category `' . $cat_id . '`');;
+ Minz_Log::notice('Moved feed `' . $feed_id . '` in the category `' . $cat_id . '`');
} else {
- Minz_Log::warning('Cannot move feed `' . $feed_id . '` ' .
- 'in the category `' . $cat_id . '`');
+ Minz_Log::warning('Cannot move feed `' . $feed_id . '` in the category `' . $cat_id . '`');
Minz_Error::error(404);
}
}
diff --git a/app/Controllers/importExportController.php b/app/Controllers/importExportController.php
index 80b9868ec..1d7176929 100644
--- a/app/Controllers/importExportController.php
+++ b/app/Controllers/importExportController.php
@@ -585,7 +585,7 @@ class FreshRSS_importExport_Controller extends Minz_ActionController {
$feed_id, $item['id'], $item['title'], $author,
$content, $url, $published, $is_read, $is_starred
);
- $entry->_id(min(time(), $entry->date(true)) . uSecString());
+ $entry->_id(uTimeString());
$entry->_tags($tags);
if (isset($newGuids[$entry->guid()])) {
diff --git a/app/Controllers/indexController.php b/app/Controllers/indexController.php
index fa914ef87..824d65815 100755
--- a/app/Controllers/indexController.php
+++ b/app/Controllers/indexController.php
@@ -104,6 +104,7 @@ class FreshRSS_index_Controller extends Minz_ActionController {
return;
}
+ Minz_View::appendScript(Minz_Url::display('/scripts/extra.js?' . @filemtime(PUBLIC_PATH . '/scripts/extra.js')));
Minz_View::appendScript(Minz_Url::display('/scripts/global_view.js?' . @filemtime(PUBLIC_PATH . '/scripts/global_view.js')));
try {
diff --git a/app/Controllers/statsController.php b/app/Controllers/statsController.php
index acfacb890..1d0d9c124 100644
--- a/app/Controllers/statsController.php
+++ b/app/Controllers/statsController.php
@@ -52,6 +52,7 @@ class FreshRSS_stats_Controller extends Minz_ActionController {
*/
public function indexAction() {
$statsDAO = FreshRSS_Factory::createStatsDAO();
+ Minz_View::prependScript(Minz_Url::display('/scripts/jquery.min.js?' . @filemtime(PUBLIC_PATH . '/scripts/jquery.min.js')));
Minz_View::appendScript(Minz_Url::display('/scripts/flotr2.min.js?' . @filemtime(PUBLIC_PATH . '/scripts/flotr2.min.js')));
$this->view->repartition = $statsDAO->calculateEntryRepartition();
$entryCount = $statsDAO->calculateEntryCount();
diff --git a/app/Controllers/subscriptionController.php b/app/Controllers/subscriptionController.php
index 46503fc19..9cf41ed0b 100644
--- a/app/Controllers/subscriptionController.php
+++ b/app/Controllers/subscriptionController.php
@@ -29,8 +29,7 @@ class FreshRSS_subscription_Controller extends Minz_ActionController {
* It displays categories and associated feeds.
*/
public function indexAction() {
- Minz_View::appendScript(Minz_Url::display('/scripts/category.js?' .
- @filemtime(PUBLIC_PATH . '/scripts/category.js')));
+ Minz_View::appendScript(Minz_Url::display('/scripts/category.js?' . @filemtime(PUBLIC_PATH . '/scripts/category.js')));
Minz_View::prependTitle(_t('sub.title') . ' · ');
$this->view->onlyFeedsWithError = Minz_Request::paramTernary('error');
@@ -111,6 +110,8 @@ class FreshRSS_subscription_Controller extends Minz_ActionController {
$feed->_attributes('timeout', null);
}
+ $feed->_filtersAction('read', preg_split('/[\n\r]+/', Minz_Request::param('filteractions_read', '')));
+
$values = array(
'name' => Minz_Request::param('name', ''),
'description' => sanitizeHTML(Minz_Request::param('description', '', true)),
@@ -122,7 +123,7 @@ class FreshRSS_subscription_Controller extends Minz_ActionController {
'httpAuth' => $httpAuth,
'keep_history' => intval(Minz_Request::param('keep_history', FreshRSS_Feed::KEEP_HISTORY_DEFAULT)),
'ttl' => $ttl * ($mute ? -1 : 1),
- 'attributes' => $feed->attributes()
+ 'attributes' => $feed->attributes(),
);
invalidateHttpCache();
diff --git a/app/Controllers/userController.php b/app/Controllers/userController.php
index 2338c8b2a..be3787561 100644
--- a/app/Controllers/userController.php
+++ b/app/Controllers/userController.php
@@ -128,9 +128,7 @@ class FreshRSS_user_Controller extends Minz_ActionController {
public function profileAction() {
Minz_View::prependTitle(_t('conf.profile.title') . ' · ');
- Minz_View::appendScript(Minz_Url::display(
- '/scripts/bcrypt.min.js?' . @filemtime(PUBLIC_PATH . '/scripts/bcrypt.min.js')
- ));
+ Minz_View::appendScript(Minz_Url::display('/scripts/bcrypt.min.js?' . @filemtime(PUBLIC_PATH . '/scripts/bcrypt.min.js')));
if (Minz_Request::isPost()) {
$passwordPlain = Minz_Request::param('newPasswordPlain', '', true);
@@ -249,6 +247,7 @@ class FreshRSS_user_Controller extends Minz_ActionController {
$user_conf = get_user_configuration($new_user_name);
Minz_Session::_param('currentUser', $new_user_name);
Minz_Session::_param('passwordHash', $user_conf->passwordHash);
+ Minz_Session::_param('csrf');
FreshRSS_Auth::giveAccess();
}
diff --git a/app/FreshRSS.php b/app/FreshRSS.php
index dec446a8e..ecf13e4cf 100644
--- a/app/FreshRSS.php
+++ b/app/FreshRSS.php
@@ -57,18 +57,26 @@ class FreshRSS extends Minz_FrontController {
private static function initAuth() {
FreshRSS_Auth::init();
- if (Minz_Request::isPost() && !(is_referer_from_same_domain() && FreshRSS_Auth::isCsrfOk())) {
- // Basic protection against XSRF attacks
- FreshRSS_Auth::removeAccess();
- $http_referer = empty($_SERVER['HTTP_REFERER']) ? '' : $_SERVER['HTTP_REFERER'];
- Minz_Translate::init('en'); //TODO: Better choice of fallback language
- Minz_Error::error(
- 403,
- array('error' => array(
- _t('feedback.access.denied'),
- ' [HTTP_REFERER=' . htmlspecialchars($http_referer, ENT_NOQUOTES, 'UTF-8') . ']'
- ))
- );
+ if (Minz_Request::isPost()) {
+ if (!is_referer_from_same_domain()) {
+ // Basic protection against XSRF attacks
+ FreshRSS_Auth::removeAccess();
+ $http_referer = empty($_SERVER['HTTP_REFERER']) ? '' : $_SERVER['HTTP_REFERER'];
+ Minz_Translate::init('en'); //TODO: Better choice of fallback language
+ Minz_Error::error(403, array('error' => array(
+ _t('feedback.access.denied'),
+ ' [HTTP_REFERER=' . htmlspecialchars($http_referer, ENT_NOQUOTES, 'UTF-8') . ']'
+ )));
+ }
+ if ((!FreshRSS_Auth::isCsrfOk()) &&
+ (Minz_Request::controllerName() !== 'auth' || Minz_Request::actionName() !== 'login')) {
+ // Token-based protection against XSRF attacks, except for the login form itself
+ Minz_Translate::init('en'); //TODO: Better choice of fallback language
+ Minz_Error::error(403, array('error' => array(
+ _t('feedback.access.denied'),
+ ' [CSRF]'
+ )));
+ }
}
}
@@ -94,9 +102,10 @@ class FreshRSS extends Minz_FrontController {
}
}
//Use prepend to insert before extensions. Added in reverse order.
+ if (Minz_Request::controllerName() !== 'index') {
+ Minz_View::prependScript(Minz_Url::display('/scripts/extra.js?' . @filemtime(PUBLIC_PATH . '/scripts/extra.js')));
+ }
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() {
@@ -116,7 +125,7 @@ class FreshRSS extends Minz_FrontController {
}
}, FreshRSS_Context::$user_conf->sharing));
$connectSrc = count($urlToAuthorize) ? sprintf("; connect-src 'self' %s", implode(' ', $urlToAuthorize)) : '';
- header(sprintf("Content-Security-Policy: default-src 'self'; child-src *; frame-src *; img-src * data:; media-src *%s", $connectSrc));
+ header(sprintf("Content-Security-Policy: default-src 'self'; frame-src *; img-src * data:; media-src *%s", $connectSrc));
break;
case 'stats':
header("Content-Security-Policy: default-src 'self'; style-src 'self' 'unsafe-inline'");
diff --git a/app/Models/Auth.php b/app/Models/Auth.php
index 513a9cb2f..6d079a01f 100644
--- a/app/Models/Auth.php
+++ b/app/Models/Auth.php
@@ -13,7 +13,7 @@ class FreshRSS_Auth {
* This method initializes authentication system.
*/
public static function init() {
- if (Minz_Session::param('REMOTE_USER', '') !== httpAuthUser()) {
+ if (isset($_SESSION['REMOTE_USER']) && $_SESSION['REMOTE_USER'] !== httpAuthUser()) {
//HTTP REMOTE_USER has changed
self::removeAccess();
}
@@ -24,6 +24,7 @@ class FreshRSS_Auth {
$conf = Minz_Configuration::get('system');
$current_user = $conf->default_user;
Minz_Session::_param('currentUser', $current_user);
+ Minz_Session::_param('csrf');
}
if (self::$login_ok) {
@@ -56,6 +57,7 @@ class FreshRSS_Auth {
$current_user = trim($credentials[0]);
Minz_Session::_param('currentUser', $current_user);
Minz_Session::_param('passwordHash', trim($credentials[1]));
+ Minz_Session::_param('csrf');
}
return $current_user != '';
case 'http_auth':
@@ -63,6 +65,7 @@ class FreshRSS_Auth {
$login_ok = $current_user != '' && FreshRSS_UserDAO::exists($current_user);
if ($login_ok) {
Minz_Session::_param('currentUser', $current_user);
+ Minz_Session::_param('csrf');
}
return $login_ok;
case 'none':
@@ -196,13 +199,10 @@ class FreshRSS_Auth {
}
public static function isCsrfOk($token = null) {
$csrf = Minz_Session::param('csrf');
- if ($csrf == '') {
- return true; //Not logged in yet
- }
if ($token === null) {
$token = Minz_Request::fetchPOST('_csrf');
}
- return $token === $csrf;
+ return $token != '' && $token === $csrf;
}
}
diff --git a/app/Models/Context.php b/app/Models/Context.php
index 60ec6ff77..95dc47c8c 100644
--- a/app/Models/Context.php
+++ b/app/Models/Context.php
@@ -252,37 +252,29 @@ class FreshRSS_Context {
$found_current_get = false;
switch ($get[0]) {
case 'f':
- // We search the next feed with at least one unread article in
- // same category as the currend feed.
+ // We search the next unread feed with the following priorities: next in same category, or previous in same category, or next, or previous.
foreach (self::$categories as $cat) {
- if ($cat->id() != self::$current_get['category']) {
- // We look into the category of the current feed!
- continue;
- }
-
+ $sameCat = false;
foreach ($cat->feeds() as $feed) {
- if ($feed->id() == self::$current_get['feed']) {
- // Here is our current feed! Fine, the next one will
- // be a potential candidate.
+ if ($found_current_get) {
+ if ($feed->nbNotRead() > 0) {
+ $another_unread_id = $feed->id();
+ break 2;
+ }
+ } elseif ($feed->id() == self::$current_get['feed']) {
$found_current_get = true;
- continue;
- }
-
- if ($feed->nbNotRead() > 0) {
+ } elseif ($feed->nbNotRead() > 0) {
$another_unread_id = $feed->id();
- if ($found_current_get) {
- // We have found our current feed and now we
- // have an feed with unread articles. Leave the
- // loop!
- break;
- }
+ $sameCat = true;
}
}
- break;
+ if ($found_current_get && $sameCat) {
+ break;
+ }
}
- // If no feed have been found, next_get is the current category.
- self::$next_get = empty($another_unread_id) ? 'c_' . self::$current_get['category'] : 'f_' . $another_unread_id;
+ // If there is no more unread feed, show main stream
+ self::$next_get = $another_unread_id == '' ? 'a' : 'f_' . $another_unread_id;
break;
case 'c':
// We search the next category with at least one unread article.
@@ -304,8 +296,8 @@ class FreshRSS_Context {
}
}
- // No unread category? The main stream will be our destination!
- self::$next_get = empty($another_unread_id) ? 'a' : 'c_' . $another_unread_id;
+ // If there is no more unread category, show main stream
+ self::$next_get = $another_unread_id == '' ? 'a' : 'c_' . $another_unread_id;
break;
}
}
diff --git a/app/Models/Entry.php b/app/Models/Entry.php
index f2f3d08fe..3bb977283 100644
--- a/app/Models/Entry.php
+++ b/app/Models/Entry.php
@@ -185,6 +185,119 @@ class FreshRSS_Entry extends Minz_Model {
$this->tags = $value;
}
+ public function matches($booleanSearch) {
+ if (!$booleanSearch || count($booleanSearch->searches()) <= 0) {
+ return true;
+ }
+ foreach ($booleanSearch->searches() as $filter) {
+ $ok = true;
+ if ($ok && $filter->getMinPubdate()) {
+ $ok &= $this->date >= $filter->getMinPubdate();
+ }
+ if ($ok && $filter->getMaxPubdate()) {
+ $ok &= $this->date <= $filter->getMaxPubdate();
+ }
+ if ($ok && $filter->getMinDate()) {
+ $ok &= strnatcmp($this->id, $filter->getMinDate() . '000000') >= 0;
+ }
+ if ($ok && $filter->getMaxDate()) {
+ $ok &= strnatcmp($this->id, $filter->getMaxDate() . '000000') <= 0;
+ }
+ if ($ok && $filter->getInurl()) {
+ foreach ($filter->getInurl() as $url) {
+ $ok &= stripos($this->link, $url) !== false;
+ }
+ }
+ if ($ok && $filter->getNotInurl()) {
+ foreach ($filter->getNotInurl() as $url) {
+ $ok &= stripos($this->link, $url) === false;
+ }
+ }
+ if ($ok && $filter->getAuthor()) {
+ foreach ($filter->getAuthor() as $author) {
+ $ok &= stripos($this->authors, $author) !== false;
+ }
+ }
+ if ($ok && $filter->getNotAuthor()) {
+ foreach ($filter->getNotAuthor() as $author) {
+ $ok &= stripos($this->authors, $author) === false;
+ }
+ }
+ if ($ok && $filter->getIntitle()) {
+ foreach ($filter->getIntitle() as $title) {
+ $ok &= stripos($this->title, $title) !== false;
+ }
+ }
+ if ($ok && $filter->getNotIntitle()) {
+ foreach ($filter->getNotIntitle() as $title) {
+ $ok &= stripos($this->title, $title) === false;
+ }
+ }
+ if ($ok && $filter->getTags()) {
+ foreach ($filter->getTags() as $tag2) {
+ $found = false;
+ foreach ($this->tags as $tag1) {
+ if (strcasecmp($tag1, $tag2) === 0) {
+ $found = true;
+ }
+ }
+ $ok &= $found;
+ }
+ }
+ if ($ok && $filter->getNotTags()) {
+ foreach ($filter->getNotTags() as $tag2) {
+ $found = false;
+ foreach ($this->tags as $tag1) {
+ if (strcasecmp($tag1, $tag2) === 0) {
+ $found = true;
+ }
+ }
+ $ok &= !$found;
+ }
+ }
+ if ($ok && $filter->getSearch()) {
+ foreach ($filter->getSearch() as $needle) {
+ $ok &= (stripos($this->title, $needle) !== false || stripos($this->content, $needle) !== false);
+ }
+ }
+ if ($ok && $filter->getNotSearch()) {
+ foreach ($filter->getNotSearch() as $needle) {
+ $ok &= (stripos($this->title, $needle) === false && stripos($this->content, $needle) === false);
+ }
+ }
+ if ($ok) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ public function applyFilterActions() {
+ if ($this->feed != null) {
+ if ($this->feed->attributes('read_upon_reception') ||
+ ($this->feed->attributes('read_upon_reception') === null && FreshRSS_Context::$user_conf->mark_when['reception'])) {
+ $this->_isRead(true);
+ }
+ foreach ($this->feed->filterActions() as $filterAction) {
+ if ($this->matches($filterAction->booleanSearch())) {
+ foreach ($filterAction->actions() as $action => $params) {
+ switch ($action) {
+ case 'read':
+ $this->_isRead(true);
+ break;
+ case 'star':
+ $this->_is_favorite(true);
+ break;
+ case 'label':
+ //TODO: Implement more actions
+ break;
+ }
+ }
+ }
+ }
+ }
+ }
+
public function isDay($day, $today) {
$date = $this->dateAdded(true);
switch ($day) {
diff --git a/app/Models/EntryDAO.php b/app/Models/EntryDAO.php
index 08927196e..93d1183c9 100644
--- a/app/Models/EntryDAO.php
+++ b/app/Models/EntryDAO.php
@@ -383,7 +383,7 @@ class FreshRSS_EntryDAO extends Minz_ModelPdo implements FreshRSS_Searchable {
*/
public function markRead($ids, $is_read = true) {
FreshRSS_UserDAO::touch();
- if (is_array($ids)) { //Many IDs at once (used by API)
+ if (is_array($ids)) { //Many IDs at once
if (count($ids) < 6) { //Speed heuristics
$affected = 0;
foreach ($ids as $id) {
@@ -1065,7 +1065,7 @@ SQL;
$dao['date'],
$dao['is_read'],
$dao['is_favorite'],
- $dao['tags']
+ isset($dao['tags']) ? $dao['tags'] : ''
);
if (isset($dao['id'])) {
$entry->_id($dao['id']);
diff --git a/app/Models/EntryDAOPGSQL.php b/app/Models/EntryDAOPGSQL.php
index aef258b6f..e571e457f 100644
--- a/app/Models/EntryDAOPGSQL.php
+++ b/app/Models/EntryDAOPGSQL.php
@@ -37,7 +37,9 @@ 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` AS etmp
- WHERE NOT EXISTS (SELECT 1 FROM `' . $this->prefix . 'entry` AS ereal WHERE etmp.id_feed = ereal.id_feed AND etmp.guid = ereal.guid)
+ WHERE NOT EXISTS (
+ SELECT 1 FROM `' . $this->prefix . 'entry` AS ereal
+ WHERE (etmp.id = ereal.id) OR (etmp.id_feed = ereal.id_feed AND etmp.guid = ereal.guid))
ORDER BY date);
DELETE FROM `' . $this->prefix . 'entrytmp` WHERE id <= maxrank;
END $$;';
diff --git a/app/Models/Feed.php b/app/Models/Feed.php
index b21a8bbbe..89989236c 100644
--- a/app/Models/Feed.php
+++ b/app/Models/Feed.php
@@ -32,6 +32,7 @@ class FreshRSS_Feed extends Minz_Model {
private $lockPath = '';
private $hubUrl = '';
private $selfUrl = '';
+ private $filterActions = null;
public function __construct($url, $validate = true) {
if ($validate) {
@@ -498,6 +499,109 @@ class FreshRSS_Feed extends Minz_Model {
@unlink($this->lockPath);
}
+ public function filterActions() {
+ if ($this->filterActions == null) {
+ $this->filterActions = array();
+ $filters = $this->attributes('filters');
+ if (is_array($filters)) {
+ foreach ($filters as $filter) {
+ $filterAction = FreshRSS_FilterAction::fromJSON($filter);
+ if ($filterAction != null) {
+ $this->filterActions[] = $filterAction;
+ }
+ }
+ }
+ }
+ return $this->filterActions;
+ }
+
+ private function _filterActions($filterActions) {
+ $this->filterActions = $filterActions;
+ if (is_array($this->filterActions) && !empty($this->filterActions)) {
+ $this->_attributes('filters', array_map(function ($af) {
+ return $af == null ? null : $af->toJSON();
+ }, $this->filterActions));
+ } else {
+ $this->_attributes('filters', null);
+ }
+ }
+
+ public function filtersAction($action) {
+ $action = trim($action);
+ if ($action == '') {
+ return array();
+ }
+ $filters = array();
+ $filterActions = $this->filterActions();
+ for ($i = count($filterActions) - 1; $i >= 0; $i--) {
+ $filterAction = $filterActions[$i];
+ if ($filterAction != null && $filterAction->booleanSearch() != null &&
+ $filterAction->actions() != null && in_array($action, $filterAction->actions(), true)) {
+ $filters[] = $filterAction->booleanSearch();
+ }
+ }
+ return $filters;
+ }
+
+ public function _filtersAction($action, $filters) {
+ $action = trim($action);
+ if ($action == '' || !is_array($filters)) {
+ return false;
+ }
+ $filters = array_unique(array_map('trim', $filters));
+ $filterActions = $this->filterActions();
+
+ //Check existing filters
+ for ($i = count($filterActions) - 1; $i >= 0; $i--) {
+ $filterAction = $filterActions[$i];
+ if ($filterAction == null || !is_array($filterAction->actions()) ||
+ $filterAction->booleanSearch() == null || trim($filterAction->booleanSearch()->getRawInput()) == '') {
+ array_splice($filterAction, $i, 1);
+ continue;
+ }
+ $actions = $filterAction->actions();
+ //Remove existing rules with same action
+ for ($j = count($actions) - 1; $j >= 0; $j--) {
+ if ($actions[$j] === $action) {
+ array_splice($actions, $j, 1);
+ }
+ }
+ //Update existing filter with new action
+ for ($k = count($filters) - 1; $k >= 0; $k --) {
+ $filter = $filters[$k];
+ if ($filter === $filterAction->booleanSearch()->getRawInput()) {
+ $actions[] = $action;
+ array_splice($filters, $k, 1);
+ }
+ }
+ //Save result
+ if (empty($actions)) {
+ array_splice($filterActions, $i, 1);
+ } else {
+ $filterAction->_actions($actions);
+ }
+ }
+
+ //Add new filters
+ for ($k = count($filters) - 1; $k >= 0; $k --) {
+ $filter = $filters[$k];
+ if ($filter != '') {
+ $filterAction = FreshRSS_FilterAction::fromJSON(array(
+ 'search' => $filter,
+ 'actions' => array($action),
+ ));
+ if ($filterAction != null) {
+ $filterActions[] = $filterAction;
+ }
+ }
+ }
+
+ if (empty($filterActions)) {
+ $filterActions = null;
+ }
+ $this->_filterActions($filterActions);
+ }
+
//<WebSub>
public function pubSubHubbubEnabled() {
diff --git a/app/Models/FilterAction.php b/app/Models/FilterAction.php
new file mode 100644
index 000000000..23a45d14e
--- /dev/null
+++ b/app/Models/FilterAction.php
@@ -0,0 +1,45 @@
+<?php
+
+class FreshRSS_FilterAction {
+
+ private $booleanSearch = null;
+ private $actions = null;
+
+ private function __construct($booleanSearch, $actions) {
+ $this->booleanSearch = $booleanSearch;
+ $this->_actions($actions);
+ }
+
+ public function booleanSearch() {
+ return $this->booleanSearch;
+ }
+
+ public function actions() {
+ return $this->actions;
+ }
+
+ public function _actions($actions) {
+ if (is_array($actions)) {
+ $this->actions = array_unique($actions);
+ } else {
+ $this->actions = null;
+ }
+ }
+
+ public function toJSON() {
+ if (is_array($this->actions) && $this->booleanSearch != null) {
+ return array(
+ 'search' => $this->booleanSearch->getRawInput(),
+ 'actions' => $this->actions,
+ );
+ }
+ return '';
+ }
+
+ public static function fromJSON($json) {
+ if (!empty($json['search']) && !empty($json['actions']) && is_array($json['actions'])) {
+ return new FreshRSS_FilterAction(new FreshRSS_BooleanSearch($json['search']), $json['actions']);
+ }
+ return null;
+ }
+}
diff --git a/app/Models/TagDAO.php b/app/Models/TagDAO.php
index dba854aa4..297d24c96 100644
--- a/app/Models/TagDAO.php
+++ b/app/Models/TagDAO.php
@@ -187,9 +187,17 @@ class FreshRSS_TagDAO extends Minz_ModelPdo implements FreshRSS_Searchable {
public function count() {
$sql = 'SELECT COUNT(*) AS count FROM `' . $this->prefix . 'tag`';
$stm = $this->bd->prepare($sql);
- $stm->execute();
- $res = $stm->fetchAll(PDO::FETCH_ASSOC);
- return $res[0]['count'];
+ if ($stm && $stm->execute()) {
+ $res = $stm->fetchAll(PDO::FETCH_ASSOC);
+ return $res[0]['count'];
+ } else {
+ $info = $stm == null ? array(0 => '', 1 => '', 2 => 'syntax error') : $stm->errorInfo();
+ if ($this->autoUpdateDb($info)) {
+ return $this->count();
+ }
+ Minz_Log::error('SQL error TagDAO::count: ' . $info[2]);
+ return false;
+ }
}
public function countEntries($id) {
@@ -256,9 +264,8 @@ class FreshRSS_TagDAO extends Minz_ModelPdo implements FreshRSS_Searchable {
}
}
- //For API
- public function getEntryIdsTagNames($entries) {
- $sql = 'SELECT et.id_entry, t.name '
+ public function getTagsForEntries($entries) {
+ $sql = 'SELECT et.id_entry, et.id_tag, t.name '
. 'FROM `' . $this->prefix . 'tag` t '
. 'INNER JOIN `' . $this->prefix . 'entrytag` et ON et.id_tag = t.id';
@@ -282,26 +289,31 @@ class FreshRSS_TagDAO extends Minz_ModelPdo implements FreshRSS_Searchable {
$stm = $this->bd->prepare($sql);
if ($stm && $stm->execute($values)) {
- $result = array();
- foreach ($stm->fetchAll(PDO::FETCH_ASSOC) as $line) {
- $entryId = 'e_' . $line['id_entry'];
- $tagName = $line['name'];
- if (empty($result[$entryId])) {
- $result[$entryId] = array();
- }
- $result[$entryId][] = $tagName;
- }
- return $result;
+ return $stm->fetchAll(PDO::FETCH_ASSOC);
} else {
$info = $stm == null ? array(0 => '', 1 => '', 2 => 'syntax error') : $stm->errorInfo();
if ($this->autoUpdateDb($info)) {
- return $this->getTagNamesEntryIds($id_entry);
+ return $this->getTagsForEntries($entries);
}
- Minz_Log::error('SQL error getTagNamesEntryIds: ' . $info[2]);
+ Minz_Log::error('SQL error getTagsForEntries: ' . $info[2]);
return false;
}
}
+ //For API
+ public function getEntryIdsTagNames($entries) {
+ $result = array();
+ foreach ($this->getTagsForEntries($entries) as $line) {
+ $entryId = 'e_' . $line['id_entry'];
+ $tagName = $line['name'];
+ if (empty($result[$entryId])) {
+ $result[$entryId] = array();
+ }
+ $result[$entryId][] = $tagName;
+ }
+ return $result;
+ }
+
public static function daoToTag($listDAO) {
$list = array();
if (!is_array($listDAO)) {
diff --git a/app/i18n/cz/sub.php b/app/i18n/cz/sub.php
index 5b5634fed..2e81c928d 100644
--- a/app/i18n/cz/sub.php
+++ b/app/i18n/cz/sub.php
@@ -33,6 +33,10 @@ return array(
'description' => 'Popis',
'empty' => 'Kanál je prázdný. Ověřte prosím zda je ještě autorem udržován.',
'error' => 'Vyskytl se problém s kanálem. Ověřte že je vždy dostupný, prosím, a poté jej aktualizujte.',
+ 'filteractions' => array(
+ '_' => 'Filter actions', //TODO - Translation
+ 'help' => 'Write one search filter per line.', //TODO - Translation
+ ),
'informations' => 'Informace',
'keep_history' => 'Zachovat tento minimální počet článků',
'moved_category_deleted' => 'Po smazání kategorie budou v ní obsažené kanály automaticky přesunuty do <em>%s</em>.',
diff --git a/app/i18n/de/sub.php b/app/i18n/de/sub.php
index 27e893177..bd050671e 100644
--- a/app/i18n/de/sub.php
+++ b/app/i18n/de/sub.php
@@ -33,6 +33,10 @@ return array(
'description' => 'Beschreibung',
'empty' => 'Dieser Feed ist leer. Bitte stellen Sie sicher, dass er noch gepflegt wird.',
'error' => 'Dieser Feed ist auf ein Problem gestoßen. Bitte stellen Sie sicher, dass er immer lesbar ist und aktualisieren Sie ihn dann.',
+ 'filteractions' => array(
+ '_' => 'Filter actions', //TODO - Translation
+ 'help' => 'Write one search filter per line.', //TODO - Translation
+ ),
'informations' => 'Information',
'keep_history' => 'Minimale Anzahl an Artikeln, die behalten wird',
'moved_category_deleted' => 'Wenn Sie eine Kategorie entfernen, werden deren Feeds automatisch in die Kategorie <em>%s</em> eingefügt.',
diff --git a/app/i18n/en/conf.php b/app/i18n/en/conf.php
index c6471426f..fde78f5b5 100644
--- a/app/i18n/en/conf.php
+++ b/app/i18n/en/conf.php
@@ -92,7 +92,7 @@ return array(
'auto_remove_article' => 'Hide articles after reading',
'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',
+ 'display_categories_unfolded' => 'Show categories unfolded by default',
'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',
'jump_next' => 'jump to next unread sibling (feed or category)',
@@ -158,15 +158,12 @@ return array(
'javascript' => 'JavaScript must be enabled in order to use shortcuts',
'last_article' => 'Open the last article',
'load_more' => 'Load more articles',
- 'mark_favorite' => 'Mark as favourite',
- 'mark_read' => 'Mark as read',
+ 'mark_favorite' => 'Toggle favourite',
+ 'mark_read' => 'Toggle read',
'navigation' => 'Navigation',
'navigation_help' => 'With the "Shift" modifier, navigation shortcuts apply on feeds.<br/>With the "Alt" modifier, navigation shortcuts apply on categories.',
'navigation_no_mod_help' => 'The following navigation shortcuts do not support modifiers.',
'next_article' => 'Open the next article',
- 'other_action' => 'Other actions',
- 'previous_article' => 'Open the previous article',
- 'next_article' => 'Open the next article',
'normal_view' => 'Switch to normal view',
'other_action' => 'Other actions',
'previous_article' => 'Open the previous article',
diff --git a/app/i18n/en/sub.php b/app/i18n/en/sub.php
index 4efd81ba4..f11eb9b99 100644
--- a/app/i18n/en/sub.php
+++ b/app/i18n/en/sub.php
@@ -33,6 +33,10 @@ return array(
'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 update it.',
+ 'filteractions' => array(
+ '_' => 'Filter actions',
+ 'help' => 'Write one search filter per line.',
+ ),
'informations' => 'Information',
'keep_history' => 'Minimum number of articles to keep',
'moved_category_deleted' => 'When you delete a category, its feeds are automatically classified under <em>%s</em>.',
diff --git a/app/i18n/es/sub.php b/app/i18n/es/sub.php
index 854984891..c0526106f 100755
--- a/app/i18n/es/sub.php
+++ b/app/i18n/es/sub.php
@@ -33,6 +33,10 @@ return array(
'description' => 'Descripción',
'empty' => 'La fuente está vacía. Por favor, verifica que siga activa.',
'error' => 'Hay un problema con esta fuente. Por favor, veritica que esté disponible y prueba de nuevo.',
+ 'filteractions' => array(
+ '_' => 'Filter actions', //TODO - Translation
+ 'help' => 'Write one search filter per line.', //TODO - Translation
+ ),
'informations' => 'Información',
'keep_history' => 'Número mínimo de artículos a conservar',
'moved_category_deleted' => 'Al borrar una categoría todas sus fuentes pasan automáticamente a la categoría <em>%s</em>.',
diff --git a/app/i18n/fr/conf.php b/app/i18n/fr/conf.php
index 5c8e91c89..d0d146c89 100644
--- a/app/i18n/fr/conf.php
+++ b/app/i18n/fr/conf.php
@@ -158,11 +158,11 @@ return array(
'javascript' => 'Le JavaScript doit être activé pour pouvoir profiter des raccourcis.',
'last_article' => 'Passer au dernier article',
'load_more' => 'Charger plus d’articles',
- 'mark_favorite' => 'Mettre en favori',
- 'mark_read' => 'Marquer comme lu',
+ 'mark_favorite' => 'Basculer l’indicateur de favori',
+ 'mark_read' => 'Basculer l’indicateur de lecture',
'navigation' => 'Navigation',
'navigation_help' => 'Avec le modificateur "Shift", les raccourcis de navigation s’appliquent aux flux.<br/>Avec le modificateur "Alt", les raccourcis de navigation s’appliquent aux catégories.',
- 'navigation_no_mod_help' => 'The following navigation shortcuts do not support modifiers.', //TODO - Translation
+ 'navigation_no_mod_help' => 'Les raccourcis suivant ne supportent pas les modificateurs.',
'next_article' => 'Passer à l’article suivant',
'normal_view' => 'Basculer vers la vue normale',
'other_action' => 'Autres actions',
@@ -171,8 +171,8 @@ return array(
'rss_view' => 'Ouvrir le flux RSS dans un nouvel onglet',
'see_on_website' => 'Voir sur le site d’origine',
'shift_for_all_read' => '+ <code>shift</code> pour marquer tous les articles comme lus',
- 'skip_next_article' => 'Focus next without opening', //TODO - Translation
- 'skip_previous_article' => 'Focus previous without opening', //TODO - Translation
+ 'skip_next_article' => 'Passer au suivant sans ouvrir',
+ 'skip_previous_article' => 'Passer au précédent sans ouvrir',
'title' => 'Raccourcis',
'user_filter' => 'Accéder aux filtres utilisateur',
'user_filter_help' => 'S’il n’y a qu’un filtre utilisateur, celui-ci est utilisé automatiquement. Sinon ils sont accessibles par leur numéro.',
diff --git a/app/i18n/fr/sub.php b/app/i18n/fr/sub.php
index d9964ac6e..b71019faa 100644
--- a/app/i18n/fr/sub.php
+++ b/app/i18n/fr/sub.php
@@ -33,6 +33,10 @@ return array(
'description' => 'Description',
'empty' => 'Ce flux est vide. Veuillez vérifier qu’il est toujours maintenu.',
'error' => 'Ce flux a rencontré un problème. Veuillez vérifier qu’il est toujours accessible puis actualisez-le.',
+ 'filteractions' => array(
+ '_' => 'Filtres d’action',
+ 'help' => 'Écrivez une recherche par ligne.',
+ ),
'informations' => 'Informations',
'keep_history' => 'Nombre minimum d’articles à conserver',
'moved_category_deleted' => 'Lors de la suppression d’une catégorie, ses flux seront automatiquement classés dans <em>%s</em>.',
diff --git a/app/i18n/he/sub.php b/app/i18n/he/sub.php
index 6d824e349..bb2025bc3 100644
--- a/app/i18n/he/sub.php
+++ b/app/i18n/he/sub.php
@@ -33,6 +33,10 @@ return array(
'description' => 'תיאור',
'empty' => 'הזנה זו ריקה. אנא ודאו שהיא עדיין מתוחזקת.',
'error' => 'הזנה זו נתקלה בשגיאה, אנא ודאו שהיא תקינה ואז נסו שנית.',
+ 'filteractions' => array(
+ '_' => 'Filter actions', //TODO - Translation
+ 'help' => 'Write one search filter per line.', //TODO - Translation
+ ),
'informations' => 'מידע',
'keep_history' => 'מסםר מינימלי של מאמרים לשמור',
'moved_category_deleted' => 'כאשר הקטגוריה נמחקת ההזנות שבתוכה אוטומטית מקוטלגות תחת <em>%s</em>.',
diff --git a/app/i18n/it/sub.php b/app/i18n/it/sub.php
index ff7fa6f1d..bf279e059 100644
--- a/app/i18n/it/sub.php
+++ b/app/i18n/it/sub.php
@@ -33,6 +33,10 @@ return array(
'description' => 'Descrizione',
'empty' => 'Questo feed non contiene articoli. Per favore verifica il sito direttamente.',
'error' => 'Questo feed ha generato un errore. Per favore verifica se ancora disponibile.',
+ 'filteractions' => array(
+ '_' => 'Filter actions', //TODO - Translation
+ 'help' => 'Write one search filter per line.', //TODO - Translation
+ ),
'informations' => 'Informazioni',
'keep_history' => 'Numero minimo di articoli da mantenere',
'moved_category_deleted' => 'Cancellando una categoria i feed al suo interno verranno classificati automaticamente come <em>%s</em>.',
diff --git a/app/i18n/kr/admin.php b/app/i18n/kr/admin.php
index 532fe30a5..6312bd3fe 100644
--- a/app/i18n/kr/admin.php
+++ b/app/i18n/kr/admin.php
@@ -67,8 +67,8 @@ return array(
'ok' => 'JSON 확장 기능이 설치되어 있습니다.',
),
'mbstring' => array(
- 'nok' => 'Cannot find the recommended library mbstring for Unicode.', //TODO - Translation
- 'ok' => 'You have the recommended library mbstring for Unicode.', //TODO - Translation
+ 'nok' => '유니코드 지원을 위한 mbstring 라이브러리를 찾을 수 없습니다.',
+ 'ok' => '유니코드 지원을 위한 mbstring 라이브러리가 설치되어 있습니다.',
),
'minz' => array(
'nok' => 'Minz 프레임워크를 찾을 수 없습니다.',
@@ -163,8 +163,8 @@ return array(
'max-categories' => '사용자별 카테고리 개수 제한',
'max-feeds' => '사용자별 피드 개수 제한',
'cookie-duration' => array(
- 'help' => 'in seconds', // @todo translate
- 'number' => 'Duration to keep logged in', // @todo translate
+ 'help' => '초',
+ 'number' => '로그인 유지 시간',
),
'registration' => array(
'help' => '0: 제한 없음',
diff --git a/app/i18n/kr/conf.php b/app/i18n/kr/conf.php
index 5c3d95d17..11a8494c5 100644
--- a/app/i18n/kr/conf.php
+++ b/app/i18n/kr/conf.php
@@ -162,7 +162,7 @@ return array(
'mark_read' => '읽음으로 표시',
'navigation' => '탐색',
'navigation_help' => '"Shift" 키를 누른 상태에선 탐색 단축키가 피드에 적용됩니다.<br/>"Alt" 키를 누른 상태에선 탐색 단축키가 카테고리에 적용됩니다.',
- 'navigation_no_mod_help' => 'The following navigation shortcuts do not support modifiers.', //TODO - Translation
+ 'navigation_no_mod_help' => '아래 탐색 단축키에는 "Shift"와 "Alt" 키가 적용되지 않습니다.',
'next_article' => '다음 글 보기',
'normal_view' => '일반 모드로 전환',
'other_action' => '다른 동작',
@@ -171,8 +171,8 @@ return array(
'rss_view' => '새 탭에서 RSS 피드 열기',
'see_on_website' => '글이 게재된 웹사이트에서 보기',
'shift_for_all_read' => '+ <code>shift</code>를 누른 상태에선 모두 읽음으로 표시',
- 'skip_next_article' => 'Focus next without opening', //TODO - Translation
- 'skip_previous_article' => 'Focus previous without opening', //TODO - Translation
+ 'skip_next_article' => '다음 글로 커서 이동',
+ 'skip_previous_article' => '이전 글로 커서 이동',
'title' => '단축키',
'user_filter' => '사용자 필터 사용하기',
'user_filter_help' => '사용자 필터가 하나만 설정되어 있다면 해당 필터를 사용하고, 그렇지 않다면 필터를 번호로 선택할 수 있습니다.',
diff --git a/app/i18n/kr/feedback.php b/app/i18n/kr/feedback.php
index 550904894..0e31536f8 100644
--- a/app/i18n/kr/feedback.php
+++ b/app/i18n/kr/feedback.php
@@ -57,8 +57,8 @@ return array(
'sub' => array(
'actualize' => '피드를 가져오는 중입니다',
'articles' => array(
- 'marked_read' => 'The selected articles have been marked as read.', //TODO - Translation
- 'marked_unread' => 'The articles have been marked as unread.', //TODO - Translation
+ 'marked_read' => '선택된 글들을 읽음으로 표시하였습니다.',
+ 'marked_unread' => '선택된 글들을 읽지 않음으로 표시하였습니다.',
),
'category' => array(
'created' => '%s 카테고리가 생성되었습니다.',
diff --git a/app/i18n/kr/gen.php b/app/i18n/kr/gen.php
index 86a50e9c4..f7855c499 100644
--- a/app/i18n/kr/gen.php
+++ b/app/i18n/kr/gen.php
@@ -152,13 +152,13 @@ return array(
'user_profile' => '프로필',
),
'pagination' => array(
- 'first' => 'First',
- 'last' => 'Last',
+ 'first' => '처음으로',
+ 'last' => '마지막으로',
'load_more' => '글 더 불러오기',
'mark_all_read' => '모두 읽음으로 표시',
- 'next' => 'Next',
+ 'next' => '다음',
'nothing_to_load' => '더 이상 글이 없습니다',
- 'previous' => 'Previous',
+ 'previous' => '이전',
),
'share' => array(
'blogotext' => 'Blogotext',
@@ -183,13 +183,13 @@ return array(
'short' => array(
'attention' => '경고!',
'blank_to_disable' => '빈 칸으로 두면 비활성화',
- 'by_author' => 'By:',
+ 'by_author' => '글쓴이:',
'by_default' => '기본값',
'damn' => '이런!',
'default_category' => '분류 없음',
'no' => '아니요',
'not_applicable' => '사용할 수 없음',
- 'ok' => 'Ok!',
+ 'ok' => '좋습니다!',
'or' => '또는',
'yes' => '네',
),
diff --git a/app/i18n/kr/index.php b/app/i18n/kr/index.php
index 3c63fd664..bebc8bdec 100644
--- a/app/i18n/kr/index.php
+++ b/app/i18n/kr/index.php
@@ -40,7 +40,7 @@ return array(
'mark_all_read' => '모두 읽음으로 표시',
'mark_cat_read' => '카테고리를 읽음으로 표시',
'mark_feed_read' => '피드를 읽음으로 표시',
- 'mark_selection_unread' => 'Mark selection as unread', //TODO - Translation
+ 'mark_selection_unread' => '선택된 글을 읽지 않음으로 표시',
'newer_first' => '최근 글 먼저',
'non-starred' => '즐겨찾기를 제외하고 표시',
'normal_view' => '일반 모드',
@@ -53,11 +53,11 @@ return array(
'starred' => '즐겨찾기만 표시',
'stats' => '통계',
'subscription' => '구독 관리',
- 'tags' => 'My labels', //TODO - Translation
+ 'tags' => '내 라벨',
'unread' => '읽지 않은 글만 표시',
),
'share' => '공유',
'tag' => array(
- 'related' => '관련 태그', //TODO - Translation
+ 'related' => '관련 태그',
),
);
diff --git a/app/i18n/kr/install.php b/app/i18n/kr/install.php
index 6b60f6ffd..65e8726e1 100644
--- a/app/i18n/kr/install.php
+++ b/app/i18n/kr/install.php
@@ -69,8 +69,8 @@ return array(
'ok' => 'JSON 확장 기능이 설치되어 있습니다.',
),
'mbstring' => array(
- 'nok' => 'Cannot find the recommended library mbstring for Unicode.', //TODO - Translation
- 'ok' => 'You have the recommended library mbstring for Unicode.', //TODO - Translation
+ 'nok' => '유니코드 지원을 위한 mbstring 라이브러리를 찾을 수 없습니다.',
+ 'ok' => '유니코드 지원을 위한 mbstring 라이브러리가 설치되어 있습니다.',
),
'minz' => array(
'nok' => 'Minz 프레임워크를 찾을 수 없습니다.',
diff --git a/app/i18n/kr/sub.php b/app/i18n/kr/sub.php
index 9f8967053..151775c1c 100644
--- a/app/i18n/kr/sub.php
+++ b/app/i18n/kr/sub.php
@@ -27,12 +27,16 @@ return array(
'password' => 'HTTP 암호',
'username' => 'HTTP 사용자 이름',
),
- 'clear_cache' => 'Always clear cache', //TODO - Translation
+ 'clear_cache' => '항상 캐시 지우기',
'css_help' => '글의 일부가 포함된 RSS 피드를 가져옵니다 (주의, 시간이 좀 더 걸립니다!)',
'css_path' => '웹사이트 상의 글 본문에 해당하는 CSS 경로',
'description' => '설명',
'empty' => '이 피드는 비어있습니다. 피드가 계속 운영되고 있는지 확인하세요.',
'error' => '이 피드에 문제가 발생했습니다. 이 피드에 접근 권한이 있는지 확인하세요.',
+ 'filteractions' => array(
+ '_' => 'Filter actions', //TODO - Translation
+ 'help' => 'Write one search filter per line.', //TODO - Translation
+ ),
'informations' => '정보',
'keep_history' => '최소 유지 글 개수',
'moved_category_deleted' => '카테고리를 삭제하면, 해당 카테고리 아래에 있던 피드들은 자동적으로 <em>%s</em> 아래로 분류됩니다.',
@@ -47,11 +51,11 @@ return array(
),
'websub' => 'WebSub을 사용한 즉시 알림',
'show' => array(
- 'all' => 'Show all feeds', //TODO - Translation
- 'error' => 'Show only feeds with error', //TODO - Translation
+ 'all' => '모든 피드 보기',
+ 'error' => '오류가 발생한 피드만 보기',
),
'showing' => array(
- 'error' => 'Showing only feeds with error', //TODO - Translation
+ 'error' => '오류가 발생한 피드만 보여주고 있습니다',
),
'ssl_verify' => 'SSL 유효성 검사',
'stats' => '통계',
@@ -72,7 +76,7 @@ return array(
'export' => '내보내기',
'export_opml' => '피드 목록 내보내기 (OPML)',
'export_starred' => '즐겨찾기 내보내기',
- 'export_labelled' => 'Export your labelled articles', //TODO
+ 'export_labelled' => '라벨이 표시된 글들 내보내기',
'feed_list' => '%s 개의 글 목록',
'file_to_import' => '불러올 파일<br />(OPML, JSON 또는 ZIP)',
'file_to_import_no_zip' => '불러올 파일<br />(OPML 또는 JSON)',
diff --git a/app/i18n/nl/sub.php b/app/i18n/nl/sub.php
index 1d6c9f806..8ba9c020d 100644
--- a/app/i18n/nl/sub.php
+++ b/app/i18n/nl/sub.php
@@ -33,6 +33,10 @@ return array(
'description' => 'Omschrijving',
'empty' => 'Deze feed is leeg. Controleer of deze nog actueel is.',
'error' => 'Deze feed heeft problemen. Verifieer a.u.b het doeladres en actualiseer het.',
+ 'filteractions' => array(
+ '_' => 'Filter actions', //TODO - Translation
+ 'help' => 'Write one search filter per line.', //TODO - Translation
+ ),
'informations' => 'Informatie',
'keep_history' => 'Minimum aantal artikelen om te houden',
'moved_category_deleted' => 'Als u een categorie verwijderd, worden de feeds automatisch geclassificeerd onder <em>%s</em>.',
diff --git a/app/i18n/oc/admin.php b/app/i18n/oc/admin.php
index 4a47374d7..2f8ede873 100644
--- a/app/i18n/oc/admin.php
+++ b/app/i18n/oc/admin.php
@@ -130,7 +130,7 @@ return array(
'category' => 'Categoria',
'entry_count' => 'Nombre d’articles',
'entry_per_category' => 'Articles per categoria',
- 'entry_per_day' => 'Nombre d’articles per jorn (30 darrièrs jorns)',
+ 'entry_per_day' => 'Nombre d’articles per jorn (darrièrs 30 jorns)',
'entry_per_day_of_week' => 'Per jorn de la setmana (mejana : %.2f messatges)',
'entry_per_hour' => 'Per ora (mejana : %.2f messatges)',
'entry_per_month' => 'Per mes (mejana : %.2f messatges)',
diff --git a/app/i18n/oc/gen.php b/app/i18n/oc/gen.php
index 168ea4732..7f9793283 100644
--- a/app/i18n/oc/gen.php
+++ b/app/i18n/oc/gen.php
@@ -68,8 +68,8 @@ return array(
'Jun' => '\\j\\u\\n\\h',
'jun' => 'junh',
'june' => 'junh',
- 'last_3_month' => 'Dempuèi los tres darrièrs meses',
- 'last_6_month' => 'Dempuèi los sièis darrièrs meses',
+ 'last_3_month' => 'Dempuèi los darrièrs tres meses',
+ 'last_6_month' => 'Dempuèi los darrièrs sièis meses',
'last_month' => 'Dempuèi lo mes passat',
'last_week' => 'Dempuèi la setmana passada',
'last_year' => 'Dempuèi l’annada passada',
diff --git a/app/i18n/oc/index.php b/app/i18n/oc/index.php
index 5211fd24a..5cc71c9a9 100644
--- a/app/i18n/oc/index.php
+++ b/app/i18n/oc/index.php
@@ -6,7 +6,7 @@ return array(
'agpl3' => '<a href="https://www.gnu.org/licenses/agpl-3.0.html">AGPL 3</a>',
'bugs_reports' => 'Senhalament de problèmas',
'credits' => 'Crèdits',
- 'credits_content' => 'Unes elements de l’estil venon del <a href="http://twitter.github.io/bootstrap/">projècte Bootstrap</a> encara que FreshRSS utilize pas aqueste framework. Las<a href="https://git.gnome.org/browse/gnome-icon-theme-symbolic">icònas</a> venon del <a href="https://www.gnome.org/">projècte GNOME</a>. La polissa <em>Open Sans</em> utilizada foguèt creada per en <a href="https://fonts.google.com/specimen/Open+Sans">Steve Matteson</a>. FreshRSS es basat sus <a href="https://github.com/marienfressinaud/MINZ">Minz</a>, un framework PHP.',
+ 'credits_content' => 'Unes elements de l’estil venon del <a href="http://twitter.github.io/bootstrap/">projècte Bootstrap</a> encara que FreshRSS utilize pas aqueste framework. Las <a href="https://git.gnome.org/browse/gnome-icon-theme-symbolic">icònas</a> venon del <a href="https://www.gnome.org/">projècte GNOME</a>. La polissa <em>Open Sans</em> utilizada foguèt creada per en <a href="https://fonts.google.com/specimen/Open+Sans">Steve Matteson</a>. FreshRSS es basat sus <a href="https://github.com/marienfressinaud/MINZ">Minz</a>, un framework PHP.',
'freshrss_description' => 'FreshRSS es un agregador de fluxes RSS per l’auto-albergar tal coma <a href="http://tontof.net/kriss/feed/">Kriss Feed</a> o <a href="http://projet.idleman.fr/leed/">Leed</a>. Sa tòca es d’èsser leugièr e de bon utilizar de prima abòrd mas tanben d’èsser potent e parametrable.',
'github' => '<a href="https://github.com/FreshRSS/FreshRSS/issues">on Github</a>',
'license' => 'Licéncia',
diff --git a/app/i18n/oc/sub.php b/app/i18n/oc/sub.php
index fc5a0cc1f..5a7bb2b57 100644
--- a/app/i18n/oc/sub.php
+++ b/app/i18n/oc/sub.php
@@ -32,6 +32,10 @@ return array(
'description' => 'Descripcion',
'empty' => 'Aqueste flux es void. Assegurats-vos qu’es totjorn mantengut.',
'error' => 'Aqueste flux a rescontrat un problèma. Volgatz verificar que siá totjorn accessible puèi actualizatz-lo.',
+ 'filteractions' => array(
+ '_' => 'Filtre d’accion',
+ 'help' => 'Escrivètz una recèrca per linha.',
+ ),
'informations' => 'Informacions',
'keep_history' => 'Nombre minimum d’articles de servar',
'moved_category_deleted' => 'Quand escafatz una categoria, sos fluxes son automaticament classats dins <em>%s</em>.',
diff --git a/app/i18n/pt-br/sub.php b/app/i18n/pt-br/sub.php
index 58b2fc1f9..fc26e89e7 100644
--- a/app/i18n/pt-br/sub.php
+++ b/app/i18n/pt-br/sub.php
@@ -33,6 +33,10 @@ return array(
'description' => 'Descrição',
'empty' => 'Este feed está vazio. Por favor verifique ele ainda é mantido.',
'error' => 'Este feed encontra-se com problema. Por favor verifique se ele ainda está disponível e atualize-o.',
+ 'filteractions' => array(
+ '_' => 'Filter actions', //TODO - Translation
+ 'help' => 'Write one search filter per line.', //TODO - Translation
+ ),
'informations' => 'Informações',
'keep_history' => 'Número mínimo de artigos para manter',
'moved_category_deleted' => 'Quando você deleta uma categoria, seus feeds são automaticamente classificados como <em>%s</em>.',
diff --git a/app/i18n/ru/conf.php b/app/i18n/ru/conf.php
index 59ac480bc..841477964 100644
--- a/app/i18n/ru/conf.php
+++ b/app/i18n/ru/conf.php
@@ -92,7 +92,7 @@ return array(
'auto_remove_article' => 'Hide articles after reading', //TODO - Translation
'confirm_enabled' => 'Display a confirmation dialog on “mark all as read” actions', //TODO - Translation
'display_articles_unfolded' => 'Show articles unfolded by default', //TODO - Translation
- 'display_categories_unfolded' => 'Show categories folded by default', //TODO - Translation
+ 'display_categories_unfolded' => 'Show categories unfolded by default', //TODO - Translation
'hide_read_feeds' => 'Hide categories & feeds with no unread article (does not work with “Show all articles” configuration)', //TODO - Translation
'img_with_lazyload' => 'Use "lazy load" mode to load pictures', //TODO - Translation
'jump_next' => 'jump to next unread sibling (feed or category)', //TODO - Translation
diff --git a/app/i18n/ru/sub.php b/app/i18n/ru/sub.php
index 62f8a8e3a..e125d549e 100644
--- a/app/i18n/ru/sub.php
+++ b/app/i18n/ru/sub.php
@@ -33,6 +33,10 @@ return array(
'description' => 'Description', //TODO - Translation
'empty' => 'This feed is empty. Please verify that it is still maintained.', //TODO - Translation
'error' => 'This feed has encountered a problem. Please verify that it is always reachable then actualize it.', //TODO - Translation
+ 'filteractions' => array(
+ '_' => 'Filter actions', //TODO - Translation
+ 'help' => 'Write one search filter per line.', //TODO - Translation
+ ),
'informations' => 'Information', //TODO - Translation
'keep_history' => 'Minimum number of articles to keep', //TODO - Translation
'moved_category_deleted' => 'When you delete a category, its feeds are automatically classified under <em>%s</em>.', //TODO - Translation
diff --git a/app/i18n/tr/conf.php b/app/i18n/tr/conf.php
index 507558487..6c57d39da 100644
--- a/app/i18n/tr/conf.php
+++ b/app/i18n/tr/conf.php
@@ -92,7 +92,7 @@ return array(
'auto_remove_article' => 'Okuduktan sonra makaleleri gizle',
'confirm_enabled' => '"Hepsini okundu say" eylemi için onay iste',
'display_articles_unfolded' => 'Show articles unfolded by default',
- 'display_categories_unfolded' => 'Show categories folded by default',
+ 'display_categories_unfolded' => 'Show categories unfolded 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',
'jump_next' => 'Bir sonraki benzer okunmamışa geç (akış veya kategori)',
diff --git a/app/i18n/tr/sub.php b/app/i18n/tr/sub.php
index 7f29633be..9f4945c0a 100644
--- a/app/i18n/tr/sub.php
+++ b/app/i18n/tr/sub.php
@@ -33,6 +33,10 @@ return array(
'description' => 'Tanım',
'empty' => 'Bu akış boş. Lütfen akışın aktif olduğuna emin olun.',
'error' => 'Bu akışda bir hatayla karşılaşıldı. Lütfen akışın sürekli ulaşılabilir olduğuna emin olun.',
+ 'filteractions' => array(
+ '_' => 'Filter actions', //TODO - Translation
+ 'help' => 'Write one search filter per line.', //TODO - Translation
+ ),
'informations' => 'Bilgi',
'keep_history' => 'En az tutulacak makale sayısı',
'moved_category_deleted' => 'Bir kategoriyi silerseniz, içerisindeki akışlar <em>%s</em> içerisine yerleşir.',
diff --git a/app/i18n/zh-cn/admin.php b/app/i18n/zh-cn/admin.php
index e34070526..74f57b6e8 100644
--- a/app/i18n/zh-cn/admin.php
+++ b/app/i18n/zh-cn/admin.php
@@ -64,11 +64,11 @@ return array(
'files' => '文件相关',
'json' => array(
'nok' => '找不到 JSON 扩展 (php-json ) 。',
- 'ok' => '已找到 JSON 扩展',
+ 'ok' => '已找到 JSON 扩展。',
),
'mbstring' => array(
- 'nok' => 'Cannot find the recommended library mbstring for Unicode.', //TODO - Translation
- 'ok' => 'You have the recommended library mbstring for Unicode.', //TODO - Translation
+ 'nok' => '找不到推荐的 Unicode 解析库 (mbstring)。',
+ 'ok' => '已找到推荐的 Unicode 解析库 (mbstring)。',
),
'minz' => array(
'nok' => '找不到 Minz 框架。',
@@ -163,8 +163,8 @@ return array(
'max-categories' => '每用户分类限制',
'max-feeds' => '每用户 RSS 源限制',
'cookie-duration' => array(
- 'help' => 'in seconds', // @todo translate
- 'number' => 'Duration to keep logged in', // @todo translate
+ 'help' => '单位(秒)',
+ 'number' => '保持登录的时长',
),
'registration' => array(
'help' => '0 表示无账户数限制',
@@ -183,15 +183,15 @@ return array(
'user' => array(
'articles_and_size' => '%s 篇文章 (%s)',
'create' => '创建新用户',
- 'delete_users' => 'Delete user', //TODO - Translation
+ 'delete_users' => '删除用户',
'language' => '语言',
- 'number' => '已有 %d 个帐户',
- 'numbers' => '已有 %d 个帐户',
+ 'number' => '已有 %d 个用户',
+ 'numbers' => '已有 %d 个用户',
'password_form' => '密码<br /><small>(用于 Web-form 登录方式)</small>',
'password_format' => '至少 7 个字符',
- 'selected' => 'Selected user', //TODO - Translation
+ 'selected' => '已选中用户',
'title' => '用户管理',
- 'update_users' => 'Update user', //TODO - Translation
+ 'update_users' => '更新用户',
'user_list' => '用户列表',
'username' => '用户名',
'users' => '用户',
diff --git a/app/i18n/zh-cn/conf.php b/app/i18n/zh-cn/conf.php
index 1216aaaca..535dfd358 100644
--- a/app/i18n/zh-cn/conf.php
+++ b/app/i18n/zh-cn/conf.php
@@ -28,7 +28,7 @@ return array(
'seconds' => '秒 (0 表示不超时)',
'timeout' => 'HTML5 通知超时时间',
),
- 'show_nav_buttons' => 'Show the navigation buttons', //TODO - Translation
+ 'show_nav_buttons' => '显示导航按钮',
'theme' => '主题',
'title' => '显示',
'width' => array(
@@ -53,7 +53,7 @@ return array(
'query' => array(
'_' => '自定义查询',
'deprecated' => '此查询不再有效。相关的分类或 RSS 源已被删除。',
- 'display' => 'Display user query results', //TODO - Translation
+ 'display' => '显示查询结果',
'filter' => '生效的过滤器:',
'get_all' => '显示所有文章',
'get_category' => '显示分类 "%s"',
@@ -64,7 +64,7 @@ return array(
'number' => '查询 n°%d',
'order_asc' => '由旧到新显示文章',
'order_desc' => '由新到旧显示文章',
- 'remove' => 'Remove user query', //TODO - Translation
+ 'remove' => '删除查询',
'search' => '搜索 "%s"',
'state_0' => '显示所有文章',
'state_1' => '显示已读文章',
@@ -128,7 +128,7 @@ return array(
),
'sharing' => array(
'_' => '分享',
- 'add' => 'Add a sharing method', //TODO - Translation
+ 'add' => '添加分享方式',
'blogotext' => 'Blogotext',
'diaspora' => 'Diaspora*',
'email' => 'Email',
@@ -136,7 +136,7 @@ return array(
'g+' => 'Google+',
'more_information' => '更多信息',
'print' => '打印',
- 'remove' => 'Remove sharing method', //TODO - Translation
+ 'remove' => '删除分享方式',
'shaarli' => 'Shaarli',
'share_name' => '名称',
'share_url' => '地址',
@@ -148,31 +148,31 @@ return array(
'_' => '快捷键',
'article_action' => '文章操作',
'auto_share' => '分享',
- 'auto_share_help' => '如果有多种分享模式,则会按照它们的编号依次访问。',
+ 'auto_share_help' => '如果有多种分享方式,则会按照它们的编号依次访问。',
'close_dropdown' => '关闭菜单',
'collapse_article' => '收起文章',
- 'first_article' => '跳转到第一篇文章',
+ 'first_article' => '打开第一篇文章',
'focus_search' => '聚焦到搜索框',
'global_view' => '切换到全屏视图',
'help' => '显示帮助文档',
'javascript' => '若要使用快捷键,必须启用 JavaScript',
- 'last_article' => '跳转到最后一篇文章',
+ 'last_article' => '打开最后一篇文章',
'load_more' => '载入更多文章',
'mark_favorite' => '加入收藏',
'mark_read' => '设为已读',
'navigation' => '浏览',
'navigation_help' => '搭配 "Shift" 键,浏览快捷键将生效于 RSS 源。<br/>搭配 "Alt" 键,浏览快捷键将生效于分类。',
- 'navigation_no_mod_help' => 'The following navigation shortcuts do not support modifiers.', //TODO - Translation
- 'next_article' => '跳转到下一篇文章',
+ 'navigation_no_mod_help' => '以下快捷键不支持组合键 (Shift 或 Alt)',
+ 'next_article' => '打开下一篇文章',
'normal_view' => '切换到普通视图',
'other_action' => '其他操作',
- 'previous_article' => '跳转到上一篇文章',
+ 'previous_article' => '打开上一篇文章',
'reading_view' => '切换到阅读视图',
'rss_view' => '在新标签中打开 RSS 视图',
'see_on_website' => '在原网站上查看',
'shift_for_all_read' => '+ <code>shift</code> 可以将全部文章设为已读',
- 'skip_next_article' => 'Focus next without opening', //TODO - Translation
- 'skip_previous_article' => 'Focus previous without opening', //TODO - Translation
+ 'skip_next_article' => '跳转到下一篇文章而不打开',
+ 'skip_previous_article' => '跳转到上一篇文章而不打开',
'title' => '快捷键',
'user_filter' => '显示自定义查询',
'user_filter_help' => '如果有多个自定义过滤器,则会按照它们的编号依次访问。',
diff --git a/app/i18n/zh-cn/feedback.php b/app/i18n/zh-cn/feedback.php
index e1778a9f2..e8ee969b0 100644
--- a/app/i18n/zh-cn/feedback.php
+++ b/app/i18n/zh-cn/feedback.php
@@ -57,8 +57,8 @@ return array(
'sub' => array(
'actualize' => '获取',
'articles' => array(
- 'marked_read' => 'The selected articles have been marked as read.', //TODO - Translation
- 'marked_unread' => 'The articles have been marked as unread.', //TODO - Translation
+ 'marked_read' => '选中文章已标记为已读',
+ 'marked_unread' => '文章已标记为未读',
),
'category' => array(
'created' => '分类 %s 已创建。',
@@ -80,7 +80,7 @@ return array(
'already_subscribed' => '你已订阅 <em>%s</em>',
'deleted' => 'RSS 源已删除',
'error' => 'RSS 源更新失败',
- 'internal_problem' => 'RSS 源添加失败。<a href="%s">检查 FreshRSS 日志</a> 查看详情。', //TODO - Translation
+ 'internal_problem' => 'RSS 源添加失败。<a href="%s">检查 FreshRSS 日志</a> 查看详情。你可以在URL后附加 <code>#force_feed</code> 从而尝试强制添加。',
'invalid_url' => 'URL <em>%s</em> 无效',
'n_actualized' => '%d 个 RSS 源已更新',
'n_entries_deleted' => '%d 篇文章已删除',
@@ -109,8 +109,8 @@ return array(
'error' => '用户 %s 删除失败',
),
'updated' => array(
- '_' => 'User %s has been updated', //TODO - Translation
- 'error' => 'User %s has not been updated', //TODO - Translation
+ '_' => '用户 %s 已更新',
+ 'error' => '用户 %s 更新失败',
),
),
);
diff --git a/app/i18n/zh-cn/gen.php b/app/i18n/zh-cn/gen.php
index 1dcd95233..11d4efdb3 100644
--- a/app/i18n/zh-cn/gen.php
+++ b/app/i18n/zh-cn/gen.php
@@ -19,7 +19,7 @@ return array(
'see_website' => '查看网站',
'submit' => '提交',
'truncate' => '删除所有文章',
- 'update' => 'Update', //TODO - Translation
+ 'update' => '更新', //TODO - Translation
),
'auth' => array(
'email' => 'Email 地址',
diff --git a/app/i18n/zh-cn/index.php b/app/i18n/zh-cn/index.php
index 3f6b44701..018813c3e 100644
--- a/app/i18n/zh-cn/index.php
+++ b/app/i18n/zh-cn/index.php
@@ -40,7 +40,7 @@ return array(
'mark_all_read' => '全部设为已读',
'mark_cat_read' => '此分类设为已读',
'mark_feed_read' => '此源设为已读',
- 'mark_selection_unread' => 'Mark selection as unread', //TODO - Translation
+ 'mark_selection_unread' => '选中设为已读',
'newer_first' => '由新到旧',
'non-starred' => '显示未收藏',
'normal_view' => '普通视图',
@@ -53,11 +53,11 @@ return array(
'starred' => '显示收藏',
'stats' => '统计',
'subscription' => '订阅管理',
- 'tags' => 'My labels', //TODO - Translation
+ 'tags' => '我的标签',
'unread' => '显示未读',
),
'share' => '分享',
'tag' => array(
- 'related' => '相关标签', //TODO - Translation
+ 'related' => '文章标签',
),
);
diff --git a/app/i18n/zh-cn/install.php b/app/i18n/zh-cn/install.php
index 29647932a..da231917b 100644
--- a/app/i18n/zh-cn/install.php
+++ b/app/i18n/zh-cn/install.php
@@ -69,8 +69,8 @@ return array(
'ok' => '已找到推荐的 JSON 解析库。',
),
'mbstring' => array(
- 'nok' => 'Cannot find the recommended library mbstring for Unicode.', //TODO - Translation
- 'ok' => 'You have the recommended library mbstring for Unicode.', //TODO - Translation
+ 'nok' => '找不到推荐的 Unicode 解析库 (mbstring)。',
+ 'ok' => '已找到推荐的 Unicode 解析库 (mbstring)。',
),
'minz' => array(
'nok' => '找不到 Minz 框架。',
diff --git a/app/i18n/zh-cn/sub.php b/app/i18n/zh-cn/sub.php
index 3a9623468..90f9fd942 100644
--- a/app/i18n/zh-cn/sub.php
+++ b/app/i18n/zh-cn/sub.php
@@ -27,12 +27,16 @@ return array(
'password' => 'HTTP 密码',
'username' => 'HTTP 用户名',
),
- 'clear_cache' => 'Always clear cache', //TODO - Translation
+ 'clear_cache' => '总是清除缓存',
'css_help' => '用于获取全文(注意,这将耗费更多时间!)',
'css_path' => '原文的 CSS 选择器',
'description' => '描述',
'empty' => '此源为空。请确认它是否正常更新。',
'error' => '此源遇到一些问题。请在确认是否能正常访问后重试。',
+ 'filteractions' => array(
+ '_' => 'Filter actions', //TODO - Translation
+ 'help' => 'Write one search filter per line.', //TODO - Translation
+ ),
'informations' => '信息',
'keep_history' => '至少保存的文章数',
'moved_category_deleted' => '删除分类时,其中的 RSS 源会自动归类到 <em>%s</em>',
@@ -47,16 +51,16 @@ return array(
),
'websub' => 'WebSub 即时通知',
'show' => array(
- 'all' => 'Show all feeds', //TODO - Translation
- 'error' => 'Show only feeds with error', //TODO - Translation
+ 'all' => '显示所有 RSS 源',
+ 'error' => '仅显示有错误的 RSS 源',
),
'showing' => array(
- 'error' => 'Showing only feeds with error', //TODO - Translation
+ 'error' => '正在显示有错误的 RSS 源',
),
- 'ssl_verify' => 'Verify SSL security', //TODO - Translation
+ 'ssl_verify' => '验证 SSL 安全',
'stats' => '统计',
'think_to_add' => '你可以添加一些 RSS 源。',
- 'timeout' => 'Timeout in seconds', //TODO - Translation
+ 'timeout' => '超时时间(秒)',
'title' => '标题',
'title_add' => '添加 RSS 源',
'ttl' => '最小自动更新时间',
@@ -72,7 +76,7 @@ return array(
'export' => '导出',
'export_opml' => '导出 RSS 源列表 (OPML)',
'export_starred' => '导出你的收藏',
- 'export_labelled' => 'Export your labelled articles', //TODO
+ 'export_labelled' => '导出有标签的文章',
'feed_list' => '%s 文章列表',
'file_to_import' => '需要导入的文件<br />(OPML, JSON 或 ZIP)',
'file_to_import_no_zip' => '需要导入的文件<br />(OPML 或 JSON)',
diff --git a/app/layout/aside_feed.phtml b/app/layout/aside_feed.phtml
index 5efaa54d1..637acf4a4 100644
--- a/app/layout/aside_feed.phtml
+++ b/app/layout/aside_feed.phtml
@@ -37,13 +37,14 @@
<?php
$t_active = FreshRSS_Context::isCurrentGet('T');
+ $t_show = $t_active || FreshRSS_Context::$user_conf->display_categories;
?>
<li class="tree-folder category tags<?php echo $t_active ? ' active' : ''; ?>">
<div class="tree-folder-title">
<a class="dropdown-toggle" href="#"><?php echo _i($t_active ? 'up' : 'down'); ?></a>
<a class="title" data-unread="<?php echo format_number($this->nbUnreadTags); ?>" href="<?php echo _url('index', $actual_view, 'get', 'T'); ?>"><?php echo _t('index.menu.tags'); ?></a>
</div>
- <ul class="tree-folder-items<?php echo $t_active ? ' active' : ''; ?>">
+ <ul class="tree-folder-items<?php echo $t_show ? ' active' : ''; ?>">
<?php
foreach ($this->tags as $tag):
?>
@@ -64,8 +65,7 @@
$feeds = $cat->feeds();
if (!empty($feeds)) {
$c_active = FreshRSS_Context::isCurrentGet('c_' . $cat->id());
- $c_show = $c_active && (!FreshRSS_Context::$user_conf->display_categories ||
- FreshRSS_Context::$current_get['feed']);
+ $c_show = $c_active || FreshRSS_Context::$user_conf->display_categories;
?>
<li class="tree-folder category<?php echo $c_active ? ' active' : ''; ?>" data-unread="<?php echo $cat->nbNotRead(); ?>">
<div class="tree-folder-title">
diff --git a/app/views/entry/read.phtml b/app/views/entry/read.phtml
index fb9e129f2..44193da9c 100755
--- a/app/views/entry/read.phtml
+++ b/app/views/entry/read.phtml
@@ -1,17 +1,7 @@
<?php
header('Content-Type: application/json; charset=UTF-8');
-$url = array(
- 'c' => Minz_Request::controllerName(),
- 'a' => Minz_Request::actionName(),
- 'params' => Minz_Request::fetchGET(),
-);
-
-$url['params']['is_read'] = Minz_Request::param('is_read', true) ? '0' : '1';
-
FreshRSS::loadStylesAndScripts();
echo json_encode(array(
- 'url' => str_ireplace('&amp;', '&', Minz_Url::display($url)),
- 'icon' => _i($url['params']['is_read'] === '1' ? 'unread' : 'read'),
'tags' => $this->tags,
));
diff --git a/app/views/helpers/feed/update.phtml b/app/views/helpers/feed/update.phtml
index bc90ba456..be8034c0d 100644
--- a/app/views/helpers/feed/update.phtml
+++ b/app/views/helpers/feed/update.phtml
@@ -234,6 +234,19 @@
</div>
<?php } ?>
+ <legend><?php echo _t('sub.feed.filteractions'); ?></legend>
+ <div class="form-group">
+ <label class="group-name" for="filteractions_read"><?php echo _t('conf.reading.read.when'); ?></label>
+ <div class="group-controls">
+ <textarea name="filteractions_read" id="filteractions_read"><?php
+ foreach ($this->feed->filtersAction('read') as $filterRead) {
+ echo htmlspecialchars($filterRead->getRawInput(), ENT_NOQUOTES, 'UTF-8'), "\n\n";
+ }
+ ?></textarea>
+ <?php echo _i('help'); ?> <?php echo _t('sub.feed.filteractions.help'); ?>
+ </div>
+ </div>
+
<div class="form-group form-actions">
<div class="group-controls">
<button type="submit" class="btn btn-important"><?php echo _t('gen.action.submit'); ?></button>
diff --git a/app/views/helpers/index/normal/entry_bottom.phtml b/app/views/helpers/index/normal/entry_bottom.phtml
index 1f35318e3..c0edbdf7d 100644
--- a/app/views/helpers/index/normal/entry_bottom.phtml
+++ b/app/views/helpers/index/normal/entry_bottom.phtml
@@ -93,7 +93,7 @@
<a target="_blank" rel="noreferrer" href="<?php echo $share->url(); ?>"><?php echo $share->name(); ?></a>
<?php } else {?>
<a href="POST"><?php echo $share->name(); ?></a>
- <form method="POST" data-url="<?php echo $share->url(); ?>">
+ <form method="POST" action="<?php echo $share->url(); ?>" disabled="disabled">
<input type="hidden" value="<?php echo $link; ?>" name="<?php echo $share->field(); ?>"/>
</form>
<?php } ?>
diff --git a/app/views/helpers/javascript_vars.phtml b/app/views/helpers/javascript_vars.phtml
index a434a04a3..b62263ecf 100644
--- a/app/views/helpers/javascript_vars.phtml
+++ b/app/views/helpers/javascript_vars.phtml
@@ -42,7 +42,7 @@ echo htmlspecialchars(json_encode(array(
'reading_view' => @$s['reading_view'],
'rss_view' => @$s['rss_view'],
),
- 'url' => array(
+ 'urls' => array(
'index' => _url('index', 'index'),
'login' => Minz_Url::display(array('c' => 'auth', 'a' => 'login'), 'php'),
'logout' => Minz_Url::display(array('c' => 'auth', 'a' => 'logout'), 'php'),
@@ -56,6 +56,7 @@ echo htmlspecialchars(json_encode(array(
'category_empty' => _t('gen.js.category_empty'),
),
'icons' => array(
- 'close' => _i('close'),
+ 'read' => rawurlencode(_i('read')),
+ 'unread' => rawurlencode(_i('unread')),
),
), JSON_UNESCAPED_UNICODE), ENT_NOQUOTES, 'UTF-8');
diff --git a/app/views/index/reader.phtml b/app/views/index/reader.phtml
index 1485a1997..fbe37d2e3 100644
--- a/app/views/index/reader.phtml
+++ b/app/views/index/reader.phtml
@@ -42,7 +42,7 @@ if (!empty($this->entries)) {
<a class="bookmark" href="<?php echo Minz_Url::display($favoriteUrl); ?>">
<?php echo _i($item->isFavorite() ? 'starred' : 'non-starred'); ?>
</a>
- <a href="<?php echo _url('index', 'reader', 'get', 'f_' . $feed->id()); ?>">
+ <a class="website" href="<?php echo _url('index', 'reader', 'get', 'f_' . $feed->id()); ?>">
<img class="favicon" src="<?php echo $feed->favicon(); ?>" alt="✇" /> <span><?php echo $feed->name(); ?></span>
</a>
<h1 class="title"><a target="_blank" rel="noreferrer" class="go_website" href="<?php echo $item->link(); ?>"><?php echo $item->title(); ?></a></h1>
diff --git a/app/views/user/manage.phtml b/app/views/user/manage.phtml
index 9d457f7a5..d0e5928ef 100644
--- a/app/views/user/manage.phtml
+++ b/app/views/user/manage.phtml
@@ -53,7 +53,7 @@
<div class="form-group">
<label class="group-name" for="current_user"><?php echo _t('admin.user.selected'); ?></label>
<div class="group-controls">
- <select id="current_user" class="select-change" name="username">
+ <select id="current_user" name="username">
<option selected="selected"> </option>
<?php foreach (listUsers() as $username) { ?>
<option value="<?php echo $username; ?>"><?php echo $username; ?></option>