aboutsummaryrefslogtreecommitdiff
path: root/app
diff options
context:
space:
mode:
authorGravatar Alexandre Alapetite <alexandre@alapetite.fr> 2018-10-28 09:49:10 +0100
committerGravatar GitHub <noreply@github.com> 2018-10-28 09:49:10 +0100
commite04804d0f67dd43fd3f072b9a127768ee7b7b56c (patch)
treea49023ed25aab7fb1c1aafe749f7d462de0027b2 /app
parent44bd07e506ade204151c276fdc05994d51efdd7a (diff)
parent4234dfe0d72b61fe931d2c76a1d8a335ce65a209 (diff)
Merge pull request #2049 from FreshRSS/dev1.12.0
FreshRSS 1.12.0
Diffstat (limited to 'app')
-rw-r--r--app/Controllers/categoryController.php8
-rwxr-xr-xapp/Controllers/configureController.php10
-rwxr-xr-xapp/Controllers/entryController.php17
-rwxr-xr-xapp/Controllers/feedController.php12
-rwxr-xr-xapp/Controllers/indexController.php35
-rwxr-xr-xapp/Controllers/javascriptController.php5
-rw-r--r--app/Controllers/statsController.php2
-rw-r--r--app/Controllers/subscriptionController.php3
-rw-r--r--app/Controllers/tagController.php80
-rw-r--r--app/Controllers/updateController.php8
-rw-r--r--app/Controllers/userController.php23
-rw-r--r--app/FreshRSS.php1
-rw-r--r--app/Models/Category.php6
-rw-r--r--app/Models/CategoryDAO.php17
-rw-r--r--app/Models/Context.php46
-rw-r--r--app/Models/DatabaseDAO.php89
-rw-r--r--app/Models/DatabaseDAOPGSQL.php38
-rw-r--r--app/Models/DatabaseDAOSQLite.php14
-rw-r--r--app/Models/Entry.php53
-rw-r--r--app/Models/EntryDAO.php103
-rw-r--r--app/Models/EntryDAOPGSQL.php9
-rw-r--r--app/Models/EntryDAOSQLite.php48
-rw-r--r--app/Models/Factory.php16
-rw-r--r--app/Models/Feed.php34
-rw-r--r--app/Models/FeedDAO.php9
-rw-r--r--app/Models/Search.php37
-rw-r--r--app/Models/Tag.php76
-rw-r--r--app/Models/TagDAO.php315
-rw-r--r--app/Models/TagDAOPGSQL.php9
-rw-r--r--app/Models/TagDAOSQLite.php19
-rw-r--r--app/Models/Themes.php12
-rw-r--r--app/Models/UserDAO.php7
-rw-r--r--app/Models/UserQuery.php26
-rw-r--r--app/SQL/install.sql.mysql.php108
-rw-r--r--app/SQL/install.sql.pgsql.php56
-rw-r--r--app/SQL/install.sql.sqlite.php124
-rw-r--r--app/i18n/cz/conf.php2
-rw-r--r--app/i18n/cz/gen.php2
-rw-r--r--app/i18n/cz/index.php5
-rw-r--r--app/i18n/cz/sub.php1
-rw-r--r--app/i18n/de/admin.php4
-rw-r--r--app/i18n/de/conf.php4
-rw-r--r--app/i18n/de/feedback.php4
-rw-r--r--app/i18n/de/gen.php3
-rw-r--r--app/i18n/de/index.php7
-rw-r--r--app/i18n/de/install.php4
-rw-r--r--app/i18n/de/sub.php5
-rw-r--r--app/i18n/en/conf.php2
-rw-r--r--app/i18n/en/gen.php2
-rw-r--r--app/i18n/en/index.php5
-rw-r--r--app/i18n/en/sub.php1
-rwxr-xr-xapp/i18n/es/conf.php2
-rwxr-xr-xapp/i18n/es/gen.php2
-rwxr-xr-xapp/i18n/es/index.php5
-rwxr-xr-xapp/i18n/es/sub.php1
-rw-r--r--app/i18n/fr/conf.php2
-rw-r--r--app/i18n/fr/gen.php2
-rw-r--r--app/i18n/fr/index.php5
-rw-r--r--app/i18n/fr/sub.php1
-rw-r--r--app/i18n/he/conf.php2
-rw-r--r--app/i18n/he/gen.php2
-rw-r--r--app/i18n/he/index.php5
-rw-r--r--app/i18n/he/sub.php1
-rw-r--r--app/i18n/it/conf.php2
-rw-r--r--app/i18n/it/gen.php2
-rw-r--r--app/i18n/it/index.php5
-rw-r--r--app/i18n/it/sub.php1
-rw-r--r--app/i18n/kr/conf.php2
-rw-r--r--app/i18n/kr/gen.php2
-rw-r--r--app/i18n/kr/index.php5
-rw-r--r--app/i18n/kr/sub.php1
-rw-r--r--app/i18n/nl/conf.php2
-rw-r--r--app/i18n/nl/gen.php2
-rw-r--r--app/i18n/nl/index.php4
-rw-r--r--app/i18n/nl/sub.php1
-rw-r--r--app/i18n/pt-br/conf.php2
-rw-r--r--app/i18n/pt-br/gen.php2
-rw-r--r--app/i18n/pt-br/index.php4
-rw-r--r--app/i18n/pt-br/sub.php1
-rw-r--r--app/i18n/ru/conf.php2
-rw-r--r--app/i18n/ru/gen.php2
-rw-r--r--app/i18n/ru/index.php4
-rw-r--r--app/i18n/ru/sub.php1
-rw-r--r--app/i18n/tr/conf.php2
-rw-r--r--app/i18n/tr/gen.php2
-rw-r--r--app/i18n/tr/index.php5
-rw-r--r--app/i18n/tr/sub.php1
-rw-r--r--app/i18n/zh-cn/conf.php2
-rw-r--r--app/i18n/zh-cn/gen.php2
-rw-r--r--app/i18n/zh-cn/index.php5
-rw-r--r--app/i18n/zh-cn/sub.php1
-rw-r--r--app/install.php6
-rw-r--r--app/layout/aside_feed.phtml35
-rw-r--r--app/layout/layout.phtml34
-rw-r--r--app/views/configure/display.phtml6
-rwxr-xr-xapp/views/entry/read.phtml3
-rw-r--r--app/views/helpers/export/articles.phtml2
-rw-r--r--app/views/helpers/extension/configure.phtml2
-rw-r--r--app/views/helpers/feed/update.phtml7
-rw-r--r--app/views/helpers/index/normal/entry_bottom.phtml56
-rw-r--r--app/views/index/global.phtml5
-rw-r--r--app/views/index/normal.phtml24
-rw-r--r--app/views/index/reader.phtml21
-rwxr-xr-xapp/views/index/rss.phtml18
-rw-r--r--app/views/javascript/nbUnreadsPerFeed.phtml10
-rw-r--r--app/views/tag/getTagsForEntry.phtml2
106 files changed, 1441 insertions, 413 deletions
diff --git a/app/Controllers/categoryController.php b/app/Controllers/categoryController.php
index f3b35a323..2551a79d4 100644
--- a/app/Controllers/categoryController.php
+++ b/app/Controllers/categoryController.php
@@ -16,7 +16,7 @@ class FreshRSS_category_Controller extends Minz_ActionController {
Minz_Error::error(403);
}
- $catDAO = new FreshRSS_CategoryDAO();
+ $catDAO = FreshRSS_Factory::createCategoryDao();
$catDAO->checkDefault();
}
@@ -27,7 +27,7 @@ class FreshRSS_category_Controller extends Minz_ActionController {
* - new-category
*/
public function createAction() {
- $catDAO = new FreshRSS_CategoryDAO();
+ $catDAO = FreshRSS_Factory::createCategoryDao();
$url_redirect = array('c' => 'subscription', 'a' => 'index');
$limits = FreshRSS_Context::$system_conf->limits;
@@ -75,7 +75,7 @@ class FreshRSS_category_Controller extends Minz_ActionController {
* - name
*/
public function updateAction() {
- $catDAO = new FreshRSS_CategoryDAO();
+ $catDAO = FreshRSS_Factory::createCategoryDao();
$url_redirect = array('c' => 'subscription', 'a' => 'index');
if (Minz_Request::isPost()) {
@@ -116,7 +116,7 @@ class FreshRSS_category_Controller extends Minz_ActionController {
*/
public function deleteAction() {
$feedDAO = FreshRSS_Factory::createFeedDao();
- $catDAO = new FreshRSS_CategoryDAO();
+ $catDAO = FreshRSS_Factory::createCategoryDao();
$url_redirect = array('c' => 'subscription', 'a' => 'index');
if (Minz_Request::isPost()) {
diff --git a/app/Controllers/configureController.php b/app/Controllers/configureController.php
index d34b5d59d..20bcd2e76 100755
--- a/app/Controllers/configureController.php
+++ b/app/Controllers/configureController.php
@@ -243,8 +243,9 @@ class FreshRSS_configure_Controller extends Minz_ActionController {
* checking if categories and feeds are still in use.
*/
public function queriesAction() {
- $category_dao = new FreshRSS_CategoryDAO();
+ $category_dao = FreshRSS_Factory::createCategoryDao();
$feed_dao = FreshRSS_Factory::createFeedDao();
+ $tag_dao = FreshRSS_Factory::createTagDao();
if (Minz_Request::isPost()) {
$params = Minz_Request::param('queries', array());
@@ -277,16 +278,17 @@ class FreshRSS_configure_Controller extends Minz_ActionController {
* lean data.
*/
public function addQueryAction() {
- $category_dao = new FreshRSS_CategoryDAO();
+ $category_dao = FreshRSS_Factory::createCategoryDao();
$feed_dao = FreshRSS_Factory::createFeedDao();
+ $tag_dao = FreshRSS_Factory::createTagDao();
$queries = array();
foreach (FreshRSS_Context::$user_conf->queries as $key => $query) {
- $queries[$key] = new FreshRSS_UserQuery($query, $feed_dao, $category_dao);
+ $queries[$key] = new FreshRSS_UserQuery($query, $feed_dao, $category_dao, $tag_dao);
}
$params = Minz_Request::fetchGET();
$params['url'] = Minz_Url::display(array('params' => $params));
$params['name'] = _t('conf.query.number', count($queries) + 1);
- $queries[] = new FreshRSS_UserQuery($params, $feed_dao, $category_dao);
+ $queries[] = new FreshRSS_UserQuery($params, $feed_dao, $category_dao, $tag_dao);
FreshRSS_Context::$user_conf->queries = $queries;
FreshRSS_Context::$user_conf->save();
diff --git a/app/Controllers/entryController.php b/app/Controllers/entryController.php
index 16a15c447..fc0af0639 100755
--- a/app/Controllers/entryController.php
+++ b/app/Controllers/entryController.php
@@ -53,6 +53,7 @@ class FreshRSS_entry_Controller extends Minz_ActionController {
}
$params = array();
+ $this->view->tags = array();
$entryDAO = FreshRSS_Factory::createEntryDao();
if ($id === false) {
@@ -81,6 +82,12 @@ class FreshRSS_entry_Controller extends Minz_ActionController {
case 'a':
$entryDAO->markReadEntries($id_max, false, 0, FreshRSS_Context::$search, FreshRSS_Context::$state, $is_read);
break;
+ case 't':
+ $entryDAO->markReadTag($get, $id_max, FreshRSS_Context::$search, FreshRSS_Context::$state, $is_read);
+ break;
+ case 'T':
+ $entryDAO->markReadTag('', $id_max, FreshRSS_Context::$search, FreshRSS_Context::$state, $is_read);
+ break;
}
if ($next_get !== 'a') {
@@ -91,6 +98,13 @@ class FreshRSS_entry_Controller extends Minz_ActionController {
}
} else {
$entryDAO->markRead($id, $is_read);
+
+ $tagDAO = FreshRSS_Factory::createTagDao();
+ foreach ($tagDAO->getTagsForEntry($id) as $tag) {
+ if (!empty($tag['checked'])) {
+ $this->view->tags[] = $tag['id'];
+ }
+ }
}
if (!$this->ajax) {
@@ -193,6 +207,9 @@ class FreshRSS_entry_Controller extends Minz_ActionController {
$feedDAO->updateCachedValues();
+ $databaseDAO = FreshRSS_Factory::createDatabaseDAO();
+ $databaseDAO->minorDbMaintenance();
+
invalidateHttpCache();
Minz_Request::good(_t('feedback.sub.purge_completed', $nb_total), array(
'c' => 'configure',
diff --git a/app/Controllers/feedController.php b/app/Controllers/feedController.php
index 2f7495884..f2b1b8960 100755
--- a/app/Controllers/feedController.php
+++ b/app/Controllers/feedController.php
@@ -43,7 +43,7 @@ class FreshRSS_feed_Controller extends Minz_ActionController {
FreshRSS_UserDAO::touch();
@set_time_limit(300);
- $catDAO = new FreshRSS_CategoryDAO();
+ $catDAO = FreshRSS_Factory::createCategoryDao();
$url = trim($url);
@@ -192,7 +192,7 @@ class FreshRSS_feed_Controller extends Minz_ActionController {
// GET request: we must ask confirmation to user before adding feed.
Minz_View::prependTitle(_t('sub.feed.title_add') . ' · ');
- $this->catDAO = new FreshRSS_CategoryDAO();
+ $this->catDAO = FreshRSS_Factory::createCategoryDao();
$this->view->categories = $this->catDAO->listCategories(false);
$this->view->feed = new FreshRSS_Feed($url);
try {
@@ -481,6 +481,9 @@ class FreshRSS_feed_Controller extends Minz_ActionController {
if ($entryDAO->inTransaction()) {
$entryDAO->commit();
}
+
+ $databaseDAO = FreshRSS_Factory::createDatabaseDAO();
+ $databaseDAO->minorDbMaintenance();
}
return array($updated_feeds, reset($feeds), $nb_new_articles);
}
@@ -511,6 +514,9 @@ class FreshRSS_feed_Controller extends Minz_ActionController {
$entryDAO->commitNewEntries();
$feedDAO->updateCachedValues();
$entryDAO->commit();
+
+ $databaseDAO = FreshRSS_Factory::createDatabaseDAO();
+ $databaseDAO->minorDbMaintenance();
} else {
list($updated_feeds, $feed, $nb_new_articles) = self::actualizeFeed($id, $url, $force, null, false, $noCommit);
}
@@ -556,7 +562,7 @@ class FreshRSS_feed_Controller extends Minz_ActionController {
}
FreshRSS_UserDAO::touch();
- $catDAO = new FreshRSS_CategoryDAO();
+ $catDAO = FreshRSS_Factory::createCategoryDao();
if ($cat_id > 0) {
$cat = $catDAO->searchById($cat_id);
$cat_id = $cat == null ? 0 : $cat->id();
diff --git a/app/Controllers/indexController.php b/app/Controllers/indexController.php
index ddffdba73..fa914ef87 100755
--- a/app/Controllers/indexController.php
+++ b/app/Controllers/indexController.php
@@ -32,7 +32,29 @@ class FreshRSS_index_Controller extends Minz_ActionController {
Minz_Error::error(404);
}
- $this->view->callbackBeforeContent = function($view) {
+ $this->view->categories = FreshRSS_Context::$categories;
+
+ $this->view->rss_title = FreshRSS_Context::$name . ' | ' . Minz_View::title();
+ $title = FreshRSS_Context::$name;
+ if (FreshRSS_Context::$get_unread > 0) {
+ $title = '(' . FreshRSS_Context::$get_unread . ') ' . $title;
+ }
+ Minz_View::prependTitle($title . ' · ');
+
+ $this->view->callbackBeforeFeeds = function ($view) {
+ try {
+ $tagDAO = FreshRSS_Factory::createTagDao();
+ $view->tags = $tagDAO->listTags(true);
+ $view->nbUnreadTags = 0;
+ foreach ($view->tags as $tag) {
+ $view->nbUnreadTags += $tag->nbUnread();
+ }
+ } catch (Exception $e) {
+ Minz_Log::notice($e->getMessage());
+ }
+ };
+
+ $this->view->callbackBeforeEntries = function ($view) {
try {
FreshRSS_Context::$number++; //+1 for pagination
$entries = FreshRSS_index_Controller::listEntriesByContext();
@@ -60,15 +82,6 @@ class FreshRSS_index_Controller extends Minz_ActionController {
Minz_Log::notice($e->getMessage());
Minz_Error::error(404);
}
-
- $view->categories = FreshRSS_Context::$categories;
-
- $view->rss_title = FreshRSS_Context::$name . ' | ' . Minz_View::title();
- $title = FreshRSS_Context::$name;
- if (FreshRSS_Context::$get_unread > 0) {
- $title = '(' . FreshRSS_Context::$get_unread . ') ' . $title;
- }
- Minz_View::prependTitle($title . ' · ');
};
}
@@ -158,7 +171,7 @@ class FreshRSS_index_Controller extends Minz_ActionController {
*/
private function updateContext() {
if (empty(FreshRSS_Context::$categories)) {
- $catDAO = new FreshRSS_CategoryDAO();
+ $catDAO = FreshRSS_Factory::createCategoryDao();
FreshRSS_Context::$categories = $catDAO->listCategories();
}
diff --git a/app/Controllers/javascriptController.php b/app/Controllers/javascriptController.php
index 9d7acf647..d56da9cbb 100755
--- a/app/Controllers/javascriptController.php
+++ b/app/Controllers/javascriptController.php
@@ -7,14 +7,17 @@ class FreshRSS_javascript_Controller extends Minz_ActionController {
public function actualizeAction() {
header('Content-Type: application/json; charset=UTF-8');
+ Minz_Session::_param('actualize_feeds', false);
$feedDAO = FreshRSS_Factory::createFeedDao();
$this->view->feeds = $feedDAO->listFeedsOrderUpdate(FreshRSS_Context::$user_conf->ttl_default);
}
public function nbUnreadsPerFeedAction() {
header('Content-Type: application/json; charset=UTF-8');
- $catDAO = new FreshRSS_CategoryDAO();
+ $catDAO = FreshRSS_Factory::createCategoryDao();
$this->view->categories = $catDAO->listCategories(true, false);
+ $tagDAO = FreshRSS_Factory::createTagDao();
+ $this->view->tags = $tagDAO->listTags(true);
}
//For Web-form login
diff --git a/app/Controllers/statsController.php b/app/Controllers/statsController.php
index 5d1dee72c..acfacb890 100644
--- a/app/Controllers/statsController.php
+++ b/app/Controllers/statsController.php
@@ -131,7 +131,7 @@ class FreshRSS_stats_Controller extends Minz_ActionController {
*/
public function repartitionAction() {
$statsDAO = FreshRSS_Factory::createStatsDAO();
- $categoryDAO = new FreshRSS_CategoryDAO();
+ $categoryDAO = FreshRSS_Factory::createCategoryDao();
$feedDAO = FreshRSS_Factory::createFeedDao();
Minz_View::appendScript(Minz_Url::display('/scripts/flotr2.min.js?' . @filemtime(PUBLIC_PATH . '/scripts/flotr2.min.js')));
$id = Minz_Request::param('id', null);
diff --git a/app/Controllers/subscriptionController.php b/app/Controllers/subscriptionController.php
index 701a588e0..0b1439ba5 100644
--- a/app/Controllers/subscriptionController.php
+++ b/app/Controllers/subscriptionController.php
@@ -14,7 +14,7 @@ class FreshRSS_subscription_Controller extends Minz_ActionController {
Minz_Error::error(403);
}
- $catDAO = new FreshRSS_CategoryDAO();
+ $catDAO = FreshRSS_Factory::createCategoryDao();
$feedDAO = FreshRSS_Factory::createFeedDao();
$catDAO->checkDefault();
@@ -98,6 +98,7 @@ class FreshRSS_subscription_Controller extends Minz_ActionController {
$feed->_attributes('mark_updated_article_unread', Minz_Request::paramTernary('mark_updated_article_unread'));
$feed->_attributes('read_upon_reception', Minz_Request::paramTernary('read_upon_reception'));
+ $feed->_attributes('clear_cache', Minz_Request::paramTernary('clear_cache'));
if (FreshRSS_Auth::hasAccess('admin')) {
$feed->_attributes('ssl_verify', Minz_Request::paramTernary('ssl_verify'));
diff --git a/app/Controllers/tagController.php b/app/Controllers/tagController.php
new file mode 100644
index 000000000..106e0afa8
--- /dev/null
+++ b/app/Controllers/tagController.php
@@ -0,0 +1,80 @@
+<?php
+
+/**
+ * Controller to handle every tag actions.
+ */
+class FreshRSS_tag_Controller extends Minz_ActionController {
+ /**
+ * This action is called before every other action in that class. It is
+ * the common boiler plate for every action. It is triggered by the
+ * underlying framework.
+ */
+ public function firstAction() {
+ if (!FreshRSS_Auth::hasAccess()) {
+ Minz_Error::error(403);
+ }
+ // If ajax request, we do not print layout
+ $this->ajax = Minz_Request::param('ajax');
+ if ($this->ajax) {
+ $this->view->_useLayout(false);
+ Minz_Request::_param('ajax');
+ }
+ }
+
+ /**
+ * This action adds (checked=true) or removes (checked=false) a tag to an entry.
+ */
+ public function tagEntryAction() {
+ if (Minz_Request::isPost()) {
+ $id_tag = Minz_Request::param('id_tag');
+ $name_tag = trim(Minz_Request::param('name_tag'));
+ $id_entry = Minz_Request::param('id_entry');
+ $checked = Minz_Request::paramTernary('checked');
+ if ($id_entry != false) {
+ $tagDAO = FreshRSS_Factory::createTagDao();
+ if ($id_tag == 0 && $name_tag != '' && $checked) {
+ //Create new tag
+ $id_tag = $tagDAO->addTag(array('name' => $name_tag));
+ }
+ if ($id_tag != 0) {
+ $tagDAO->tagEntry($id_tag, $id_entry, $checked);
+ }
+ }
+ } else {
+ Minz_Error::error(405);
+ }
+ if (!$this->ajax) {
+ Minz_Request::forward(array(
+ 'c' => 'index',
+ 'a' => 'index',
+ ), true);
+ }
+ }
+
+ public function deleteAction() {
+ if (Minz_Request::isPost()) {
+ $id_tag = Minz_Request::param('id_tag');
+ if ($id_tag != false) {
+ $tagDAO = FreshRSS_Factory::createTagDao();
+ $tagDAO->deleteTag($id_tag);
+ }
+ } else {
+ Minz_Error::error(405);
+ }
+ if (!$this->ajax) {
+ Minz_Request::forward(array(
+ 'c' => 'index',
+ 'a' => 'index',
+ ), true);
+ }
+ }
+
+ public function getTagsForEntryAction() {
+ $this->view->_useLayout(false);
+ header('Content-Type: application/json; charset=UTF-8');
+ header('Cache-Control: private, no-cache, no-store, must-revalidate');
+ $id_entry = Minz_Request::param('id_entry', 0);
+ $tagDAO = FreshRSS_Factory::createTagDao();
+ $this->view->tags = $tagDAO->getTagsForEntry($id_entry);
+ }
+}
diff --git a/app/Controllers/updateController.php b/app/Controllers/updateController.php
index c67b358bb..2be644c85 100644
--- a/app/Controllers/updateController.php
+++ b/app/Controllers/updateController.php
@@ -32,7 +32,13 @@ class FreshRSS_update_Controller extends Minz_ActionController {
$output = array();
$return = 1;
try {
- exec('git pull --ff-only', $output, $return);
+ exec('git clean -f -d -f', $output, $return);
+ if ($return == 0) {
+ exec('git pull --ff-only', $output, $return);
+ } else {
+ $line = is_array($output) ? implode('; ', $output) : '' . $output;
+ Minz_Log::warning('git clean warning:' . $line);
+ }
} catch (Exception $e) {
Minz_Log::warning('git pull error:' . $e->getMessage());
}
diff --git a/app/Controllers/userController.php b/app/Controllers/userController.php
index 75a4303d6..2338c8b2a 100644
--- a/app/Controllers/userController.php
+++ b/app/Controllers/userController.php
@@ -38,7 +38,7 @@ class FreshRSS_user_Controller extends Minz_ActionController {
* The username is also used as folder name, file name, and part of SQL table name.
* '_' is a reserved internal username.
*/
- const USERNAME_PATTERN = '[0-9a-zA-Z_]{2,38}|[0-9a-zA-Z]';
+ const USERNAME_PATTERN = '[0-9a-zA-Z_][0-9a-zA-Z_.]{1,38}|[0-9a-zA-Z]';
public static function checkUsername($username) {
return preg_match('/^' . self::USERNAME_PATTERN . '$/', $username) === 1;
@@ -91,6 +91,10 @@ class FreshRSS_user_Controller extends Minz_ActionController {
}
public function updateAction() {
+ if (!FreshRSS_Auth::hasAccess('admin')) {
+ Minz_Error::error(403);
+ }
+
if (Minz_Request::isPost()) {
$passwordPlain = Minz_Request::param('newPasswordPlain', '', true);
Minz_Request::_param('newPasswordPlain'); //Discard plain-text password ASAP
@@ -104,8 +108,12 @@ class FreshRSS_user_Controller extends Minz_ActionController {
));
if ($ok) {
- Minz_Request::good(_t('feedback.user.updated', $username),
- array('c' => 'user', 'a' => 'manage'));
+ $isSelfUpdate = Minz_Session::param('currentUser', '_') === $username;
+ if ($passwordPlain == '' || !$isSelfUpdate) {
+ Minz_Request::good(_t('feedback.user.updated', $username), array('c' => 'user', 'a' => 'manage'));
+ } else {
+ Minz_Request::good(_t('feedback.profile.updated'), array('c' => 'index', 'a' => 'index'));
+ }
} else {
Minz_Request::bad(_t('feedback.user.updated.error', $username),
array('c' => 'user', 'a' => 'manage'));
@@ -138,8 +146,11 @@ class FreshRSS_user_Controller extends Minz_ActionController {
Minz_Session::_param('passwordHash', FreshRSS_Context::$user_conf->passwordHash);
if ($ok) {
- Minz_Request::good(_t('feedback.profile.updated'),
- array('c' => 'user', 'a' => 'profile'));
+ if ($passwordPlain == '') {
+ Minz_Request::good(_t('feedback.profile.updated'), array('c' => 'user', 'a' => 'profile'));
+ } else {
+ Minz_Request::good(_t('feedback.profile.updated'), array('c' => 'index', 'a' => 'index'));
+ }
} else {
Minz_Request::bad(_t('feedback.profile.error'),
array('c' => 'user', 'a' => 'profile'));
@@ -166,7 +177,7 @@ class FreshRSS_user_Controller extends Minz_ActionController {
$entryDAO = FreshRSS_Factory::createEntryDao($this->view->current_user);
$this->view->nb_articles = $entryDAO->count();
- $databaseDAO = FreshRSS_Factory::createDatabaseDAO();
+ $databaseDAO = FreshRSS_Factory::createDatabaseDAO($this->view->current_user);
$this->view->size_user = $databaseDAO->size();
}
}
diff --git a/app/FreshRSS.php b/app/FreshRSS.php
index 2bd5135a9..dec446a8e 100644
--- a/app/FreshRSS.php
+++ b/app/FreshRSS.php
@@ -90,7 +90,6 @@ class FreshRSS extends Minz_FrontController {
}
$filetime = @filemtime(PUBLIC_PATH . '/themes/' . $theme_id . '/' . $filename);
$url = '/themes/' . $theme_id . '/' . $filename . '?' . $filetime;
- header('Link: <' . Minz_Url::display($url, '', 'root') . '>;rel=preload', false); //HTTP2
Minz_View::prependStyle(Minz_Url::display($url));
}
}
diff --git a/app/Models/Category.php b/app/Models/Category.php
index 197faf942..240dbca73 100644
--- a/app/Models/Category.php
+++ b/app/Models/Category.php
@@ -30,7 +30,7 @@ class FreshRSS_Category extends Minz_Model {
}
public function nbFeed() {
if ($this->nbFeed < 0) {
- $catDAO = new FreshRSS_CategoryDAO();
+ $catDAO = FreshRSS_Factory::createCategoryDao();
$this->nbFeed = $catDAO->countFeed($this->id());
}
@@ -38,7 +38,7 @@ class FreshRSS_Category extends Minz_Model {
}
public function nbNotRead() {
if ($this->nbNotRead < 0) {
- $catDAO = new FreshRSS_CategoryDAO();
+ $catDAO = FreshRSS_Factory::createCategoryDao();
$this->nbNotRead = $catDAO->countNotRead($this->id());
}
@@ -68,7 +68,7 @@ class FreshRSS_Category extends Minz_Model {
$this->id = $value;
}
public function _name($value) {
- $this->name = mb_strcut(trim($value), 0, 255, 'UTF-8');
+ $this->name = trim($value);
}
public function _feeds($values) {
if (!is_array($values)) {
diff --git a/app/Models/CategoryDAO.php b/app/Models/CategoryDAO.php
index cf6b3bae3..ba7eb765e 100644
--- a/app/Models/CategoryDAO.php
+++ b/app/Models/CategoryDAO.php
@@ -5,11 +5,15 @@ class FreshRSS_CategoryDAO extends Minz_ModelPdo implements FreshRSS_Searchable
const DEFAULTCATEGORYID = 1;
public function addCategory($valuesTmp) {
- $sql = 'INSERT INTO `' . $this->prefix . 'category`(name) VALUES(?)';
+ $sql = 'INSERT INTO `' . $this->prefix . 'category`(name) '
+ . 'SELECT * FROM (SELECT TRIM(?)) c2 ' //TRIM() to provide a type hint as text for PostgreSQL
+ . 'WHERE NOT EXISTS (SELECT 1 FROM `' . $this->prefix . 'tag` WHERE name = TRIM(?))'; //No tag of the same name
$stm = $this->bd->prepare($sql);
+ $valuesTmp['name'] = mb_strcut(trim($valuesTmp['name']), 0, FreshRSS_DatabaseDAO::LENGTH_INDEX_UNICODE, 'UTF-8');
$values = array(
- mb_strcut($valuesTmp['name'], 0, 255, 'UTF-8'),
+ $valuesTmp['name'],
+ $valuesTmp['name'],
);
if ($stm && $stm->execute($values)) {
@@ -35,12 +39,15 @@ class FreshRSS_CategoryDAO extends Minz_ModelPdo implements FreshRSS_Searchable
}
public function updateCategory($id, $valuesTmp) {
- $sql = 'UPDATE `' . $this->prefix . 'category` SET name=? WHERE id=?';
+ $sql = 'UPDATE `' . $this->prefix . 'category` SET name=? WHERE id=? '
+ . 'AND NOT EXISTS (SELECT 1 FROM `' . $this->prefix . 'tag` WHERE name = ?)'; //No tag of the same name
$stm = $this->bd->prepare($sql);
+ $valuesTmp['name'] = mb_strcut(trim($valuesTmp['name']), 0, FreshRSS_DatabaseDAO::LENGTH_INDEX_UNICODE, 'UTF-8');
$values = array(
$valuesTmp['name'],
- $id
+ $id,
+ $valuesTmp['name'],
);
if ($stm && $stm->execute($values)) {
@@ -151,7 +158,7 @@ class FreshRSS_CategoryDAO extends Minz_ModelPdo implements FreshRSS_Searchable
$sql = 'INSERT INTO `' . $this->prefix . 'category`(id, name) VALUES(?, ?)';
if (parent::$sharedDbType === 'pgsql') {
//Force call to nextval()
- $sql .= " RETURNING nextval('" . $this->prefix . "category_id_seq');";
+ $sql .= ' RETURNING nextval(\'"' . $this->prefix . 'category_id_seq"\');';
}
$stm = $this->bd->prepare($sql);
diff --git a/app/Models/Context.php b/app/Models/Context.php
index 2ca8f80b0..60ec6ff77 100644
--- a/app/Models/Context.php
+++ b/app/Models/Context.php
@@ -8,6 +8,7 @@ class FreshRSS_Context {
public static $user_conf = null;
public static $system_conf = null;
public static $categories = array();
+ public static $tags = array();
public static $name = '';
public static $description = '';
@@ -25,6 +26,8 @@ class FreshRSS_Context {
'starred' => false,
'feed' => false,
'category' => false,
+ 'tag' => false,
+ 'tags' => false,
);
public static $next_get = 'a';
@@ -91,6 +94,14 @@ class FreshRSS_Context {
} else {
return 'c_' . self::$current_get['category'];
}
+ } elseif (self::$current_get['tag']) {
+ if ($array) {
+ return array('t', self::$current_get['tag']);
+ } else {
+ return 't_' . self::$current_get['tag'];
+ }
+ } elseif (self::$current_get['tags']) {
+ return 'T';
}
}
@@ -117,6 +128,10 @@ class FreshRSS_Context {
return self::$current_get['feed'] == $id;
case 'c':
return self::$current_get['category'] == $id;
+ case 't':
+ return self::$current_get['tag'] == $id;
+ case 'T':
+ return self::$current_get['tags'] || self::$current_get['tag'];
default:
return false;
}
@@ -130,6 +145,7 @@ class FreshRSS_Context {
* - s
* - f_<feed id>
* - c_<category id>
+ * - t_<tag id>
*
* $name and $get_unread attributes are also updated as $next_get
* Raise an exception if id or $get is invalid.
@@ -140,7 +156,7 @@ class FreshRSS_Context {
$nb_unread = 0;
if (empty(self::$categories)) {
- $catDAO = new FreshRSS_CategoryDAO();
+ $catDAO = FreshRSS_Factory::createCategoryDao();
self::$categories = $catDAO->listCategories();
}
@@ -166,12 +182,10 @@ class FreshRSS_Context {
if ($feed === null) {
$feedDAO = FreshRSS_Factory::createFeedDao();
$feed = $feedDAO->searchById($id);
-
if (!$feed) {
throw new FreshRSS_Context_Exception('Invalid feed: ' . $id);
}
}
-
self::$current_get['feed'] = $id;
self::$current_get['category'] = $feed->category();
self::$name = $feed->name();
@@ -182,19 +196,37 @@ class FreshRSS_Context {
// We try to find the corresponding category.
self::$current_get['category'] = $id;
if (!isset(self::$categories[$id])) {
- $catDAO = new FreshRSS_CategoryDAO();
+ $catDAO = FreshRSS_Factory::createCategoryDao();
$cat = $catDAO->searchById($id);
-
if (!$cat) {
throw new FreshRSS_Context_Exception('Invalid category: ' . $id);
}
} else {
$cat = self::$categories[$id];
}
-
self::$name = $cat->name();
self::$get_unread = $cat->nbNotRead();
break;
+ case 't':
+ // We try to find the corresponding tag.
+ self::$current_get['tag'] = $id;
+ if (!isset(self::$tags[$id])) {
+ $tagDAO = FreshRSS_Factory::createTagDao();
+ $tag = $tagDAO->searchById($id);
+ if (!$tag) {
+ throw new FreshRSS_Context_Exception('Invalid tag: ' . $id);
+ }
+ } else {
+ $tag = self::$tags[$id];
+ }
+ self::$name = $tag->name();
+ self::$get_unread = $tag->nbUnread();
+ break;
+ case 'T':
+ self::$current_get['tags'] = true;
+ self::$name = _t('index.menu.tags');
+ self::$get_unread = 0;
+ break;
default:
throw new FreshRSS_Context_Exception('Invalid getter: ' . $get);
}
@@ -211,7 +243,7 @@ class FreshRSS_Context {
self::$next_get = $get;
if (empty(self::$categories)) {
- $catDAO = new FreshRSS_CategoryDAO();
+ $catDAO = FreshRSS_Factory::createCategoryDao();
self::$categories = $catDAO->listCategories();
}
diff --git a/app/Models/DatabaseDAO.php b/app/Models/DatabaseDAO.php
index b8e5577e4..b331eccc3 100644
--- a/app/Models/DatabaseDAO.php
+++ b/app/Models/DatabaseDAO.php
@@ -4,6 +4,16 @@
* This class is used to test database is well-constructed.
*/
class FreshRSS_DatabaseDAO extends Minz_ModelPdo {
+
+ //MySQL error codes
+ const ER_BAD_FIELD_ERROR = '42S22';
+ const ER_BAD_TABLE_ERROR = '42S02';
+ const ER_TRUNCATED_WRONG_VALUE_FOR_FIELD = '1366';
+
+ //MySQL InnoDB maximum index length for UTF8MB4
+ //https://dev.mysql.com/doc/refman/8.0/en/innodb-restrictions.html
+ const LENGTH_INDEX_UNICODE = 191;
+
public function tablesAreCorrect() {
$sql = 'SHOW TABLES';
$stm = $this->bd->prepare($sql);
@@ -14,6 +24,9 @@ class FreshRSS_DatabaseDAO extends Minz_ModelPdo {
$this->prefix . 'category' => false,
$this->prefix . 'feed' => false,
$this->prefix . 'entry' => false,
+ $this->prefix . 'entrytmp' => false,
+ $this->prefix . 'tag' => false,
+ $this->prefix . 'entrytag' => false,
);
foreach ($res as $value) {
$tables[array_pop($value)] = true;
@@ -43,7 +56,7 @@ class FreshRSS_DatabaseDAO extends Minz_ModelPdo {
public function categoryIsCorrect() {
return $this->checkTable('category', array(
- 'id', 'name'
+ 'id', 'name',
));
}
@@ -51,14 +64,33 @@ class FreshRSS_DatabaseDAO extends Minz_ModelPdo {
return $this->checkTable('feed', array(
'id', 'url', 'category', 'name', 'website', 'description', 'lastUpdate',
'priority', 'pathEntries', 'httpAuth', 'error', 'keep_history', 'ttl', 'attributes',
- 'cache_nbEntries', 'cache_nbUnreads'
+ 'cache_nbEntries', 'cache_nbUnreads',
));
}
public function entryIsCorrect() {
return $this->checkTable('entry', array(
- 'id', 'guid', 'title', 'author', 'content_bin', 'link', 'date', 'is_read',
- 'is_favorite', 'id_feed', 'tags'
+ 'id', 'guid', 'title', 'author', 'content_bin', 'link', 'date', 'lastSeen', 'hash', 'is_read',
+ 'is_favorite', 'id_feed', 'tags',
+ ));
+ }
+
+ public function entrytmpIsCorrect() {
+ return $this->checkTable('entrytmp', array(
+ 'id', 'guid', 'title', 'author', 'content_bin', 'link', 'date', 'lastSeen', 'hash', 'is_read',
+ 'is_favorite', 'id_feed', 'tags',
+ ));
+ }
+
+ public function tagIsCorrect() {
+ return $this->checkTable('tag', array(
+ 'id', 'name', 'attributes',
+ ));
+ }
+
+ public function entrytagIsCorrect() {
+ return $this->checkTable('entrytag', array(
+ 'id_tag', 'id_entry',
));
}
@@ -97,28 +129,39 @@ class FreshRSS_DatabaseDAO extends Minz_ModelPdo {
public function optimize() {
$ok = true;
-
- $sql = 'OPTIMIZE TABLE `' . $this->prefix . 'entry`'; //MySQL
- $stm = $this->bd->prepare($sql);
- $ok &= $stm != false;
- if ($stm) {
- $ok &= $stm->execute();
- }
-
- $sql = 'OPTIMIZE TABLE `' . $this->prefix . 'feed`'; //MySQL
- $stm = $this->bd->prepare($sql);
- $ok &= $stm != false;
- if ($stm) {
- $ok &= $stm->execute();
+ $tables = array('category', 'feed', 'entry', 'entrytmp', 'tag', 'entrytag');
+
+ foreach ($tables as $table) {
+ $sql = 'OPTIMIZE TABLE `' . $this->prefix . $table . '`'; //MySQL
+ $stm = $this->bd->prepare($sql);
+ $ok &= $stm != false;
+ if ($stm) {
+ $ok &= $stm->execute();
+ }
}
+ return $ok;
+ }
- $sql = 'OPTIMIZE TABLE `' . $this->prefix . 'category`'; //MySQL
- $stm = $this->bd->prepare($sql);
- $ok &= $stm != false;
- if ($stm) {
- $ok &= $stm->execute();
+ public function ensureCaseInsensitiveGuids() {
+ $ok = true;
+ $db = FreshRSS_Context::$system_conf->db;
+ if ($db['type'] === 'mysql') {
+ include_once(APP_PATH . '/SQL/install.sql.mysql.php');
+ if (defined('SQL_UPDATE_GUID_LATIN1_BIN')) { //FreshRSS 1.12
+ try {
+ $sql = sprintf(SQL_UPDATE_GUID_LATIN1_BIN, $this->prefix);
+ $stm = $this->bd->prepare($sql);
+ $ok = $stm->execute();
+ } catch (Exception $e) {
+ $ok = false;
+ Minz_Log::error('FreshRSS_DatabaseDAO::ensureCaseInsensitiveGuids error: ' . $e->getMessage());
+ }
+ }
}
-
return $ok;
}
+
+ public function minorDbMaintenance() {
+ $this->ensureCaseInsensitiveGuids();
+ }
}
diff --git a/app/Models/DatabaseDAOPGSQL.php b/app/Models/DatabaseDAOPGSQL.php
index 1b3f7408d..8582b5719 100644
--- a/app/Models/DatabaseDAOPGSQL.php
+++ b/app/Models/DatabaseDAOPGSQL.php
@@ -3,7 +3,12 @@
/**
* This class is used to test database is well-constructed.
*/
-class FreshRSS_DatabaseDAOPGSQL extends FreshRSS_DatabaseDAO {
+class FreshRSS_DatabaseDAOPGSQL extends FreshRSS_DatabaseDAOSQLite {
+
+ //PostgreSQL error codes
+ const UNDEFINED_COLUMN = '42703';
+ const UNDEFINED_TABLE = '42P01';
+
public function tablesAreCorrect() {
$db = FreshRSS_Context::$system_conf->db;
$dbowner = $db['user'];
@@ -17,6 +22,9 @@ class FreshRSS_DatabaseDAOPGSQL extends FreshRSS_DatabaseDAO {
$this->prefix . 'category' => false,
$this->prefix . 'feed' => false,
$this->prefix . 'entry' => false,
+ $this->prefix . 'entrytmp' => false,
+ $this->prefix . 'tag' => false,
+ $this->prefix . 'entrytag' => false,
);
foreach ($res as $value) {
$tables[array_pop($value)] = true;
@@ -53,28 +61,16 @@ class FreshRSS_DatabaseDAOPGSQL extends FreshRSS_DatabaseDAO {
public function optimize() {
$ok = true;
+ $tables = array('category', 'feed', 'entry', 'entrytmp', 'tag', 'entrytag');
- $sql = 'VACUUM `' . $this->prefix . 'entry`';
- $stm = $this->bd->prepare($sql);
- $ok &= $stm != false;
- if ($stm) {
- $ok &= $stm->execute();
- }
-
- $sql = 'VACUUM `' . $this->prefix . 'feed`';
- $stm = $this->bd->prepare($sql);
- $ok &= $stm != false;
- if ($stm) {
- $ok &= $stm->execute();
+ foreach ($tables as $table) {
+ $sql = 'VACUUM `' . $this->prefix . $table . '`';
+ $stm = $this->bd->prepare($sql);
+ $ok &= $stm != false;
+ if ($stm) {
+ $ok &= $stm->execute();
+ }
}
-
- $sql = 'VACUUM `' . $this->prefix . 'category`';
- $stm = $this->bd->prepare($sql);
- $ok &= $stm != false;
- if ($stm) {
- $ok &= $stm->execute();
- }
-
return $ok;
}
}
diff --git a/app/Models/DatabaseDAOSQLite.php b/app/Models/DatabaseDAOSQLite.php
index d3aedb3c0..a93a209b2 100644
--- a/app/Models/DatabaseDAOSQLite.php
+++ b/app/Models/DatabaseDAOSQLite.php
@@ -14,6 +14,9 @@ class FreshRSS_DatabaseDAOSQLite extends FreshRSS_DatabaseDAO {
'category' => false,
'feed' => false,
'entry' => false,
+ 'entrytmp' => false,
+ 'tag' => false,
+ 'entrytag' => false,
);
foreach ($res as $value) {
$tables[$value['name']] = true;
@@ -32,8 +35,15 @@ class FreshRSS_DatabaseDAOSQLite extends FreshRSS_DatabaseDAO {
public function entryIsCorrect() {
return $this->checkTable('entry', array(
- 'id', 'guid', 'title', 'author', 'content', 'link', 'date', 'is_read',
- 'is_favorite', 'id_feed', 'tags'
+ 'id', 'guid', 'title', 'author', 'content', 'link', 'date', 'lastSeen', 'hash', 'is_read',
+ 'is_favorite', 'id_feed', 'tags',
+ ));
+ }
+
+ public function entrytmpIsCorrect() {
+ return $this->checkTable('entrytmp', array(
+ 'id', 'guid', 'title', 'author', 'content', 'link', 'date', 'lastSeen', 'hash', 'is_read',
+ 'is_favorite', 'id_feed', 'tags',
));
}
diff --git a/app/Models/Entry.php b/app/Models/Entry.php
index ccbad5724..985276734 100644
--- a/app/Models/Entry.php
+++ b/app/Models/Entry.php
@@ -10,7 +10,7 @@ class FreshRSS_Entry extends Minz_Model {
private $id = 0;
private $guid;
private $title;
- private $author;
+ private $authors;
private $content;
private $link;
private $date;
@@ -21,18 +21,17 @@ class FreshRSS_Entry extends Minz_Model {
private $feed;
private $tags;
- public function __construct($feedId = '', $guid = '', $title = '', $author = '', $content = '',
+ public function __construct($feedId = '', $guid = '', $title = '', $authors = '', $content = '',
$link = '', $pubdate = 0, $is_read = false, $is_favorite = false, $tags = '') {
$this->_title($title);
- $this->_author($author);
+ $this->_authors($authors);
$this->_content($content);
$this->_link($link);
$this->_date($pubdate);
$this->_isRead($is_read);
$this->_isFavorite($is_favorite);
$this->_feedId($feedId);
- $tags = mb_strcut($tags, 0, 1023, 'UTF-8');
- $this->_tags(preg_split('/[\s#]/', $tags));
+ $this->_tags($tags);
$this->_guid($guid);
}
@@ -46,7 +45,15 @@ class FreshRSS_Entry extends Minz_Model {
return $this->title;
}
public function author() {
- return $this->author === null ? '' : $this->author;
+ //Deprecated
+ return $this->authors(true);
+ }
+ public function authors($asString = false) {
+ if ($asString) {
+ return $this->authors == null ? '' : ';' . implode('; ', $this->authors);
+ } else {
+ return $this->authors;
+ }
}
public function content() {
return $this->content;
@@ -86,9 +93,9 @@ class FreshRSS_Entry extends Minz_Model {
return $this->feedId;
}
}
- public function tags($inString = false) {
- if ($inString) {
- return empty($this->tags) ? '' : '#' . implode(' #', $this->tags);
+ public function tags($asString = false) {
+ if ($asString) {
+ return $this->tags == null ? '' : '#' . implode(' #', $this->tags);
} else {
return $this->tags;
}
@@ -97,7 +104,7 @@ class FreshRSS_Entry extends Minz_Model {
public function hash() {
if ($this->hash === null) {
//Do not include $this->date because it may be automatically generated when lacking
- $this->hash = md5($this->link . $this->title . $this->author . $this->content . $this->tags(true));
+ $this->hash = md5($this->link . $this->title . $this->authors(true) . $this->content . $this->tags(true));
}
return $this->hash;
}
@@ -124,11 +131,22 @@ class FreshRSS_Entry extends Minz_Model {
}
public function _title($value) {
$this->hash = null;
- $this->title = mb_strcut($value, 0, 255, 'UTF-8');
+ $this->title = $value;
}
public function _author($value) {
+ //Deprecated
+ $this->_authors($value);
+ }
+ public function _authors($value) {
$this->hash = null;
- $this->author = mb_strcut($value, 0, 255, 'UTF-8');
+ if (!is_array($value)) {
+ if (strpos($value, ';') !== false) {
+ $value = preg_split('/\s*[;]\s*/', $value, -1, PREG_SPLIT_NO_EMPTY);
+ } else {
+ $value = preg_split('/\s*[,]\s*/', $value, -1, PREG_SPLIT_NO_EMPTY);
+ }
+ }
+ $this->authors = $value;
}
public function _content($value) {
$this->hash = null;
@@ -162,15 +180,8 @@ class FreshRSS_Entry extends Minz_Model {
public function _tags($value) {
$this->hash = null;
if (!is_array($value)) {
- $value = array($value);
- }
-
- foreach ($value as $key => $t) {
- if (!$t) {
- unset($value[$key]);
- }
+ $value = preg_split('/\s*[#,]\s*/', $value, -1, PREG_SPLIT_NO_EMPTY);
}
-
$this->tags = $value;
}
@@ -287,7 +298,7 @@ class FreshRSS_Entry extends Minz_Model {
'id' => $this->id(),
'guid' => $this->guid(),
'title' => $this->title(),
- 'author' => $this->author(),
+ 'author' => $this->authors(true),
'content' => $this->content(),
'link' => $this->link(),
'date' => $this->date(true),
diff --git a/app/Models/EntryDAO.php b/app/Models/EntryDAO.php
index f0e164995..a01c2227b 100644
--- a/app/Models/EntryDAO.php
+++ b/app/Models/EntryDAO.php
@@ -18,6 +18,7 @@ class FreshRSS_EntryDAO extends Minz_ModelPdo implements FreshRSS_Searchable {
return 'hex(' . $x . ')';
}
+ //TODO: Move the database auto-updates to DatabaseDAO
protected function addColumn($name) {
Minz_Log::warning('FreshRSS_EntryDAO::addColumn: ' . $name);
$hasTransaction = false;
@@ -56,6 +57,7 @@ class FreshRSS_EntryDAO extends Minz_ModelPdo implements FreshRSS_Searchable {
private $triedUpdateToUtf8mb4 = false;
+ //TODO: Move the database auto-updates to DatabaseDAO
protected function updateToUtf8mb4() {
if ($this->triedUpdateToUtf8mb4) {
return false;
@@ -65,7 +67,7 @@ class FreshRSS_EntryDAO extends Minz_ModelPdo implements FreshRSS_Searchable {
if ($db['type'] === 'mysql') {
include_once(APP_PATH . '/SQL/install.sql.mysql.php');
if (defined('SQL_UPDATE_UTF8MB4')) {
- Minz_Log::warning('Updating MySQL to UTF8MB4...');
+ Minz_Log::warning('Updating MySQL to UTF8MB4...'); //v1.5.0
$hadTransaction = $this->bd->inTransaction();
if ($hadTransaction) {
$this->bd->commit();
@@ -88,6 +90,7 @@ class FreshRSS_EntryDAO extends Minz_ModelPdo implements FreshRSS_Searchable {
return false;
}
+ //TODO: Move the database auto-updates to DatabaseDAO
protected function createEntryTempTable() {
$ok = false;
$hadTransaction = $this->bd->inTransaction();
@@ -120,22 +123,28 @@ class FreshRSS_EntryDAO extends Minz_ModelPdo implements FreshRSS_Searchable {
return $ok;
}
+ //TODO: Move the database auto-updates to DatabaseDAO
protected function autoUpdateDb($errorInfo) {
if (isset($errorInfo[0])) {
- if ($errorInfo[0] === '42S22') { //ER_BAD_FIELD_ERROR
+ if ($errorInfo[0] === FreshRSS_DatabaseDAO::ER_BAD_FIELD_ERROR) {
//autoAddColumn
foreach (array('lastSeen', 'hash') as $column) {
if (stripos($errorInfo[2], $column) !== false) {
return $this->addColumn($column);
}
}
- } elseif ($errorInfo[0] === '42S02' && stripos($errorInfo[2], 'entrytmp') !== false) { //ER_BAD_TABLE_ERROR
- return $this->createEntryTempTable(); //v1.7
+ } elseif ($errorInfo[0] === FreshRSS_DatabaseDAO::ER_BAD_TABLE_ERROR) {
+ if (stripos($errorInfo[2], 'tag') !== false) {
+ $tagDAO = FreshRSS_Factory::createTagDao();
+ return $tagDAO->createTagTable(); //v1.12.0
+ } elseif (stripos($errorInfo[2], 'entrytmp') !== false) {
+ return $this->createEntryTempTable(); //v1.7.0
+ }
}
}
if (isset($errorInfo[1])) {
- if ($errorInfo[1] == '1366') { //ER_TRUNCATED_WRONG_VALUE_FOR_FIELD
- return $this->updateToUtf8mb4();
+ if ($errorInfo[1] == FreshRSS_DatabaseDAO::ER_TRUNCATED_WRONG_VALUE_FOR_FIELD) {
+ return $this->updateToUtf8mb4(); //v1.5.0
}
}
return false;
@@ -560,11 +569,52 @@ class FreshRSS_EntryDAO extends Minz_ModelPdo implements FreshRSS_Searchable {
return $affected;
}
+ /**
+ * Mark all the articles in a tag as read.
+ * @param integer $id tag ID, or empty for targetting any tag
+ * @param integer $idMax max article ID
+ * @return integer affected rows
+ */
+ public function markReadTag($id = '', $idMax = 0, $filters = null, $state = 0, $is_read = true) {
+ FreshRSS_UserDAO::touch();
+ if ($idMax == 0) {
+ $idMax = time() . '000000';
+ Minz_Log::debug('Calling markReadTag(0) is deprecated!');
+ }
+
+ $sql = 'UPDATE `' . $this->prefix . 'entry` e INNER JOIN `' . $this->prefix . 'entrytag` et ON et.id_entry = e.id '
+ . 'SET e.is_read = ? '
+ . 'WHERE '
+ . ($id == '' ? '' : 'et.id_tag = ? AND ')
+ . 'e.is_read <> ? AND e.id <= ?';
+ $values = array($is_read ? 1 : 0);
+ if ($id != '') {
+ $values[] = $id;
+ }
+ $values[] = $is_read ? 1 : 0;
+ $values[] = $idMax;
+
+ list($searchValues, $search) = $this->sqlListEntriesWhere('e.', $filters, $state);
+
+ $stm = $this->bd->prepare($sql . $search);
+ if (!($stm && $stm->execute(array_merge($values, $searchValues)))) {
+ $info = $stm == null ? array(2 => 'syntax error') : $stm->errorInfo();
+ Minz_Log::error('SQL error markReadTag: ' . $info[2]);
+ return false;
+ }
+ $affected = $stm->rowCount();
+ if (($affected > 0) && (!$this->updateCacheUnreads(false, false))) {
+ return false;
+ }
+ return $affected;
+ }
+
public function cleanOldEntries($id_feed, $date_min, $keep = 15) { //Remember to call updateCachedValue($id_feed) or updateCachedValues() just after
$sql = 'DELETE FROM `' . $this->prefix . 'entry` '
. 'WHERE id_feed=:id_feed AND id<=:id_max '
. 'AND is_favorite=0 ' //Do not remove favourites
. 'AND `lastSeen` < (SELECT maxLastSeen FROM (SELECT (MAX(e3.`lastSeen`)-99) AS maxLastSeen FROM `' . $this->prefix . 'entry` e3 WHERE e3.id_feed=:id_feed) recent) ' //Do not remove the most newly seen articles, plus a few seconds of tolerance
+ . 'AND id NOT IN (SELECT id_entry FROM `' . $this->prefix . 'entrytag`) ' //Do not purge tagged entries
. 'AND id NOT IN (SELECT id FROM (SELECT e2.id FROM `' . $this->prefix . 'entry` e2 WHERE e2.id_feed=:id_feed ORDER BY id DESC LIMIT :keep) keep)'; //Double select: MySQL doesn't support 'LIMIT & IN/ALL/ANY/SOME subquery'
$stm = $this->bd->prepare($sql);
@@ -770,24 +820,31 @@ class FreshRSS_EntryDAO extends Minz_ModelPdo implements FreshRSS_Searchable {
$joinFeed = false;
$values = array();
switch ($type) {
- case 'a':
+ case 'a': //All PRIORITY_MAIN_STREAM
$where .= 'f.priority > ' . FreshRSS_Feed::PRIORITY_NORMAL . ' ';
break;
- case 's': //Deprecated: use $state instead
+ case 'A': //All except PRIORITY_ARCHIVED
+ $where .= 'f.priority >= ' . FreshRSS_Feed::PRIORITY_NORMAL . ' ';
+ break;
+ case 's': //Starred. Deprecated: use $state instead
$where .= 'f.priority >= ' . FreshRSS_Feed::PRIORITY_NORMAL . ' ';
$where .= 'AND e.is_favorite=1 ';
break;
- case 'c':
+ case 'c': //Category
$where .= 'f.priority >= ' . FreshRSS_Feed::PRIORITY_NORMAL . ' ';
$where .= 'AND f.category=? ';
$values[] = intval($id);
break;
- case 'f':
+ case 'f': //Feed
$where .= 'e.id_feed=? ';
$values[] = intval($id);
break;
- case 'A':
- $where .= 'f.priority >= ' . FreshRSS_Feed::PRIORITY_NORMAL . ' ';
+ case 't': //Tag
+ $where .= 'et.id_tag=? ';
+ $values[] = intval($id);
+ break;
+ case 'T': //Any tag
+ $where .= '1=1 ';
break;
default:
throw new FreshRSS_EntriesGetter_Exception('Bad type in Entry->listByType: [' . $type . ']!');
@@ -796,8 +853,11 @@ class FreshRSS_EntryDAO extends Minz_ModelPdo implements FreshRSS_Searchable {
list($searchValues, $search) = $this->sqlListEntriesWhere('e.', $filters, $state, $order, $firstId, $date_min);
return array(array_merge($values, $searchValues),
- 'SELECT e.id FROM `' . $this->prefix . 'entry` e '
+ 'SELECT '
+ . ($type === 'T' ? 'DISTINCT ' : '')
+ . 'e.id FROM `' . $this->prefix . 'entry` e '
. 'INNER JOIN `' . $this->prefix . 'feed` f ON e.id_feed = f.id '
+ . ($type === 't' || $type === 'T' ? 'INNER JOIN `' . $this->prefix . 'entrytag` et ON et.id_entry = e.id ' : '')
. 'WHERE ' . $where
. $search
. 'ORDER BY e.id ' . $order
@@ -817,13 +877,22 @@ class FreshRSS_EntryDAO extends Minz_ModelPdo implements FreshRSS_Searchable {
. 'ORDER BY e0.id ' . $order;
$stm = $this->bd->prepare($sql);
- $stm->execute($values);
- return $stm;
+ if ($stm && $stm->execute($values)) {
+ return $stm;
+ } else {
+ $info = $stm == null ? array(0 => '', 1 => '', 2 => 'syntax error') : $stm->errorInfo();
+ Minz_Log::error('SQL error listWhereRaw: ' . $info[2]);
+ return false;
+ }
}
public function listWhere($type = 'a', $id = '', $state = FreshRSS_Entry::STATE_ALL, $order = 'DESC', $limit = 1, $firstId = '', $filters = null, $date_min = 0) {
$stm = $this->listWhereRaw($type, $id, $state, $order, $limit, $firstId, $filters, $date_min);
- return self::daoToEntries($stm->fetchAll(PDO::FETCH_ASSOC));
+ if ($stm) {
+ return self::daoToEntries($stm->fetchAll(PDO::FETCH_ASSOC));
+ } else {
+ return false;
+ }
}
public function listByIds($ids, $order = 'DESC') {
@@ -923,7 +992,7 @@ class FreshRSS_EntryDAO extends Minz_ModelPdo implements FreshRSS_Searchable {
$stm = $this->bd->prepare($sql);
$stm->execute();
$res = $stm->fetchAll(PDO::FETCH_COLUMN, 0);
- return $res[0];
+ return isset($res[0]) ? $res[0] : 0;
}
public function countNotRead($minPriority = null) {
$sql = 'SELECT COUNT(e.id) AS count FROM `' . $this->prefix . 'entry` e';
diff --git a/app/Models/EntryDAOPGSQL.php b/app/Models/EntryDAOPGSQL.php
index f09fe8e75..aef258b6f 100644
--- a/app/Models/EntryDAOPGSQL.php
+++ b/app/Models/EntryDAOPGSQL.php
@@ -12,8 +12,13 @@ class FreshRSS_EntryDAOPGSQL extends FreshRSS_EntryDAOSQLite {
protected function autoUpdateDb($errorInfo) {
if (isset($errorInfo[0])) {
- if ($errorInfo[0] === '42P01' && stripos($errorInfo[2], 'entrytmp') !== false) { //undefined_table
- return $this->createEntryTempTable();
+ if ($errorInfo[0] === FreshRSS_DatabaseDAOPGSQL::UNDEFINED_TABLE) {
+ if (stripos($errorInfo[2], 'tag') !== false) {
+ $tagDAO = FreshRSS_Factory::createTagDao();
+ return $tagDAO->createTagTable(); //v1.12.0
+ } elseif (stripos($errorInfo[2], 'entrytmp') !== false) {
+ return $this->createEntryTempTable(); //v1.7.0
+ }
}
}
return false;
diff --git a/app/Models/EntryDAOSQLite.php b/app/Models/EntryDAOSQLite.php
index 944de8470..f8cd14fe6 100644
--- a/app/Models/EntryDAOSQLite.php
+++ b/app/Models/EntryDAOSQLite.php
@@ -7,10 +7,17 @@ class FreshRSS_EntryDAOSQLite extends FreshRSS_EntryDAO {
}
protected function autoUpdateDb($errorInfo) {
+ if ($tableInfo = $this->bd->query("SELECT sql FROM sqlite_master where name='tag'")) {
+ $showCreate = $tableInfo->fetchColumn();
+ if (stripos($showCreate, 'tag') === false) {
+ $tagDAO = FreshRSS_Factory::createTagDao();
+ return $tagDAO->createTagTable(); //v1.12.0
+ }
+ }
if ($tableInfo = $this->bd->query("SELECT sql FROM sqlite_master where name='entrytmp'")) {
$showCreate = $tableInfo->fetchColumn();
if (stripos($showCreate, 'entrytmp') === false) {
- return $this->createEntryTempTable();
+ return $this->createEntryTempTable(); //v1.7.0
}
}
if ($tableInfo = $this->bd->query("SELECT sql FROM sqlite_master where name='entry'")) {
@@ -228,4 +235,43 @@ DROP TABLE IF EXISTS `tmp`;
}
return $affected;
}
+
+ /**
+ * Mark all the articles in a tag as read.
+ * @param integer $id tag ID, or empty for targetting any tag
+ * @param integer $idMax max article ID
+ * @return integer affected rows
+ */
+ public function markReadTag($id = '', $idMax = 0, $filters = null, $state = 0, $is_read = true) {
+ FreshRSS_UserDAO::touch();
+ if ($idMax == 0) {
+ $idMax = time() . '000000';
+ Minz_Log::debug('Calling markReadTag(0) is deprecated!');
+ }
+
+ $sql = 'UPDATE `' . $this->prefix . 'entry` e '
+ . 'SET e.is_read = ? '
+ . 'WHERE e.is_read <> ? AND e.id <= ? AND '
+ . 'e.id IN (SELECT et.id_entry FROM `' . $this->prefix . 'entrytag` et '
+ . ($id == '' ? '' : 'WHERE et.id = ?')
+ . ')';
+ $values = array($is_read ? 1 : 0, $is_read ? 1 : 0, $idMax);
+ if ($id != '') {
+ $values[] = $id;
+ }
+
+ list($searchValues, $search) = $this->sqlListEntriesWhere('e.', $filters, $state);
+
+ $stm = $this->bd->prepare($sql . $search);
+ if (!($stm && $stm->execute(array_merge($values, $searchValues)))) {
+ $info = $stm == null ? array(2 => 'syntax error') : $stm->errorInfo();
+ Minz_Log::error('SQL error markReadTag: ' . $info[2]);
+ return false;
+ }
+ $affected = $stm->rowCount();
+ if (($affected > 0) && (!$this->updateCacheUnreads(false, false))) {
+ return false;
+ }
+ return $affected;
+ }
}
diff --git a/app/Models/Factory.php b/app/Models/Factory.php
index 764987c46..1accb491c 100644
--- a/app/Models/Factory.php
+++ b/app/Models/Factory.php
@@ -2,6 +2,10 @@
class FreshRSS_Factory {
+ public static function createCategoryDao($username = null) {
+ return new FreshRSS_CategoryDAO($username);
+ }
+
public static function createFeedDao($username = null) {
$conf = Minz_Configuration::get('system');
switch ($conf->db['type']) {
@@ -24,6 +28,18 @@ class FreshRSS_Factory {
}
}
+ public static function createTagDao($username = null) {
+ $conf = Minz_Configuration::get('system');
+ switch ($conf->db['type']) {
+ case 'sqlite':
+ return new FreshRSS_TagDAOSQLite($username);
+ case 'pgsql':
+ return new FreshRSS_TagDAOPGSQL($username);
+ default:
+ return new FreshRSS_TagDAO($username);
+ }
+ }
+
public static function createStatsDAO($username = null) {
$conf = Minz_Configuration::get('system');
switch ($conf->db['type']) {
diff --git a/app/Models/Feed.php b/app/Models/Feed.php
index ed381a867..e1dd2990d 100644
--- a/app/Models/Feed.php
+++ b/app/Models/Feed.php
@@ -286,6 +286,10 @@ class FreshRSS_Feed extends Minz_Model {
if (!$loadDetails) { //Only activates auto-discovery when adding a new feed
$feed->set_autodiscovery_level(SIMPLEPIE_LOCATOR_NONE);
}
+ if ($this->attributes('clear_cache')) {
+ // Do not use `$simplePie->enable_cache(false);` as it would prevent caching in multiuser context
+ $this->clearCache();
+ }
Minz_ExtensionManager::callHook('simplepie_before_init', $feed, $this);
$mtime = $feed->init();
@@ -345,13 +349,21 @@ class FreshRSS_Feed extends Minz_Model {
$link = $item->get_permalink();
$date = @strtotime($item->get_date());
- // gestion des tags (catégorie == tag)
- $tags_tmp = $item->get_categories();
+ //Tag processing (tag == category)
+ $categories = $item->get_categories();
$tags = array();
- if ($tags_tmp !== null) {
- foreach ($tags_tmp as $tag) {
- $tags[] = html_only_entity_decode($tag->get_label());
+ if (is_array($categories)) {
+ foreach ($categories as $category) {
+ $text = html_only_entity_decode($category->get_label());
+ //Some feeds use a single category with comma-separated tags
+ $labels = explode(',', $text);
+ if (is_array($labels)) {
+ foreach ($labels as $label) {
+ $tags[] = trim($label);
+ }
+ }
}
+ $tags = array_unique($tags);
}
$content = html_only_entity_decode($item->get_content());
@@ -412,7 +424,7 @@ class FreshRSS_Feed extends Minz_Model {
$author_names = '';
if (is_array($authors)) {
foreach ($authors as $author) {
- $author_names .= html_only_entity_decode(strip_tags($author->name == '' ? $author->email : $author->name)) . ', ';
+ $author_names .= html_only_entity_decode(strip_tags($author->name == '' ? $author->email : $author->name)) . '; ';
}
}
$author_names = substr($author_names, 0, -2);
@@ -457,8 +469,16 @@ class FreshRSS_Feed extends Minz_Model {
$this->entries = $entries;
}
+ protected function cacheFilename() {
+ return CACHE_PATH . '/' . md5($this->url) . '.spc';
+ }
+
+ public function clearCache() {
+ return @unlink($this->cacheFilename());
+ }
+
public function cacheModifiedTime() {
- return @filemtime(CACHE_PATH . '/' . md5($this->url) . '.spc');
+ return @filemtime($this->cacheFilename());
}
public function lock() {
diff --git a/app/Models/FeedDAO.php b/app/Models/FeedDAO.php
index 285f17193..e579f5881 100644
--- a/app/Models/FeedDAO.php
+++ b/app/Models/FeedDAO.php
@@ -17,7 +17,7 @@ class FreshRSS_FeedDAO extends Minz_ModelPdo implements FreshRSS_Searchable {
protected function autoUpdateDb($errorInfo) {
if (isset($errorInfo[0])) {
- if ($errorInfo[0] === '42S22' || $errorInfo[0] === '42703') { //ER_BAD_FIELD_ERROR (Mysql), undefined_column (PostgreSQL)
+ if ($errorInfo[0] === FreshRSS_DatabaseDAO::ER_BAD_FIELD_ERROR || $errorInfo[0] === FreshRSS_DatabaseDAOPGSQL::UNDEFINED_COLUMN) {
foreach (array('attributes') as $column) {
if (stripos($errorInfo[2], $column) !== false) {
return $this->addColumn($column);
@@ -55,7 +55,7 @@ class FreshRSS_FeedDAO extends Minz_ModelPdo implements FreshRSS_Searchable {
$values = array(
substr($valuesTmp['url'], 0, 511),
$valuesTmp['category'],
- mb_strcut($valuesTmp['name'], 0, 255, 'UTF-8'),
+ mb_strcut(trim($valuesTmp['name']), 0, FreshRSS_DatabaseDAO::LENGTH_INDEX_UNICODE, 'UTF-8'),
substr($valuesTmp['website'], 0, 255),
mb_strcut($valuesTmp['description'], 0, 1023, 'UTF-8'),
$valuesTmp['lastUpdate'],
@@ -109,6 +109,9 @@ class FreshRSS_FeedDAO extends Minz_ModelPdo implements FreshRSS_Searchable {
}
public function updateFeed($id, $valuesTmp) {
+ if (isset($valuesTmp['name'])) {
+ $valuesTmp['name'] = mb_strcut(trim($valuesTmp['name']), 0, FreshRSS_DatabaseDAO::LENGTH_INDEX_UNICODE, 'UTF-8');
+ }
if (isset($valuesTmp['url'])) {
$valuesTmp['url'] = safe_ascii($valuesTmp['url']);
}
@@ -180,7 +183,7 @@ class FreshRSS_FeedDAO extends Minz_ModelPdo implements FreshRSS_Searchable {
}
public function changeCategory($idOldCat, $idNewCat) {
- $catDAO = new FreshRSS_CategoryDAO();
+ $catDAO = FreshRSS_Factory::createCategoryDao();
$newCat = $catDAO->searchById($idNewCat);
if (!$newCat) {
$newCat = $catDAO->getDefault();
diff --git a/app/Models/Search.php b/app/Models/Search.php
index 5cc7f8e8d..c52e391fa 100644
--- a/app/Models/Search.php
+++ b/app/Models/Search.php
@@ -40,7 +40,7 @@ class FreshRSS_Search {
$input = $this->parseNotIntitleSearch($input);
$input = $this->parseNotAuthorSearch($input);
$input = $this->parseNotInurlSearch($input);
- $input = $this->parseNotTagsSeach($input);
+ $input = $this->parseNotTagsSearch($input);
$input = $this->parsePubdateSearch($input);
$input = $this->parseDateSearch($input);
@@ -48,7 +48,7 @@ class FreshRSS_Search {
$input = $this->parseIntitleSearch($input);
$input = $this->parseAuthorSearch($input);
$input = $this->parseInurlSearch($input);
- $input = $this->parseTagsSeach($input);
+ $input = $this->parseTagsSearch($input);
$input = $this->parseNotSearch($input);
$input = $this->parseSearch($input);
@@ -117,6 +117,17 @@ class FreshRSS_Search {
return is_array($anArray) ? array_filter($anArray, function($value) { return $value !== ''; }) : array();
}
+ private static function decodeSpaces($value) {
+ if (is_array($value)) {
+ for ($i = count($value) - 1; $i >= 0; $i--) {
+ $value[$i] = self::decodeSpaces($value[$i]);
+ }
+ } else {
+ $value = trim(str_replace('+', ' ', $value));
+ }
+ return $value;
+ }
+
/**
* Parse the search string to find intitle keyword and the search related
* to it.
@@ -130,11 +141,12 @@ class FreshRSS_Search {
$this->intitle = $matches['search'];
$input = str_replace($matches[0], '', $input);
}
- if (preg_match_all('/\bintitle:(?P<search>\w*)/', $input, $matches)) {
+ if (preg_match_all('/\bintitle:(?P<search>[^\s"]*)/', $input, $matches)) {
$this->intitle = array_merge($this->intitle ? $this->intitle : array(), $matches['search']);
$input = str_replace($matches[0], '', $input);
}
$this->intitle = self::removeEmptyValues($this->intitle);
+ $this->intitle = self::decodeSpaces($this->intitle);
return $input;
}
@@ -143,11 +155,12 @@ class FreshRSS_Search {
$this->not_intitle = $matches['search'];
$input = str_replace($matches[0], '', $input);
}
- if (preg_match_all('/[!-]intitle:(?P<search>\w*)/', $input, $matches)) {
+ if (preg_match_all('/[!-]intitle:(?P<search>[^\s"]*)/', $input, $matches)) {
$this->not_intitle = array_merge($this->not_intitle ? $this->not_intitle : array(), $matches['search']);
$input = str_replace($matches[0], '', $input);
}
$this->not_intitle = self::removeEmptyValues($this->not_intitle);
+ $this->not_intitle = self::decodeSpaces($this->not_intitle);
return $input;
}
@@ -166,11 +179,12 @@ class FreshRSS_Search {
$this->author = $matches['search'];
$input = str_replace($matches[0], '', $input);
}
- if (preg_match_all('/\bauthor:(?P<search>\w*)/', $input, $matches)) {
+ if (preg_match_all('/\bauthor:(?P<search>[^\s"]*)/', $input, $matches)) {
$this->author = array_merge($this->author ? $this->author : array(), $matches['search']);
$input = str_replace($matches[0], '', $input);
}
$this->author = self::removeEmptyValues($this->author);
+ $this->author = self::decodeSpaces($this->author);
return $input;
}
@@ -179,11 +193,12 @@ class FreshRSS_Search {
$this->not_author = $matches['search'];
$input = str_replace($matches[0], '', $input);
}
- if (preg_match_all('/[!-]author:(?P<search>\w*)/', $input, $matches)) {
+ if (preg_match_all('/[!-]author:(?P<search>[^\s"]*)/', $input, $matches)) {
$this->not_author = array_merge($this->not_author ? $this->not_author : array(), $matches['search']);
$input = str_replace($matches[0], '', $input);
}
$this->not_author = self::removeEmptyValues($this->not_author);
+ $this->not_author = self::decodeSpaces($this->not_author);
return $input;
}
@@ -201,6 +216,7 @@ class FreshRSS_Search {
$input = str_replace($matches[0], '', $input);
}
$this->inurl = self::removeEmptyValues($this->inurl);
+ $this->inurl = self::decodeSpaces($this->inurl);
return $input;
}
@@ -210,6 +226,7 @@ class FreshRSS_Search {
$input = str_replace($matches[0], '', $input);
}
$this->not_inurl = self::removeEmptyValues($this->not_inurl);
+ $this->not_inurl = self::decodeSpaces($this->not_inurl);
return $input;
}
@@ -259,21 +276,23 @@ class FreshRSS_Search {
* @param string $input
* @return string
*/
- private function parseTagsSeach($input) {
+ private function parseTagsSearch($input) {
if (preg_match_all('/#(?P<search>[^\s]+)/', $input, $matches)) {
$this->tags = $matches['search'];
$input = str_replace($matches[0], '', $input);
}
$this->tags = self::removeEmptyValues($this->tags);
+ $this->tags = self::decodeSpaces($this->tags);
return $input;
}
- private function parseNotTagsSeach($input) {
+ private function parseNotTagsSearch($input) {
if (preg_match_all('/[!-]#(?P<search>[^\s]+)/', $input, $matches)) {
$this->not_tags = $matches['search'];
$input = str_replace($matches[0], '', $input);
}
$this->not_tags = self::removeEmptyValues($this->not_tags);
+ $this->not_tags = self::decodeSpaces($this->not_tags);
return $input;
}
@@ -303,6 +322,7 @@ class FreshRSS_Search {
} else {
$this->search = explode(' ', $input);
}
+ $this->search = self::decodeSpaces($this->search);
}
private function parseNotSearch($input) {
@@ -322,6 +342,7 @@ class FreshRSS_Search {
$input = str_replace($matches[0], '', $input);
}
$this->not_search = self::removeEmptyValues($this->not_search);
+ $this->not_search = self::decodeSpaces($this->not_search);
return $input;
}
diff --git a/app/Models/Tag.php b/app/Models/Tag.php
new file mode 100644
index 000000000..3eb989cc1
--- /dev/null
+++ b/app/Models/Tag.php
@@ -0,0 +1,76 @@
+<?php
+
+class FreshRSS_Tag extends Minz_Model {
+ private $id = 0;
+ private $name;
+ private $attributes = array();
+ private $nbEntries = -1;
+ private $nbUnread = -1;
+
+ public function __construct($name = '') {
+ $this->_name($name);
+ }
+
+ public function id() {
+ return $this->id;
+ }
+
+ public function _id($value) {
+ $this->id = (int)$value;
+ }
+
+ public function name() {
+ return $this->name;
+ }
+
+ public function _name($value) {
+ $this->name = trim($value);
+ }
+
+ public function attributes($key = '') {
+ if ($key == '') {
+ return $this->attributes;
+ } else {
+ return isset($this->attributes[$key]) ? $this->attributes[$key] : null;
+ }
+ }
+
+ public function _attributes($key, $value) {
+ if ($key == '') {
+ if (is_string($value)) {
+ $value = json_decode($value, true);
+ }
+ if (is_array($value)) {
+ $this->attributes = $value;
+ }
+ } elseif ($value === null) {
+ unset($this->attributes[$key]);
+ } else {
+ $this->attributes[$key] = $value;
+ }
+ }
+
+ public function nbEntries() {
+ if ($this->nbEntries < 0) {
+ $tagDAO = FreshRSS_Factory::createTagDao();
+ $this->nbEntries = $tagDAO->countEntries($this->id());
+ }
+ return $this->nbFeed;
+ }
+
+ public function _nbEntries($value) {
+ $this->nbEntries = (int)$value;
+ }
+
+ public function nbUnread() {
+ if ($this->nbUnread < 0) {
+ $tagDAO = FreshRSS_Factory::createTagDao();
+ $this->nbUnread = $tagDAO->countNotRead($this->id());
+ }
+ return $this->nbUnread;
+ }
+
+ public function _nbUnread($value) {
+ $this->nbUnread = (int)$value;
+ }
+}
diff --git a/app/Models/TagDAO.php b/app/Models/TagDAO.php
new file mode 100644
index 000000000..1b59c8971
--- /dev/null
+++ b/app/Models/TagDAO.php
@@ -0,0 +1,315 @@
+<?php
+
+class FreshRSS_TagDAO extends Minz_ModelPdo implements FreshRSS_Searchable {
+
+ public function sqlIgnore() {
+ return 'IGNORE';
+ }
+
+ public function createTagTable() {
+ $ok = false;
+ $hadTransaction = $this->bd->inTransaction();
+ if ($hadTransaction) {
+ $this->bd->commit();
+ }
+ try {
+ $db = FreshRSS_Context::$system_conf->db;
+ require_once(APP_PATH . '/SQL/install.sql.' . $db['type'] . '.php');
+
+ Minz_Log::warning('SQL ALTER GUID case sensitivity...');
+ $databaseDAO = FreshRSS_Factory::createDatabaseDAO();
+ $databaseDAO->ensureCaseInsensitiveGuids();
+
+ Minz_Log::warning('SQL CREATE TABLE tag...');
+ if (defined('SQL_CREATE_TABLE_TAGS')) {
+ $sql = sprintf(SQL_CREATE_TABLE_TAGS, $this->prefix);
+ $stm = $this->bd->prepare($sql);
+ $ok = $stm && $stm->execute();
+ } else {
+ global $SQL_CREATE_TABLE_TAGS;
+ $ok = !empty($SQL_CREATE_TABLE_TAGS);
+ foreach ($SQL_CREATE_TABLE_TAGS as $instruction) {
+ $sql = sprintf($instruction, $this->prefix);
+ $stm = $this->bd->prepare($sql);
+ $ok &= $stm && $stm->execute();
+ }
+ }
+ } catch (Exception $e) {
+ Minz_Log::error('FreshRSS_EntryDAO::createTagTable error: ' . $e->getMessage());
+ }
+ if ($hadTransaction) {
+ $this->bd->beginTransaction();
+ }
+ return $ok;
+ }
+
+ protected function autoUpdateDb($errorInfo) {
+ if (isset($errorInfo[0])) {
+ if ($errorInfo[0] === FreshRSS_DatabaseDAO::ER_BAD_TABLE_ERROR || $errorInfo[0] === FreshRSS_DatabaseDAOPGSQL::UNDEFINED_TABLE) {
+ if (stripos($errorInfo[2], 'tag') !== false) {
+ return $this->createTagTable(); //v1.12.0
+ }
+ }
+ }
+ return false;
+ }
+
+ public function addTag($valuesTmp) {
+ $sql = 'INSERT INTO `' . $this->prefix . 'tag`(name, attributes) '
+ . 'SELECT * FROM (SELECT TRIM(?), TRIM(?)) t2 ' //TRIM() to provide a type hint as text for PostgreSQL
+ . 'WHERE NOT EXISTS (SELECT 1 FROM `' . $this->prefix . 'category` WHERE name = TRIM(?))'; //No category of the same name
+ $stm = $this->bd->prepare($sql);
+
+ $valuesTmp['name'] = mb_strcut(trim($valuesTmp['name']), 0, 63, 'UTF-8');
+ $values = array(
+ $valuesTmp['name'],
+ isset($valuesTmp['attributes']) ? json_encode($valuesTmp['attributes']) : '',
+ $valuesTmp['name'],
+ );
+
+ if ($stm && $stm->execute($values)) {
+ return $this->bd->lastInsertId('"' . $this->prefix . 'tag_id_seq"');
+ } else {
+ $info = $stm == null ? array(2 => 'syntax error') : $stm->errorInfo();
+ Minz_Log::error('SQL error addTag: ' . $info[2]);
+ return false;
+ }
+ }
+
+ public function addTagObject($tag) {
+ $tag = $this->searchByName($tag->name());
+ if (!$tag) {
+ $values = array(
+ 'name' => $tag->name(),
+ 'attributes' => $tag->attributes(),
+ );
+ return $this->addTag($values);
+ }
+ return $tag->id();
+ }
+
+ public function updateTag($id, $valuesTmp) {
+ $sql = 'UPDATE `' . $this->prefix . 'tag` SET name=?, attributes=? WHERE id=? '
+ . 'AND NOT EXISTS (SELECT 1 FROM `' . $this->prefix . 'category` WHERE name = ?)'; //No category of the same name
+ $stm = $this->bd->prepare($sql);
+
+ $valuesTmp['name'] = mb_strcut(trim($valuesTmp['name']), 0, 63, 'UTF-8');
+ $values = array(
+ $valuesTmp['name'],
+ isset($valuesTmp['attributes']) ? json_encode($valuesTmp['attributes']) : '',
+ $id,
+ $valuesTmp['name'],
+ );
+
+ if ($stm && $stm->execute($values)) {
+ return $stm->rowCount();
+ } else {
+ $info = $stm == null ? array(2 => 'syntax error') : $stm->errorInfo();
+ Minz_Log::error('SQL error updateTag: ' . $info[2]);
+ return false;
+ }
+ }
+
+ public function updateTagAttribute($tag, $key, $value) {
+ if ($tag instanceof FreshRSS_Tag) {
+ $tag->_attributes($key, $value);
+ return $this->updateFeed(
+ $tag->id(),
+ array('attributes' => $feed->attributes())
+ );
+ }
+ return false;
+ }
+
+ public function deleteTag($id) {
+ if ($id <= 0) {
+ return false;
+ }
+ $sql = 'DELETE FROM `' . $this->prefix . 'tag` WHERE id=?';
+ $stm = $this->bd->prepare($sql);
+
+ $values = array($id);
+
+ if ($stm && $stm->execute($values)) {
+ return $stm->rowCount();
+ } else {
+ $info = $stm == null ? array(2 => 'syntax error') : $stm->errorInfo();
+ Minz_Log::error('SQL error deleteTag: ' . $info[2]);
+ return false;
+ }
+ }
+
+ public function searchById($id) {
+ $sql = 'SELECT * FROM `' . $this->prefix . 'tag` WHERE id=?';
+ $stm = $this->bd->prepare($sql);
+ $values = array($id);
+ $stm->execute($values);
+ $res = $stm->fetchAll(PDO::FETCH_ASSOC);
+ $tag = self::daoToTag($res);
+ return isset($tag[0]) ? $tag[0] : null;
+ }
+
+ public function searchByName($name) {
+ $sql = 'SELECT * FROM `' . $this->prefix . 'tag` WHERE name=?';
+ $stm = $this->bd->prepare($sql);
+ $values = array($name);
+ $stm->execute($values);
+ $res = $stm->fetchAll(PDO::FETCH_ASSOC);
+ $tag = self::daoToTag($res);
+ return isset($tag[0]) ? $tag[0] : null;
+ }
+
+ public function listTags($precounts = false) {
+ if ($precounts) {
+ $sql = 'SELECT t.id, t.name, count(e.id) AS unreads '
+ . 'FROM `' . $this->prefix . 'tag` t '
+ . 'LEFT OUTER JOIN `' . $this->prefix . 'entrytag` et ON et.id_tag = t.id '
+ . 'LEFT OUTER JOIN `' . $this->prefix . 'entry` e ON et.id_entry = e.id AND e.is_read = 0 '
+ . 'GROUP BY t.id '
+ . 'ORDER BY t.name';
+ } else {
+ $sql = 'SELECT * FROM `' . $this->prefix . 'tag` ORDER BY name';
+ }
+
+ $stm = $this->bd->prepare($sql);
+ if ($stm && $stm->execute()) {
+ return self::daoToTag($stm->fetchAll(PDO::FETCH_ASSOC));
+ } else {
+ $info = $stm == null ? array(0 => '', 1 => '', 2 => 'syntax error') : $stm->errorInfo();
+ if ($this->autoUpdateDb($info)) {
+ return $this->listTags($precounts);
+ }
+ Minz_Log::error('SQL error listTags: ' . $info[2]);
+ return false;
+ }
+ }
+
+ 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'];
+ }
+
+ public function countEntries($id) {
+ $sql = 'SELECT COUNT(*) AS count FROM `' . $this->prefix . 'entrytag` WHERE id_tag=?';
+ $stm = $this->bd->prepare($sql);
+ $values = array($id);
+ $stm->execute($values);
+ $res = $stm->fetchAll(PDO::FETCH_ASSOC);
+ return $res[0]['count'];
+ }
+
+ public function countNotRead($id) {
+ $sql = 'SELECT COUNT(*) AS count FROM `' . $this->prefix . 'entrytag` et '
+ . 'INNER JOIN `' . $this->prefix . 'entry` e ON et.id_entry=e.id '
+ . 'WHERE et.id_tag=? AND e.is_read=0';
+ $stm = $this->bd->prepare($sql);
+ $values = array($id);
+ $stm->execute($values);
+ $res = $stm->fetchAll(PDO::FETCH_ASSOC);
+ return $res[0]['count'];
+ }
+
+ public function tagEntry($id_tag, $id_entry, $checked = true) {
+ if ($checked) {
+ $sql = 'INSERT ' . $this->sqlIgnore() . ' INTO `' . $this->prefix . 'entrytag`(id_tag, id_entry) VALUES(?, ?)';
+ } else {
+ $sql = 'DELETE FROM `' . $this->prefix . 'entrytag` WHERE id_tag=? AND id_entry=?';
+ }
+ $stm = $this->bd->prepare($sql);
+ $values = array($id_tag, $id_entry);
+
+ if ($stm && $stm->execute($values)) {
+ return true;
+ } else {
+ $info = $stm == null ? array(2 => 'syntax error') : $stm->errorInfo();
+ Minz_Log::error('SQL error tagEntry: ' . $info[2]);
+ return false;
+ }
+ }
+
+ public function getTagsForEntry($id_entry) {
+ $sql = 'SELECT t.id, t.name, et.id_entry IS NOT NULL as checked '
+ . 'FROM `' . $this->prefix . 'tag` t '
+ . 'LEFT OUTER JOIN `' . $this->prefix . 'entrytag` et ON et.id_tag = t.id AND et.id_entry=? '
+ . 'ORDER BY t.name';
+
+ $stm = $this->bd->prepare($sql);
+ $values = array($id_entry);
+
+ if ($stm && $stm->execute($values)) {
+ $lines = $stm->fetchAll(PDO::FETCH_ASSOC);
+ for ($i = count($lines) - 1; $i >= 0; $i--) {
+ $lines[$i]['id'] = intval($lines[$i]['id']);
+ $lines[$i]['checked'] = !empty($lines[$i]['checked']);
+ }
+ return $lines;
+ } else {
+ $info = $stm == null ? array(0 => '', 1 => '', 2 => 'syntax error') : $stm->errorInfo();
+ if ($this->autoUpdateDb($info)) {
+ return $this->getTagsForEntry($id_entry);
+ }
+ Minz_Log::error('SQL error getTagsForEntry: ' . $info[2]);
+ return false;
+ }
+ }
+
+ //For API
+ public function getEntryIdsTagNames($entries) {
+ $sql = 'SELECT et.id_entry, t.name '
+ . 'FROM `' . $this->prefix . 'tag` t '
+ . 'INNER JOIN `' . $this->prefix . 'entrytag` et ON et.id_tag = t.id';
+
+ $values = array();
+ if (is_array($entries) && count($entries) > 0) {
+ $sql .= ' AND et.id_entry IN (' . str_repeat('?,', count($entries) - 1). '?)';
+ foreach ($entries as $entry) {
+ $values[] = $entry->id();
+ }
+ }
+ $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;
+ } else {
+ $info = $stm == null ? array(0 => '', 1 => '', 2 => 'syntax error') : $stm->errorInfo();
+ if ($this->autoUpdateDb($info)) {
+ return $this->getTagNamesEntryIds($id_entry);
+ }
+ Minz_Log::error('SQL error getTagNamesEntryIds: ' . $info[2]);
+ return false;
+ }
+ }
+
+ public static function daoToTag($listDAO) {
+ $list = array();
+ if (!is_array($listDAO)) {
+ $listDAO = array($listDAO);
+ }
+ foreach ($listDAO as $key => $dao) {
+ $tag = new FreshRSS_Tag(
+ $dao['name']
+ );
+ $tag->_id($dao['id']);
+ if (!empty($dao['attributes'])) {
+ $tag->_attributes('', $dao['attributes']);
+ }
+ if (isset($dao['unreads'])) {
+ $tag->_nbUnread($dao['unreads']);
+ }
+ $list[$key] = $tag;
+ }
+ return $list;
+ }
+}
diff --git a/app/Models/TagDAOPGSQL.php b/app/Models/TagDAOPGSQL.php
new file mode 100644
index 000000000..56a28e294
--- /dev/null
+++ b/app/Models/TagDAOPGSQL.php
@@ -0,0 +1,9 @@
+<?php
+
+class FreshRSS_TagDAOPGSQL extends FreshRSS_TagDAO {
+
+ public function sqlIgnore() {
+ return ''; //TODO
+ }
+
+}
diff --git a/app/Models/TagDAOSQLite.php b/app/Models/TagDAOSQLite.php
new file mode 100644
index 000000000..b1deb6c65
--- /dev/null
+++ b/app/Models/TagDAOSQLite.php
@@ -0,0 +1,19 @@
+<?php
+
+class FreshRSS_TagDAOSQLite extends FreshRSS_TagDAO {
+
+ public function sqlIgnore() {
+ return 'OR IGNORE';
+ }
+
+ protected function autoUpdateDb($errorInfo) {
+ if ($tableInfo = $this->bd->query("SELECT sql FROM sqlite_master where name='tag'")) {
+ $showCreate = $tableInfo->fetchColumn();
+ if (stripos($showCreate, 'tag') === false) {
+ return $this->createTagTable(); //v1.12.0
+ }
+ }
+ return false;
+ }
+
+}
diff --git a/app/Models/Themes.php b/app/Models/Themes.php
index 8920fbf7e..235269e39 100644
--- a/app/Models/Themes.php
+++ b/app/Models/Themes.php
@@ -68,7 +68,7 @@ class FreshRSS_Themes extends Minz_Model {
return $infos;
}
- public static function icon($name, $urlOnly = false) {
+ public static function alt($name) {
static $alts = array(
'add' => '✚',
'all' => '☰',
@@ -84,6 +84,7 @@ class FreshRSS_Themes extends Minz_Model {
'icon' => '⊚',
'import' => '⤓',
'key' => '⚿',
+ 'label' => '🏷️',
'link' => '↗',
'login' => '🔒',
'logout' => '🔓',
@@ -104,13 +105,18 @@ class FreshRSS_Themes extends Minz_Model {
'view-global' => '☷',
'view-reader' => '☕',
);
- if (!isset($alts[$name])) {
+ return isset($name) ? $alts[$name] : '';
+ }
+
+ public static function icon($name, $urlOnly = false, $altOnly = false) {
+ $alt = self::alt($name);
+ if ($alt == '') {
return '';
}
$url = $name . '.svg';
$url = isset(self::$themeIcons[$url]) ? (self::$themeIconsUrl . $url) : (self::$defaultIconsUrl . $url);
- return $urlOnly ? Minz_Url::display($url) : '<img class="icon" src="' . Minz_Url::display($url) . '" alt="' . $alts[$name] . '" />';
+ return $urlOnly ? Minz_Url::display($url) : '<img class="icon" src="' . Minz_Url::display($url) . '" alt="' . $alt . '" />';
}
}
diff --git a/app/Models/UserDAO.php b/app/Models/UserDAO.php
index c921d54c9..5fb46c947 100644
--- a/app/Models/UserDAO.php
+++ b/app/Models/UserDAO.php
@@ -14,14 +14,13 @@ class FreshRSS_UserDAO extends Minz_ModelPdo {
$ok = false;
$bd_prefix_user = $db['prefix'] . $username . '_';
if (defined('SQL_CREATE_TABLES')) { //E.g. MySQL
- $sql = sprintf(SQL_CREATE_TABLES . SQL_CREATE_TABLE_ENTRYTMP, $bd_prefix_user, _t('gen.short.default_category'));
+ $sql = sprintf(SQL_CREATE_TABLES . SQL_CREATE_TABLE_ENTRYTMP . SQL_CREATE_TABLE_TAGS, $bd_prefix_user, _t('gen.short.default_category'));
$stm = $userPDO->bd->prepare($sql);
$ok = $stm && $stm->execute();
} else { //E.g. SQLite
- global $SQL_CREATE_TABLES;
- global $SQL_CREATE_TABLE_ENTRYTMP;
+ global $SQL_CREATE_TABLES, $SQL_CREATE_TABLE_ENTRYTMP, $SQL_CREATE_TABLE_TAGS;
if (is_array($SQL_CREATE_TABLES)) {
- $instructions = array_merge($SQL_CREATE_TABLES, $SQL_CREATE_TABLE_ENTRYTMP);
+ $instructions = array_merge($SQL_CREATE_TABLES, $SQL_CREATE_TABLE_ENTRYTMP, $SQL_CREATE_TABLE_TAGS);
$ok = !empty($instructions);
foreach ($instructions as $instruction) {
$sql = sprintf($instruction, $bd_prefix_user, _t('gen.short.default_category'));
diff --git a/app/Models/UserQuery.php b/app/Models/UserQuery.php
index ef94fdaf6..f607084f8 100644
--- a/app/Models/UserQuery.php
+++ b/app/Models/UserQuery.php
@@ -19,15 +19,17 @@ class FreshRSS_UserQuery {
private $url;
private $feed_dao;
private $category_dao;
+ private $tag_dao;
/**
* @param array $query
* @param FreshRSS_Searchable $feed_dao
* @param FreshRSS_Searchable $category_dao
*/
- public function __construct($query, FreshRSS_Searchable $feed_dao = null, FreshRSS_Searchable $category_dao = null) {
+ public function __construct($query, FreshRSS_Searchable $feed_dao = null, FreshRSS_Searchable $category_dao = null, FreshRSS_Searchable $tag_dao = null) {
$this->category_dao = $category_dao;
$this->feed_dao = $feed_dao;
+ $this->tag_dao = $tag_dao;
if (isset($query['get'])) {
$this->parseGet($query['get']);
}
@@ -88,6 +90,9 @@ class FreshRSS_UserQuery {
case 's':
$this->parseFavorite();
break;
+ case 't':
+ $this->parseTag($matches['id']);
+ break;
}
}
}
@@ -139,6 +144,25 @@ class FreshRSS_UserQuery {
}
/**
+ * Parse the query string when it is a "tag" query
+ *
+ * @param integer $id
+ * @throws FreshRSS_DAO_Exception
+ */
+ private function parseTag($id) {
+ if ($this->tag_dao == null) {
+ throw new FreshRSS_DAO_Exception('Tag DAO is not loaded in UserQuery');
+ }
+ $category = $this->category_dao->searchById($id);
+ if ($tag) {
+ $this->get_name = $tag->name();
+ } else {
+ $this->deprecated = true;
+ }
+ $this->get_type = 'tag';
+ }
+
+ /**
* Parse the query string when it is a "favorite" query
*/
private function parseFavorite() {
diff --git a/app/SQL/install.sql.mysql.php b/app/SQL/install.sql.mysql.php
index 747a0a6b3..b3353ac95 100644
--- a/app/SQL/install.sql.mysql.php
+++ b/app/SQL/install.sql.mysql.php
@@ -1,10 +1,10 @@
<?php
-define('SQL_CREATE_DB', 'CREATE DATABASE IF NOT EXISTS %1$s DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;');
+define('SQL_CREATE_DB', 'CREATE DATABASE IF NOT EXISTS `%1$s` DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;');
define('SQL_CREATE_TABLES', '
CREATE TABLE IF NOT EXISTS `%1$scategory` (
`id` SMALLINT NOT NULL AUTO_INCREMENT, -- v0.7
- `name` varchar(191) NOT NULL,
+ `name` VARCHAR(' . FreshRSS_DatabaseDAO::LENGTH_INDEX_UNICODE . ') NOT NULL, -- Max index length for Unicode is 191 characters (767 bytes)
PRIMARY KEY (`id`),
UNIQUE KEY (`name`) -- v0.7
) DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci
@@ -12,21 +12,21 @@ ENGINE = INNODB;
CREATE TABLE IF NOT EXISTS `%1$sfeed` (
`id` SMALLINT NOT NULL AUTO_INCREMENT, -- v0.7
- `url` varchar(511) CHARACTER SET latin1 NOT NULL,
+ `url` VARCHAR(511) CHARACTER SET latin1 COLLATE latin1_bin NOT NULL,
`category` SMALLINT DEFAULT 0, -- v0.7
- `name` varchar(191) NOT NULL,
- `website` varchar(255) CHARACTER SET latin1,
- `description` text,
- `lastUpdate` int(11) DEFAULT 0, -- Until year 2038
- `priority` tinyint(2) NOT NULL DEFAULT 10,
- `pathEntries` varchar(511) DEFAULT NULL,
- `httpAuth` varchar(511) DEFAULT NULL,
- `error` boolean DEFAULT 0,
+ `name` VARCHAR(' . FreshRSS_DatabaseDAO::LENGTH_INDEX_UNICODE . ') NOT NULL,
+ `website` VARCHAR(255) CHARACTER SET latin1 COLLATE latin1_bin,
+ `description` TEXT,
+ `lastUpdate` INT(11) DEFAULT 0, -- Until year 2038
+ `priority` TINYINT(2) NOT NULL DEFAULT 10,
+ `pathEntries` VARCHAR(511) DEFAULT NULL,
+ `httpAuth` VARCHAR(511) DEFAULT NULL,
+ `error` BOOLEAN DEFAULT 0,
`keep_history` MEDIUMINT NOT NULL DEFAULT -2, -- v0.7
`ttl` INT NOT NULL DEFAULT 0, -- v0.7.3
`attributes` TEXT, -- v1.11.0
- `cache_nbEntries` int DEFAULT 0, -- v0.7
- `cache_nbUnreads` int DEFAULT 0, -- v0.7
+ `cache_nbEntries` INT DEFAULT 0, -- v0.7
+ `cache_nbUnreads` INT DEFAULT 0, -- v0.7
PRIMARY KEY (`id`),
FOREIGN KEY (`category`) REFERENCES `%1$scategory`(`id`) ON DELETE SET NULL ON UPDATE CASCADE,
UNIQUE KEY (`url`), -- v0.7
@@ -37,19 +37,19 @@ CREATE TABLE IF NOT EXISTS `%1$sfeed` (
ENGINE = INNODB;
CREATE TABLE IF NOT EXISTS `%1$sentry` (
- `id` bigint NOT NULL, -- v0.7
- `guid` varchar(760) CHARACTER SET latin1 NOT NULL, -- Maximum for UNIQUE is 767B
- `title` varchar(255) NOT NULL,
- `author` varchar(255),
- `content_bin` blob, -- v0.7
- `link` varchar(1023) CHARACTER SET latin1 NOT NULL,
- `date` int(11), -- Until year 2038
+ `id` BIGINT NOT NULL, -- v0.7
+ `guid` VARCHAR(760) CHARACTER SET latin1 COLLATE latin1_bin NOT NULL, -- Maximum for UNIQUE is 767B
+ `title` VARCHAR(255) NOT NULL,
+ `author` VARCHAR(255),
+ `content_bin` BLOB, -- v0.7
+ `link` VARCHAR(1023) CHARACTER SET latin1 COLLATE latin1_bin NOT NULL,
+ `date` INT(11), -- Until year 2038
`lastSeen` INT(11) DEFAULT 0, -- v1.1.1, Until year 2038
`hash` BINARY(16), -- v1.1.1
- `is_read` boolean NOT NULL DEFAULT 0,
- `is_favorite` boolean NOT NULL DEFAULT 0,
+ `is_read` BOOLEAN NOT NULL DEFAULT 0,
+ `is_favorite` BOOLEAN NOT NULL DEFAULT 0,
`id_feed` SMALLINT, -- v0.7
- `tags` varchar(1023),
+ `tags` VARCHAR(1023),
PRIMARY KEY (`id`),
FOREIGN KEY (`id_feed`) REFERENCES `%1$sfeed`(`id`) ON DELETE CASCADE ON UPDATE CASCADE,
UNIQUE KEY (`id_feed`,`guid`), -- v0.7
@@ -65,19 +65,19 @@ INSERT IGNORE INTO `%1$scategory` (id, name) VALUES(1, "%2$s");
define('SQL_CREATE_TABLE_ENTRYTMP', '
CREATE TABLE IF NOT EXISTS `%1$sentrytmp` ( -- v1.7
- `id` bigint NOT NULL,
- `guid` varchar(760) CHARACTER SET latin1 NOT NULL,
- `title` varchar(255) NOT NULL,
- `author` varchar(255),
- `content_bin` blob,
- `link` varchar(1023) CHARACTER SET latin1 NOT NULL,
- `date` int(11),
+ `id` BIGINT NOT NULL,
+ `guid` VARCHAR(760) CHARACTER SET latin1 COLLATE latin1_bin NOT NULL,
+ `title` VARCHAR(255) NOT NULL,
+ `author` VARCHAR(255),
+ `content_bin` BLOB,
+ `link` VARCHAR(1023) CHARACTER SET latin1 COLLATE latin1_bin NOT NULL,
+ `date` INT(11),
`lastSeen` INT(11) DEFAULT 0,
`hash` BINARY(16),
- `is_read` boolean NOT NULL DEFAULT 0,
- `is_favorite` boolean NOT NULL DEFAULT 0,
+ `is_read` BOOLEAN NOT NULL DEFAULT 0,
+ `is_favorite` BOOLEAN NOT NULL DEFAULT 0,
`id_feed` SMALLINT,
- `tags` varchar(1023),
+ `tags` VARCHAR(1023),
PRIMARY KEY (`id`),
FOREIGN KEY (`id_feed`) REFERENCES `%1$sfeed`(`id`) ON DELETE CASCADE ON UPDATE CASCADE,
UNIQUE KEY (`id_feed`,`guid`),
@@ -88,25 +88,46 @@ ENGINE = INNODB;
CREATE INDEX `entry_feed_read_index` ON `%1$sentry`(`id_feed`,`is_read`); -- v1.7 Located here to be auto-added
');
+define('SQL_CREATE_TABLE_TAGS', '
+CREATE TABLE IF NOT EXISTS `%1$stag` ( -- v1.12
+ `id` SMALLINT NOT NULL AUTO_INCREMENT,
+ `name` VARCHAR(63) NOT NULL,
+ `attributes` TEXT,
+ PRIMARY KEY (`id`),
+ UNIQUE KEY (`name`)
+) DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci
+ENGINE = INNODB;
+
+CREATE TABLE IF NOT EXISTS `%1$sentrytag` ( -- v1.12
+ `id_tag` SMALLINT,
+ `id_entry` BIGINT,
+ PRIMARY KEY (`id_tag`,`id_entry`),
+ FOREIGN KEY (`id_tag`) REFERENCES `%1$stag`(`id`) ON DELETE CASCADE ON UPDATE CASCADE,
+ FOREIGN KEY (`id_entry`) REFERENCES `%1$sentry`(`id`) ON DELETE CASCADE ON UPDATE CASCADE,
+ INDEX (`id_entry`)
+) DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci
+ENGINE = INNODB;
+');
+
define('SQL_INSERT_FEEDS', '
-INSERT IGNORE INTO `%1$sfeed` (url, category, name, website, description, ttl) VALUES("http://freshrss.org/feeds/all.atom.xml", 1, "FreshRSS.org", "http://freshrss.org/", "FreshRSS, a free, self-hostable aggregator…", 86400);
+INSERT IGNORE INTO `%1$sfeed` (url, category, name, website, description, ttl) VALUES("https://freshrss.org/feeds/all.atom.xml", 1, "FreshRSS.org", "https://freshrss.org/", "FreshRSS, a free, self-hostable aggregator…", 86400);
INSERT IGNORE INTO `%1$sfeed` (url, category, name, website, description, ttl) VALUES("https://github.com/FreshRSS/FreshRSS/releases.atom", 1, "FreshRSS @ GitHub", "https://github.com/FreshRSS/FreshRSS/", "FreshRSS releases @ GitHub", 86400);
');
-define('SQL_DROP_TABLES', 'DROP TABLE IF EXISTS `%1$sentrytmp`, `%1$sentry`, `%1$sfeed`, `%1$scategory`');
+define('SQL_DROP_TABLES', 'DROP TABLE IF EXISTS `%1$sentrytag`, `%1$stag`, `%1$sentrytmp`, `%1$sentry`, `%1$sfeed`, `%1$scategory`');
define('SQL_UPDATE_UTF8MB4', '
-ALTER DATABASE `%2$s` CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
+ALTER DATABASE `%2$s` CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci; -- v1.5.0
ALTER TABLE `%1$scategory` CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
-UPDATE `%1$scategory` SET name=SUBSTRING(name,1,190) WHERE LENGTH(name) > 191;
-ALTER TABLE `%1$scategory` MODIFY `name` VARCHAR(191) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL;
+UPDATE `%1$scategory` SET name=SUBSTRING(name,1,' . FreshRSS_DatabaseDAO::LENGTH_INDEX_UNICODE . ') WHERE LENGTH(name) > ' . FreshRSS_DatabaseDAO::LENGTH_INDEX_UNICODE . ';
+ALTER TABLE `%1$scategory` MODIFY `name` VARCHAR(' . FreshRSS_DatabaseDAO::LENGTH_INDEX_UNICODE . ') CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL;
OPTIMIZE TABLE `%1$scategory`;
ALTER TABLE `%1$sfeed` CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
-UPDATE `%1$sfeed` SET name=SUBSTRING(name,1,190) WHERE LENGTH(name) > 191;
-ALTER TABLE `%1$sfeed` MODIFY `name` VARCHAR(191) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL;
-ALTER TABLE `%1$sfeed` MODIFY `description` text CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
+UPDATE `%1$sfeed` SET name=SUBSTRING(name,1,' . FreshRSS_DatabaseDAO::LENGTH_INDEX_UNICODE . ') WHERE LENGTH(name) > ' . FreshRSS_DatabaseDAO::LENGTH_INDEX_UNICODE . ';
+ALTER TABLE `%1$sfeed` MODIFY `name` VARCHAR(' . FreshRSS_DatabaseDAO::LENGTH_INDEX_UNICODE . ') CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL;
+ALTER TABLE `%1$sfeed` MODIFY `description` TEXT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
OPTIMIZE TABLE `%1$sfeed`;
ALTER TABLE `%1$sentry` CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
@@ -115,3 +136,8 @@ ALTER TABLE `%1$sentry` MODIFY `author` VARCHAR(255) CHARACTER SET utf8mb4 COLLA
ALTER TABLE `%1$sentry` MODIFY `tags` VARCHAR(1023) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
OPTIMIZE TABLE `%1$sentry`;
');
+
+define('SQL_UPDATE_GUID_LATIN1_BIN', ' -- v1.12
+ALTER TABLE `%1$sentrytmp` MODIFY `guid` VARCHAR(760) CHARACTER SET latin1 COLLATE latin1_bin NOT NULL;
+ALTER TABLE `%1$sentry` MODIFY `guid` VARCHAR(760) CHARACTER SET latin1 COLLATE latin1_bin NOT NULL;
+');
diff --git a/app/SQL/install.sql.pgsql.php b/app/SQL/install.sql.pgsql.php
index b80fbf1e7..e68e6f3be 100644
--- a/app/SQL/install.sql.pgsql.php
+++ b/app/SQL/install.sql.pgsql.php
@@ -1,5 +1,5 @@
<?php
-define('SQL_CREATE_DB', 'CREATE DATABASE %1$s ENCODING \'UTF8\';');
+define('SQL_CREATE_DB', 'CREATE DATABASE "%1$s" ENCODING \'UTF8\';');
global $SQL_CREATE_TABLES;
$SQL_CREATE_TABLES = array(
@@ -10,16 +10,16 @@ $SQL_CREATE_TABLES = array(
'CREATE TABLE IF NOT EXISTS "%1$sfeed" (
"id" SERIAL PRIMARY KEY,
- "url" varchar(511) UNIQUE NOT NULL,
+ "url" VARCHAR(511) UNIQUE NOT NULL,
"category" SMALLINT DEFAULT 0,
"name" VARCHAR(255) NOT NULL,
"website" VARCHAR(255),
- "description" text,
+ "description" TEXT,
"lastUpdate" INT DEFAULT 0,
"priority" SMALLINT NOT NULL DEFAULT 10,
"pathEntries" VARCHAR(511) DEFAULT NULL,
"httpAuth" VARCHAR(511) DEFAULT NULL,
- "error" smallint DEFAULT 0,
+ "error" SMALLINT DEFAULT 0,
"keep_history" INT NOT NULL DEFAULT -2,
"ttl" INT NOT NULL DEFAULT 0,
"attributes" TEXT, -- v1.11.0
@@ -27,9 +27,9 @@ $SQL_CREATE_TABLES = array(
"cache_nbUnreads" INT DEFAULT 0,
FOREIGN KEY ("category") REFERENCES "%1$scategory" ("id") ON DELETE SET NULL ON UPDATE CASCADE
);',
-'CREATE INDEX %1$sname_index ON "%1$sfeed" ("name");',
-'CREATE INDEX %1$spriority_index ON "%1$sfeed" ("priority");',
-'CREATE INDEX %1$skeep_history_index ON "%1$sfeed" ("keep_history");',
+'CREATE INDEX "%1$sname_index" ON "%1$sfeed" ("name");',
+'CREATE INDEX "%1$spriority_index" ON "%1$sfeed" ("priority");',
+'CREATE INDEX "%1$skeep_history_index" ON "%1$sfeed" ("keep_history");',
'CREATE TABLE IF NOT EXISTS "%1$sentry" (
"id" BIGINT NOT NULL PRIMARY KEY,
@@ -48,11 +48,14 @@ $SQL_CREATE_TABLES = array(
FOREIGN KEY ("id_feed") REFERENCES "%1$sfeed" ("id") ON DELETE CASCADE ON UPDATE CASCADE,
UNIQUE ("id_feed","guid")
);',
-'CREATE INDEX %1$sis_favorite_index ON "%1$sentry" ("is_favorite");',
-'CREATE INDEX %1$sis_read_index ON "%1$sentry" ("is_read");',
-'CREATE INDEX %1$sentry_lastSeen_index ON "%1$sentry" ("lastSeen");',
+'CREATE INDEX "%1$sis_favorite_index" ON "%1$sentry" ("is_favorite");',
+'CREATE INDEX "%1$sis_read_index" ON "%1$sentry" ("is_read");',
+'CREATE INDEX "%1$sentry_lastSeen_index" ON "%1$sentry" ("lastSeen");',
-'INSERT INTO "%1$scategory" (id, name) SELECT 1, \'%2$s\' WHERE NOT EXISTS (SELECT id FROM "%1$scategory" WHERE id = 1) RETURNING nextval(\'%1$scategory_id_seq\');',
+'INSERT INTO "%1$scategory" (id, name)
+ SELECT 1, \'%2$s\'
+ WHERE NOT EXISTS (SELECT id FROM "%1$scategory" WHERE id = 1)
+ RETURNING nextval(\'"%1$scategory_id_seq"\');',
);
global $SQL_CREATE_TABLE_ENTRYTMP;
@@ -74,15 +77,36 @@ $SQL_CREATE_TABLE_ENTRYTMP = array(
FOREIGN KEY ("id_feed") REFERENCES "%1$sfeed" ("id") ON DELETE CASCADE ON UPDATE CASCADE,
UNIQUE ("id_feed","guid")
);',
-'CREATE INDEX %1$sentrytmp_date_index ON "%1$sentrytmp" ("date");',
+'CREATE INDEX "%1$sentrytmp_date_index" ON "%1$sentrytmp" ("date");',
-'CREATE INDEX %1$sentry_feed_read_index ON "%1$sentry" ("id_feed","is_read");', //v1.7
+'CREATE INDEX "%1$sentry_feed_read_index" ON "%1$sentry" ("id_feed","is_read");', //v1.7
+);
+
+global $SQL_CREATE_TABLE_TAGS;
+$SQL_CREATE_TABLE_TAGS = array(
+'CREATE TABLE IF NOT EXISTS "%1$stag" ( -- v1.12
+ "id" SERIAL PRIMARY KEY,
+ "name" VARCHAR(63) UNIQUE NOT NULL,
+ "attributes" TEXT
+);',
+'CREATE TABLE IF NOT EXISTS "%1$sentrytag" (
+ "id_tag" SMALLINT,
+ "id_entry" BIGINT,
+ PRIMARY KEY ("id_tag","id_entry"),
+ FOREIGN KEY ("id_tag") REFERENCES "%1$stag" ("id") ON DELETE CASCADE ON UPDATE CASCADE,
+ FOREIGN KEY ("id_entry") REFERENCES "%1$sentry" ("id") ON DELETE CASCADE ON UPDATE CASCADE
+);',
+'CREATE INDEX "%1$sentrytag_id_entry_index" ON "%1$sentrytag" ("id_entry");',
);
global $SQL_INSERT_FEEDS;
$SQL_INSERT_FEEDS = array(
-'INSERT INTO "%1$sfeed" (url, category, name, website, description, ttl) SELECT \'http://freshrss.org/feeds/all.atom.xml\', 1, \'FreshRSS.org\', \'http://freshrss.org/\', \'FreshRSS, a free, self-hostable aggregator…\', 86400 WHERE NOT EXISTS (SELECT id FROM "%1$sfeed" WHERE url = \'http://freshrss.org/feeds/all.atom.xml\');',
-'INSERT INTO "%1$sfeed" (url, category, name, website, description, ttl) SELECT \'https://github.com/FreshRSS/FreshRSS/releases.atom\', 1, \'FreshRSS @ GitHub\', \'https://github.com/FreshRSS/FreshRSS/\', \'FreshRSS releases @ GitHub\', 86400 WHERE NOT EXISTS (SELECT id FROM "%1$sfeed" WHERE url = \'https://github.com/FreshRSS/FreshRSS/releases.atom\');',
+'INSERT INTO "%1$sfeed" (url, category, name, website, description, ttl)
+ SELECT \'https://freshrss.org/feeds/all.atom.xml\', 1, \'FreshRSS.org\', \'https://freshrss.org/\', \'FreshRSS, a free, self-hostable aggregator…\', 86400
+ WHERE NOT EXISTS (SELECT id FROM "%1$sfeed" WHERE url = \'https://freshrss.org/feeds/all.atom.xml\');',
+'INSERT INTO "%1$sfeed" (url, category, name, website, description, ttl)
+ SELECT \'https://github.com/FreshRSS/FreshRSS/releases.atom\', 1, \'FreshRSS @ GitHub\', \'https://github.com/FreshRSS/FreshRSS/\', \'FreshRSS releases @ GitHub\', 86400
+ WHERE NOT EXISTS (SELECT id FROM "%1$sfeed" WHERE url = \'https://github.com/FreshRSS/FreshRSS/releases.atom\');',
);
-define('SQL_DROP_TABLES', 'DROP TABLE IF EXISTS "%1$sentrytmp", "%1$sentry", "%1$sfeed", "%1$scategory"');
+define('SQL_DROP_TABLES', 'DROP TABLE IF EXISTS "%1$sentrytag", "%1$stag", "%1$sentrytmp", "%1$sentry", "%1$sfeed", "%1$scategory"');
diff --git a/app/SQL/install.sql.sqlite.php b/app/SQL/install.sql.sqlite.php
index cbfb719e5..1dd5f2647 100644
--- a/app/SQL/install.sql.sqlite.php
+++ b/app/SQL/install.sql.sqlite.php
@@ -3,27 +3,27 @@ global $SQL_CREATE_TABLES;
$SQL_CREATE_TABLES = array(
'CREATE TABLE IF NOT EXISTS `category` (
`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL,
- `name` varchar(255) NOT NULL,
+ `name` VARCHAR(255) NOT NULL,
UNIQUE (`name`)
);',
'CREATE TABLE IF NOT EXISTS `feed` (
`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL,
- `url` varchar(511) NOT NULL,
+ `url` VARCHAR(511) NOT NULL,
`category` SMALLINT DEFAULT 0,
- `name` varchar(255) NOT NULL,
- `website` varchar(255),
- `description` text,
- `lastUpdate` int(11) DEFAULT 0, -- Until year 2038
- `priority` tinyint(2) NOT NULL DEFAULT 10,
- `pathEntries` varchar(511) DEFAULT NULL,
- `httpAuth` varchar(511) DEFAULT NULL,
- `error` boolean DEFAULT 0,
+ `name` VARCHAR(255) NOT NULL,
+ `website` VARCHAR(255),
+ `description` TEXT,
+ `lastUpdate` INT(11) DEFAULT 0, -- Until year 2038
+ `priority` TINYINT(2) NOT NULL DEFAULT 10,
+ `pathEntries` VARCHAR(511) DEFAULT NULL,
+ `httpAuth` VARCHAR(511) DEFAULT NULL,
+ `error` BOOLEAN DEFAULT 0,
`keep_history` MEDIUMINT NOT NULL DEFAULT -2,
`ttl` INT NOT NULL DEFAULT 0,
`attributes` TEXT, -- v1.11.0
- `cache_nbEntries` int DEFAULT 0,
- `cache_nbUnreads` int DEFAULT 0,
+ `cache_nbEntries` INT DEFAULT 0,
+ `cache_nbUnreads` INT DEFAULT 0,
FOREIGN KEY (`category`) REFERENCES `category`(`id`) ON DELETE SET NULL ON UPDATE CASCADE,
UNIQUE (`url`)
);',
@@ -32,19 +32,19 @@ $SQL_CREATE_TABLES = array(
'CREATE INDEX IF NOT EXISTS feed_keep_history_index ON `feed`(`keep_history`);',
'CREATE TABLE IF NOT EXISTS `entry` (
- `id` bigint NOT NULL,
- `guid` varchar(760) NOT NULL,
- `title` varchar(255) NOT NULL,
- `author` varchar(255),
- `content` text,
- `link` varchar(1023) NOT NULL,
- `date` int(11), -- Until year 2038
+ `id` BIGINT NOT NULL,
+ `guid` VARCHAR(760) NOT NULL,
+ `title` VARCHAR(255) NOT NULL,
+ `author` VARCHAR(255),
+ `content` TEXT,
+ `link` VARCHAR(1023) NOT NULL,
+ `date` INT(11), -- Until year 2038
`lastSeen` INT(11) DEFAULT 0, -- v1.1.1, Until year 2038
`hash` BINARY(16), -- v1.1.1
- `is_read` boolean NOT NULL DEFAULT 0,
- `is_favorite` boolean NOT NULL DEFAULT 0,
+ `is_read` BOOLEAN NOT NULL DEFAULT 0,
+ `is_favorite` BOOLEAN NOT NULL DEFAULT 0,
`id_feed` SMALLINT,
- `tags` varchar(1023),
+ `tags` VARCHAR(1023),
PRIMARY KEY (`id`),
FOREIGN KEY (`id_feed`) REFERENCES `feed`(`id`) ON DELETE CASCADE ON UPDATE CASCADE,
UNIQUE (`id_feed`,`guid`)
@@ -59,19 +59,19 @@ $SQL_CREATE_TABLES = array(
global $SQL_CREATE_TABLE_ENTRYTMP;
$SQL_CREATE_TABLE_ENTRYTMP = array(
'CREATE TABLE IF NOT EXISTS `entrytmp` ( -- v1.7
- `id` bigint NOT NULL,
- `guid` varchar(760) NOT NULL,
- `title` varchar(255) NOT NULL,
- `author` varchar(255),
- `content` text,
- `link` varchar(1023) NOT NULL,
- `date` int(11),
+ `id` BIGINT NOT NULL,
+ `guid` VARCHAR(760) NOT NULL,
+ `title` VARCHAR(255) NOT NULL,
+ `author` VARCHAR(255),
+ `content` TEXT,
+ `link` VARCHAR(1023) NOT NULL,
+ `date` INT(11),
`lastSeen` INT(11) DEFAULT 0,
`hash` BINARY(16),
- `is_read` boolean NOT NULL DEFAULT 0,
- `is_favorite` boolean NOT NULL DEFAULT 0,
+ `is_read` BOOLEAN NOT NULL DEFAULT 0,
+ `is_favorite` BOOLEAN NOT NULL DEFAULT 0,
`id_feed` SMALLINT,
- `tags` varchar(1023),
+ `tags` VARCHAR(1023),
PRIMARY KEY (`id`),
FOREIGN KEY (`id_feed`) REFERENCES `feed`(`id`) ON DELETE CASCADE ON UPDATE CASCADE,
UNIQUE (`id_feed`,`guid`)
@@ -81,44 +81,30 @@ $SQL_CREATE_TABLE_ENTRYTMP = array(
'CREATE INDEX IF NOT EXISTS `entry_feed_read_index` ON `entry`(`id_feed`,`is_read`);', //v1.7
);
+global $SQL_CREATE_TABLE_TAGS;
+$SQL_CREATE_TABLE_TAGS = array(
+'CREATE TABLE IF NOT EXISTS `tag` ( -- v1.12
+ `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL,
+ `name` VARCHAR(63) NOT NULL,
+ `attributes` TEXT,
+ UNIQUE (`name`)
+);',
+'CREATE TABLE IF NOT EXISTS `entrytag` (
+ `id_tag` SMALLINT,
+ `id_entry` SMALLINT,
+ PRIMARY KEY (`id_tag`,`id_entry`),
+ FOREIGN KEY (`id_tag`) REFERENCES `tag` (`id`) ON DELETE CASCADE ON UPDATE CASCADE,
+ FOREIGN KEY (`id_entry`) REFERENCES `entry` (`id`) ON DELETE CASCADE ON UPDATE CASCADE
+);',
+'CREATE INDEX entrytag_id_entry_index ON `entrytag` (`id_entry`);',
+);
+
global $SQL_INSERT_FEEDS;
$SQL_INSERT_FEEDS = array(
-'INSERT OR IGNORE INTO `feed`
- (
- url,
- category,
- name,
- website,
- description,
- ttl
- )
- VALUES
- (
- "http://freshrss.org/feeds/all.atom.xml",
- 1,
- "FreshRSS.org",
- "http://freshrss.org/",
- "FreshRSS, a free, self-hostable aggregator…",
- 86400
- );',
-'INSERT OR IGNORE INTO `feed`
- (
- url,
- category,
- name,
- website,
- description,
- ttl
- )
- VALUES
- (
- "https://github.com/FreshRSS/FreshRSS/releases.atom",
- 1,
- "FreshRSS releases",
- "https://github.com/FreshRSS/FreshRSS/",
- "FreshRSS releases @ GitHub",
- 86400
- );',
+'INSERT OR IGNORE INTO `feed` (url, category, name, website, description, ttl)
+ VALUES ("https://freshrss.org/feeds/all.atom.xml", 1, "FreshRSS.org", "https://freshrss.org/", "FreshRSS, a free, self-hostable aggregator…", 86400);',
+'INSERT OR IGNORE INTO `feed` (url, category, name, website, description, ttl)
+ VALUES ("https://github.com/FreshRSS/FreshRSS/releases.atom", 1, "FreshRSS releases", "https://github.com/FreshRSS/FreshRSS/", "FreshRSS releases @ GitHub", 86400);',
);
-define('SQL_DROP_TABLES', 'DROP TABLE IF EXISTS `entrytmp`, `entry`, `feed`, `category`');
+define('SQL_DROP_TABLES', 'DROP TABLE IF EXISTS `entrytag`, `tag`, `entrytmp`, `entry`, `feed`, `category`');
diff --git a/app/i18n/cz/conf.php b/app/i18n/cz/conf.php
index e73ab168f..84ee78c73 100644
--- a/app/i18n/cz/conf.php
+++ b/app/i18n/cz/conf.php
@@ -19,7 +19,7 @@ return array(
'bottom_line' => 'Spodní řádek',
'entry' => 'Ikony článků',
'publication_date' => 'Datum vydání',
- 'related_tags' => 'Související tagy',
+ 'related_tags' => 'Související tagy', //TODO
'sharing' => 'Sdílení',
'top_line' => 'Horní řádek',
),
diff --git a/app/i18n/cz/gen.php b/app/i18n/cz/gen.php
index 66c011da3..b9a65f210 100644
--- a/app/i18n/cz/gen.php
+++ b/app/i18n/cz/gen.php
@@ -181,7 +181,7 @@ return array(
'short' => array(
'attention' => 'Upozornění!',
'blank_to_disable' => 'Zakázat - ponechte prázdné',
- 'by_author' => 'Od <em>%s</em>',
+ 'by_author' => 'Od:',
'by_default' => 'Výchozí',
'damn' => 'Sakra!',
'default_category' => 'Nezařazeno',
diff --git a/app/i18n/cz/index.php b/app/i18n/cz/index.php
index 48a28d4da..7e60ca379 100644
--- a/app/i18n/cz/index.php
+++ b/app/i18n/cz/index.php
@@ -7,7 +7,7 @@ return array(
'bugs_reports' => 'Hlášení chyb',
'credits' => 'Poděkování',
'credits_content' => 'Některé designové prvky pocházejí z <a href="http://twitter.github.io/bootstrap/">Bootstrap</a>, FreshRSS ale tuto platformu nevyužívá. <a href="https://git.gnome.org/browse/gnome-icon-theme-symbolic">Ikony</a> pocházejí z <a href="https://www.gnome.org/">GNOME projektu</a>. Font <em>Open Sans</em> vytvořil <a href="https://fonts.google.com/specimen/Open+Sans">Steve Matteson</a>. FreshRSS je založen na PHP framework <a href="https://github.com/marienfressinaud/MINZ">Minz</a>.',
- 'freshrss_description' => 'FreshRSS je čtečka RSS kanálů určená k provozu na vlastním serveru, podobná <a href="http://tontof.net/kriss/feed/">Kriss Feed</a> nebo <a href="http://projet.idleman.fr/leed/">Leed</a>. Je to nenáročný a jednoduchý, zároveň ale mocný a konfigurovatelný nástroj.',
+ 'freshrss_description' => 'FreshRSS je čtečka RSS kanálů určená k provozu na vlastním serveru, podobná <a href="http://tontof.net/kriss/feed/">Kriss Feed</a> nebo <a href="http://leed.idleman.fr/">Leed</a>. Je to nenáročný a jednoduchý, zároveň ale mocný a konfigurovatelný nástroj.',
'github' => '<a href="https://github.com/FreshRSS/FreshRSS/issues">na Github</a>',
'license' => 'Licence',
'project_website' => 'Stránka projektu',
@@ -53,10 +53,11 @@ return array(
'starred' => 'Zobrazit oblíbené',
'stats' => 'Statistika',
'subscription' => 'Správa subskripcí',
+ 'tags' => 'My labels', //TODO
'unread' => 'Zobrazovat nepřečtené',
),
'share' => 'Sdílet',
'tag' => array(
- 'related' => 'Související tagy',
+ 'related' => 'Související tagy', //TODO
),
);
diff --git a/app/i18n/cz/sub.php b/app/i18n/cz/sub.php
index 5caf9acbe..55441aaf8 100644
--- a/app/i18n/cz/sub.php
+++ b/app/i18n/cz/sub.php
@@ -27,6 +27,7 @@ return array(
'password' => 'Heslo',
'username' => 'Přihlašovací jméno',
),
+ 'clear_cache' => 'Always clear cache', //TODO
'css_help' => 'Stáhne zkrácenou verzi RSS kanálů (pozor, náročnější na čas!)',
'css_path' => 'Původní CSS soubor článku z webových stránek',
'description' => 'Popis',
diff --git a/app/i18n/de/admin.php b/app/i18n/de/admin.php
index fbeb80296..2eb4a69f6 100644
--- a/app/i18n/de/admin.php
+++ b/app/i18n/de/admin.php
@@ -67,8 +67,8 @@ return array(
'ok' => 'Sie haben die JSON-Erweiterung.',
),
'mbstring' => array(
- 'nok' => 'Cannot find the recommended library mbstring for Unicode.', //TODO
- 'ok' => 'You have the recommended library mbstring for Unicode.', //TODO
+ 'nok' => 'Ihnen fehlt die mbstring-Bibliothek für Unicode.', //TODO
+ 'ok' => 'Sie haben die empfohlene mbstring-Bliothek für Unicode.', //TODO
),
'minz' => array(
'nok' => 'Ihnen fehlt das Minz-Framework.',
diff --git a/app/i18n/de/conf.php b/app/i18n/de/conf.php
index 78f3b4510..579363cb5 100644
--- a/app/i18n/de/conf.php
+++ b/app/i18n/de/conf.php
@@ -19,7 +19,7 @@ return array(
'bottom_line' => 'Fußzeile',
'entry' => 'Artikel-Symbole',
'publication_date' => 'Datum der Veröffentlichung',
- 'related_tags' => 'Verwandte Tags',
+ 'related_tags' => 'Verwandte Tags', //TODO
'sharing' => 'Teilen',
'top_line' => 'Kopfzeile',
),
@@ -102,7 +102,7 @@ return array(
'read' => array(
'article_open_on_website' => 'wenn der Artikel auf der Original-Webseite geöffnet wird',
'article_viewed' => 'wenn der Artikel angesehen wird',
- 'scroll' => 'beim Blättern',
+ 'scroll' => 'beim Scrollen bzw. Überspringen',
'upon_reception' => 'beim Empfang des Artikels',
'when' => 'Artikel als gelesen markieren…',
),
diff --git a/app/i18n/de/feedback.php b/app/i18n/de/feedback.php
index c20f58487..dc4f679f9 100644
--- a/app/i18n/de/feedback.php
+++ b/app/i18n/de/feedback.php
@@ -53,8 +53,8 @@ return array(
'sub' => array(
'actualize' => 'Aktualisieren',
'articles' => array(
- 'marked_read' => 'The selected articles have been marked as read.', //TODO
- 'marked_unread' => 'The articles have been marked as unread.', //TODO
+ 'marked_read' => 'Die ausgewählten Artikel wurden als gelesen markiert.',
+ 'marked_unread' => 'Die ausgewählten Artikel wurden als ungelesen markiert.',
),
'category' => array(
'created' => 'Die Kategorie %s ist erstellt worden.',
diff --git a/app/i18n/de/gen.php b/app/i18n/de/gen.php
index eb1e74ed6..617b2a494 100644
--- a/app/i18n/de/gen.php
+++ b/app/i18n/de/gen.php
@@ -167,6 +167,7 @@ return array(
'g+' => 'Google+',
'gnusocial' => 'GNU social',
'jdh' => 'Journal du hacker',
+ 'Known' => 'Known-Seite (https://withknown.com)',
'linkedin' => 'LinkedIn',
'mastodon' => 'Mastodon',
'movim' => 'Movim',
@@ -180,7 +181,7 @@ return array(
'short' => array(
'attention' => 'Achtung!',
'blank_to_disable' => 'Zum Deaktivieren frei lassen',
- 'by_author' => 'Von <em>%s</em>',
+ 'by_author' => 'Von:',
'by_default' => 'standardmäßig',
'damn' => 'Verdammt!',
'default_category' => 'Unkategorisiert',
diff --git a/app/i18n/de/index.php b/app/i18n/de/index.php
index 1fa3e3933..2d0dcc2dd 100644
--- a/app/i18n/de/index.php
+++ b/app/i18n/de/index.php
@@ -7,7 +7,7 @@ return array(
'bugs_reports' => 'Fehlerberichte',
'credits' => 'Credits',
'credits_content' => 'Einige Designelemente stammen von <a href="http://twitter.github.io/bootstrap/">Bootstrap</a>, obwohl FreshRSS dieses Framework nicht nutzt. <a href="https://git.gnome.org/browse/gnome-icon-theme-symbolic">Icons</a> stammen vom <a href="https://www.gnome.org/">GNOME project</a>. <em>Open Sans</em> Font wurde von <a href="https://fonts.google.com/specimen/Open+Sans">Steve Matteson</a> erstellt. FreshRSS basiert auf <a href="https://github.com/marienfressinaud/MINZ">Minz</a>, einem PHP-Framework.',
- 'freshrss_description' => 'FreshRSS ist ein RSS-Feedsaggregator zum selbst hosten wie zum Beispiel <a href="http://tontof.net/kriss/feed/">Kriss Feed</a> oder <a href="http://projet.idleman.fr/leed/">Leed</a>. Er ist leicht und einfach zu handhaben und gleichzeitig ein leistungsstarkes und konfigurierbares Werkzeug.',
+ 'freshrss_description' => 'FreshRSS ist ein RSS-Feedsaggregator zum selbst hosten wie zum Beispiel <a href="http://tontof.net/kriss/feed/">Kriss Feed</a> oder <a href="http://leed.idleman.fr/">Leed</a>. Er ist leicht und einfach zu handhaben und gleichzeitig ein leistungsstarkes und konfigurierbares Werkzeug.',
'github' => '<a href="https://github.com/FreshRSS/FreshRSS/issues">on Github</a>',
'license' => 'Lizenz',
'project_website' => 'Projekt-Webseite',
@@ -40,7 +40,7 @@ return array(
'mark_all_read' => 'Alle als gelesen markieren',
'mark_cat_read' => 'Kategorie als gelesen markieren',
'mark_feed_read' => 'Feed als gelesen markieren',
- 'mark_selection_unread' => 'Mark selection as unread', //TODO
+ 'mark_selection_unread' => 'Auswahl als ungelesen markieren',
'newer_first' => 'Neuere zuerst',
'non-starred' => 'Alle außer Favoriten zeigen',
'normal_view' => 'Normale Ansicht',
@@ -53,10 +53,11 @@ return array(
'starred' => 'Nur Favoriten zeigen',
'stats' => 'Statistiken',
'subscription' => 'Abonnementverwaltung',
+ 'tags' => 'My labels', //TODO
'unread' => 'Nur ungelesene zeigen',
),
'share' => 'Teilen',
'tag' => array(
- 'related' => 'Verwandte Tags',
+ 'related' => 'Verwandte Tags', //TODO
),
);
diff --git a/app/i18n/de/install.php b/app/i18n/de/install.php
index d28b22840..d5a28f440 100644
--- a/app/i18n/de/install.php
+++ b/app/i18n/de/install.php
@@ -69,8 +69,8 @@ return array(
'ok' => 'Sie haben eine empfohlene Bibliothek um JSON zu parsen.',
),
'mbstring' => array(
- 'nok' => 'Cannot find the recommended library mbstring for Unicode.', //TODO
- 'ok' => 'You have the recommended library mbstring for Unicode.', //TODO
+ 'nok' => 'Es fehlt die empfohlene mbstring-Bibliothek für Unicode.',
+ 'ok' => 'Sie haben die empfohlene mbstring-Bibliothek für Unicode.',
),
'minz' => array(
'nok' => 'Ihnen fehlt das Minz-Framework.',
diff --git a/app/i18n/de/sub.php b/app/i18n/de/sub.php
index 0ba818c69..6a1100dba 100644
--- a/app/i18n/de/sub.php
+++ b/app/i18n/de/sub.php
@@ -27,6 +27,7 @@ return array(
'password' => 'HTTP-Passwort',
'username' => 'HTTP-Nutzername',
),
+ 'clear_cache' => 'Nicht cachen (für defekte Feeds)',
'css_help' => 'Ruft gekürzte RSS-Feeds ab (Achtung, benötigt mehr Zeit!)',
'css_path' => 'Pfad zur CSS-Datei des Artikels auf der Original-Webseite',
'description' => 'Beschreibung',
@@ -44,10 +45,10 @@ return array(
'main_stream' => 'In Haupt-Feeds zeigen',
'normal' => 'Zeige in eigener Kategorie',
),
- 'ssl_verify' => 'Verify SSL security', //TODO
+ 'ssl_verify' => 'Überprüfe SSL Sicherheit',
'stats' => 'Statistiken',
'think_to_add' => 'Sie können Feeds hinzufügen.',
- 'timeout' => 'Timeout in seconds', //TODO
+ 'timeout' => 'Zeitlimit in Sekunden',
'title' => 'Titel',
'title_add' => 'Einen RSS-Feed hinzufügen',
'ttl' => 'Aktualisiere automatisch nicht öfter als',
diff --git a/app/i18n/en/conf.php b/app/i18n/en/conf.php
index fd91ed8f6..5c128f8e7 100644
--- a/app/i18n/en/conf.php
+++ b/app/i18n/en/conf.php
@@ -19,7 +19,7 @@ return array(
'bottom_line' => 'Bottom line',
'entry' => 'Article icons',
'publication_date' => 'Date of publication',
- 'related_tags' => 'Related tags',
+ 'related_tags' => 'Article tags',
'sharing' => 'Sharing',
'top_line' => 'Top line',
),
diff --git a/app/i18n/en/gen.php b/app/i18n/en/gen.php
index 34e81af2e..9f7da55a5 100644
--- a/app/i18n/en/gen.php
+++ b/app/i18n/en/gen.php
@@ -181,7 +181,7 @@ return array(
'short' => array(
'attention' => 'Warning!',
'blank_to_disable' => 'Leave blank to disable',
- 'by_author' => 'By <em>%s</em>',
+ 'by_author' => 'By:',
'by_default' => 'By default',
'damn' => 'Blast!',
'default_category' => 'Uncategorized',
diff --git a/app/i18n/en/index.php b/app/i18n/en/index.php
index 1c13abdb7..427a769a0 100644
--- a/app/i18n/en/index.php
+++ b/app/i18n/en/index.php
@@ -7,7 +7,7 @@ return array(
'bugs_reports' => 'Bugs reports',
'credits' => 'Credits',
'credits_content' => 'Some design elements come from <a href="http://twitter.github.io/bootstrap/">Bootstrap</a> although FreshRSS doesn’t use this framework. <a href="https://git.gnome.org/browse/gnome-icon-theme-symbolic">Icons</a> come from <a href="https://www.gnome.org/">GNOME project</a>. <em>Open Sans</em> font police has been created by <a href="https://fonts.google.com/specimen/Open+Sans">Steve Matteson</a>. FreshRSS is based on <a href="https://github.com/marienfressinaud/MINZ">Minz</a>, a PHP framework.',
- 'freshrss_description' => 'FreshRSS is a RSS feeds aggregator to self-host like <a href="http://tontof.net/kriss/feed/">Kriss Feed</a> or <a href="http://projet.idleman.fr/leed/">Leed</a>. It is light and easy to take in hand while being powerful and configurable tool.',
+ 'freshrss_description' => 'FreshRSS is a RSS feeds aggregator to self-host like <a href="http://tontof.net/kriss/feed/">Kriss Feed</a> or <a href="http://leed.idleman.fr/">Leed</a>. It is light and easy to take in hand while being powerful and configurable tool.',
'github' => '<a href="https://github.com/FreshRSS/FreshRSS/issues">on Github</a>',
'license' => 'License',
'project_website' => 'Project website',
@@ -53,10 +53,11 @@ return array(
'starred' => 'Show favourites',
'stats' => 'Statistics',
'subscription' => 'Subscriptions management',
+ 'tags' => 'My labels',
'unread' => 'Show unread',
),
'share' => 'Share',
'tag' => array(
- 'related' => 'Related tags',
+ 'related' => 'Article tags', //TODO
),
);
diff --git a/app/i18n/en/sub.php b/app/i18n/en/sub.php
index 5ff41a4b3..22c7edc30 100644
--- a/app/i18n/en/sub.php
+++ b/app/i18n/en/sub.php
@@ -27,6 +27,7 @@ return array(
'password' => 'HTTP password',
'username' => 'HTTP username',
),
+ 'clear_cache' => 'Always clear cache',
'css_help' => 'Retrieves truncated RSS feeds (caution, requires more time!)',
'css_path' => 'Articles CSS path on original website',
'description' => 'Description',
diff --git a/app/i18n/es/conf.php b/app/i18n/es/conf.php
index 0e198caf8..095015d47 100755
--- a/app/i18n/es/conf.php
+++ b/app/i18n/es/conf.php
@@ -19,7 +19,7 @@ return array(
'bottom_line' => 'Línea inferior',
'entry' => 'Iconos de artículos',
'publication_date' => 'Fecha de publicación',
- 'related_tags' => 'Etiquetas relacionadas',
+ 'related_tags' => 'Etiquetas relacionadas', //TODO
'sharing' => 'Compartir',
'top_line' => 'Línea superior',
),
diff --git a/app/i18n/es/gen.php b/app/i18n/es/gen.php
index 4dc1145b2..fe3d62e2d 100755
--- a/app/i18n/es/gen.php
+++ b/app/i18n/es/gen.php
@@ -181,7 +181,7 @@ return array(
'short' => array(
'attention' => '¡Aviso!',
'blank_to_disable' => 'Deja en blanco para desactivar',
- 'by_author' => 'Por <em>%s</em>',
+ 'by_author' => 'Por:',
'by_default' => 'Por defecto',
'damn' => '¡Córcholis!',
'default_category' => 'Sin categorizar',
diff --git a/app/i18n/es/index.php b/app/i18n/es/index.php
index c88152459..1ed6066fb 100755
--- a/app/i18n/es/index.php
+++ b/app/i18n/es/index.php
@@ -7,7 +7,7 @@ return array(
'bugs_reports' => 'Informe de fallos',
'credits' => 'Créditos',
'credits_content' => 'Aunque FreshRSS no usa ese entorno, algunos elementos del diseño están obtenidos de <a href="http://twitter.github.io/bootstrap/">Bootstrap</a>. Los <a href="https://git.gnome.org/browse/gnome-icon-theme-symbolic">Iconos</a> han sido obtenidos del <a href="https://www.gnome.org/">proyecto GNOME</a>. La fuente <em>Open Sans</em> es una creación de <a href="https://fonts.google.com/specimen/Open+Sans">Steve Matteson</a>. FreshRSS usa el entorno PHP <a href="https://github.com/marienfressinaud/MINZ">Minz</a>.',
- 'freshrss_description' => 'FreshRSS es un agregador de fuentes RSS de alojamiento privado al estilo de <a href="http://tontof.net/kriss/feed/">Kriss Feed</a> o <a href="http://projet.idleman.fr/leed/">Leed</a>. Es una herramienta potente, pero ligera y fácil de usar y configurar.',
+ 'freshrss_description' => 'FreshRSS es un agregador de fuentes RSS de alojamiento privado al estilo de <a href="http://tontof.net/kriss/feed/">Kriss Feed</a> o <a href="http://leed.idleman.fr/">Leed</a>. Es una herramienta potente, pero ligera y fácil de usar y configurar.',
'github' => '<a href="https://github.com/FreshRSS/FreshRSS/issues">en Github</a>',
'license' => 'Licencia',
'project_website' => 'Web del proyecto',
@@ -53,10 +53,11 @@ return array(
'starred' => 'Mostrar solo los favoritos',
'stats' => 'Estadísticas',
'subscription' => 'Administración de suscripciones',
+ 'tags' => 'My labels', //TODO
'unread' => 'Mostar solo no leídos',
),
'share' => 'Compartir',
'tag' => array(
- 'related' => 'Etiquetas relacionadas',
+ 'related' => 'Etiquetas relacionadas', //TODO
),
);
diff --git a/app/i18n/es/sub.php b/app/i18n/es/sub.php
index 3abc85578..8a4fb98de 100755
--- a/app/i18n/es/sub.php
+++ b/app/i18n/es/sub.php
@@ -22,6 +22,7 @@ return array(
'password' => 'Contraseña HTTP',
'username' => 'Nombre de usuario HTTP',
),
+ 'clear_cache' => 'Always clear cache', //TODO
'css_help' => 'Recibir fuentes RSS truncadas (aviso, ¡necesita más tiempo!)',
'css_path' => 'Ruta a la CSS de los artículos en la web original',
'description' => 'Descripción',
diff --git a/app/i18n/fr/conf.php b/app/i18n/fr/conf.php
index 52b2f7e0d..01239770b 100644
--- a/app/i18n/fr/conf.php
+++ b/app/i18n/fr/conf.php
@@ -19,7 +19,7 @@ return array(
'bottom_line' => 'Ligne du bas',
'entry' => 'Icônes d’article',
'publication_date' => 'Date de publication',
- 'related_tags' => 'Tags associés',
+ 'related_tags' => 'Tags de l’article',
'sharing' => 'Partage',
'top_line' => 'Ligne du haut',
),
diff --git a/app/i18n/fr/gen.php b/app/i18n/fr/gen.php
index 13e19283f..1e1cef590 100644
--- a/app/i18n/fr/gen.php
+++ b/app/i18n/fr/gen.php
@@ -181,7 +181,7 @@ return array(
'short' => array(
'attention' => 'Attention !',
'blank_to_disable' => 'Laissez vide pour désactiver',
- 'by_author' => 'Par <em>%s</em>',
+ 'by_author' => 'Par :',
'by_default' => 'Par défaut',
'damn' => 'Arf !',
'default_category' => 'Sans catégorie',
diff --git a/app/i18n/fr/index.php b/app/i18n/fr/index.php
index bb0d14faf..c9595e449 100644
--- a/app/i18n/fr/index.php
+++ b/app/i18n/fr/index.php
@@ -7,7 +7,7 @@ return array(
'bugs_reports' => 'Rapports de bugs',
'credits' => 'Crédits',
'credits_content' => 'Des éléments de design sont issus du <a href="http://twitter.github.io/bootstrap/">projet Bootstrap</a> bien que FreshRSS n’utilise pas ce framework. Les <a href="https://git.gnome.org/browse/gnome-icon-theme-symbolic">icônes</a> sont issues du <a href="https://www.gnome.org/">projet GNOME</a>. La police <em>Open Sans</em> utilisée a été créée par <a href="https://fonts.google.com/specimen/Open+Sans">Steve Matteson</a>. FreshRSS repose sur <a href="https://github.com/marienfressinaud/MINZ">Minz</a>, un framework PHP.',
- 'freshrss_description' => 'FreshRSS est un agrégateur de flux RSS à auto-héberger à l’image de <a href="http://tontof.net/kriss/feed/">Kriss Feed</a> ou <a href="http://projet.idleman.fr/leed/">Leed</a>. Il se veut léger et facile à prendre en main tout en étant un outil puissant et paramétrable.',
+ 'freshrss_description' => 'FreshRSS est un agrégateur de flux RSS à auto-héberger à l’image de <a href="http://tontof.net/kriss/feed/">Kriss Feed</a> ou <a href="http://leed.idleman.fr/">Leed</a>. Il se veut léger et facile à prendre en main tout en étant un outil puissant et paramétrable.',
'github' => '<a href="https://github.com/FreshRSS/FreshRSS/issues">sur Github</a>',
'license' => 'Licence',
'project_website' => 'Site du projet',
@@ -53,10 +53,11 @@ return array(
'starred' => 'Afficher les favoris',
'stats' => 'Statistiques',
'subscription' => 'Gestion des abonnements',
+ 'tags' => 'Mes étiquettes',
'unread' => 'Afficher les non-lus',
),
'share' => 'Partager',
'tag' => array(
- 'related' => 'Tags associés',
+ 'related' => 'Tags de l’article',
),
);
diff --git a/app/i18n/fr/sub.php b/app/i18n/fr/sub.php
index c6af2fb90..d3921f1d9 100644
--- a/app/i18n/fr/sub.php
+++ b/app/i18n/fr/sub.php
@@ -27,6 +27,7 @@ return array(
'password' => 'Mot de passe HTTP',
'username' => 'Identifiant HTTP',
),
+ 'clear_cache' => 'Toujours vider le cache',
'css_help' => 'Permet de récupérer les flux tronqués (attention, demande plus de temps !)',
'css_path' => 'Sélecteur CSS des articles sur le site d’origine',
'description' => 'Description',
diff --git a/app/i18n/he/conf.php b/app/i18n/he/conf.php
index a682461a6..2ab8aefa9 100644
--- a/app/i18n/he/conf.php
+++ b/app/i18n/he/conf.php
@@ -19,7 +19,7 @@ return array(
'bottom_line' => 'שורה תחתונה',
'entry' => 'סמלילי מאמרים',
'publication_date' => 'תאריך הפרסום',
- 'related_tags' => 'תגיות קשורות',
+ 'related_tags' => 'תגיות קשורות', //TODO
'sharing' => 'שיתוף',
'top_line' => 'שורה עליונה',
),
diff --git a/app/i18n/he/gen.php b/app/i18n/he/gen.php
index a59f6b178..26b8f99e6 100644
--- a/app/i18n/he/gen.php
+++ b/app/i18n/he/gen.php
@@ -181,7 +181,7 @@ return array(
'short' => array(
'attention' => 'זהירות!',
'blank_to_disable' => 'יש להשאיר ריק על מנת לנטרל',
- 'by_author' => 'מאת <em>%s</em>',
+ 'by_author' => 'מאת :',
'by_default' => 'ברירת מחדל',
'damn' => 'הו לא!',
'default_category' => 'ללא קטגוריה',
diff --git a/app/i18n/he/index.php b/app/i18n/he/index.php
index 8ca6e76f7..d33c09b08 100644
--- a/app/i18n/he/index.php
+++ b/app/i18n/he/index.php
@@ -7,7 +7,7 @@ return array(
'bugs_reports' => 'דיווח באגים',
'credits' => 'קרדיטים',
'credits_content' => 'מאפייני עיצוב מסויימים הגיעו מ <a href="http://twitter.github.io/bootstrap/">Bootstrap</a> אף על פי ש FreshRSS אינו משתמש בתשתית הזו. <a href="https://git.gnome.org/browse/gnome-icon-theme-symbolic">סמלילים</a> הגיעו מ <a href="https://www.gnome.org/"> פרוייקט GNOME </a>. <em>Open Sans</em> הגופן police נוצר על ידי <a href="https://www.google.com/webfonts/specimen/Open+Sans">Steve Matteson</a>. Favicons נאספים בעזרת <a href="https://getfavicon.appspot.com/">getFavicon API</a>. FreshRSS מבוסס על <a href="https://github.com/marienfressinaud/MINZ">Minz</a>, תשתית PHP.',
- 'freshrss_description' => 'FreshRSS הוא קורא RSS לאחסון עצמי בדומה ל <a href="http://tontof.net/kriss/feed/">Kriss Feed</a> או <a href="http://projet.idleman.fr/leed/">Leed</a>. אינו צורך משאבים רבים, וקל לתפעול אך בו בזמן חזק וניתן להתאמה.',
+ 'freshrss_description' => 'FreshRSS הוא קורא RSS לאחסון עצמי בדומה ל <a href="http://tontof.net/kriss/feed/">Kriss Feed</a> או <a href="http://leed.idleman.fr/">Leed</a>. אינו צורך משאבים רבים, וקל לתפעול אך בו בזמן חזק וניתן להתאמה.',
'github' => '<a href="https://github.com/FreshRSS/FreshRSS/issues">בגיטהאב</a>',
'license' => 'רישיון',
'project_website' => 'אתר',
@@ -53,10 +53,11 @@ return array(
'starred' => 'הצגת מועדפים בלבד',
'stats' => 'סטטיסטיקות',
'subscription' => 'ניהול הרשמות',
+ 'tags' => 'My labels', //TODO
'unread' => 'הצגת מאמרים שלא נקראו בלבד',
),
'share' => 'שיתוף',
'tag' => array(
- 'related' => 'תגיות קשורות',
+ 'related' => 'תגיות קשורות', //TODO
),
);
diff --git a/app/i18n/he/sub.php b/app/i18n/he/sub.php
index a263cd728..711004662 100644
--- a/app/i18n/he/sub.php
+++ b/app/i18n/he/sub.php
@@ -27,6 +27,7 @@ return array(
'password' => 'HTTP סיסמה',
'username' => 'HTTP שם משתמש',
),
+ 'clear_cache' => 'Always clear cache', //TODO
'css_help' => 'קבלת הזנות RSS קטומות (זהירות, לוקח זמן רב יותר!)',
'css_path' => 'נתיב הCSS של המאמר באתר המקורי',
'description' => 'תיאור',
diff --git a/app/i18n/it/conf.php b/app/i18n/it/conf.php
index 65b979c51..83beb2df5 100644
--- a/app/i18n/it/conf.php
+++ b/app/i18n/it/conf.php
@@ -19,7 +19,7 @@ return array(
'bottom_line' => 'Barra in fondo',
'entry' => 'Icone degli articoli',
'publication_date' => 'Data di pubblicazione',
- 'related_tags' => 'Tags correlati',
+ 'related_tags' => 'Tags correlati', //TODO
'sharing' => 'Condivisione',
'top_line' => 'Barra in alto',
),
diff --git a/app/i18n/it/gen.php b/app/i18n/it/gen.php
index 2a90693f9..ab17441e7 100644
--- a/app/i18n/it/gen.php
+++ b/app/i18n/it/gen.php
@@ -181,7 +181,7 @@ return array(
'short' => array(
'attention' => 'Attenzione!',
'blank_to_disable' => 'Lascia vuoto per disabilitare',
- 'by_author' => 'di <em>%s</em>',
+ 'by_author' => 'di:',
'by_default' => 'predefinito',
'damn' => 'Ops!',
'default_category' => 'Senza categoria',
diff --git a/app/i18n/it/index.php b/app/i18n/it/index.php
index 718093327..909db1440 100644
--- a/app/i18n/it/index.php
+++ b/app/i18n/it/index.php
@@ -7,7 +7,7 @@ return array(
'bugs_reports' => 'Bugs',
'credits' => 'Crediti',
'credits_content' => 'Alcuni elementi di design provengono da <a href="http://twitter.github.io/bootstrap/">Bootstrap</a> sebbene FreshRSS non usi questo framework. Le <a href="https://git.gnome.org/browse/gnome-icon-theme-symbolic">icone</a> provengono dal progetto <a href="https://www.gnome.org/">GNOME</a>. Il carattere <em>Open Sans</em> è stato creato da <a href="https://fonts.google.com/specimen/Open+Sans">Steve Matteson</a>. FreshRSS è basato su <a href="https://github.com/marienfressinaud/MINZ">Minz</a>, un framework PHP.',
- 'freshrss_description' => 'FreshRSS è un aggregatore di feeds RSS da installare sul proprio host come <a href="http://tontof.net/kriss/feed/">Kriss Feed</a> o <a href="http://projet.idleman.fr/leed/">Leed</a>. Leggero e facile da mantenere pur essendo molto configurabile e potente.',
+ 'freshrss_description' => 'FreshRSS è un aggregatore di feeds RSS da installare sul proprio host come <a href="http://tontof.net/kriss/feed/">Kriss Feed</a> o <a href="http://leed.idleman.fr/">Leed</a>. Leggero e facile da mantenere pur essendo molto configurabile e potente.',
'github' => '<a href="https://github.com/FreshRSS/FreshRSS/issues">su Github</a>',
'license' => 'Licenza',
'project_website' => 'Sito del progetto',
@@ -53,10 +53,11 @@ return array(
'starred' => 'Mostra solo preferiti',
'stats' => 'Statistiche',
'subscription' => 'Gestione sottoscrizioni',
+ 'tags' => 'My labels', //TODO
'unread' => 'Mostra solo non letti',
),
'share' => 'Condividi',
'tag' => array(
- 'related' => 'Tags correlati',
+ 'related' => 'Tags correlati', //TODO
),
);
diff --git a/app/i18n/it/sub.php b/app/i18n/it/sub.php
index 22d58a27f..b22340c9b 100644
--- a/app/i18n/it/sub.php
+++ b/app/i18n/it/sub.php
@@ -27,6 +27,7 @@ return array(
'password' => 'HTTP password',
'username' => 'HTTP username',
),
+ 'clear_cache' => 'Always clear cache', //TODO
'css_help' => 'In caso di RSS feeds troncati (attenzione, richiede molto tempo!)',
'css_path' => 'Percorso del foglio di stile CSS del sito di origine',
'description' => 'Descrizione',
diff --git a/app/i18n/kr/conf.php b/app/i18n/kr/conf.php
index f618d6c96..f26e2cf09 100644
--- a/app/i18n/kr/conf.php
+++ b/app/i18n/kr/conf.php
@@ -19,7 +19,7 @@ return array(
'bottom_line' => '하단',
'entry' => '문서 아이콘',
'publication_date' => '발행일',
- 'related_tags' => '관련 태그',
+ 'related_tags' => '관련 태그', //TODO
'sharing' => '공유',
'top_line' => '상단',
),
diff --git a/app/i18n/kr/gen.php b/app/i18n/kr/gen.php
index e664eaa42..6a461bdac 100644
--- a/app/i18n/kr/gen.php
+++ b/app/i18n/kr/gen.php
@@ -181,7 +181,7 @@ return array(
'short' => array(
'attention' => '경고!',
'blank_to_disable' => '빈 칸으로 두면 비활성화',
- 'by_author' => 'By <em>%s</em>',
+ 'by_author' => 'By:',
'by_default' => '기본값',
'damn' => '이런!',
'default_category' => '분류 없음',
diff --git a/app/i18n/kr/index.php b/app/i18n/kr/index.php
index cb9684dff..87cc12eca 100644
--- a/app/i18n/kr/index.php
+++ b/app/i18n/kr/index.php
@@ -7,7 +7,7 @@ return array(
'bugs_reports' => '버그 제보하기',
'credits' => '크레딧',
'credits_content' => 'FreshRSS는 <a href="http://twitter.github.io/bootstrap/">Bootstrap</a> 프레임워크를 사용하진 않지만, 일부 디자인 요소를 가져왔습니다. <a href="https://git.gnome.org/browse/gnome-icon-theme-symbolic">아이콘들</a>은 <a href="https://www.gnome.org/">GNOME 프로젝트</a>에서 가져왔습니다. <em>Open Sans</em> 글꼴은 <a href="https://fonts.google.com/specimen/Open+Sans">Steve Matteson</a>가 제작하였습니다. FreshRSS는 PHP 프레임워크인 <a href="https://github.com/marienfressinaud/MINZ">Minz</a>에 기반하고 있습니다.',
- 'freshrss_description' => 'FreshRSS는 <a href="http://tontof.net/kriss/feed/">Kriss Feed</a> 또는 <a href="http://projet.idleman.fr/leed/">Leed</a>와 같은 셀프 호스팅 기반의 RSS 피드 수집기입니다. FreshRSS는 강력하고 다양한 설정을 할 수 있으면서 도 가볍고 사용하기 쉽습니다.',
+ 'freshrss_description' => 'FreshRSS는 <a href="http://tontof.net/kriss/feed/">Kriss Feed</a> 또는 <a href="http://leed.idleman.fr/">Leed</a>와 같은 셀프 호스팅 기반의 RSS 피드 수집기입니다. FreshRSS는 강력하고 다양한 설정을 할 수 있으면서 도 가볍고 사용하기 쉽습니다.',
'github' => '<a href="https://github.com/FreshRSS/FreshRSS/issues">Github 저장소에 제보</a>',
'license' => '라이센스',
'project_website' => '프로젝트 웹사이트',
@@ -53,10 +53,11 @@ return array(
'starred' => '즐겨찾기만 표시',
'stats' => '통계',
'subscription' => '구독 관리',
+ 'tags' => 'My labels', //TODO
'unread' => '읽지 않은 글만 표시',
),
'share' => '공유',
'tag' => array(
- 'related' => '관련 태그',
+ 'related' => '관련 태그', //TODO
),
);
diff --git a/app/i18n/kr/sub.php b/app/i18n/kr/sub.php
index de200c330..ee6b25e3f 100644
--- a/app/i18n/kr/sub.php
+++ b/app/i18n/kr/sub.php
@@ -27,6 +27,7 @@ return array(
'password' => 'HTTP 암호',
'username' => 'HTTP 사용자 이름',
),
+ 'clear_cache' => 'Always clear cache', //TODO
'css_help' => '글의 일부가 포함된 RSS 피드를 가져옵니다 (주의, 시간이 좀 더 걸립니다!)',
'css_path' => '웹사이트 상의 글 본문에 해당하는 CSS 경로',
'description' => '설명',
diff --git a/app/i18n/nl/conf.php b/app/i18n/nl/conf.php
index 041b482b9..883d932ab 100644
--- a/app/i18n/nl/conf.php
+++ b/app/i18n/nl/conf.php
@@ -19,7 +19,7 @@ return array(
'bottom_line' => 'Onderaan',
'entry' => 'Artikel pictogrammen',
'publication_date' => 'Publicatie datum',
- 'related_tags' => 'Gerelateerde labels',
+ 'related_tags' => 'Gerelateerde labels', //TODO
'sharing' => 'Delen',
'top_line' => 'Bovenaan',
),
diff --git a/app/i18n/nl/gen.php b/app/i18n/nl/gen.php
index ccbd86579..fdc4338c3 100644
--- a/app/i18n/nl/gen.php
+++ b/app/i18n/nl/gen.php
@@ -181,7 +181,7 @@ return array(
'short' => array(
'attention' => 'Attentie!',
'blank_to_disable' => 'Laat leeg om uit te zetten',
- 'by_author' => 'Door <em>%s</em>',
+ 'by_author' => 'Door:',
'by_default' => 'Door standaard',
'damn' => 'Potverdorie!',
'default_category' => 'Niet ingedeeld',
diff --git a/app/i18n/nl/index.php b/app/i18n/nl/index.php
index 67b3886ea..33fec43c0 100644
--- a/app/i18n/nl/index.php
+++ b/app/i18n/nl/index.php
@@ -7,7 +7,7 @@ return array(
'bugs_reports' => 'Rapporteer fouten',
'credits' => 'Waarderingen',
'credits_content' => 'Sommige ontwerp elementen komen van <a href="http://twitter.github.io/bootstrap/">Bootstrap</a> alhoewel FreshRSS dit raamwerk niet gebruikt. <a href="https://git.gnome.org/browse/gnome-icon-theme-symbolic">Pictogrammen</a> komen van het <a href="https://www.gnome.org/">GNOME project</a>. <em>De Open Sans</em> font police is gemaakt door <a href="https://fonts.google.com/specimen/Open+Sans">Steve Matteson</a>. FreshRSS is gebaseerd op <a href="https://github.com/marienfressinaud/MINZ">Minz</a>, een PHP raamwerk. Nederlandse vertaling door Wanabo, <a href="http://www.nieuwskop.be" title="NieuwsKop">NieuwsKop.be</a>. Link naar de Nederlandse vertaling, <a href="https://github.com/Wanabo/FreshRSS-Dutch-translation/tree/master">FreshRSS-Dutch-translation</a>.',
- 'freshrss_description' => 'FreshRSS is een RSS feed aggregator om zelf te hosten zoals <a href="http://tontof.net/kriss/feed/">Kriss Feed</a> of <a href="http://projet.idleman.fr/leed/">Leed</a>. Het gebruikt weinig systeembronnen en is makkelijk te administreren terwijl het een krachtig en makkelijk te configureren programma is.',
+ 'freshrss_description' => 'FreshRSS is een RSS feed aggregator om zelf te hosten zoals <a href="http://tontof.net/kriss/feed/">Kriss Feed</a> of <a href="http://leed.idleman.fr/">Leed</a>. Het gebruikt weinig systeembronnen en is makkelijk te administreren terwijl het een krachtig en makkelijk te configureren programma is.',
'github' => '<a href="https://github.com/FreshRSS/FreshRSS/issues">op Github</a>',
'license' => 'License',
'project_website' => 'Project website',
@@ -57,6 +57,6 @@ return array(
),
'share' => 'Delen',
'tag' => array(
- 'related' => 'Verwante labels',
+ 'related' => 'Verwante labels', //TODO
),
);
diff --git a/app/i18n/nl/sub.php b/app/i18n/nl/sub.php
index 4ce254ef5..fec7fb4e7 100644
--- a/app/i18n/nl/sub.php
+++ b/app/i18n/nl/sub.php
@@ -27,6 +27,7 @@ return array(
'password' => 'HTTP wachtwoord',
'username' => 'HTTP gebruikers naam',
),
+ 'clear_cache' => 'Always clear cache', //TODO
'css_help' => 'Haalt verstoorde RSS feeds op (attentie, heeft meer tijd nodig!)',
'css_path' => 'Artikelen CSS pad op originele website',
'description' => 'Omschrijving',
diff --git a/app/i18n/pt-br/conf.php b/app/i18n/pt-br/conf.php
index 61a12160c..2547a8624 100644
--- a/app/i18n/pt-br/conf.php
+++ b/app/i18n/pt-br/conf.php
@@ -19,7 +19,7 @@ return array(
'bottom_line' => 'Linha inferior',
'entry' => 'Ícones de artigos',
'publication_date' => 'Data da publicação',
- 'related_tags' => 'Tags relacionadas',
+ 'related_tags' => 'Tags relacionadas', //TODO
'sharing' => 'Compartilhar',
'top_line' => 'Linha superior',
),
diff --git a/app/i18n/pt-br/gen.php b/app/i18n/pt-br/gen.php
index 558482f07..59218597b 100644
--- a/app/i18n/pt-br/gen.php
+++ b/app/i18n/pt-br/gen.php
@@ -180,7 +180,7 @@ return array(
'short' => array(
'attention' => 'Atencão!',
'blank_to_disable' => 'Deixe em branco para desativar',
- 'by_author' => 'Por <em>%s</em>',
+ 'by_author' => 'Por:',
'by_default' => 'Por padrão',
'damn' => 'Buumm!',
'default_category' => 'Sem categoria',
diff --git a/app/i18n/pt-br/index.php b/app/i18n/pt-br/index.php
index 2eff8d948..9f98902ed 100644
--- a/app/i18n/pt-br/index.php
+++ b/app/i18n/pt-br/index.php
@@ -7,7 +7,7 @@ return array(
'bugs_reports' => 'Reportar Bugs',
'credits' => 'Créditos',
'credits_content' => 'Alguns elementos de design vieram do <a href="http://twitter.github.io/bootstrap/">Bootstrap</a> Embora FreshRRS não utiliza este framework. <a href="https://git.gnome.org/browse/gnome-icon-theme-symbolic">Ícones</a> vieram do <a href="https://www.gnome.org/">GNOME project</a>. <em>Open Sans</em> font police foi criada por <a href="https://fonts.google.com/specimen/Open+Sans">Steve Matteson</a>. FreshRSS é baseado no <a href="https://github.com/marienfressinaud/MINZ">Minz</a>, um framework PHP.',
- 'freshrss_description' => 'FreshRSS é um RSS feeds aggregator para um host próprio como o <a href="http://tontof.net/kriss/feed/">Kriss Feed</a> ou <a href="http://projet.idleman.fr/leed/">Leed</a>. É leve e fácil de utilizar enquanto é uma ferramenta poderosa e configurável. ',
+ 'freshrss_description' => 'FreshRSS é um RSS feeds aggregator para um host próprio como o <a href="http://tontof.net/kriss/feed/">Kriss Feed</a> ou <a href="http://leed.idleman.fr/">Leed</a>. É leve e fácil de utilizar enquanto é uma ferramenta poderosa e configurável. ',
'github' => '<a href="https://github.com/FreshRSS/FreshRSS/issues">no Github</a>',
'license' => 'licença',
'project_website' => 'Site do projeto',
@@ -57,6 +57,6 @@ return array(
),
'share' => 'Compartilhar',
'tag' => array(
- 'related' => 'Tags relacionadas',
+ 'related' => 'Tags relacionadas', //TODO
),
);
diff --git a/app/i18n/pt-br/sub.php b/app/i18n/pt-br/sub.php
index 1b084f08f..daa24e8f3 100644
--- a/app/i18n/pt-br/sub.php
+++ b/app/i18n/pt-br/sub.php
@@ -27,6 +27,7 @@ return array(
'password' => 'Senha HTTP',
'username' => 'Usuário HTTP',
),
+ 'clear_cache' => 'Always clear cache', //TODO
'css_help' => 'Retorna RSS feeds truncados (atenção, requer mais tempo!)',
'css_path' => 'Caminho do CSS do artigo no site original',
'description' => 'Descrição',
diff --git a/app/i18n/ru/conf.php b/app/i18n/ru/conf.php
index 90a1a6797..b9d45fb20 100644
--- a/app/i18n/ru/conf.php
+++ b/app/i18n/ru/conf.php
@@ -19,7 +19,7 @@ return array(
'bottom_line' => 'Bottom line',
'entry' => 'Article icons',
'publication_date' => 'Date of publication',
- 'related_tags' => 'Related tags',
+ 'related_tags' => 'Related tags', //TODO
'sharing' => 'Sharing',
'top_line' => 'Top line',
),
diff --git a/app/i18n/ru/gen.php b/app/i18n/ru/gen.php
index 911410a1c..6c8dd2adf 100644
--- a/app/i18n/ru/gen.php
+++ b/app/i18n/ru/gen.php
@@ -181,7 +181,7 @@ return array(
'short' => array(
'attention' => 'Warning!',
'blank_to_disable' => 'Leave blank to disable',
- 'by_author' => 'By <em>%s</em>',
+ 'by_author' => 'By:',
'by_default' => 'By default',
'damn' => 'Damn!',
'default_category' => 'Uncategorized',
diff --git a/app/i18n/ru/index.php b/app/i18n/ru/index.php
index 9bb327786..aaf25a3ab 100644
--- a/app/i18n/ru/index.php
+++ b/app/i18n/ru/index.php
@@ -7,7 +7,7 @@ return array(
'bugs_reports' => 'Bugs reports',
'credits' => 'Credits',
'credits_content' => 'Some design elements come from <a href="http://twitter.github.io/bootstrap/">Bootstrap</a> although FreshRSS doesn’t use this framework. <a href="https://git.gnome.org/browse/gnome-icon-theme-symbolic">Icons</a> come from <a href="https://www.gnome.org/">GNOME project</a>. <em>Open Sans</em> font police has been created by <a href="https://fonts.google.com/specimen/Open+Sans">Steve Matteson</a>. FreshRSS is based on <a href="https://github.com/marienfressinaud/MINZ">Minz</a>, a PHP framework.',
- 'freshrss_description' => 'FreshRSS is a RSS feeds aggregator to self-host like <a href="http://tontof.net/kriss/feed/">Kriss Feed</a> or <a href="http://projet.idleman.fr/leed/">Leed</a>. It is light and easy to take in hand while being powerful and configurable tool.',
+ 'freshrss_description' => 'FreshRSS is a RSS feeds aggregator to self-host like <a href="http://tontof.net/kriss/feed/">Kriss Feed</a> or <a href="http://leed.idleman.fr/">Leed</a>. It is light and easy to take in hand while being powerful and configurable tool.',
'github' => '<a href="https://github.com/FreshRSS/FreshRSS/issues">on Github</a>',
'license' => 'License',
'project_website' => 'Project website',
@@ -57,6 +57,6 @@ return array(
),
'share' => 'Share',
'tag' => array(
- 'related' => 'Related tags',
+ 'related' => 'Article tags', //TODO
),
);
diff --git a/app/i18n/ru/sub.php b/app/i18n/ru/sub.php
index bef49623f..12901998d 100644
--- a/app/i18n/ru/sub.php
+++ b/app/i18n/ru/sub.php
@@ -27,6 +27,7 @@ return array(
'password' => 'HTTP password',// TODO
'username' => 'HTTP username',// TODO
),
+ 'clear_cache' => 'Always clear cache', //TODO
'css_help' => 'Retrieves truncated RSS feeds (caution, requires more time!)',// TODO
'css_path' => 'Articles CSS path on original website',// TODO
'description' => 'Description',// TODO
diff --git a/app/i18n/tr/conf.php b/app/i18n/tr/conf.php
index cae1e4cac..49533bb6a 100644
--- a/app/i18n/tr/conf.php
+++ b/app/i18n/tr/conf.php
@@ -19,7 +19,7 @@ return array(
'bottom_line' => 'Alt çizgi',
'entry' => 'Makale ikonları',
'publication_date' => 'Yayınlama Tarihi',
- 'related_tags' => 'İlgili etiketler',
+ 'related_tags' => 'İlgili etiketler', //TODO
'sharing' => 'Paylaşım',
'top_line' => 'Üst çizgi',
),
diff --git a/app/i18n/tr/gen.php b/app/i18n/tr/gen.php
index 2e1761517..b8dc18c01 100644
--- a/app/i18n/tr/gen.php
+++ b/app/i18n/tr/gen.php
@@ -181,7 +181,7 @@ return array(
'short' => array(
'attention' => 'Tehlike!',
'blank_to_disable' => 'Devredışı bırakmak için boş bırakın',
- 'by_author' => '<em>%s</em> tarafından',
+ 'by_author' => 'Tarafından:',
'by_default' => 'Öntanımlı',
'damn' => 'Hay aksi!',
'default_category' => 'Kategorisiz',
diff --git a/app/i18n/tr/index.php b/app/i18n/tr/index.php
index 1357c05e7..e7db73b96 100644
--- a/app/i18n/tr/index.php
+++ b/app/i18n/tr/index.php
@@ -7,7 +7,7 @@ return array(
'bugs_reports' => 'Hata raporu',
'credits' => 'Tanıtım',
'credits_content' => 'Bu frameworkü kullanmamasına rağmen FreshRSS bazı tasarım ögelerini <a href="http://twitter.github.io/bootstrap/">Bootstrap</a> dan almıştır. <a href="https://git.gnome.org/browse/gnome-icon-theme-symbolic">İkonlar</a> <a href="https://www.gnome.org/">GNOME projesinden</a> alınmıştır. <em>Open Sans</em> yazı tipi <a href="https://fonts.google.com/specimen/Open+Sans">Steve Matteson</a> tarafından oluşturulmuştur. FreshRSS bir PHP framework olan <a href="https://github.com/marienfressinaud/MINZ">Minz</a> i temel alır.',
- 'freshrss_description' => 'FreshRSS <a href="http://tontof.net/kriss/feed/">Kriss Feed</a> veya <a href="http://projet.idleman.fr/leed/">Leed</a> gibi kendi hostunuzda çalışan bir RSS akış toplayıcısıdır. Güçlü ve yapılandırılabilir araçlarıyla basit ve kullanımı kolay bir uygulamadır.',
+ 'freshrss_description' => 'FreshRSS <a href="http://tontof.net/kriss/feed/">Kriss Feed</a> veya <a href="http://leed.idleman.fr/">Leed</a> gibi kendi hostunuzda çalışan bir RSS akış toplayıcısıdır. Güçlü ve yapılandırılabilir araçlarıyla basit ve kullanımı kolay bir uygulamadır.',
'github' => '<a href="https://github.com/FreshRSS/FreshRSS/issues">Github sayfası</a>',
'license' => 'Lisans',
'project_website' => 'Proje sayfası',
@@ -53,10 +53,11 @@ return array(
'starred' => 'Favorileri göster',
'stats' => 'İstatistikler',
'subscription' => 'Abonelik yönetimi',
+ 'tags' => 'My labels', //TODO
'unread' => 'Okunmamışları göster',
),
'share' => 'Share',
'tag' => array(
- 'related' => 'İlgili etiketler',
+ 'related' => 'İlgili etiketler', //TODO
),
);
diff --git a/app/i18n/tr/sub.php b/app/i18n/tr/sub.php
index e8cd15d0d..ef0c8ffbd 100644
--- a/app/i18n/tr/sub.php
+++ b/app/i18n/tr/sub.php
@@ -27,6 +27,7 @@ return array(
'password' => 'HTTP şifre',
'username' => 'HTTP kullanıcı adı',
),
+ 'clear_cache' => 'Always clear cache', //TODO
'css_help' => 'Dikkat, daha çok zaman gerekir!',
'css_path' => 'Makaleleri kendi CSS görünümü ile göster',
'description' => 'Tanım',
diff --git a/app/i18n/zh-cn/conf.php b/app/i18n/zh-cn/conf.php
index 00bea4d79..6c62349c2 100644
--- a/app/i18n/zh-cn/conf.php
+++ b/app/i18n/zh-cn/conf.php
@@ -19,7 +19,7 @@ return array(
'bottom_line' => '底栏',
'entry' => '文章图标',
'publication_date' => '更新日期',
- 'related_tags' => '相关标签',
+ 'related_tags' => '相关标签', //TODO
'sharing' => '分享',
'top_line' => '顶栏',
),
diff --git a/app/i18n/zh-cn/gen.php b/app/i18n/zh-cn/gen.php
index 4ea2d73ab..078e1d378 100644
--- a/app/i18n/zh-cn/gen.php
+++ b/app/i18n/zh-cn/gen.php
@@ -181,7 +181,7 @@ return array(
'short' => array(
'attention' => '警告!',
'blank_to_disable' => '留空以禁用',
- 'by_author' => '作者 <em>%s</em>',
+ 'by_author' => '作者',
'by_default' => '默认',
'damn' => '错误!',
'default_category' => '未分类',
diff --git a/app/i18n/zh-cn/index.php b/app/i18n/zh-cn/index.php
index 2b76961ef..dd8eafda7 100644
--- a/app/i18n/zh-cn/index.php
+++ b/app/i18n/zh-cn/index.php
@@ -7,7 +7,7 @@ return array(
'bugs_reports' => 'Bug 报告',
'credits' => '致谢',
'credits_content' => '某些设计元素来自于 <a href="http://twitter.github.io/bootstrap/">Bootstrap</a> ,尽管 FreshRSS 并没有使用此框架。<a href="https://git.gnome.org/browse/gnome-icon-theme-symbolic">图标</a> 来自于 <a href="https://www.gnome.org/">GNOME 项目</a>。<em>Open Sans</em> 字体出自 <a href="https://fonts.google.com/specimen/Open+Sans">Steve Matteson</a> 之手。FreshRSS 基于 PHP 框架 <a href="https://github.com/marienfressinaud/MINZ">Minz</a>。',
- 'freshrss_description' => 'FreshRSS 是一个自托管的 RSS 聚合服务,类似于 <a href="http://tontof.net/kriss/feed/">Kriss Feed</a> 或 <a href="http://projet.idleman.fr/leed/">Leed</a>。 它不仅轻快又易用,而且强大又易于配置。',
+ 'freshrss_description' => 'FreshRSS 是一个自托管的 RSS 聚合服务,类似于 <a href="http://tontof.net/kriss/feed/">Kriss Feed</a> 或 <a href="http://leed.idleman.fr/">Leed</a>。 它不仅轻快又易用,而且强大又易于配置。',
'github' => '<a href="https://github.com/FreshRSS/FreshRSS/issues">Github Issues</a>',
'license' => '授权',
'project_website' => '项目网站',
@@ -53,10 +53,11 @@ return array(
'starred' => '显示收藏',
'stats' => '统计',
'subscription' => '订阅管理',
+ 'tags' => 'My labels', //TODO
'unread' => '显示未读',
),
'share' => '分享',
'tag' => array(
- 'related' => '相关标签',
+ 'related' => '相关标签', //TODO
),
);
diff --git a/app/i18n/zh-cn/sub.php b/app/i18n/zh-cn/sub.php
index 034f8a9d9..4980b803a 100644
--- a/app/i18n/zh-cn/sub.php
+++ b/app/i18n/zh-cn/sub.php
@@ -27,6 +27,7 @@ return array(
'password' => 'HTTP 密码',
'username' => 'HTTP 用户名',
),
+ 'clear_cache' => 'Always clear cache', //TODO
'css_help' => '用于获取全文(注意,这将耗费更多时间!)',
'css_path' => '原文的 CSS 选择器',
'description' => '描述',
diff --git a/app/install.php b/app/install.php
index eec65be9c..dc79c2388 100644
--- a/app/install.php
+++ b/app/install.php
@@ -343,13 +343,13 @@ function checkDbUser(&$dbOptions) {
try {
$c = new PDO($str, $dbOptions['user'], $dbOptions['password'], $driver_options);
if (defined('SQL_CREATE_TABLES')) {
- $sql = sprintf(SQL_CREATE_TABLES . SQL_CREATE_TABLE_ENTRYTMP . SQL_INSERT_FEEDS,
+ $sql = sprintf(SQL_CREATE_TABLES . SQL_CREATE_TABLE_ENTRYTMP . SQL_CREATE_TABLE_TAGS . SQL_INSERT_FEEDS,
$dbOptions['prefix_user'], _t('gen.short.default_category'));
$stm = $c->prepare($sql);
$ok = $stm && $stm->execute();
} else {
- global $SQL_CREATE_TABLES, $SQL_CREATE_TABLE_ENTRYTMP, $SQL_INSERT_FEEDS;
- $instructions = array_merge($SQL_CREATE_TABLES, $SQL_CREATE_TABLE_ENTRYTMP, $SQL_INSERT_FEEDS);
+ global $SQL_CREATE_TABLES, $SQL_CREATE_TABLE_ENTRYTMP, $SQL_CREATE_TABLE_TAGS, $SQL_INSERT_FEEDS;
+ $instructions = array_merge($SQL_CREATE_TABLES, $SQL_CREATE_TABLE_ENTRYTMP, $SQL_CREATE_TABLE_TAGS, $SQL_INSERT_FEEDS);
$ok = !empty($instructions);
foreach ($instructions as $instruction) {
$sql = sprintf($instruction, $dbOptions['prefix_user'], _t('gen.short.default_category'));
diff --git a/app/layout/aside_feed.phtml b/app/layout/aside_feed.phtml
index 97c0fb0d9..ce029cfa0 100644
--- a/app/layout/aside_feed.phtml
+++ b/app/layout/aside_feed.phtml
@@ -35,6 +35,30 @@
</li>
<?php
+ $t_active = FreshRSS_Context::isCurrentGet('T');
+ ?>
+ <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', 'index', 'get', 'T'); ?>"><?php echo _t('index.menu.tags'); ?></a>
+ </div>
+ <ul class="tree-folder-items<?php echo $t_active ? ' active' : ''; ?>">
+ <?php
+ foreach ($this->tags as $tag):
+ ?>
+ <li id="t_<?php echo $tag->id(); ?>" class="item feed<?php echo FreshRSS_Context::isCurrentGet('t_' . $tag->id()) ? ' active' : ''; ?>" data-unread="<?php echo $tag->nbUnread(); ?>">
+ <div class="dropdown no-mobile">
+ <div class="dropdown-target"></div>
+ <a class="dropdown-toggle"><?php echo _i('configure'); ?></a>
+ <?php /* tag_config_template */ ?>
+ </div>
+ <?php echo FreshRSS_Themes::alt('label'); ?> <a class="item-title" data-unread="<?php echo format_number($tag->nbUnread()); ?>" href="<?php echo _url('index', 'index', 'get', 't_' . $tag->id()); ?>"><?php echo $tag->name(); ?></a>
+ </li>
+ <?php endforeach; ?>
+ </ul>
+ </li>
+
+ <?php
foreach ($this->categories as $cat) {
$feeds = $cat->feeds();
if (!empty($feeds)) {
@@ -72,6 +96,17 @@
</form>
</div>
+<script id="tag_config_template" type="text/html">
+ <ul class="dropdown-menu">
+ <li class="dropdown-close"><a href="#close">❌</a></li>
+ <li class="item">
+ <button class="as-link confirm" disabled="disabled"
+ form="mark-read-aside" formaction="<?php echo _url('tag', 'delete', 'id_tag', '------'); ?>"
+ type="submit"><?php echo _t('gen.action.remove'); ?></button>
+ </li>
+ </ul>
+</script>
+
<script id="feed_config_template" type="text/html">
<ul class="dropdown-menu">
<li class="dropdown-close"><a href="#close">❌</a></li>
diff --git a/app/layout/layout.phtml b/app/layout/layout.phtml
index 1f11e0af1..2e16672e6 100644
--- a/app/layout/layout.phtml
+++ b/app/layout/layout.phtml
@@ -11,10 +11,6 @@
<?php echo self::headScript(); ?>
<link rel="shortcut icon" id="favicon" type="image/x-icon" sizes="16x16 64x64" href="<?php echo Minz_Url::display('/favicon.ico'); ?>" />
<link rel="icon msapplication-TileImage apple-touch-icon" type="image/png" sizes="256x256" href="<?php echo Minz_Url::display('/themes/icons/favicon-256.png'); ?>" />
- <link rel="prefetch" href="<?php echo FreshRSS_Themes::icon('starred', true); ?>" />
- <link rel="prefetch" href="<?php echo FreshRSS_Themes::icon('non-starred', true); ?>" />
- <link rel="prefetch" href="<?php echo FreshRSS_Themes::icon('read', true); ?>" />
- <link rel="prefetch" href="<?php echo FreshRSS_Themes::icon('unread', true); ?>" />
<link rel="apple-touch-icon" href="<?php echo Minz_Url::display('/themes/icons/apple-touch-icon.png'); ?>" />
<meta name="apple-mobile-web-app-capable" content="yes" />
<meta name="apple-mobile-web-app-status-bar-style" content="black" />
@@ -22,24 +18,11 @@
<meta name="msapplication-TileColor" content="#FFF" />
<?php if (!FreshRSS_Context::$system_conf->allow_referrer) { ?>
<meta name="referrer" content="never" />
-<?php
- }
- flush();
- if (isset($this->callbackBeforeContent)) {
- call_user_func($this->callbackBeforeContent, $this);
- }
-?>
+<?php } ?>
<?php echo self::headTitle(); ?>
<?php
$url_base = Minz_Request::currentRequest();
- if (FreshRSS_Context::$next_id !== '') {
- $url_next = $url_base;
- $url_next['params']['next'] = FreshRSS_Context::$next_id;
- $url_next['params']['ajax'] = 1;
-?>
- <link id="prefetch" rel="next prefetch" href="<?php echo Minz_Url::display($url_next); ?>" />
-<?php
- } if (isset($this->rss_title)) {
+ if (isset($this->rss_title)) {
$url_rss = $url_base;
$url_rss['a'] = 'rss';
if (FreshRSS_Context::$user_conf->since_hours_posts_per_rss) {
@@ -54,10 +37,19 @@
<?php } ?>
</head>
<body class="<?php echo Minz_Request::actionName(); ?>">
-<?php $this->partial('header'); ?>
+<?php
+ flush();
+ $this->partial('header');
+?>
<div id="global">
- <?php $this->render(); ?>
+ <?php
+ flush();
+ if (isset($this->callbackBeforeFeeds)) {
+ call_user_func($this->callbackBeforeFeeds, $this);
+ }
+ $this->render();
+ ?>
</div>
<?php
diff --git a/app/views/configure/display.phtml b/app/views/configure/display.phtml
index 414fd2cd6..c6c08e3bc 100644
--- a/app/views/configure/display.phtml
+++ b/app/views/configure/display.phtml
@@ -39,7 +39,7 @@
<?php } ?>
</div>
<div class="properties">
- <div><?php echo sprintf('%s — %s', $theme['name'], _t('gen.short.by_author', $theme['author'])); ?></div>
+ <div><?php echo sprintf('%s — %s %s', $theme['name'], _t('gen.short.by_author'), $theme['author']); ?></div>
<div><?php echo $theme['description'] ?></div>
<div class="page-number"><?php echo sprintf('%d/%d', $i, $slides) ?></div>
</div>
@@ -79,8 +79,8 @@
<th> </th>
<th title="<?php echo _t('gen.action.mark_read'); ?>"><?php echo _i('read'); ?></th>
<th title="<?php echo _t('gen.action.mark_favorite'); ?>"><?php echo _i('bookmark'); ?></th>
- <th><?php echo _t('conf.display.icon.sharing'); ?></th>
<th><?php echo _t('conf.display.icon.related_tags'); ?></th>
+ <th><?php echo _t('conf.display.icon.sharing'); ?></th>
<th><?php echo _t('conf.display.icon.publication_date'); ?></th>
<th><?php echo _i('link'); ?></th>
</tr>
@@ -98,8 +98,8 @@
<th><?php echo _t('conf.display.icon.bottom_line'); ?></th>
<td><input type="checkbox" name="bottomline_read" value="1"<?php echo FreshRSS_Context::$user_conf->bottomline_read ? ' checked="checked"' : ''; ?> data-leave-validation="<?php echo FreshRSS_Context::$user_conf->bottomline_read; ?>"/></td>
<td><input type="checkbox" name="bottomline_favorite" value="1"<?php echo FreshRSS_Context::$user_conf->bottomline_favorite ? ' checked="checked"' : ''; ?> data-leave-validation="<?php echo FreshRSS_Context::$user_conf->bottomline_favorite; ?>"/></td>
- <td><input type="checkbox" name="bottomline_sharing" value="1"<?php echo FreshRSS_Context::$user_conf->bottomline_sharing ? ' checked="checked"' : ''; ?> data-leave-validation="<?php echo FreshRSS_Context::$user_conf->bottomline_sharing; ?>"/></td>
<td><input type="checkbox" name="bottomline_tags" value="1"<?php echo FreshRSS_Context::$user_conf->bottomline_tags ? ' checked="checked"' : ''; ?> data-leave-validation="<?php echo FreshRSS_Context::$user_conf->bottomline_tags; ?>"/></td>
+ <td><input type="checkbox" name="bottomline_sharing" value="1"<?php echo FreshRSS_Context::$user_conf->bottomline_sharing ? ' checked="checked"' : ''; ?> data-leave-validation="<?php echo FreshRSS_Context::$user_conf->bottomline_sharing; ?>"/></td>
<td><input type="checkbox" name="bottomline_date" value="1"<?php echo FreshRSS_Context::$user_conf->bottomline_date ? ' checked="checked"' : ''; ?> data-leave-validation="<?php echo FreshRSS_Context::$user_conf->bottomline_date; ?>"/></td>
<td><input type="checkbox" name="bottomline_link" value="1"<?php echo FreshRSS_Context::$user_conf->bottomline_link ? ' checked="checked"' : ''; ?> data-leave-validation="<?php echo FreshRSS_Context::$user_conf->bottomline_link; ?>"/></td>
</tr>
diff --git a/app/views/entry/read.phtml b/app/views/entry/read.phtml
index 73977d94b..fb9e129f2 100755
--- a/app/views/entry/read.phtml
+++ b/app/views/entry/read.phtml
@@ -12,5 +12,6 @@ $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')
+ 'icon' => _i($url['params']['is_read'] === '1' ? 'unread' : 'read'),
+ 'tags' => $this->tags,
));
diff --git a/app/views/helpers/export/articles.phtml b/app/views/helpers/export/articles.phtml
index 75651483a..b8958f527 100644
--- a/app/views/helpers/export/articles.phtml
+++ b/app/views/helpers/export/articles.phtml
@@ -34,7 +34,7 @@ foreach ($this->entriesRaw as $entryRaw) {
'id' => $entry->guid(),
'categories' => array_values($entry->tags()),
'title' => $entry->title(),
- 'author' => $entry->author(),
+ 'author' => $entry->authors(true), //TODO: Make an array like tags?
'published' => $entry->date(true),
'updated' => $entry->date(true),
'alternate' => array(array(
diff --git a/app/views/helpers/extension/configure.phtml b/app/views/helpers/extension/configure.phtml
index 95d968aba..cde872aa0 100644
--- a/app/views/helpers/extension/configure.phtml
+++ b/app/views/helpers/extension/configure.phtml
@@ -5,7 +5,7 @@
: _t('admin.extensions.disabled'); ?>
</h1>
- <p class="alert alert-warn"><?php echo $this->extension->getDescription(); ?> — <?php echo _t('gen.short.by_author', $this->extension->getAuthor()); ?></p>
+ <p class="alert alert-warn"><?php echo $this->extension->getDescription(); ?> — <?php echo _t('gen.short.by_author'), ' ', $this->extension->getAuthor(); ?></p>
<h2><?php echo _t('gen.action.manage'); ?></h2>
<?php
diff --git a/app/views/helpers/feed/update.phtml b/app/views/helpers/feed/update.phtml
index 7144aab46..4dbaacd04 100644
--- a/app/views/helpers/feed/update.phtml
+++ b/app/views/helpers/feed/update.phtml
@@ -205,6 +205,13 @@
</div>
</div>
+ <div class="form-group">
+ <label class="group-name" for="clear_cache"><?php echo _t('sub.feed.clear_cache'); ?></label>
+ <div class="group-controls">
+ <input type="checkbox" name="clear_cache" id="clear_cache" value="1"<?php echo $this->feed->attributes('clear_cache') ? ' checked="checked"' : ''; ?> />
+ </div>
+ </div>
+
<?php if (FreshRSS_Auth::hasAccess('admin')) { ?>
<div class="form-group">
<label class="group-name" for="timeout"><?php echo _t('sub.feed.timeout'); ?></label>
diff --git a/app/views/helpers/index/normal/entry_bottom.phtml b/app/views/helpers/index/normal/entry_bottom.phtml
index 6417da4cb..784a41e1f 100644
--- a/app/views/helpers/index/normal/entry_bottom.phtml
+++ b/app/views/helpers/index/normal/entry_bottom.phtml
@@ -7,6 +7,7 @@
$bottomline_read = FreshRSS_Context::$user_conf->bottomline_read;
$bottomline_favorite = FreshRSS_Context::$user_conf->bottomline_favorite;
$bottomline_sharing = FreshRSS_Context::$user_conf->bottomline_sharing && (count($sharing) > 0);
+ $bottomline_labels = true; //TODO
$bottomline_tags = FreshRSS_Context::$user_conf->bottomline_tags;
$bottomline_date = FreshRSS_Context::$user_conf->bottomline_date;
$bottomline_link = FreshRSS_Context::$user_conf->bottomline_link;
@@ -32,8 +33,41 @@
echo _i($this->entry->isFavorite() ? 'starred' : 'non-starred'); ?></a><?php
?></li><?php
}
- } ?>
- <li class="item"><?php
+ }
+ if ($bottomline_labels) {
+ ?><li class="item">
+ <div class="dropdown dynamictags">
+ <div id="dropdown-labels-<?php echo $this->entry->id();?>" class="dropdown-target"></div>
+ <?php echo FreshRSS_Themes::alt('label'); ?>
+ <a class="dropdown-toggle" href="#dropdown-labels-<?php echo $this->entry->id();?>"><?php
+ echo _t('index.menu.tags');
+ ?></a>
+ <ul class="dropdown-menu">
+ <li class="dropdown-close"><a href="#close">❌</a></li>
+ <!-- Ajax -->
+ </ul>
+ </div>
+ </li><?php
+ }
+ $tags = $bottomline_tags ? $this->entry->tags() : null;
+ if (!empty($tags)) {
+ ?><li class="item">
+ <div class="dropdown">
+ <div id="dropdown-tags-<?php echo $this->entry->id();?>" class="dropdown-target"></div>
+ <?php echo _i('tag'); ?>
+ <a class="dropdown-toggle" href="#dropdown-tags-<?php echo $this->entry->id();?>"><?php
+ echo _t('index.tag.related');
+ ?></a>
+ <ul class="dropdown-menu">
+ <li class="dropdown-close"><a href="#close">❌</a></li><?php
+ foreach ($tags as $tag) {
+ ?><li class="item"><a href="<?php echo _url('index', 'index', 'search', '#' . str_replace(' ', '+', htmlspecialchars_decode($tag, ENT_QUOTES))); ?>"><?php echo $tag; ?></a></li><?php
+ } ?>
+ </ul>
+ </div>
+ </li><?php
+ }
+ ?><li class="item"><?php
if ($bottomline_sharing) {
?><div class="dropdown">
<div id="dropdown-share-<?php echo $this->entry->id();?>" class="dropdown-target"></div>
@@ -69,24 +103,6 @@
</div>
<?php } ?>
</li><?php
- $tags = $bottomline_tags ? $this->entry->tags() : null;
- if (!empty($tags)) {
- ?><li class="item">
- <div class="dropdown">
- <div id="dropdown-tags-<?php echo $this->entry->id();?>" class="dropdown-target"></div>
- <?php echo _i('tag'); ?>
- <a class="dropdown-toggle" href="#dropdown-tags-<?php echo $this->entry->id();?>"><?php
- echo _t('index.tag.related');
- ?></a>
- <ul class="dropdown-menu">
- <li class="dropdown-close"><a href="#close">❌</a></li><?php
- foreach($tags as $tag) {
- ?><li class="item"><a href="<?php echo _url('index', 'index', 'search', '#' . htmlspecialchars_decode($tag, ENT_QUOTES)); ?>"><?php echo $tag; ?></a></li><?php
- } ?>
- </ul>
- </div>
- </li><?php
- }
if ($bottomline_date) {
?><li class="item date"><?php echo $this->entry->date(); ?></li><?php
}
diff --git a/app/views/index/global.phtml b/app/views/index/global.phtml
index 2f25b6dc2..3566abe7e 100644
--- a/app/views/index/global.phtml
+++ b/app/views/index/global.phtml
@@ -1,6 +1,11 @@
<?php
$this->partial('nav_menu');
+ flush();
+ if (isset($this->callbackBeforeEntries)) {
+ call_user_func($this->callbackBeforeEntries, $this);
+ }
+
$class = '';
if (FreshRSS_Context::$user_conf->hide_read_feeds &&
FreshRSS_Context::isStateEnabled(FreshRSS_Entry::STATE_NOT_READ) &&
diff --git a/app/views/index/normal.phtml b/app/views/index/normal.phtml
index c7cab2d3f..d5ae8e2f9 100644
--- a/app/views/index/normal.phtml
+++ b/app/views/index/normal.phtml
@@ -3,6 +3,11 @@
$this->partial('aside_feed');
$this->partial('nav_menu');
+flush();
+if (isset($this->callbackBeforeEntries)) {
+ call_user_func($this->callbackBeforeEntries, $this);
+}
+
if (!empty($this->entries)) {
$display_today = true;
$display_yesterday = true;
@@ -20,7 +25,7 @@ if (!empty($this->entries)) {
</div><?php
foreach ($this->entries as $item) {
$this->entry = Minz_ExtensionManager::callHook('entry_before_display', $item);
- if (is_null($this->entry)) {
+ if ($this->entry == null) {
continue;
}
@@ -67,10 +72,19 @@ if (!empty($this->entries)) {
?><div class="flux_content">
<div class="content <?php echo $content_width; ?>">
<h1 class="title"><a target="_blank" rel="noreferrer" class="go_website" href="<?php echo $this->entry->link(); ?>"><?php echo $this->entry->title(); ?></a></h1>
- <?php
- $author = $this->entry->author();
- echo $author != '' ? '<div class="author">' . _t('gen.short.by_author', $author) . '</div>' : '',
- $lazyload && $hidePosts ? lazyimg($this->entry->content()) : $this->entry->content();
+ <div class="author"><?php
+ $authors = $this->entry->authors();
+ if (is_array($authors)):
+ $first = true;
+ foreach ($authors as $author):
+ echo $first ? _t('gen.short.by_author') . ' ' : '· ';
+ $first = false;
+ ?>
+<em><a href="<?php echo _url('index', 'index', 'search', 'author:' . str_replace(' ', '+', htmlspecialchars_decode($author, ENT_QUOTES))); ?>"><?php echo $author; ?></a></em>
+ <?php endforeach; ?>
+ </div><?php
+ endif;
+ echo $lazyload && $hidePosts ? lazyimg($this->entry->content()) : $this->entry->content();
?>
</div><?php
diff --git a/app/views/index/reader.phtml b/app/views/index/reader.phtml
index eb6613b28..c15b936ee 100644
--- a/app/views/index/reader.phtml
+++ b/app/views/index/reader.phtml
@@ -1,6 +1,11 @@
<?php
$this->partial('nav_menu');
+flush();
+if (isset($this->callbackBeforeEntries)) {
+ call_user_func($this->callbackBeforeEntries, $this);
+}
+
if (!empty($this->entries)) {
$lazyload = FreshRSS_Context::$user_conf->lazyload;
$content_width = FreshRSS_Context::$user_conf->content_width;
@@ -39,9 +44,19 @@ if (!empty($this->entries)) {
<h1 class="title"><a target="_blank" rel="noreferrer" class="go_website" href="<?php echo $item->link(); ?>"><?php echo $item->title(); ?></a></h1>
<div class="author"><?php
- $author = $item->author();
- echo $author != '' ? _t('gen.short.by_author', $author) . ' — ' : '',
- $item->date();
+ $authors = $item->authors();
+ if (is_array($authors)):
+ $first = true;
+ foreach ($authors as $author):
+ echo $first ? _t('gen.short.by_author') . ' ' : '· ';
+ $first = false;
+ ?>
+<em><a href="<?php echo _url('index', 'index', 'search', 'author:' . str_replace(' ', '+', htmlspecialchars_decode($author, ENT_QUOTES))); ?>"><?php echo $author; ?></a></em>
+ <?php
+ endforeach;
+ echo ' — ';
+ endif;
+ echo $item->date();
?></div>
<?php echo $item->content(); ?>
diff --git a/app/views/index/rss.phtml b/app/views/index/rss.phtml
index 86074517c..104e03d15 100755
--- a/app/views/index/rss.phtml
+++ b/app/views/index/rss.phtml
@@ -13,10 +13,20 @@ foreach ($this->entries as $item) {
<item>
<title><?php echo $item->title(); ?></title>
<link><?php echo $item->link(); ?></link>
- <?php $author = $item->author(); ?>
- <?php if ($author != '') { ?>
- <dc:creator><?php echo $author; ?></dc:creator>
- <?php } ?>
+ <?php
+ $authors = $item->authors();
+ if (is_array($authors)) {
+ foreach ($authors as $author) {
+ echo "\t\t\t" , '<author>', $author, '</author>', "\n";
+ }
+ }
+ $categories = $item->tags();
+ if (is_array($categories)) {
+ foreach ($categories as $category) {
+ echo "\t\t\t" , '<category>', $category, '</category>', "\n";
+ }
+ }
+ ?>
<description><![CDATA[<?php
echo $item->content();
?>]]></description>
diff --git a/app/views/javascript/nbUnreadsPerFeed.phtml b/app/views/javascript/nbUnreadsPerFeed.phtml
index 68f98ce9e..ce4db37b7 100644
--- a/app/views/javascript/nbUnreadsPerFeed.phtml
+++ b/app/views/javascript/nbUnreadsPerFeed.phtml
@@ -1,8 +1,14 @@
<?php
-$result = array();
+$result = array(
+ 'feeds' => array(),
+ 'tags' => array(),
+);
foreach ($this->categories as $cat) {
foreach ($cat->feeds() as $feed) {
- $result[$feed->id()] = $feed->nbNotRead();
+ $result['feeds'][$feed->id()] = $feed->nbNotRead();
}
}
+foreach ($this->tags as $tag) {
+ $result['tags'][$tag->id()] = $tag->nbUnread();
+}
echo json_encode($result);
diff --git a/app/views/tag/getTagsForEntry.phtml b/app/views/tag/getTagsForEntry.phtml
new file mode 100644
index 000000000..76b2ada4e
--- /dev/null
+++ b/app/views/tag/getTagsForEntry.phtml
@@ -0,0 +1,2 @@
+<?php
+echo json_encode($this->tags);