diff options
| author | 2019-03-31 16:38:46 +0200 | |
|---|---|---|
| committer | 2019-03-31 16:38:46 +0200 | |
| commit | d413f67dd28738f4a6d8cf036e00714737f757b8 (patch) | |
| tree | 1509f631dc8814bcf85d907a292ddd6437a2efcd /app | |
| parent | 8dcdde6251ae4dfc690b1a014488df125c5e5cdc (diff) | |
| parent | 2a935516d850d63a215f9650b96ede102311f7ca (diff) | |
Merge pull request #2298 from FreshRSS/dev1.14.0
FreshRSS 1.14.0
Diffstat (limited to 'app')
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('&', '&', 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> |
