aboutsummaryrefslogtreecommitdiff
path: root/app
diff options
context:
space:
mode:
authorGravatar Alexandre Alapetite <alexandre@alapetite.fr> 2015-05-30 20:46:41 +0200
committerGravatar Alexandre Alapetite <alexandre@alapetite.fr> 2015-05-30 20:46:41 +0200
commit185dba88c1da23f3fa4e787635e42fd81002b2cc (patch)
tree8122644648450a7722b617a0d106bca4d1106d95 /app
parentca58a265e6a702e42e25f5bf2393896b5517b0be (diff)
parent00e00849815f35ab3e3a1da2cbfa515f4bace392 (diff)
Merge 1.1.1-dev into /beta
https://github.com/FreshRSS/FreshRSS/issues/845
Diffstat (limited to 'app')
-rwxr-xr-xapp/Controllers/configureController.php84
-rwxr-xr-xapp/Controllers/feedController.php100
-rw-r--r--app/Controllers/importExportController.php3
-rwxr-xr-xapp/Controllers/indexController.php3
-rw-r--r--app/Exceptions/BadUrlException.php3
-rw-r--r--app/Exceptions/ContextException.php6
-rw-r--r--app/Exceptions/DAOException.php5
-rw-r--r--app/Exceptions/EntriesGetterException.php6
-rw-r--r--app/Exceptions/FeedException.php7
-rw-r--r--app/FreshRSS.php3
-rw-r--r--app/Models/CategoryDAO.php2
-rw-r--r--app/Models/ConfigurationSetter.php13
-rw-r--r--app/Models/Context.php3
-rw-r--r--app/Models/Entry.php20
-rw-r--r--app/Models/EntryDAO.php307
-rw-r--r--app/Models/EntryDAOSQLite.php15
-rw-r--r--app/Models/Feed.php8
-rw-r--r--app/Models/FeedDAO.php21
-rw-r--r--app/Models/Search.php229
-rw-r--r--app/Models/Searchable.php6
-rw-r--r--app/Models/Share.php2
-rw-r--r--app/Models/UserQuery.php226
-rw-r--r--app/SQL/install.sql.mysql.php7
-rw-r--r--app/SQL/install.sql.sqlite.php7
-rw-r--r--app/i18n/cz/admin.php170
-rw-r--r--app/i18n/cz/conf.php169
-rw-r--r--app/i18n/cz/feedback.php110
-rw-r--r--app/i18n/cz/gen.php164
-rw-r--r--app/i18n/cz/index.php61
-rw-r--r--app/i18n/cz/install.php107
-rw-r--r--app/i18n/cz/sub.php61
-rw-r--r--app/i18n/de/admin.php4
-rw-r--r--app/i18n/de/conf.php1
-rw-r--r--app/i18n/de/gen.php2
-rw-r--r--app/i18n/en/admin.php2
-rw-r--r--app/i18n/en/conf.php7
-rw-r--r--app/i18n/en/feedback.php6
-rw-r--r--app/i18n/en/gen.php6
-rw-r--r--app/i18n/en/install.php6
-rw-r--r--app/i18n/en/sub.php4
-rw-r--r--app/i18n/fr/conf.php1
-rw-r--r--app/i18n/fr/gen.php2
-rw-r--r--app/install.php9
-rw-r--r--app/views/auth/index.phtml12
-rw-r--r--app/views/configure/archiving.phtml6
-rw-r--r--app/views/configure/display.phtml28
-rw-r--r--app/views/configure/queries.phtml48
-rw-r--r--app/views/configure/reading.phtml43
-rw-r--r--app/views/configure/sharing.phtml20
-rw-r--r--app/views/configure/shortcut.phtml28
50 files changed, 1801 insertions, 362 deletions
diff --git a/app/Controllers/configureController.php b/app/Controllers/configureController.php
index 38ccd2b2d..248a3edcc 100755
--- a/app/Controllers/configureController.php
+++ b/app/Controllers/configureController.php
@@ -112,6 +112,7 @@ class FreshRSS_configure_Controller extends Minz_ActionController {
FreshRSS_Context::$user_conf->sticky_post = Minz_Request::param('sticky_post', false);
FreshRSS_Context::$user_conf->reading_confirm = Minz_Request::param('reading_confirm', false);
FreshRSS_Context::$user_conf->auto_remove_article = Minz_Request::param('auto_remove_article', false);
+ FreshRSS_Context::$user_conf->mark_updated_article_unread = Minz_Request::param('mark_updated_article_unread', false);
FreshRSS_Context::$user_conf->sort_order = Minz_Request::param('sort_order', 'DESC');
FreshRSS_Context::$user_conf->mark_when = array(
'article' => Minz_Request::param('mark_open_article', false),
@@ -241,13 +242,16 @@ class FreshRSS_configure_Controller extends Minz_ActionController {
* checking if categories and feeds are still in use.
*/
public function queriesAction() {
+ $category_dao = new FreshRSS_CategoryDAO();
+ $feed_dao = FreshRSS_Factory::createFeedDao();
if (Minz_Request::isPost()) {
- $queries = Minz_Request::param('queries', array());
+ $params = Minz_Request::param('queries', array());
- foreach ($queries as $key => $query) {
+ foreach ($params as $key => $query) {
if (!$query['name']) {
$query['name'] = _t('conf.query.number', $key + 1);
}
+ $queries[] = new FreshRSS_UserQuery($query, $feed_dao, $category_dao);
}
FreshRSS_Context::$user_conf->queries = $queries;
FreshRSS_Context::$user_conf->save();
@@ -255,62 +259,9 @@ class FreshRSS_configure_Controller extends Minz_ActionController {
Minz_Request::good(_t('feedback.conf.updated'),
array('c' => 'configure', 'a' => 'queries'));
} else {
- $this->view->query_get = array();
- $cat_dao = new FreshRSS_CategoryDAO();
- $feed_dao = FreshRSS_Factory::createFeedDao();
+ $this->view->queries = array();
foreach (FreshRSS_Context::$user_conf->queries as $key => $query) {
- if (!isset($query['get'])) {
- continue;
- }
-
- switch ($query['get'][0]) {
- case 'c':
- $category = $cat_dao->searchById(substr($query['get'], 2));
-
- $deprecated = true;
- $cat_name = '';
- if ($category) {
- $cat_name = $category->name();
- $deprecated = false;
- }
-
- $this->view->query_get[$key] = array(
- 'type' => 'category',
- 'name' => $cat_name,
- 'deprecated' => $deprecated,
- );
- break;
- case 'f':
- $feed = $feed_dao->searchById(substr($query['get'], 2));
-
- $deprecated = true;
- $feed_name = '';
- if ($feed) {
- $feed_name = $feed->name();
- $deprecated = false;
- }
-
- $this->view->query_get[$key] = array(
- 'type' => 'feed',
- 'name' => $feed_name,
- 'deprecated' => $deprecated,
- );
- break;
- case 's':
- $this->view->query_get[$key] = array(
- 'type' => 'favorite',
- 'name' => 'favorite',
- 'deprecated' => false,
- );
- break;
- case 'a':
- $this->view->query_get[$key] = array(
- 'type' => 'all',
- 'name' => 'all',
- 'deprecated' => false,
- );
- break;
- }
+ $this->view->queries[$key] = new FreshRSS_UserQuery($query, $feed_dao, $category_dao);
}
}
@@ -325,16 +276,17 @@ class FreshRSS_configure_Controller extends Minz_ActionController {
* lean data.
*/
public function addQueryAction() {
- $whitelist = array('get', 'order', 'name', 'search', 'state');
- $queries = FreshRSS_Context::$user_conf->queries;
- $query = Minz_Request::params();
- $query['name'] = _t('conf.query.number', count($queries) + 1);
- foreach ($query as $key => $value) {
- if (!in_array($key, $whitelist)) {
- unset($query[$key]);
- }
+ $category_dao = new FreshRSS_CategoryDAO();
+ $feed_dao = FreshRSS_Factory::createFeedDao();
+ $queries = array();
+ foreach (FreshRSS_Context::$user_conf->queries as $key => $query) {
+ $queries[$key] = new FreshRSS_UserQuery($query, $feed_dao, $category_dao);
}
- $queries[] = $query;
+ $params = Minz_Request::params();
+ $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);
+
FreshRSS_Context::$user_conf->queries = $queries;
FreshRSS_Context::$user_conf->save();
diff --git a/app/Controllers/feedController.php b/app/Controllers/feedController.php
index 6f544d834..8db273aca 100755
--- a/app/Controllers/feedController.php
+++ b/app/Controllers/feedController.php
@@ -145,8 +145,8 @@ class FreshRSS_feed_Controller extends Minz_ActionController {
// Call the extension hook
$name = $feed->name();
$feed = Minz_ExtensionManager::callHook('feed_before_insert', $feed);
- if (is_null($feed)) {
- Minz_Request::bad(_t('feed_not_added', $name), $url_redirect);
+ if ($feed === null) {
+ Minz_Request::bad(_t('feedback.sub.feed.not_added', $name), $url_redirect);
}
$values = array(
@@ -181,7 +181,6 @@ class FreshRSS_feed_Controller extends Minz_ActionController {
// Use a shared statement and a transaction to improve a LOT the
// performances.
- $prepared_statement = $entryDAO->addEntryPrepare();
$feedDAO->beginTransaction();
foreach ($entries as $entry) {
// Entries are added without any verification.
@@ -190,13 +189,13 @@ class FreshRSS_feed_Controller extends Minz_ActionController {
$entry->_isRead($is_read);
$entry = Minz_ExtensionManager::callHook('entry_before_insert', $entry);
- if (is_null($entry)) {
+ if ($entry === null) {
// An extension has returned a null value, there is nothing to insert.
continue;
}
$values = $entry->toArray();
- $entryDAO->addEntry($values, $prepared_statement);
+ $entryDAO->addEntry($values);
}
$feedDAO->updateLastUpdate($feed->id());
$feedDAO->commit();
@@ -302,17 +301,17 @@ class FreshRSS_feed_Controller extends Minz_ActionController {
continue;
}
+ $url = $feed->url(); //For detection of HTTP 301
try {
// Load entries
$feed->load(false);
} catch (FreshRSS_Feed_Exception $e) {
Minz_Log::notice($e->getMessage());
- $feedDAO->updateLastUpdate($feed->id(), 1);
+ $feedDAO->updateLastUpdate($feed->id(), true);
$feed->unlock();
continue;
}
- $url = $feed->url();
$feed_history = $feed->keepHistory();
if ($feed_history == -2) {
// TODO: -2 must be a constant!
@@ -323,49 +322,66 @@ class FreshRSS_feed_Controller extends Minz_ActionController {
// We want chronological order and SimplePie uses reverse order.
$entries = array_reverse($feed->entries());
if (count($entries) > 0) {
- // For this feed, check last n entry GUIDs already in database.
- $existing_guids = array_fill_keys($entryDAO->listLastGuidsByFeed(
- $feed->id(), count($entries) + 10
- ), 1);
- $use_declared_date = empty($existing_guids);
+ $newGuids = array();
+ foreach ($entries as $entry) {
+ $newGuids[] = $entry->guid();
+ }
+ // For this feed, check existing GUIDs already in database.
+ $existingHashForGuids = $entryDAO->listHashForFeedGuids($feed->id(), $newGuids);
+ unset($newGuids);
+ $oldGuids = array();
// Add entries in database if possible.
- $prepared_statement = $entryDAO->addEntryPrepare();
- $feedDAO->beginTransaction();
foreach ($entries as $entry) {
$entry_date = $entry->date(true);
- if (isset($existing_guids[$entry->guid()]) ||
- ($feed_history == 0 && $entry_date < $date_min)) {
- // This entry already exists in DB or should not be added
- // considering configuration and date.
- continue;
- }
-
- $id = uTimeString();
- if ($use_declared_date || $entry_date < $date_min) {
- // Use declared date at first import.
- $id = min(time(), $entry_date) . uSecString();
+ if (isset($existingHashForGuids[$entry->guid()])) {
+ $existingHash = $existingHashForGuids[$entry->guid()];
+ if (strcasecmp($existingHash, $entry->hash()) === 0 || $existingHash === '00000000000000000000000000000000') {
+ //This entry already exists and is unchanged. TODO: Remove the test with the zero'ed hash in FreshRSS v1.3
+ $oldGuids[] = $entry->guid();
+ } else { //This entry already exists but has been updated
+ Minz_Log::debug('Entry with GUID `' . $entry->guid() . '` updated in feed ' . $feed->id() .
+ ', old hash ' . $existingHash . ', new hash ' . $entry->hash());
+ //TODO: Make an updated/is_read policy by feed, in addition to the global one.
+ $entry->_isRead(FreshRSS_Context::$user_conf->mark_updated_article_unread ? false : null); //Change is_read according to policy.
+ if (!$entryDAO->hasTransaction()) {
+ $entryDAO->beginTransaction();
+ }
+ $entryDAO->updateEntry($entry->toArray());
+ }
+ } elseif ($feed_history == 0 && $entry_date < $date_min) {
+ // This entry should not be added considering configuration and date.
+ $oldGuids[] = $entry->guid();
+ } else {
+ if ($entry_date < $date_min) {
+ $id = min(time(), $entry_date) . uSecString();
+ $entry->_isRead(true); //Old article that was not in database. Probably an error, so mark as read
+ } else {
+ $id = uTimeString();
+ $entry->_isRead($is_read);
+ }
+ $entry->_id($id);
+
+ $entry = Minz_ExtensionManager::callHook('entry_before_insert', $entry);
+ if ($entry === null) {
+ // An extension has returned a null value, there is nothing to insert.
+ continue;
+ }
+
+ if (!$entryDAO->hasTransaction()) {
+ $entryDAO->beginTransaction();
+ }
+ $entryDAO->addEntry($entry->toArray());
}
-
- $entry->_id($id);
- $entry->_isRead($is_read);
-
- $entry = Minz_ExtensionManager::callHook('entry_before_insert', $entry);
- if (is_null($entry)) {
- // An extension has returned a null value, there is nothing to insert.
- continue;
- }
-
- $values = $entry->toArray();
- $entryDAO->addEntry($values, $prepared_statement);
}
+ $entryDAO->updateLastSeen($feed->id(), $oldGuids);
}
if ($feed_history >= 0 && rand(0, 30) === 1) {
// TODO: move this function in web cron when available (see entry::purge)
// Remove old entries once in 30.
- if (!$feedDAO->hasTransaction()) {
- $feedDAO->beginTransaction();
+ if (!$entryDAO->hasTransaction()) {
+ $entryDAO->beginTransaction();
}
$nb = $feedDAO->cleanOldEntries($feed->id(),
@@ -377,9 +393,9 @@ class FreshRSS_feed_Controller extends Minz_ActionController {
}
}
- $feedDAO->updateLastUpdate($feed->id(), 0, $feedDAO->hasTransaction());
- if ($feedDAO->hasTransaction()) {
- $feedDAO->commit();
+ $feedDAO->updateLastUpdate($feed->id(), 0, $entryDAO->hasTransaction());
+ if ($entryDAO->hasTransaction()) {
+ $entryDAO->commit();
}
if ($feed->url() !== $url) {
diff --git a/app/Controllers/importExportController.php b/app/Controllers/importExportController.php
index 589777b2a..26b163e43 100644
--- a/app/Controllers/importExportController.php
+++ b/app/Controllers/importExportController.php
@@ -361,7 +361,6 @@ class FreshRSS_importExport_Controller extends Minz_ActionController {
}
// Then, articles are imported.
- $prepared_statement = $this->entryDAO->addEntryPrepare();
$this->entryDAO->beginTransaction();
foreach ($article_object['items'] as $item) {
if (!isset($article_to_feed[$item['id']])) {
@@ -396,7 +395,7 @@ class FreshRSS_importExport_Controller extends Minz_ActionController {
}
$values = $entry->toArray();
- $id = $this->entryDAO->addEntry($values, $prepared_statement);
+ $id = $this->entryDAO->addEntry($values);
if (!$error && ($id === false)) {
$error = true;
diff --git a/app/Controllers/indexController.php b/app/Controllers/indexController.php
index c53d3223e..baaf99065 100755
--- a/app/Controllers/indexController.php
+++ b/app/Controllers/indexController.php
@@ -137,6 +137,7 @@ class FreshRSS_index_Controller extends Minz_ActionController {
}
// No layout for RSS output.
+ $this->view->url = empty($_SERVER['QUERY_STRING']) ? '' : '?' . $_SERVER['QUERY_STRING'];
$this->view->rss_title = FreshRSS_Context::$name . ' | ' . Minz_View::title();
$this->view->_useLayout(false);
header('Content-Type: application/rss+xml; charset=utf-8');
@@ -173,7 +174,7 @@ class FreshRSS_index_Controller extends Minz_ActionController {
FreshRSS_Context::$state |= FreshRSS_Entry::STATE_READ;
}
- FreshRSS_Context::$search = Minz_Request::param('search', '');
+ FreshRSS_Context::$search = new FreshRSS_Search(Minz_Request::param('search', ''));
FreshRSS_Context::$order = Minz_Request::param(
'order', FreshRSS_Context::$user_conf->sort_order
);
diff --git a/app/Exceptions/BadUrlException.php b/app/Exceptions/BadUrlException.php
index 59574e1e5..d2509e4ba 100644
--- a/app/Exceptions/BadUrlException.php
+++ b/app/Exceptions/BadUrlException.php
@@ -1,6 +1,9 @@
<?php
+
class FreshRSS_BadUrl_Exception extends FreshRSS_Feed_Exception {
+
public function __construct($url) {
parent::__construct('`' . $url . '` is not a valid URL');
}
+
}
diff --git a/app/Exceptions/ContextException.php b/app/Exceptions/ContextException.php
index 357751b7c..8f05eccd6 100644
--- a/app/Exceptions/ContextException.php
+++ b/app/Exceptions/ContextException.php
@@ -3,8 +3,6 @@
/**
* An exception raised when a context is invalid
*/
-class FreshRSS_Context_Exception extends Exception {
- public function __construct($message) {
- parent::__construct($message);
- }
+class FreshRSS_Context_Exception extends \Exception {
+
}
diff --git a/app/Exceptions/DAOException.php b/app/Exceptions/DAOException.php
new file mode 100644
index 000000000..e48e521ab
--- /dev/null
+++ b/app/Exceptions/DAOException.php
@@ -0,0 +1,5 @@
+<?php
+
+class FreshRSS_DAO_Exception extends \Exception {
+
+}
diff --git a/app/Exceptions/EntriesGetterException.php b/app/Exceptions/EntriesGetterException.php
index 5f47c830b..8aace1840 100644
--- a/app/Exceptions/EntriesGetterException.php
+++ b/app/Exceptions/EntriesGetterException.php
@@ -1,7 +1,5 @@
<?php
-class FreshRSS_EntriesGetter_Exception extends Exception {
- public function __construct($message) {
- parent::__construct($message);
- }
+class FreshRSS_EntriesGetter_Exception extends \Exception {
+
}
diff --git a/app/Exceptions/FeedException.php b/app/Exceptions/FeedException.php
index 2433b3964..6c0cb3755 100644
--- a/app/Exceptions/FeedException.php
+++ b/app/Exceptions/FeedException.php
@@ -1,6 +1,5 @@
<?php
-class FreshRSS_Feed_Exception extends Exception {
- public function __construct($message) {
- parent::__construct($message);
- }
+
+class FreshRSS_Feed_Exception extends \Exception {
+
}
diff --git a/app/FreshRSS.php b/app/FreshRSS.php
index 021687999..044de9cd4 100644
--- a/app/FreshRSS.php
+++ b/app/FreshRSS.php
@@ -63,10 +63,11 @@ class FreshRSS extends Minz_FrontController {
// Basic protection against XSRF attacks
FreshRSS_Auth::removeAccess();
$http_referer = empty($_SERVER['HTTP_REFERER']) ? '' : $_SERVER['HTTP_REFERER'];
+ Minz_Translate::init('en'); //TODO: Better choice of fallback language
Minz_Error::error(
403,
array('error' => array(
- _t('access_denied'),
+ _t('feedback.access.denied'),
' [HTTP_REFERER=' . htmlspecialchars($http_referer) . ']'
))
);
diff --git a/app/Models/CategoryDAO.php b/app/Models/CategoryDAO.php
index 27a558522..189a5f0e4 100644
--- a/app/Models/CategoryDAO.php
+++ b/app/Models/CategoryDAO.php
@@ -1,6 +1,6 @@
<?php
-class FreshRSS_CategoryDAO extends Minz_ModelPdo {
+class FreshRSS_CategoryDAO extends Minz_ModelPdo implements FreshRSS_Searchable {
public function addCategory($valuesTmp) {
$sql = 'INSERT INTO `' . $this->prefix . 'category`(name) VALUES(?)';
$stm = $this->bd->prepare($sql);
diff --git a/app/Models/ConfigurationSetter.php b/app/Models/ConfigurationSetter.php
index eeb1f2f4c..4bd29ecb0 100644
--- a/app/Models/ConfigurationSetter.php
+++ b/app/Models/ConfigurationSetter.php
@@ -117,12 +117,9 @@ class FreshRSS_ConfigurationSetter {
private function _queries(&$data, $values) {
$data['queries'] = array();
foreach ($values as $value) {
- $value = array_filter($value);
- $params = $value;
- unset($params['name']);
- unset($params['url']);
- $value['url'] = Minz_Url::display(array('params' => $params));
- $data['queries'][] = $value;
+ if ($value instanceof FreshRSS_UserQuery) {
+ $data['queries'][] = $value->toArray();
+ }
}
}
@@ -192,6 +189,10 @@ class FreshRSS_ConfigurationSetter {
$data['auto_remove_article'] = $this->handleBool($value);
}
+ private function _mark_updated_article_unread(&$data, $value) {
+ $data['mark_updated_article_unread'] = $this->handleBool($value);
+ }
+
private function _display_categories(&$data, $value) {
$data['display_categories'] = $this->handleBool($value);
}
diff --git a/app/Models/Context.php b/app/Models/Context.php
index 1c770c756..dbdbfaa69 100644
--- a/app/Models/Context.php
+++ b/app/Models/Context.php
@@ -30,7 +30,7 @@ class FreshRSS_Context {
public static $state = 0;
public static $order = 'DESC';
public static $number = 0;
- public static $search = '';
+ public static $search;
public static $first_id = '';
public static $next_id = '';
public static $id_max = '';
@@ -301,4 +301,5 @@ class FreshRSS_Context {
}
return false;
}
+
}
diff --git a/app/Models/Entry.php b/app/Models/Entry.php
index 346c98a92..a562a963a 100644
--- a/app/Models/Entry.php
+++ b/app/Models/Entry.php
@@ -14,7 +14,8 @@ class FreshRSS_Entry extends Minz_Model {
private $content;
private $link;
private $date;
- private $is_read;
+ private $hash = null;
+ private $is_read; //Nullable boolean
private $is_favorite;
private $feed;
private $tags;
@@ -88,6 +89,14 @@ 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));
+ }
+ return $this->hash;
+ }
+
public function _id($value) {
$this->id = $value;
}
@@ -95,23 +104,28 @@ class FreshRSS_Entry extends Minz_Model {
$this->guid = $value;
}
public function _title($value) {
+ $this->hash = null;
$this->title = $value;
}
public function _author($value) {
+ $this->hash = null;
$this->author = $value;
}
public function _content($value) {
+ $this->hash = null;
$this->content = $value;
}
public function _link($value) {
+ $this->hash = null;
$this->link = $value;
}
public function _date($value) {
+ $this->hash = null;
$value = intval($value);
$this->date = $value > 1 ? $value : time();
}
public function _isRead($value) {
- $this->is_read = $value;
+ $this->is_read = $value === null ? null : (bool)$value;
}
public function _isFavorite($value) {
$this->is_favorite = $value;
@@ -120,6 +134,7 @@ class FreshRSS_Entry extends Minz_Model {
$this->feed = $value;
}
public function _tags($value) {
+ $this->hash = null;
if (!is_array($value)) {
$value = array($value);
}
@@ -182,6 +197,7 @@ class FreshRSS_Entry extends Minz_Model {
'content' => $this->content(),
'link' => $this->link(),
'date' => $this->date(true),
+ 'hash' => $this->hash(),
'is_read' => $this->isRead(),
'is_favorite' => $this->isFavorite(),
'id_feed' => $this->feed(),
diff --git a/app/Models/EntryDAO.php b/app/Models/EntryDAO.php
index 61beeea13..f939a0fb3 100644
--- a/app/Models/EntryDAO.php
+++ b/app/Models/EntryDAO.php
@@ -1,25 +1,72 @@
<?php
-class FreshRSS_EntryDAO extends Minz_ModelPdo {
+class FreshRSS_EntryDAO extends Minz_ModelPdo implements FreshRSS_Searchable {
public function isCompressed() {
return parent::$sharedDbType !== 'sqlite';
}
- public function addEntryPrepare() {
- $sql = 'INSERT INTO `' . $this->prefix . 'entry`(id, guid, title, author, '
- . ($this->isCompressed() ? 'content_bin' : 'content')
- . ', link, date, is_read, is_favorite, id_feed, tags) '
- . 'VALUES(?, ?, ?, ?, '
- . ($this->isCompressed() ? 'COMPRESS(?)' : '?')
- . ', ?, ?, ?, ?, ?, ?)';
- return $this->bd->prepare($sql);
+ protected function addColumn($name) {
+ Minz_Log::debug('FreshRSS_EntryDAO::autoAddColumn: ' . $name);
+ $hasTransaction = false;
+ try {
+ $stm = null;
+ if ($name === 'lastSeen') { //v1.2
+ if (!$this->bd->inTransaction()) {
+ $this->bd->beginTransaction();
+ $hasTransaction = true;
+ }
+ $stm = $this->bd->prepare('ALTER TABLE `' . $this->prefix . 'entry` ADD COLUMN lastSeen INT(11) DEFAULT 0');
+ if ($stm && $stm->execute()) {
+ $stm = $this->bd->prepare('CREATE INDEX entry_lastSeen_index ON `' . $this->prefix . 'entry`(`lastSeen`);'); //"IF NOT EXISTS" does not exist in MySQL 5.7
+ if ($stm && $stm->execute()) {
+ if ($hasTransaction) {
+ $this->bd->commit();
+ }
+ return true;
+ }
+ }
+ if ($hasTransaction) {
+ $this->bd->rollBack();
+ }
+ } elseif ($name === 'hash') { //v1.2
+ $stm = $this->bd->prepare('ALTER TABLE `' . $this->prefix . 'entry` ADD COLUMN hash BINARY(16)');
+ return $stm && $stm->execute();
+ }
+ } catch (Exception $e) {
+ Minz_Log::debug('FreshRSS_EntryDAO::autoAddColumn error: ' . $e->getMessage());
+ if ($hasTransaction) {
+ $this->bd->rollBack();
+ }
+ }
+ return false;
+ }
+
+ protected function autoAddColumn($errorInfo) {
+ if (isset($errorInfo[0])) {
+ if ($errorInfo[0] == '42S22') { //ER_BAD_FIELD_ERROR
+ foreach (array('lastSeen', 'hash') as $column) {
+ if (stripos($errorInfo[2], $column) !== false) {
+ return $this->addColumn($column);
+ }
+ }
+ }
+ }
+ return false;
}
- public function addEntry($valuesTmp, $preparedStatement = null) {
- $stm = $preparedStatement === null ?
- FreshRSS_EntryDAO::addEntryPrepare() :
- $preparedStatement;
+ private $addEntryPrepared = null;
+
+ public function addEntry($valuesTmp) {
+ if ($this->addEntryPrepared === null) {
+ $sql = 'INSERT INTO `' . $this->prefix . 'entry` (id, guid, title, author, '
+ . ($this->isCompressed() ? 'content_bin' : 'content')
+ . ', link, date, lastSeen, hash, is_read, is_favorite, id_feed, tags) '
+ . 'VALUES(?, ?, ?, ?, '
+ . ($this->isCompressed() ? 'COMPRESS(?)' : '?')
+ . ', ?, ?, ?, ?, ?, ?, ?, ?)';
+ $this->addEntryPrepared = $this->bd->prepare($sql);
+ }
$values = array(
$valuesTmp['id'],
@@ -29,55 +76,75 @@ class FreshRSS_EntryDAO extends Minz_ModelPdo {
$valuesTmp['content'],
substr($valuesTmp['link'], 0, 1023),
$valuesTmp['date'],
+ time(),
+ hex2bin($valuesTmp['hash']), // X'09AF' hexadecimal literals do not work with SQLite/PDO
$valuesTmp['is_read'] ? 1 : 0,
$valuesTmp['is_favorite'] ? 1 : 0,
$valuesTmp['id_feed'],
substr($valuesTmp['tags'], 0, 1023),
);
- if ($stm && $stm->execute($values)) {
+ if ($this->addEntryPrepared && $this->addEntryPrepared->execute($values)) {
return $this->bd->lastInsertId();
} else {
- $info = $stm == null ? array(2 => 'syntax error') : $stm->errorInfo();
- if ((int)($info[0] / 1000) !== 23) { //Filter out "SQLSTATE Class code 23: Constraint Violation" because of expected duplicate entries
+ $info = $this->addEntryPrepared == null ? array(0 => '', 1 => '', 2 => 'syntax error') : $this->addEntryPrepared->errorInfo();
+ if ($this->autoAddColumn($info)) {
+ return $this->addEntry($valuesTmp);
+ } elseif ((int)($info[0] / 1000) !== 23) { //Filter out "SQLSTATE Class code 23: Constraint Violation" because of expected duplicate entries
Minz_Log::error('SQL error addEntry: ' . $info[0] . ': ' . $info[1] . ' ' . $info[2]
- . ' while adding entry in feed ' . $valuesTmp['id_feed'] . ' with title: ' . $valuesTmp['title']);
- } /*else {
- Minz_Log::debug('SQL error ' . $info[0] . ': ' . $info[1] . ' ' . $info[2]
- . ' while adding entry in feed ' . $valuesTmp['id_feed'] . ' with title: ' . $valuesTmp['title']);
- }*/
+ . ' while adding entry in feed ' . $valuesTmp['id_feed'] . ' with title: ' . $valuesTmp['title']. ' ' . $this->addEntryPrepared);
+ }
return false;
}
}
- public function addEntryObject($entry, $conf, $feedHistory) {
- $existingGuids = array_fill_keys(
- $this->listLastGuidsByFeed($entry->feed(), 20), 1
- );
+ private $updateEntryPrepared = null;
- $nb_month_old = max($conf->old_entries, 1);
- $date_min = time() - (3600 * 24 * 30 * $nb_month_old);
-
- $eDate = $entry->date(true);
-
- if ($feedHistory == -2) {
- $feedHistory = $conf->keep_history_default;
+ public function updateEntry($valuesTmp) {
+ if (!isset($valuesTmp['is_read'])) {
+ $valuesTmp['is_read'] = null;
}
- if (!isset($existingGuids[$entry->guid()]) &&
- ($feedHistory != 0 || $eDate >= $date_min || $entry->isFavorite())) {
- $values = $entry->toArray();
-
- $useDeclaredDate = empty($existingGuids);
- $values['id'] = ($useDeclaredDate || $eDate < $date_min) ?
- min(time(), $eDate) . uSecString() :
- uTimeString();
+ if ($this->updateEntryPrepared === null) {
+ $sql = 'UPDATE `' . $this->prefix . 'entry` '
+ . 'SET title=?, author=?, '
+ . ($this->isCompressed() ? 'content_bin=COMPRESS(?)' : 'content=?')
+ . ', link=?, date=?, lastSeen=?, hash=?, '
+ . ($valuesTmp['is_read'] === null ? '' : 'is_read=?, ')
+ . 'tags=? '
+ . 'WHERE id_feed=? AND guid=?';
+ $this->updateEntryPrepared = $this->bd->prepare($sql);
+ }
- return $this->addEntry($values);
+ $values = array(
+ substr($valuesTmp['title'], 0, 255),
+ substr($valuesTmp['author'], 0, 255),
+ $valuesTmp['content'],
+ substr($valuesTmp['link'], 0, 1023),
+ $valuesTmp['date'],
+ time(),
+ hex2bin($valuesTmp['hash']),
+ );
+ if ($valuesTmp['is_read'] !== null) {
+ $values[] = $valuesTmp['is_read'] ? 1 : 0;
}
+ $values = array_merge($values, array(
+ substr($valuesTmp['tags'], 0, 1023),
+ $valuesTmp['id_feed'],
+ substr($valuesTmp['guid'], 0, 760),
+ ));
- // We don't return Entry object to avoid a research in DB
- return -1;
+ if ($this->updateEntryPrepared && $this->updateEntryPrepared->execute($values)) {
+ return $this->bd->lastInsertId();
+ } else {
+ $info = $this->updateEntryPrepared == null ? array(0 => '', 1 => '', 2 => 'syntax error') : $this->updateEntryPrepared->errorInfo();
+ if ($this->autoAddColumn($info)) {
+ return $this->updateEntry($valuesTmp);
+ }
+ Minz_Log::error('SQL error updateEntry: ' . $info[0] . ': ' . $info[1] . ' ' . $info[2]
+ . ' while updating entry with GUID ' . $valuesTmp['guid'] . ' in feed ' . $valuesTmp['id_feed']);
+ return false;
+ }
}
/**
@@ -94,6 +161,9 @@ class FreshRSS_EntryDAO extends Minz_ModelPdo {
if (!is_array($ids)) {
$ids = array($ids);
}
+ if (count($ids) < 1) {
+ return 0;
+ }
$sql = 'UPDATE `' . $this->prefix . 'entry` '
. 'SET is_favorite=? '
. 'WHERE id IN (' . str_repeat('?,', count($ids) - 1). '?)';
@@ -296,11 +366,11 @@ class FreshRSS_EntryDAO extends Minz_ModelPdo {
*
* If $idMax equals 0, a deprecated debug message is logged
*
- * @param integer $id feed ID
+ * @param integer $id_feed feed ID
* @param integer $idMax fail safe article ID
* @return integer affected rows
*/
- public function markReadFeed($id, $idMax = 0) {
+ public function markReadFeed($id_feed, $idMax = 0) {
if ($idMax == 0) {
$idMax = time() . '000000';
Minz_Log::debug('Calling markReadFeed(0) is deprecated!');
@@ -310,7 +380,7 @@ class FreshRSS_EntryDAO extends Minz_ModelPdo {
$sql = 'UPDATE `' . $this->prefix . 'entry` '
. 'SET is_read=1 '
. 'WHERE id_feed=? AND is_read=0 AND id <= ?';
- $values = array($id, $idMax);
+ $values = array($id_feed, $idMax);
$stm = $this->bd->prepare($sql);
if (!($stm && $stm->execute($values))) {
$info = $stm == null ? array(2 => 'syntax error') : $stm->errorInfo();
@@ -324,7 +394,7 @@ class FreshRSS_EntryDAO extends Minz_ModelPdo {
$sql = 'UPDATE `' . $this->prefix . 'feed` '
. 'SET cache_nbUnreads=cache_nbUnreads-' . $affected
. ' WHERE id=?';
- $values = array($id);
+ $values = array($id_feed);
$stm = $this->bd->prepare($sql);
if (!($stm && $stm->execute($values))) {
$info = $stm == null ? array(2 => 'syntax error') : $stm->errorInfo();
@@ -338,7 +408,7 @@ class FreshRSS_EntryDAO extends Minz_ModelPdo {
return $affected;
}
- public function searchByGuid($feed_id, $id) {
+ public function searchByGuid($id_feed, $guid) {
// un guid est unique pour un flux donné
$sql = 'SELECT id, guid, title, author, '
. ($this->isCompressed() ? 'UNCOMPRESS(content_bin) AS content' : 'content')
@@ -347,8 +417,8 @@ class FreshRSS_EntryDAO extends Minz_ModelPdo {
$stm = $this->bd->prepare($sql);
$values = array(
- $feed_id,
- $id
+ $id_feed,
+ $guid,
);
$stm->execute($values);
@@ -441,56 +511,50 @@ class FreshRSS_EntryDAO extends Minz_ModelPdo {
$where .= 'AND e1.id >= ' . $date_min . '000000 ';
}
$search = '';
- if ($filter !== '') {
- require_once(LIB_PATH . '/lib_date.php');
- $filter = trim($filter);
- $filter = addcslashes($filter, '\\%_');
- $terms = array_unique(explode(' ', $filter));
- //sort($terms); //Put #tags first //TODO: Put the cheapest filters first
- foreach ($terms as $word) {
- $word = trim($word);
- if (stripos($word, 'intitle:') === 0) {
- $word = substr($word, strlen('intitle:'));
- $search .= 'AND e1.title LIKE ? ';
- $values[] = '%' . $word .'%';
- } elseif (stripos($word, 'inurl:') === 0) {
- $word = substr($word, strlen('inurl:'));
- $search .= 'AND CONCAT(e1.link, e1.guid) LIKE ? ';
- $values[] = '%' . $word .'%';
- } elseif (stripos($word, 'author:') === 0) {
- $word = substr($word, strlen('author:'));
- $search .= 'AND e1.author LIKE ? ';
- $values[] = '%' . $word .'%';
- } elseif (stripos($word, 'date:') === 0) {
- $word = substr($word, strlen('date:'));
- list($minDate, $maxDate) = parseDateInterval($word);
- if ($minDate) {
- $search .= 'AND e1.id >= ' . $minDate . '000000 ';
- }
- if ($maxDate) {
- $search .= 'AND e1.id <= ' . $maxDate . '000000 ';
- }
- } elseif (stripos($word, 'pubdate:') === 0) {
- $word = substr($word, strlen('pubdate:'));
- list($minDate, $maxDate) = parseDateInterval($word);
- if ($minDate) {
- $search .= 'AND e1.date >= ' . $minDate . ' ';
- }
- if ($maxDate) {
- $search .= 'AND e1.date <= ' . $maxDate . ' ';
- }
- } else {
- if ($word[0] === '#' && isset($word[1])) {
- $search .= 'AND e1.tags LIKE ? ';
- $values[] = '%' . $word .'%';
- } else {
- $search .= 'AND ' . $this->sqlconcat('e1.title', $this->isCompressed() ? 'UNCOMPRESS(content_bin)' : 'content') . ' LIKE ? ';
- $values[] = '%' . $word .'%';
- }
+ if ($filter) {
+ if ($filter->getIntitle()) {
+ $search .= 'AND e1.title LIKE ? ';
+ $values[] = "%{$filter->getIntitle()}%";
+ }
+ if ($filter->getInurl()) {
+ $search .= 'AND CONCAT(e1.link, e1.guid) LIKE ? ';
+ $values[] = "%{$filter->getInurl()}%";
+ }
+ if ($filter->getAuthor()) {
+ $search .= 'AND e1.author LIKE ? ';
+ $values[] = "%{$filter->getAuthor()}%";
+ }
+ if ($filter->getMinDate()) {
+ $search .= 'AND e1.id >= ? ';
+ $values[] = "{$filter->getMinDate()}000000";
+ }
+ if ($filter->getMaxDate()) {
+ $search .= 'AND e1.id <= ? ';
+ $values[] = "{$filter->getMaxDate()}000000";
+ }
+ if ($filter->getMinPubdate()) {
+ $search .= 'AND e1.date >= ? ';
+ $values[] = $filter->getMinPubdate();
+ }
+ if ($filter->getMaxPubdate()) {
+ $search .= 'AND e1.date <= ? ';
+ $values[] = $filter->getMaxPubdate();
+ }
+ if ($filter->getTags()) {
+ $tags = $filter->getTags();
+ foreach ($tags as $tag) {
+ $search .= 'AND e1.tags LIKE ? ';
+ $values[] = "%{$tag}%";
+ }
+ }
+ if ($filter->getSearch()) {
+ $search_values = $filter->getSearch();
+ foreach ($search_values as $search_value) {
+ $search .= 'AND ' . $this->sqlconcat('e1.title', $this->isCompressed() ? 'UNCOMPRESS(content_bin)' : 'content') . ' LIKE ? ';
+ $values[] = "%{$search_value}%";
}
}
}
-
return array($values,
'SELECT e1.id FROM `' . $this->prefix . 'entry` e1 '
. ($joinFeed ? 'INNER JOIN `' . $this->prefix . 'feed` f ON e1.id_feed=f.id ' : '')
@@ -527,12 +591,51 @@ class FreshRSS_EntryDAO extends Minz_ModelPdo {
return $stm->fetchAll(PDO::FETCH_COLUMN, 0);
}
- public function listLastGuidsByFeed($id, $n) {
- $sql = 'SELECT guid FROM `' . $this->prefix . 'entry` WHERE id_feed=? ORDER BY id DESC LIMIT ' . intval($n);
+ public function listHashForFeedGuids($id_feed, $guids) {
+ if (count($guids) < 1) {
+ return array();
+ }
+ $sql = 'SELECT guid, hex(hash) AS hexHash FROM `' . $this->prefix . 'entry` WHERE id_feed=? AND guid IN (' . str_repeat('?,', count($guids) - 1). '?)';
+ $stm = $this->bd->prepare($sql);
+ $values = array($id_feed);
+ $values = array_merge($values, $guids);
+ if ($stm && $stm->execute($values)) {
+ $result = array();
+ $rows = $stm->fetchAll(PDO::FETCH_ASSOC);
+ foreach ($rows as $row) {
+ $result[$row['guid']] = $row['hexHash'];
+ }
+ return $result;
+ } else {
+ $info = $stm == null ? array(0 => '', 1 => '', 2 => 'syntax error') : $stm->errorInfo();
+ if ($this->autoAddColumn($info)) {
+ return $this->listHashForFeedGuids($id_feed, $guids);
+ }
+ Minz_Log::error('SQL error listHashForFeedGuids: ' . $info[0] . ': ' . $info[1] . ' ' . $info[2]
+ . ' while querying feed ' . $id_feed);
+ return false;
+ }
+ }
+
+ public function updateLastSeen($id_feed, $guids) {
+ if (count($guids) < 1) {
+ return 0;
+ }
+ $sql = 'UPDATE `' . $this->prefix . 'entry` SET lastSeen=? WHERE id_feed=? AND guid IN (' . str_repeat('?,', count($guids) - 1). '?)';
$stm = $this->bd->prepare($sql);
- $values = array($id);
- $stm->execute($values);
- return $stm->fetchAll(PDO::FETCH_COLUMN, 0);
+ $values = array(time(), $id_feed);
+ $values = array_merge($values, $guids);
+ if ($stm && $stm->execute($values)) {
+ return $stm->rowCount();
+ } else {
+ $info = $stm == null ? array(0 => '', 1 => '', 2 => 'syntax error') : $stm->errorInfo();
+ if ($this->autoAddColumn($info)) {
+ return $this->updateLastSeen($id_feed, $guids);
+ }
+ Minz_Log::error('SQL error updateLastSeen: ' . $info[0] . ': ' . $info[1] . ' ' . $info[2]
+ . ' while updating feed ' . $id_feed);
+ return false;
+ }
}
public function countUnreadRead() {
diff --git a/app/Models/EntryDAOSQLite.php b/app/Models/EntryDAOSQLite.php
index ffe0f037c..ff049d813 100644
--- a/app/Models/EntryDAOSQLite.php
+++ b/app/Models/EntryDAOSQLite.php
@@ -2,6 +2,21 @@
class FreshRSS_EntryDAOSQLite extends FreshRSS_EntryDAO {
+ protected function autoAddColumn($errorInfo) {
+ if (empty($errorInfo[0]) || $errorInfo[0] == '42S22') { //ER_BAD_FIELD_ERROR
+ if ($tableInfo = $this->bd->query("SELECT sql FROM sqlite_master where name='entry'")) {
+ $showCreate = $tableInfo->fetchColumn();
+ Minz_Log::debug('FreshRSS_EntryDAOSQLite::autoAddColumn: ' . $showCreate);
+ foreach (array('lastSeen', 'hash') as $column) {
+ if (stripos($showCreate, $column) === false) {
+ return $this->addColumn($column);
+ }
+ }
+ }
+ }
+ return false;
+ }
+
protected function sqlConcat($s1, $s2) {
return $s1 . '||' . $s2;
}
diff --git a/app/Models/Feed.php b/app/Models/Feed.php
index 5ce03be5d..85fb173ec 100644
--- a/app/Models/Feed.php
+++ b/app/Models/Feed.php
@@ -240,16 +240,16 @@ class FreshRSS_Feed extends Minz_Model {
$subscribe_url = $feed->subscribe_url(true);
}
- $clean_url = url_remove_credentials($subscribe_url);
+ $clean_url = SimplePie_Misc::url_remove_credentials($subscribe_url);
if ($subscribe_url !== null && $subscribe_url !== $url) {
$this->_url($clean_url);
}
- if (($mtime === true) ||($mtime > $this->lastUpdate)) {
- Minz_Log::notice('FreshRSS no cache ' . $mtime . ' > ' . $this->lastUpdate . ' for ' . $clean_url);
+ if (($mtime === true) || ($mtime > $this->lastUpdate)) {
+ //Minz_Log::debug('FreshRSS no cache ' . $mtime . ' > ' . $this->lastUpdate . ' for ' . $clean_url);
$this->loadEntries($feed); // et on charge les articles du flux
} else {
- Minz_Log::notice('FreshRSS use cache for ' . $clean_url);
+ //Minz_Log::debug('FreshRSS use cache for ' . $clean_url);
$this->entries = array();
}
diff --git a/app/Models/FeedDAO.php b/app/Models/FeedDAO.php
index 74597c730..475d39286 100644
--- a/app/Models/FeedDAO.php
+++ b/app/Models/FeedDAO.php
@@ -1,6 +1,6 @@
<?php
-class FreshRSS_FeedDAO extends Minz_ModelPdo {
+class FreshRSS_FeedDAO extends Minz_ModelPdo implements FreshRSS_Searchable {
public function addFeed($valuesTmp) {
$sql = 'INSERT INTO `' . $this->prefix . 'feed` (url, category, name, website, description, lastUpdate, priority, httpAuth, error, keep_history, ttl) VALUES(?, ?, ?, ?, ?, ?, 10, ?, 0, -2, -2)';
$stm = $this->bd->prepare($sql);
@@ -322,17 +322,20 @@ class FreshRSS_FeedDAO extends Minz_ModelPdo {
return $affected;
}
- public function cleanOldEntries($id, $date_min, $keep = 15) { //Remember to call updateLastUpdate($id) just after
+ public function cleanOldEntries($id, $date_min, $keep = 15) { //Remember to call updateLastUpdate($id) or updateCachedValues() just after
$sql = 'DELETE FROM `' . $this->prefix . 'entry` '
- . 'WHERE id_feed = :id_feed AND id <= :id_max AND is_favorite=0 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 yet support 'LIMIT & IN/ALL/ANY/SOME subquery'
+ . '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 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);
- $id_max = intval($date_min) . '000000';
-
- $stm->bindParam(':id_feed', $id, PDO::PARAM_INT);
- $stm->bindParam(':id_max', $id_max, PDO::PARAM_STR);
- $stm->bindParam(':keep', $keep, PDO::PARAM_INT);
+ if ($stm) {
+ $id_max = intval($date_min) . '000000';
+ $stm->bindParam(':id_feed', $id, PDO::PARAM_INT);
+ $stm->bindParam(':id_max', $id_max, PDO::PARAM_STR);
+ $stm->bindParam(':keep', $keep, PDO::PARAM_INT);
+ }
if ($stm && $stm->execute()) {
return $stm->rowCount();
diff --git a/app/Models/Search.php b/app/Models/Search.php
new file mode 100644
index 000000000..575a9a2cb
--- /dev/null
+++ b/app/Models/Search.php
@@ -0,0 +1,229 @@
+<?php
+
+require_once(LIB_PATH . '/lib_date.php');
+
+/**
+ * Contains a search from the search form.
+ *
+ * It allows to extract meaningful bits of the search and store them in a
+ * convenient object
+ */
+class FreshRSS_Search {
+
+ // This contains the user input string
+ private $raw_input = '';
+ // The following properties are extracted from the raw input
+ private $intitle;
+ private $min_date;
+ private $max_date;
+ private $min_pubdate;
+ private $max_pubdate;
+ private $inurl;
+ private $author;
+ private $tags;
+ private $search;
+
+ public function __construct($input) {
+ if (strcmp($input, '') == 0) {
+ return;
+ }
+ $this->raw_input = $input;
+ $input = $this->parseIntitleSearch($input);
+ $input = $this->parseAuthorSearch($input);
+ $input = $this->parseInurlSearch($input);
+ $input = $this->parsePubdateSearch($input);
+ $input = $this->parseDateSearch($input);
+ $input = $this->parseTagsSeach($input);
+ $this->parseSearch($input);
+ }
+
+ public function __toString() {
+ return $this->getRawInput();
+ }
+
+ public function getRawInput() {
+ return $this->raw_input;
+ }
+
+ public function getIntitle() {
+ return $this->intitle;
+ }
+
+ public function getMinDate() {
+ return $this->min_date;
+ }
+
+ public function getMaxDate() {
+ return $this->max_date;
+ }
+
+ public function getMinPubdate() {
+ return $this->min_pubdate;
+ }
+
+ public function getMaxPubdate() {
+ return $this->max_pubdate;
+ }
+
+ public function getInurl() {
+ return $this->inurl;
+ }
+
+ public function getAuthor() {
+ return $this->author;
+ }
+
+ public function getTags() {
+ return $this->tags;
+ }
+
+ public function getSearch() {
+ return $this->search;
+ }
+
+ /**
+ * Parse the search string to find intitle keyword and the search related
+ * to it.
+ * The search is the first word following the keyword.
+ *
+ * @param string $input
+ * @return string
+ */
+ private function parseIntitleSearch($input) {
+ if (preg_match('/intitle:(?P<delim>[\'"])(?P<search>.*)(?P=delim)/U', $input, $matches)) {
+ $this->intitle = $matches['search'];
+ return str_replace($matches[0], '', $input);
+ }
+ if (preg_match('/intitle:(?P<search>\w*)/', $input, $matches)) {
+ $this->intitle = $matches['search'];
+ return str_replace($matches[0], '', $input);
+ }
+ return $input;
+ }
+
+ /**
+ * Parse the search string to find author keyword and the search related
+ * to it.
+ * The search is the first word following the keyword except when using
+ * a delimiter. Supported delimiters are single quote (') and double
+ * quotes (").
+ *
+ * @param string $input
+ * @return string
+ */
+ private function parseAuthorSearch($input) {
+ if (preg_match('/author:(?P<delim>[\'"])(?P<search>.*)(?P=delim)/U', $input, $matches)) {
+ $this->author = $matches['search'];
+ return str_replace($matches[0], '', $input);
+ }
+ if (preg_match('/author:(?P<search>\w*)/', $input, $matches)) {
+ $this->author = $matches['search'];
+ return str_replace($matches[0], '', $input);
+ }
+ return $input;
+ }
+
+ /**
+ * Parse the search string to find inurl keyword and the search related
+ * to it.
+ * The search is the first word following the keyword except.
+ *
+ * @param string $input
+ * @return string
+ */
+ private function parseInurlSearch($input) {
+ if (preg_match('/inurl:(?P<search>[^\s]*)/', $input, $matches)) {
+ $this->inurl = $matches['search'];
+ return str_replace($matches[0], '', $input);
+ }
+ return $input;
+ }
+
+ /**
+ * Parse the search string to find date keyword and the search related
+ * to it.
+ * The search is the first word following the keyword.
+ *
+ * @param string $input
+ * @return string
+ */
+ private function parseDateSearch($input) {
+ if (preg_match('/date:(?P<search>[^\s]*)/', $input, $matches)) {
+ list($this->min_date, $this->max_date) = parseDateInterval($matches['search']);
+ return str_replace($matches[0], '', $input);
+ }
+ return $input;
+ }
+
+ /**
+ * Parse the search string to find pubdate keyword and the search related
+ * to it.
+ * The search is the first word following the keyword.
+ *
+ * @param string $input
+ * @return string
+ */
+ private function parsePubdateSearch($input) {
+ if (preg_match('/pubdate:(?P<search>[^\s]*)/', $input, $matches)) {
+ list($this->min_pubdate, $this->max_pubdate) = parseDateInterval($matches['search']);
+ return str_replace($matches[0], '', $input);
+ }
+ return $input;
+ }
+
+ /**
+ * Parse the search string to find tags keyword (# followed by a word)
+ * and the search related to it.
+ * The search is the first word following the #.
+ *
+ * @param string $input
+ * @return string
+ */
+ private function parseTagsSeach($input) {
+ if (preg_match_all('/#(?P<search>[^\s]+)/', $input, $matches)) {
+ $this->tags = $matches['search'];
+ return str_replace($matches[0], '', $input);
+ }
+ return $input;
+ }
+
+ /**
+ * Parse the search string to find search values.
+ * Every word is a distinct search value, except when using a delimiter.
+ * Supported delimiters are single quote (') and double quotes (").
+ *
+ * @param string $input
+ * @return string
+ */
+ private function parseSearch($input) {
+ $input = $this->cleanSearch($input);
+ if (strcmp($input, '') == 0) {
+ return;
+ }
+ if (preg_match_all('/(?P<delim>[\'"])(?P<search>.*)(?P=delim)/U', $input, $matches)) {
+ $this->search = $matches['search'];
+ $input = str_replace($matches[0], '', $input);
+ }
+ $input = $this->cleanSearch($input);
+ if (strcmp($input, '') == 0) {
+ return;
+ }
+ if (is_array($this->search)) {
+ $this->search = array_merge($this->search, explode(' ', $input));
+ } else {
+ $this->search = explode(' ', $input);
+ }
+ }
+
+ /**
+ * Remove all unnecessary spaces in the search
+ *
+ * @param string $input
+ * @return string
+ */
+ private function cleanSearch($input) {
+ $input = preg_replace('/\s+/', ' ', $input);
+ return trim($input);
+ }
+
+}
diff --git a/app/Models/Searchable.php b/app/Models/Searchable.php
new file mode 100644
index 000000000..d5bcea49d
--- /dev/null
+++ b/app/Models/Searchable.php
@@ -0,0 +1,6 @@
+<?php
+
+interface FreshRSS_Searchable {
+
+ public function searchById($id);
+}
diff --git a/app/Models/Share.php b/app/Models/Share.php
index db6feda19..2a05f2ee9 100644
--- a/app/Models/Share.php
+++ b/app/Models/Share.php
@@ -152,7 +152,7 @@ class FreshRSS_Share {
* Return the current name of the share option.
*/
public function name($real = false) {
- if ($real || is_null($this->custom_name)) {
+ if ($real || is_null($this->custom_name) || empty($this->custom_name)) {
return $this->name;
} else {
return $this->custom_name;
diff --git a/app/Models/UserQuery.php b/app/Models/UserQuery.php
new file mode 100644
index 000000000..52747f538
--- /dev/null
+++ b/app/Models/UserQuery.php
@@ -0,0 +1,226 @@
+<?php
+
+/**
+ * Contains the description of a user query
+ *
+ * It allows to extract the meaningful bits of the query to be manipulated in an
+ * easy way.
+ */
+class FreshRSS_UserQuery {
+
+ private $deprecated = false;
+ private $get;
+ private $get_name;
+ private $get_type;
+ private $name;
+ private $order;
+ private $search;
+ private $state;
+ private $url;
+ private $feed_dao;
+ private $category_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) {
+ $this->category_dao = $category_dao;
+ $this->feed_dao = $feed_dao;
+ if (isset($query['get'])) {
+ $this->parseGet($query['get']);
+ }
+ if (isset($query['name'])) {
+ $this->name = $query['name'];
+ }
+ if (isset($query['order'])) {
+ $this->order = $query['order'];
+ }
+ if (!isset($query['search'])) {
+ $query['search'] = '';
+ }
+ // linked to deeply with the search object, need to use dependency injection
+ $this->search = new FreshRSS_Search($query['search']);
+ if (isset($query['state'])) {
+ $this->state = $query['state'];
+ }
+ if (isset($query['url'])) {
+ $this->url = $query['url'];
+ }
+ }
+
+ /**
+ * Convert the current object to an array.
+ *
+ * @return array
+ */
+ public function toArray() {
+ return array_filter(array(
+ 'get' => $this->get,
+ 'name' => $this->name,
+ 'order' => $this->order,
+ 'search' => $this->search->__toString(),
+ 'state' => $this->state,
+ 'url' => $this->url,
+ ));
+ }
+
+ /**
+ * Parse the get parameter in the query string to extract its name and
+ * type
+ *
+ * @param string $get
+ */
+ private function parseGet($get) {
+ $this->get = $get;
+ if (preg_match('/(?P<type>[acfs])(_(?P<id>\d+))?/', $get, $matches)) {
+ switch ($matches['type']) {
+ case 'a':
+ $this->parseAll();
+ break;
+ case 'c':
+ $this->parseCategory($matches['id']);
+ break;
+ case 'f':
+ $this->parseFeed($matches['id']);
+ break;
+ case 's':
+ $this->parseFavorite();
+ break;
+ }
+ }
+ }
+
+ /**
+ * Parse the query string when it is an "all" query
+ */
+ private function parseAll() {
+ $this->get_name = 'all';
+ $this->get_type = 'all';
+ }
+
+ /**
+ * Parse the query string when it is a "category" query
+ *
+ * @param integer $id
+ * @throws FreshRSS_DAO_Exception
+ */
+ private function parseCategory($id) {
+ if (is_null($this->category_dao)) {
+ throw new FreshRSS_DAO_Exception('Category DAO is not loaded in UserQuery');
+ }
+ $category = $this->category_dao->searchById($id);
+ if ($category) {
+ $this->get_name = $category->name();
+ } else {
+ $this->deprecated = true;
+ }
+ $this->get_type = 'category';
+ }
+
+ /**
+ * Parse the query string when it is a "feed" query
+ *
+ * @param integer $id
+ * @throws FreshRSS_DAO_Exception
+ */
+ private function parseFeed($id) {
+ if (is_null($this->feed_dao)) {
+ throw new FreshRSS_DAO_Exception('Feed DAO is not loaded in UserQuery');
+ }
+ $feed = $this->feed_dao->searchById($id);
+ if ($feed) {
+ $this->get_name = $feed->name();
+ } else {
+ $this->deprecated = true;
+ }
+ $this->get_type = 'feed';
+ }
+
+ /**
+ * Parse the query string when it is a "favorite" query
+ */
+ private function parseFavorite() {
+ $this->get_name = 'favorite';
+ $this->get_type = 'favorite';
+ }
+
+ /**
+ * Check if the current user query is deprecated.
+ * It is deprecated if the category or the feed used in the query are
+ * not existing.
+ *
+ * @return boolean
+ */
+ public function isDeprecated() {
+ return $this->deprecated;
+ }
+
+ /**
+ * Check if the user query has parameters.
+ * If the type is 'all', it is considered equal to no parameters
+ *
+ * @return boolean
+ */
+ public function hasParameters() {
+ if ($this->get_type === 'all') {
+ return false;
+ }
+ if ($this->hasSearch()) {
+ return true;
+ }
+ if ($this->state) {
+ return true;
+ }
+ if ($this->order) {
+ return true;
+ }
+ if ($this->get) {
+ return true;
+ }
+ return false;
+ }
+
+ /**
+ * Check if there is a search in the search object
+ *
+ * @return boolean
+ */
+ public function hasSearch() {
+ return $this->search->getRawInput() != "";
+ }
+
+ public function getGet() {
+ return $this->get;
+ }
+
+ public function getGetName() {
+ return $this->get_name;
+ }
+
+ public function getGetType() {
+ return $this->get_type;
+ }
+
+ public function getName() {
+ return $this->name;
+ }
+
+ public function getOrder() {
+ return $this->order;
+ }
+
+ public function getSearch() {
+ return $this->search;
+ }
+
+ public function getState() {
+ return $this->state;
+ }
+
+ public function getUrl() {
+ return $this->url;
+ }
+
+}
diff --git a/app/SQL/install.sql.mysql.php b/app/SQL/install.sql.mysql.php
index cf0159199..9c6af405d 100644
--- a/app/SQL/install.sql.mysql.php
+++ b/app/SQL/install.sql.mysql.php
@@ -15,7 +15,7 @@ CREATE TABLE IF NOT EXISTS `%1$sfeed` (
`name` varchar(255) NOT NULL,
`website` varchar(255) CHARACTER SET latin1,
`description` text,
- `lastUpdate` int(11) DEFAULT 0,
+ `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,
@@ -40,7 +40,9 @@ CREATE TABLE IF NOT EXISTS `%1$sentry` (
`author` varchar(255),
`content_bin` blob, -- v0.7
`link` varchar(1023) CHARACTER SET latin1 NOT NULL,
- `date` int(11),
+ `date` int(11), -- Until year 2038
+ `lastSeen` INT(11) DEFAULT 0, -- v1.2, Until year 2038
+ `hash` BINARY(16), -- v1.2
`is_read` boolean NOT NULL DEFAULT 0,
`is_favorite` boolean NOT NULL DEFAULT 0,
`id_feed` SMALLINT, -- v0.7
@@ -50,6 +52,7 @@ CREATE TABLE IF NOT EXISTS `%1$sentry` (
UNIQUE KEY (`id_feed`,`guid`), -- v0.7
INDEX (`is_favorite`), -- v0.7
INDEX (`is_read`) -- v0.7
+ INDEX entry_lastSeen_index (`lastSeen`) -- v1.2
) DEFAULT CHARACTER SET utf8 COLLATE utf8_unicode_ci
ENGINE = INNODB;
diff --git a/app/SQL/install.sql.sqlite.php b/app/SQL/install.sql.sqlite.php
index 30bca2810..77e8e094c 100644
--- a/app/SQL/install.sql.sqlite.php
+++ b/app/SQL/install.sql.sqlite.php
@@ -14,7 +14,7 @@ $SQL_CREATE_TABLES = array(
`name` varchar(255) NOT NULL,
`website` varchar(255),
`description` text,
- `lastUpdate` int(11) DEFAULT 0,
+ `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,
@@ -38,7 +38,9 @@ $SQL_CREATE_TABLES = array(
`author` varchar(255),
`content` text,
`link` varchar(1023) NOT NULL,
- `date` int(11),
+ `date` int(11), -- Until year 2038
+ `lastSeen` INT(11) DEFAULT 0, -- v1.2, Until year 2038
+ `hash` BINARY(16), -- v1.2
`is_read` boolean NOT NULL DEFAULT 0,
`is_favorite` boolean NOT NULL DEFAULT 0,
`id_feed` SMALLINT,
@@ -50,6 +52,7 @@ $SQL_CREATE_TABLES = array(
'CREATE INDEX IF NOT EXISTS entry_is_favorite_index ON `%1$sentry`(`is_favorite`);',
'CREATE INDEX IF NOT EXISTS entry_is_read_index ON `%1$sentry`(`is_read`);',
+'CREATE INDEX IF NOT EXISTS entry_lastSeen_index ON `%1$sentry`(`lastSeen`);', //v1.2
'INSERT OR IGNORE INTO `%1$scategory` (id, name) VALUES(1, "%2$s");',
);
diff --git a/app/i18n/cz/admin.php b/app/i18n/cz/admin.php
new file mode 100644
index 000000000..b9ef707cf
--- /dev/null
+++ b/app/i18n/cz/admin.php
@@ -0,0 +1,170 @@
+<?php
+
+return array(
+ 'auth' => array(
+ 'allow_anonymous' => 'Umožnit anonymně číst články výchozího uživatele (%s)',
+ 'allow_anonymous_refresh' => 'Umožnit anonymní obnovení článků',
+ 'api_enabled' => 'Povolit přístup k <abbr>API</abbr> <small>(vyžadováno mobilními aplikacemi)</small>',
+ 'form' => 'Webový formulář (tradiční, vyžaduje JavaScript)',
+ 'http' => 'HTTP (pro pokročilé uživatele s HTTPS)',
+ 'none' => 'Žádný (nebezpečné)',
+ 'persona' => 'Mozilla Persona (moderní, vyžaduje JavaScript)',
+ 'title' => 'Přihlášení',
+ 'title_reset' => 'Reset přihlášení',
+ 'token' => 'Authentizační token',
+ 'token_help' => 'Umožňuje přístup k RSS kanálu článků výchozího uživatele bez přihlášení:',
+ 'type' => 'Způsob přihlášení',
+ 'unsafe_autologin' => 'Povolit nebezpečné automatické přihlášení přes: ',
+ ),
+ 'check_install' => array(
+ 'cache' => array(
+ 'nok' => 'Zkontrolujte oprávnění adresáře <em>./data/cache</em>. HTTP server musí mít do tohoto adresáře práva zápisu',
+ 'ok' => 'Oprávnění adresáře cache jsou v pořádku.',
+ ),
+ 'categories' => array(
+ 'nok' => 'Tabulka kategorií je nastavena špatně.',
+ 'ok' => 'Tabulka kategorií je v pořádku.',
+ ),
+ 'connection' => array(
+ 'nok' => 'Nelze navázat spojení s databází.',
+ 'ok' => 'Připojení k databázi je v pořádku.',
+ ),
+ 'ctype' => array(
+ 'nok' => 'Nemáte požadovanou knihovnu pro ověřování znaků (php-ctype).',
+ 'ok' => 'Máte požadovanou knihovnu pro ověřování znaků (ctype).',
+ ),
+ 'curl' => array(
+ 'nok' => 'Nemáte cURL (balíček php5-curl).',
+ 'ok' => 'Máte rozšíření cURL.',
+ ),
+ 'data' => array(
+ 'nok' => 'Zkontrolujte oprávnění adresáře <em>./data</em>. HTTP server musí mít do tohoto adresáře práva zápisu',
+ 'ok' => 'Oprávnění adresáře data jsou v pořádku.',
+ ),
+ 'database' => 'Instalace databáze',
+ 'dom' => array(
+ 'nok' => 'Nemáte požadovanou knihovnu pro procházení DOM (balíček php-xml).',
+ 'ok' => 'Máte požadovanou knihovnu pro procházení DOM.',
+ ),
+ 'entries' => array(
+ 'nok' => 'Tabulka článků je nastavena špatně.',
+ 'ok' => 'Tabulka kategorií je v pořádku.',
+ ),
+ 'favicons' => array(
+ 'nok' => 'Zkontrolujte oprávnění adresáře <em>./data/favicons</em>. HTTP server musí mít do tohoto adresáře práva zápisu',
+ 'ok' => 'Oprávnění adresáře favicons jsou v pořádku.',
+ ),
+ 'feeds' => array(
+ 'nok' => 'Tabulka kanálů je nastavena špatně.',
+ 'ok' => 'Tabulka kanálů je v pořádku.',
+ ),
+ 'files' => 'Instalace souborů',
+ 'json' => array(
+ 'nok' => 'Nemáte JSON (balíček php5-json).',
+ 'ok' => 'Máte rozšíření JSON.',
+ ),
+ 'minz' => array(
+ 'nok' => 'Nemáte framework Minz.',
+ 'ok' => 'Máte framework Minz.',
+ ),
+ 'pcre' => array(
+ 'nok' => 'Nemáte požadovanou knihovnu pro regulární výrazy (php-pcre).',
+ 'ok' => 'Máte požadovanou knihovnu pro regulární výrazy (PCRE).',
+ ),
+ 'pdo' => array(
+ 'nok' => 'Nemáte PDO nebo některý z podporovaných ovladačů (pdo_mysql, pdo_sqlite).',
+ 'ok' => 'Máte PDO a alespoň jeden z podporovaných ovladačů (pdo_mysql, pdo_sqlite).',
+ ),
+ 'persona' => array(
+ 'nok' => 'Zkontrolujte oprávnění adresáře <em>./data/persona</em>. HTTP server musí mít do tohoto adresáře práva zápisu',
+ 'ok' => 'Oprávnění adresáře Mozilla Persona jsou v pořádku.',
+ ),
+ 'php' => array(
+ '_' => 'PHP instalace',
+ 'nok' => 'Vaše verze PHP je %s, ale FreshRSS vyžaduje alespoň verzi %s.',
+ 'ok' => 'Vaše verze PHP je %s a je kompatibilní s FreshRSS.',
+ ),
+ 'tables' => array(
+ 'nok' => 'V databázi chybí jedna nevo více tabulek.',
+ 'ok' => 'V databázi jsou všechny tabulky.',
+ ),
+ 'title' => 'Kontrola instalace',
+ 'tokens' => array(
+ 'nok' => 'Zkontrolujte oprávnění adresáře <em>./data/tokens</em>. HTTP server musí mít do tohoto adresáře práva zápisu',
+ 'ok' => 'Oprávnění adresáře tokens jsou v pořádku.',
+ ),
+ 'users' => array(
+ 'nok' => 'Zkontrolujte oprávnění adresáře <em>./data/users</em>. HTTP server musí mít do tohoto adresáře práva zápisu',
+ 'ok' => 'Oprávnění adresáře users jsou v pořádku.',
+ ),
+ 'zip' => array(
+ 'nok' => 'Nemáte rozšíření ZIP (balíček php5-zip).',
+ 'ok' => 'Máte rozšíření ZIP.',
+ ),
+ ),
+ 'extensions' => array(
+ 'disabled' => 'Vypnuto',
+ 'empty_list' => 'Není naistalováno žádné rozšíření',
+ 'enabled' => 'Zapnuto',
+ 'no_configure_view' => 'Toto rozšíření nemá žádné možnosti nastavení.',
+ 'system' => array(
+ '_' => 'Systémová rozšíření',
+ 'no_rights' => 'Systémová rozšíření (na ně nemáte oprávnění)',
+ ),
+ 'title' => 'Rozšíření',
+ 'user' => 'Uživatelská rozšíření',
+ ),
+ 'stats' => array(
+ '_' => 'Statistika',
+ 'all_feeds' => 'Všechny kanály',
+ 'category' => 'Kategorie',
+ 'entry_count' => 'Počet článků',
+ 'entry_per_category' => 'Článků na kategorii',
+ 'entry_per_day' => 'Článků za den (posledních 30 dní)',
+ 'entry_per_day_of_week' => 'Za den v týdnu (průměr: %.2f zprávy)',
+ 'entry_per_hour' => 'Za hodinu (průměr: %.2f zprávy)',
+ 'entry_per_month' => 'Za měsíc (průměr: %.2f zprávy)',
+ 'entry_repartition' => 'Rozdělení článků',
+ 'feed' => 'Kanál',
+ 'feed_per_category' => 'Článků na kategorii',
+ 'idle' => 'Neaktivní kanály',
+ 'main' => 'Přehled',
+ 'main_stream' => 'Všechny kanály',
+ 'menu' => array(
+ 'idle' => 'Neaktivní kanály',
+ 'main' => 'Přehled',
+ 'repartition' => 'Rozdělení článků',
+ ),
+ 'no_idle' => 'Žádné neaktivní kanály!',
+ 'number_entries' => '%d článků',
+ 'percent_of_total' => '%% ze všech',
+ 'repartition' => 'Rozdělení článků',
+ 'status_favorites' => 'Oblíbené',
+ 'status_read' => 'Přečtené',
+ 'status_total' => 'Celkem',
+ 'status_unread' => 'Nepřečtené',
+ 'title' => 'Statistika',
+ 'top_feed' => 'Top ten kanálů',
+ ),
+ 'update' => array(
+ '_' => 'Aktualizace systému',
+ 'apply' => 'Použít',
+ 'check' => 'Zkontrolovat aktualizace',
+ 'current_version' => 'Vaše instalace FreshRSS je verze %s.',
+ 'last' => 'Poslední kontrola: %s',
+ 'none' => 'Žádné nové aktualizace',
+ 'title' => 'Aktualizovat systém',
+ ),
+ 'user' => array(
+ 'articles_and_size' => '%s článků (%s)',
+ 'create' => 'Vytvořit nového uživatele',
+ 'email_persona' => 'Email pro přihlášení<br /><small>(pro <a href="https://persona.org/" rel="external">Mozilla Persona</a>)</small>',
+ 'language' => 'Jazyk',
+ 'password_form' => 'Heslo<br /><small>(pro přihlášení webovým formulářem)</small>',
+ 'password_format' => 'Alespoň 7 znaků',
+ 'title' => 'Správa uživatelů',
+ 'user_list' => 'Seznam uživatelů',
+ 'username' => 'Přihlašovací jméno',
+ 'users' => 'Uživatelé',
+ ),
+);
diff --git a/app/i18n/cz/conf.php b/app/i18n/cz/conf.php
new file mode 100644
index 000000000..29fb1e4d4
--- /dev/null
+++ b/app/i18n/cz/conf.php
@@ -0,0 +1,169 @@
+<?php
+
+return array(
+ 'archiving' => array(
+ '_' => 'Archivace',
+ 'advanced' => 'Pokročilé',
+ 'delete_after' => 'Smazat články starší než',
+ 'help' => 'Více možností je dostupných v nastavení jednotlivých kanálů',
+ 'keep_history_by_feed' => 'Zachovat tento minimální počet článků v každém kanálu',
+ 'optimize' => 'Optimalizovat databázi',
+ 'optimize_help' => 'Občasná údržba zmenší velikost databáze',
+ 'purge_now' => 'Vyčistit nyní',
+ 'title' => 'Archivace',
+ 'ttl' => 'Neaktualizovat častěji než',
+ ),
+ 'display' => array(
+ '_' => 'Zobrazení',
+ 'icon' => array(
+ 'bottom_line' => 'Spodní řádek',
+ 'entry' => 'Ikony článků',
+ 'publication_date' => 'Datum vydání',
+ 'related_tags' => 'Související tagy',
+ 'sharing' => 'Sdílení',
+ 'top_line' => 'Horní řádek',
+ ),
+ 'language' => 'Jazyk',
+ 'notif_html5' => array(
+ 'seconds' => 'sekund (0 znamená žádný timeout)',
+ 'timeout' => 'Timeout HTML5 notifikací',
+ ),
+ 'theme' => 'Vzhled',
+ 'title' => 'Zobrazení',
+ 'width' => array(
+ 'content' => 'Šířka obsahu',
+ 'large' => 'Velká',
+ 'medium' => 'Střední',
+ 'no_limit' => 'Bez limitu',
+ 'thin' => 'Tenká',
+ ),
+ ),
+ 'query' => array(
+ '_' => 'Uživatelské dotazy',
+ 'deprecated' => 'Tento dotaz již není platný. Odkazovaná kategorie nebo kanál byly smazány.',
+ 'filter' => 'Filtr aplikován:',
+ 'get_all' => 'Zobrazit všechny články',
+ 'get_category' => 'Zobrazit "%s" kategorii',
+ 'get_favorite' => 'Zobrazit oblíbené články',
+ 'get_feed' => 'Zobrazit "%s" článkek',
+ 'no_filter' => 'Zrušit filtr',
+ 'none' => 'Ještě jste nevytvořil žádný uživatelský dotaz.',
+ 'number' => 'Dotaz n°%d',
+ 'order_asc' => 'Zobrazit nejdříve nejstarší články',
+ 'order_desc' => 'Zobrazit nejdříve nejnovější články',
+ 'search' => 'Hledat "%s"',
+ 'state_0' => 'Zobrazit všechny články',
+ 'state_1' => 'Zobrazit přečtené články',
+ 'state_2' => 'Zobrazit nepřečtené články',
+ 'state_3' => 'Zobrazit všechny články',
+ 'state_4' => 'Zobrazit oblíbené články',
+ 'state_5' => 'Zobrazit oblíbené přečtené články',
+ 'state_6' => 'Zobrazit oblíbené nepřečtené články',
+ 'state_7' => 'Zobrazit oblíbené články',
+ 'state_8' => 'Zobrazit všechny články vyjma oblíbených',
+ 'state_9' => 'Zobrazit všechny přečtené články vyjma oblíbených',
+ 'state_10' => 'Zobrazit všechny nepřečtené články vyjma oblíbených',
+ 'state_11' => 'Zobrazit všechny články vyjma oblíbených',
+ 'state_12' => 'Zobrazit všechny články',
+ 'state_13' => 'Zobrazit přečtené články',
+ 'state_14' => 'Zobrazit nepřečtené články',
+ 'state_15' => 'Zobrazit všechny články',
+ 'title' => 'Uživatelské dotazy',
+ ),
+ 'profile' => array(
+ '_' => 'Správa profilu',
+ 'email_persona' => 'Email pro přihlášení<br /><small>(pro <a href="https://persona.org/" rel="external">Mozilla Persona</a>)</small>',
+ 'password_api' => 'Password API<br /><small>(tzn. pro mobilní aplikace)</small>',
+ 'password_form' => 'Heslo<br /><small>(pro přihlášení webovým formulářem)</small>',
+ 'password_format' => 'Alespoň 7 znaků',
+ 'title' => 'Profil',
+ ),
+ 'reading' => array(
+ '_' => 'Čtení',
+ 'after_onread' => 'Po “označit vše jako přečtené”,',
+ 'articles_per_page' => 'Počet článků na stranu',
+ 'auto_load_more' => 'Načítat další články dole na stránce',
+ 'auto_remove_article' => 'Po přečtení články schovat',
+ 'confirm_enabled' => 'Vyžadovat potvrzení pro akci “označit vše jako přečtené”',
+ 'display_articles_unfolded' => 'Ve výchozím stavu zobrazovat články otevřené',
+ 'display_categories_unfolded' => 'Ve výchozím stavu zobrazovat kategorie zavřené',
+ 'hide_read_feeds' => 'Schovat kategorie a kanály s nulovým počtem nepřečtených článků (nefunguje s nastavením “Zobrazit všechny články”)',
+ 'img_with_lazyload' => 'Použít "lazy load" mód pro načítaní obrázků',
+ 'jump_next' => 'skočit na další nepřečtený (kanál nebo kategorii)',
+ 'number_divided_when_reader' => 'V režimu “Čtení” děleno dvěma.',
+ 'read' => array(
+ 'article_open_on_website' => 'když je otevřen původní web s článkem',
+ 'article_viewed' => 'během čtení článku',
+ 'scroll' => 'během skrolování',
+ 'upon_reception' => 'po načtení článku',
+ 'when' => 'Označit článek jako přečtený…',
+ ),
+ 'show' => array(
+ '_' => 'Počet zobrazených článků',
+ 'adaptive' => 'Vyberte zobrazení',
+ 'all_articles' => 'Zobrazit všechny články',
+ 'unread' => 'Zobrazit jen nepřečtené',
+ ),
+ 'sort' => array(
+ '_' => 'Řazení',
+ 'newer_first' => 'Nejdříve nejnovější',
+ 'older_first' => 'Nejdříve nejstarší',
+ ),
+ 'sticky_post' => 'Při otevření posunout článek nahoru',
+ 'title' => 'Čtení',
+ 'view' => array(
+ 'default' => 'Výchozí',
+ 'global' => 'Přehled',
+ 'normal' => 'Normální',
+ 'reader' => 'Čtení',
+ ),
+ ),
+ 'sharing' => array(
+ '_' => 'Sdílení',
+ 'blogotext' => 'Blogotext',
+ 'diaspora' => 'Diaspora*',
+ 'email' => 'Email',
+ 'facebook' => 'Facebook',
+ 'g+' => 'Google+',
+ 'more_information' => 'Více informací',
+ 'print' => 'Tisk',
+ 'shaarli' => 'Shaarli',
+ 'share_name' => 'Jméno pro zobrazení',
+ 'share_url' => 'Jakou URL použít pro sdílení',
+ 'title' => 'Sdílení',
+ 'twitter' => 'Twitter',
+ 'wallabag' => 'wallabag',
+ ),
+ 'shortcut' => array(
+ '_' => 'Zkratky',
+ 'article_action' => 'Články - akce',
+ 'auto_share' => 'Sdílet',
+ 'auto_share_help' => 'Je-li nastavena pouze jedna možnost sdílení, bude použita. Další možnosti jsou dostupné pomocí jejich čísla.',
+ 'close_dropdown' => 'Zavřít menu',
+ 'collapse_article' => 'Srolovat',
+ 'first_article' => 'Skočit na první článek',
+ 'focus_search' => 'Hledání',
+ 'help' => 'Zobrazit documentaci',
+ 'javascript' => 'Pro použití zkratek musí být povolen JavaScript',
+ 'last_article' => 'Skočit na poslední článek',
+ 'load_more' => 'Načíst více článků',
+ 'mark_read' => 'Označit jako přečtené',
+ 'mark_favorite' => 'Označit jako oblíbené',
+ 'navigation' => 'Navigace',
+ 'navigation_help' => 'Pomocí přepínače "Shift" fungují navigační zkratky v rámci kanálů.<br/>Pomocí přepínače "Alt" fungují v rámci kategorií.',
+ 'next_article' => 'Skočit na další článek',
+ 'other_action' => 'Ostatní akce',
+ 'previous_article' => 'Skočit na předchozí článek',
+ 'see_on_website' => 'Navštívit původní webovou stránku',
+ 'shift_for_all_read' => '+ <code>shift</code> označí vše jako přečtené',
+ 'title' => 'Zkratky',
+ 'user_filter' => 'Aplikovat uživatelské filtry',
+ 'user_filter_help' => 'Je-li nastaven pouze jeden filtr, bude použit. Další filtry jsou dostupné pomocí jejich čísla.',
+ ),
+ 'user' => array(
+ 'articles_and_size' => '%s článků (%s)',
+ 'current' => 'Aktuální uživatel',
+ 'is_admin' => 'je administrátor',
+ 'users' => 'Uživatelé',
+ ),
+);
diff --git a/app/i18n/cz/feedback.php b/app/i18n/cz/feedback.php
new file mode 100644
index 000000000..b75a4a15a
--- /dev/null
+++ b/app/i18n/cz/feedback.php
@@ -0,0 +1,110 @@
+<?php
+
+return array(
+ 'admin' => array(
+ 'optimization_complete' => 'Optimalizace dokončena',
+ ),
+ 'access' => array(
+ 'denied' => 'Nemáte oprávnění přistupovat na tuto stránku',
+ 'not_found' => 'Tato stránka neexistuje',
+ ),
+ 'auth' => array(
+ 'form' => array(
+ 'not_set' => 'Nastal problém s konfigurací přihlašovacího systému. Zkuste to prosím později.',
+ 'set' => 'Webový formulář je nyní výchozí přihlašovací systém.',
+ ),
+ 'login' => array(
+ 'invalid' => 'Login není platný',
+ 'success' => 'Jste přihlášen',
+ ),
+ 'logout' => array(
+ 'success' => 'Jste odhlášen',
+ ),
+ 'no_password_set' => 'Heslo administrátora nebylo nastaveno. Tato funkce není k dispozici.',
+ 'not_persona' => 'Resetovat lze pouze systém Persona.',
+ ),
+ 'conf' => array(
+ 'error' => 'Během ukládání nastavení došlo k chybě',
+ 'query_created' => 'Dotaz "%s" byl vytvořen.',
+ 'shortcuts_updated' => 'Zkratky byly aktualizovány',
+ 'updated' => 'Nastavení bylo aktualizováno',
+ ),
+ 'extensions' => array(
+ 'already_enabled' => '%s je již zapnut',
+ 'disable' => array(
+ 'ko' => '%s nelze vypnout. Pro více detailů <a href="%s">zkontrolujte logy FressRSS</a>.',
+ 'ok' => '%s je nyní vypnut',
+ ),
+ 'enable' => array(
+ 'ko' => '%s nelze zapnout. Pro více detailů <a href="%s">zkontrolujte logy FressRSS</a>.',
+ 'ok' => '%s je nyní zapnut',
+ ),
+ 'no_access' => 'Nemáte přístup k %s',
+ 'not_enabled' => '%s není ještě zapnut',
+ 'not_found' => '%s neexistuje',
+ ),
+ 'import_export' => array(
+ 'export_no_zip_extension' => 'Na serveru není naistalována podpora zip. Zkuste prosím exportovat soubory jeden po druhém.',
+ 'feeds_imported' => 'Vaše kanály byly naimportovány a nyní budou aktualizovány',
+ 'feeds_imported_with_errors' => 'Vaše kanály byly naimportovány, došlo ale k nějakým chybám',
+ 'file_cannot_be_uploaded' => 'Soubor nelze nahrát!',
+ 'no_zip_extension' => 'Na serveru není naistalována podpora zip.',
+ 'zip_error' => 'Během importu zip souboru došlo k chybě.',
+ ),
+ 'sub' => array(
+ 'actualize' => 'Aktualizovat',
+ 'category' => array(
+ 'created' => 'Kategorie %s byla vytvořena.',
+ 'deleted' => 'Kategorie byla smazána.',
+ 'emptied' => 'Kategorie byla vyprázdněna',
+ 'error' => 'Kategorii nelze aktualizovat',
+ 'name_exists' => 'Název kategorie již existuje.',
+ 'no_id' => 'Musíte upřesnit id kategorie.',
+ 'no_name' => 'Název kategorie nemůže být prázdný.',
+ 'not_delete_default' => 'Nelze smazat výchozí kategorii!',
+ 'not_exist' => 'Tato kategorie neexistuje!',
+ 'over_max' => 'Dosáhl jste maximálního počtu kategorií (%d)',
+ 'updated' => 'Kategorie byla aktualizována.',
+ ),
+ 'feed' => array(
+ 'actualized' => '<em>%s</em> bylo aktualizováno',
+ 'actualizeds' => 'RSS kanály byly aktualizovány',
+ 'added' => 'RSS kanál <em>%s</em> byl přidán',
+ 'already_subscribed' => 'Již jste přihlášen k odběru <em>%s</em>',
+ 'deleted' => 'Kanál byl smazán',
+ 'error' => 'Kanál nelze aktualizovat',
+ 'internal_problem' => 'RSS kanál nelze přidat. Pro detaily <a href="%s">zkontrolujte logy FressRSS</a>.',
+ 'invalid_url' => 'URL <em>%s</em> není platné',
+ 'marked_read' => 'Kanály byly označeny jako přečtené',
+ 'n_actualized' => '%d kanálů bylo aktualizováno',
+ 'n_entries_deleted' => '%d článků bylo smazáno',
+ 'no_refresh' => 'Nelze obnovit žádné kanály…',
+ 'not_added' => '<em>%s</em> nemůže být přidán',
+ 'over_max' => 'Dosáhl jste maximálního počtu kanálů (%d)',
+ 'updated' => 'Kanál byl aktualizován',
+ ),
+ 'purge_completed' => 'Vyprázdněno (smazáno %d článků)',
+ ),
+ 'update' => array(
+ 'can_apply' => 'FreshRSS bude nyní upgradováno na <strong>verzi %s</strong>.',
+ 'error' => 'Během upgrade došlo k chybě: %s',
+ 'file_is_nok' => 'Zkontrolujte oprávnění adresáře <em>%s</em>. HTTP server musí mít do tohoto adresáře práva zápisu',
+ 'finished' => 'Upgrade hotov!',
+ 'none' => 'Novější verze není k dispozici',
+ 'server_not_found' => 'Nelze nalézt server s instalačním souborem. [%s]',
+ ),
+ 'user' => array(
+ 'created' => array(
+ '_' => 'Uživatel %s byl vytvořen',
+ 'error' => 'Uživatele %s nelze vytvořit',
+ ),
+ 'deleted' => array(
+ '_' => 'Uživatel %s byl smazán',
+ 'error' => 'Uživatele %s nelze smazat',
+ ),
+ ),
+ 'profile' => array(
+ 'error' => 'Váš profil nelze změnit',
+ 'updated' => 'Váš profil byl změněn',
+ ),
+);
diff --git a/app/i18n/cz/gen.php b/app/i18n/cz/gen.php
new file mode 100644
index 000000000..7c333a9c8
--- /dev/null
+++ b/app/i18n/cz/gen.php
@@ -0,0 +1,164 @@
+<?php
+
+return array(
+ 'action' => array(
+ 'actualize' => 'Aktualizovat',
+ 'back_to_rss_feeds' => '← Zpět na seznam RSS kanálů',
+ 'cancel' => 'Zrušit',
+ 'create' => 'Vytvořit',
+ 'disable' => 'Zakázat',
+ 'empty' => 'Vyprázdnit',
+ 'enable' => 'Povolit',
+ 'export' => 'Export',
+ 'filter' => 'Filtrovat',
+ 'import' => 'Import',
+ 'manage' => 'Spravovat',
+ 'mark_read' => 'Označit jako přečtené',
+ 'mark_favorite' => 'Označit jako oblíbené',
+ 'remove' => 'Odstranit',
+ 'see_website' => 'Navštívit WWW stránku',
+ 'submit' => 'Odeslat',
+ 'truncate' => 'Smazat všechny články',
+ ),
+ 'auth' => array(
+ 'keep_logged_in' => 'Zapamatovat přihlášení <small>(1 měsíc)</small>',
+ 'login' => 'Login',
+ 'login_persona' => 'Přihlášení pomocí Persona',
+ 'login_persona_problem' => 'Problém s připojením k Persona?',
+ 'logout' => 'Odhlášení',
+ 'password' => 'Heslo',
+ 'reset' => 'Reset přihlášení',
+ 'username' => 'Uživatel',
+ 'username_admin' => 'Název administrátorského účtu',
+ 'will_reset' => 'Přihlašovací systém bude vyresetován: místo sytému Persona bude použito přihlášení formulářem.',
+ ),
+ 'date' => array(
+ 'Apr' => '\\D\\u\\b\\e\\n',
+ 'Aug' => '\\S\\r\\p\\e\\n',
+ 'Dec' => '\\P\\r\\o\\s\\i\\n\\e\\c',
+ 'Feb' => '\\Ú\\n\\o\\r',
+ 'Jan' => '\\L\\e\\d\\e\\n',
+ 'Jul' => '\\Č\\e\\r\\v\\e\\n\\e\\c',
+ 'Jun' => '\\Č\\e\\r\\v\\e\\n',
+ 'Mar' => '\\B\\ř\\e\\z\\e\\n',
+ 'May' => '\\K\\v\\ě\\t\\e\\n',
+ 'Nov' => '\\L\\i\\s\\t\\o\\p\\a\\d',
+ 'Oct' => '\\Ř\\í\\j\\e\\n',
+ 'Sep' => '\\Z\\á\\ř\\í',
+ 'apr' => 'dub',
+ 'april' => 'Dub',
+ 'aug' => 'srp',
+ 'august' => 'Srp',
+ 'before_yesterday' => 'Předevčírem',
+ 'dec' => 'pro',
+ 'december' => 'Pro',
+ 'feb' => 'úno',
+ 'february' => 'Úno',
+ 'format_date' => 'j\\. %s Y',
+ 'format_date_hour' => 'j\\. %s Y \\v H\\:i',
+ 'fri' => 'Pá',
+ 'jan' => 'led',
+ 'january' => 'Led',
+ 'jul' => 'čvn',
+ 'july' => 'Čvn',
+ 'jun' => 'čer',
+ 'june' => 'Čer',
+ 'last_3_month' => 'Minulé tři měsíce',
+ 'last_6_month' => 'Minulých šest měsíců',
+ 'last_month' => 'Minulý měsíc',
+ 'last_week' => 'Minulý týden',
+ 'last_year' => 'Minulý rok',
+ 'mar' => 'bře',
+ 'march' => 'Bře',
+ 'may' => 'Kvě',
+ 'mon' => 'Po',
+ 'month' => 'měsíce',
+ 'nov' => 'lis',
+ 'november' => 'Lis',
+ 'oct' => 'říj',
+ 'october' => 'Říj',
+ 'sat' => 'So',
+ 'sep' => 'zář',
+ 'september' => 'Zář',
+ 'sun' => 'Ne',
+ 'thu' => 'Čt',
+ 'today' => 'Dnes',
+ 'tue' => 'Út',
+ 'wed' => 'St',
+ 'yesterday' => 'Včera',
+ ),
+ 'freshrss' => array(
+ '_' => 'FreshRSS',
+ 'about' => 'O FreshRSS',
+ ),
+ 'js' => array(
+ 'category_empty' => 'Prázdná kategorie',
+ 'confirm_action' => 'Jste si jist, že chcete provést tuto akci? Změny nelze vrátit zpět!',
+ 'confirm_action_feed_cat' => 'Jste si jist, že chcete provést tuto akci? Přijdete o související oblíbené položky a uživatelské dotazy. Změny nelze vrátit zpět!',
+ 'feedback' => array(
+ 'body_new_articles' => 'Je \\d nových článků k přečtení v FreshRSS.',
+ 'request_failed' => 'Požadavek selhal, což může být způsobeno problémy s připojení k internetu.',
+ 'title_new_articles' => 'FreshRSS: nové články!',
+ ),
+ 'new_article' => 'Jsou k dispozici nové články, stránku obnovíte kliknutím zde.',
+ 'should_be_activated' => 'JavaScript musí být povolen',
+ ),
+ 'lang' => array(
+ 'de' => 'Deutsch',
+ 'en' => 'English',
+ 'fr' => 'Français',
+ 'cz' => 'Čeština',
+ ),
+ 'menu' => array(
+ 'about' => 'O aplikaci',
+ 'admin' => 'Administrace',
+ 'archiving' => 'Archivace',
+ 'authentication' => 'Přihlášení',
+ 'check_install' => 'Ověření instalace',
+ 'configuration' => 'Nastavení',
+ 'display' => 'Zobrazení',
+ 'extensions' => 'Rozšíření',
+ 'logs' => 'Logy',
+ 'queries' => 'Uživatelské dotazy',
+ 'reading' => 'Čtení',
+ 'search' => 'Hledat výraz nebo #tagy',
+ 'sharing' => 'Sdílení',
+ 'shortcuts' => 'Zkratky',
+ 'stats' => 'Statistika',
+ 'update' => 'Aktualizace',
+ 'user_management' => 'Správa uživatelů',
+ 'user_profile' => 'Profil',
+ ),
+ 'pagination' => array(
+ 'first' => 'První',
+ 'last' => 'Poslední',
+ 'load_more' => 'Načíst více článků',
+ 'mark_all_read' => 'Označit vše jako přečtené',
+ 'next' => 'Další',
+ 'nothing_to_load' => 'Žádné nové články',
+ 'previous' => 'Předchozí',
+ ),
+ 'share' => array(
+ 'blogotext' => 'Blogotext',
+ 'diaspora' => 'Diaspora*',
+ 'email' => 'Email',
+ 'facebook' => 'Facebook',
+ 'g+' => 'Google+',
+ 'print' => 'Tisk',
+ 'shaarli' => 'Shaarli',
+ 'twitter' => 'Twitter',
+ 'wallabag' => 'wallabag',
+ ),
+ 'short' => array(
+ 'attention' => 'Upozornění!',
+ 'blank_to_disable' => 'Zakázat - ponechte prázdné',
+ 'by_author' => 'Od <em>%s</em>',
+ 'by_default' => 'Výchozí',
+ 'damn' => 'Sakra!',
+ 'default_category' => 'Nezařazeno',
+ 'no' => 'Ne',
+ 'ok' => 'Ok!',
+ 'or' => 'nebo',
+ 'yes' => 'Ano',
+ ),
+);
diff --git a/app/i18n/cz/index.php b/app/i18n/cz/index.php
new file mode 100644
index 000000000..5691d12af
--- /dev/null
+++ b/app/i18n/cz/index.php
@@ -0,0 +1,61 @@
+<?php
+
+return array(
+ 'about' => array(
+ '_' => 'O FreshRSS',
+ 'agpl3' => '<a href="https://www.gnu.org/licenses/agpl-3.0.html">AGPL 3</a>',
+ '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://www.google.com/webfonts/specimen/Open+Sans">Steve Matteson</a>. Favicony jsou shromažďovány pomocí <a href="https://getfavicon.appspot.com/">getFavicon API</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.',
+ 'github' => '<a href="https://github.com/FreshRSS/FreshRSS/issues">na Github</a>',
+ 'license' => 'Licence',
+ 'project_website' => 'Stránka projektu',
+ 'title' => 'O FreshRSS',
+ 'version' => 'Verze',
+ 'website' => 'Webové stránka',
+ ),
+ 'feed' => array(
+ 'add' => 'Můžete přidat kanály.',
+ 'empty' => 'Žádné články k zobrazení.',
+ 'rss_of' => 'RSS kanál %s',
+ 'title' => 'RSS kanály',
+ 'title_global' => 'Přehled',
+ 'title_fav' => 'Oblíbené',
+ ),
+ 'log' => array(
+ '_' => 'Logy',
+ 'clear' => 'Vymazat logy',
+ 'empty' => 'Log je prázdný',
+ 'title' => 'Logy',
+ ),
+ 'menu' => array(
+ 'about' => 'O FreshRSS',
+ 'add_query' => 'Vytvořit dotaz',
+ 'before_one_day' => 'Den nazpět',
+ 'before_one_week' => 'Před týdnem',
+ 'favorites' => 'Oblíbené (%s)',
+ 'global_view' => 'Přehled',
+ 'main_stream' => 'Všechny kanály',
+ 'mark_all_read' => 'Označit vše jako přečtené',
+ 'mark_cat_read' => 'Označit kategorii jako přečtenou',
+ 'mark_feed_read' => 'Označit kanál jako přečtený',
+ 'newer_first' => 'Nové nejdříve',
+ 'non-starred' => 'Zobrazit vše vyjma oblíbených',
+ 'normal_view' => 'Normální',
+ 'older_first' => 'Nejstarší nejdříve',
+ 'queries' => 'Uživatelské dotazy',
+ 'read' => 'Zobrazovat přečtené',
+ 'reader_view' => 'Čtení',
+ 'rss_view' => 'RSS kanál',
+ 'search_short' => 'Hledat',
+ 'starred' => 'Zobrazit oblíbené',
+ 'stats' => 'Statistika',
+ 'subscription' => 'Správa subskripcí',
+ 'unread' => 'Zobrazovat nepřečtené',
+ ),
+ 'share' => 'Sdílet',
+ 'tag' => array(
+ 'related' => 'Související tagy',
+ ),
+);
diff --git a/app/i18n/cz/install.php b/app/i18n/cz/install.php
new file mode 100644
index 000000000..53257c84f
--- /dev/null
+++ b/app/i18n/cz/install.php
@@ -0,0 +1,107 @@
+<?php
+
+return array(
+ 'action' => array(
+ 'finish' => 'Dokončit instalaci',
+ 'fix_errors_before' => 'Chyby prosím před přechodem na další krok opravte.',
+ 'next_step' => 'Přejít na další krok',
+ ),
+ 'auth' => array(
+ 'email_persona' => 'Email pro přihlášení<br /><small>(pro <a href="https://persona.org/" rel="external">Mozilla Persona</a>)</small>',
+ 'form' => 'Webový formulář (tradiční, vyžaduje JavaScript)',
+ 'http' => 'HTTP (pro pokročilé uživatele s HTTPS)',
+ 'none' => 'Žádný (nebezpečné)',
+ 'password_form' => 'Heslo<br /><small>(pro přihlášení webovým formulářem)</small>',
+ 'password_format' => 'Alespoň 7 znaků',
+ 'persona' => 'Mozilla Persona (moderní, vyžaduje JavaScript)',
+ 'type' => 'Způsob přihlášení',
+ ),
+ 'bdd' => array(
+ '_' => 'Databáze',
+ 'conf' => array(
+ '_' => 'Nastavení databáze',
+ 'ko' => 'Ověřte informace o databázi.',
+ 'ok' => 'Nastavení databáze bylo uloženo.',
+ ),
+ 'host' => 'Hostitel',
+ 'prefix' => 'Prefix tabulky',
+ 'password' => 'Heslo',
+ 'type' => 'Typ databáze',
+ 'username' => 'Uživatel',
+ ),
+ 'check' => array(
+ '_' => 'Kontrola',
+ 'cache' => array(
+ 'nok' => 'Zkontrolujte oprávnění adresáře <em>./data/cache</em>. HTTP server musí mít do tohoto adresáře práva zápisu',
+ 'ok' => 'Oprávnění adresáře cache jsou v pořádku.',
+ ),
+ 'ctype' => array(
+ 'nok' => 'Není nainstalována požadovaná knihovna pro ověřování znaků (php-ctype).',
+ 'ok' => 'Je nainstalována požadovaná knihovna pro ověřování znaků (ctype).',
+ ),
+ 'curl' => array(
+ 'nok' => 'Nemáte cURL (balíček php5-curl).',
+ 'ok' => 'Máte rozšíření cURL.',
+ ),
+ 'data' => array(
+ 'nok' => 'Zkontrolujte oprávnění adresáře <em>./data</em>. HTTP server musí mít do tohoto adresáře práva zápisu',
+ 'ok' => 'Oprávnění adresáře data jsou v pořádku.',
+ ),
+ 'dom' => array(
+ 'nok' => 'Nemáte požadovanou knihovnu pro procházení DOM (balíček php-xml).',
+ 'ok' => 'Máte požadovanou knihovnu pro procházení DOM.',
+ ),
+ 'favicons' => array(
+ 'nok' => 'Zkontrolujte oprávnění adresáře <em>./data/favicons</em>. HTTP server musí mít do tohoto adresáře práva zápisu',
+ 'ok' => 'Oprávnění adresáře favicons jsou v pořádku.',
+ ),
+ 'http_referer' => array(
+ 'nok' => 'Zkontrolujte prosím že neměníte HTTP REFERER.',
+ 'ok' => 'Váš HTTP REFERER je znám a odpovídá Vašemu serveru.',
+ ),
+ 'minz' => array(
+ 'nok' => 'Nemáte framework Minz.',
+ 'ok' => 'Máte framework Minz.',
+ ),
+ 'pcre' => array(
+ 'nok' => 'Nemáte požadovanou knihovnu pro regulární výrazy (php-pcre).',
+ 'ok' => 'Máte požadovanou knihovnu pro regulární výrazy (PCRE).',
+ ),
+ 'pdo' => array(
+ 'nok' => 'Nemáte PDO nebo některý z podporovaných ovladačů (pdo_mysql, pdo_sqlite).',
+ 'ok' => 'Máte PDO a alespoň jeden z podporovaných ovladačů (pdo_mysql, pdo_sqlite).',
+ ),
+ 'persona' => array(
+ 'nok' => 'Zkontrolujte oprávnění adresáře <em>./data/persona</em>. HTTP server musí mít do tohoto adresáře práva zápisu',
+ 'ok' => 'Oprávnění adresáře Mozilla Persona jsou v pořádku.',
+ ),
+ 'php' => array(
+ 'nok' => 'Vaše verze PHP je %s, ale FreshRSS vyžaduje alespoň verzi %s.',
+ 'ok' => 'Vaše verze PHP je %s a je kompatibilní s FreshRSS.',
+ ),
+ 'users' => array(
+ 'nok' => 'Zkontrolujte oprávnění adresáře <em>./data/users</em>. HTTP server musí mít do tohoto adresáře práva zápisu',
+ 'ok' => 'Oprávnění adresáře users jsou v pořádku.',
+ ),
+ ),
+ 'conf' => array(
+ '_' => 'Obecná nastavení',
+ 'ok' => 'Nastavení bylo uloženo.',
+ ),
+ 'congratulations' => 'Gratulujeme!',
+ 'default_user' => 'Jméno výchozího uživatele <small>(maximálně 16 alfanumerických znaků)</small>',
+ 'delete_articles_after' => 'Smazat články starší než',
+ 'fix_errors_before' => 'Chyby prosím před přechodem na další krok opravte.',
+ 'javascript_is_better' => 'Práce s FreshRSS je příjemnější se zapnutým JavaScriptem',
+ 'language' => array(
+ '_' => 'Jazyk',
+ 'choose' => 'Vyberte jazyk FreshRSS',
+ 'defined' => 'Jazyk byl nastaven.',
+ ),
+ 'not_deleted' => 'Nastala chyba, soubor <em>%s</em> musíte smazat ručně.',
+ 'ok' => 'Instalace byla úspěšná.',
+ 'step' => 'krok %d',
+ 'steps' => 'Kroky',
+ 'title' => 'Instalace · FreshRSS',
+ 'this_is_the_end' => 'Konec',
+);
diff --git a/app/i18n/cz/sub.php b/app/i18n/cz/sub.php
new file mode 100644
index 000000000..78712506c
--- /dev/null
+++ b/app/i18n/cz/sub.php
@@ -0,0 +1,61 @@
+<?php
+
+return array(
+ 'category' => array(
+ '_' => 'Kategorie',
+ 'add' => 'Přidat kategorii',
+ 'empty' => 'Vyprázdit kategorii',
+ 'new' => 'Nová kategorie',
+ ),
+ 'feed' => array(
+ 'add' => 'Přidat RSS kanál',
+ 'advanced' => 'Pokročilé',
+ 'archiving' => 'Archivace',
+ 'auth' => array(
+ 'configuration' => 'Přihlášení',
+ 'help' => 'Umožní přístup k RSS kanálům chráneným HTTP autentizací',
+ 'http' => 'HTTP přihlášení',
+ 'password' => 'Heslo',
+ 'username' => 'Přihlašovací jméno',
+ ),
+ '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',
+ 'empty' => 'Kanál je prázdný. Ověřte prosím zda je ještě autorem udržován.',
+ 'error' => 'Vyskytl se problém s kanálem. Ověřte že je vždy dostupný, prosím, a poté jej aktualizujte.',
+ 'in_main_stream' => 'Zobrazit ve “Všechny kanály”',
+ 'informations' => 'Informace',
+ 'keep_history' => 'Zachovat tento minimální počet článků',
+ 'moved_category_deleted' => 'Po smazání kategorie budou v ní obsažené kanály automaticky přesunuty do <em>%s</em>.',
+ 'no_selected' => 'Nejsou označeny žádné kanály.',
+ 'number_entries' => '%d článků',
+ 'stats' => 'Statistika',
+ 'think_to_add' => 'Můžete přidat kanály.',
+ 'title' => 'Název',
+ 'title_add' => 'Přidat RSS kanál',
+ 'ttl' => 'Neobnovovat častěji než',
+ 'url' => 'URL kanálu',
+ 'validator' => 'Zkontrolovat platnost kanálu',
+ 'website' => 'URL webové stránky',
+ ),
+ 'import_export' => array(
+ 'export' => 'Export',
+ 'export_opml' => 'Exportovat seznam kanálů (OPML)',
+ 'export_starred' => 'Exportovat oblíbené',
+ 'feed_list' => 'Seznam %s článků',
+ 'file_to_import' => 'Soubor k importu<br />(OPML, Json nebo Zip)',
+ 'file_to_import_no_zip' => 'Soubor k importu<br />(OPML nebo Json)',
+ 'import' => 'Import',
+ 'starred_list' => 'Seznam oblíbených článků',
+ 'title' => 'Import / export',
+ ),
+ 'menu' => array(
+ 'bookmark' => 'Přihlásit (FreshRSS bookmark)',
+ 'import_export' => 'Import / export',
+ 'subscription_management' => 'Správa subskripcí',
+ ),
+ 'title' => array(
+ '_' => 'Správa subskripcí',
+ 'feed_management' => 'Správa RSS kanálů',
+ ),
+);
diff --git a/app/i18n/de/admin.php b/app/i18n/de/admin.php
index 8550805ee..c0cbf6787 100644
--- a/app/i18n/de/admin.php
+++ b/app/i18n/de/admin.php
@@ -127,11 +127,11 @@ return array(
'entry_repartition' => 'Einträge-Verteilung',
'feed' => 'Feed',
'feed_per_category' => 'Feeds pro Kategorie',
- 'idle' => 'Inkative Feeds',
+ 'idle' => 'Inaktive Feeds',
'main' => 'Haupt-Statistiken',
'main_stream' => 'Haupt-Feeds',
'menu' => array(
- 'idle' => 'Inkative Feeds',
+ 'idle' => 'Inaktive Feeds',
'main' => 'Haupt-Statistiken',
'repartition' => 'Artikel-Verteilung',
),
diff --git a/app/i18n/de/conf.php b/app/i18n/de/conf.php
index 04ebfab7e..4a0a77ddd 100644
--- a/app/i18n/de/conf.php
+++ b/app/i18n/de/conf.php
@@ -84,6 +84,7 @@ return array(
'articles_per_page' => 'Anzahl der Artikel pro Seite',
'auto_load_more' => 'Die nächsten Artikel am Seitenende laden',
'auto_remove_article' => 'Artikel nach dem Lesen verstecken',
+ 'mark_updated_article_unread' => 'Markieren Sie aktualisierte Artikel als ungelesen',
'confirm_enabled' => 'Bei der Aktion „Alle als gelesen markieren“ einen Bestätigungsdialog anzeigen',
'display_articles_unfolded' => 'Artikel standardmäßig ausgeklappt zeigen',
'display_categories_unfolded' => 'Kategorien standardmäßig eingeklappt zeigen',
diff --git a/app/i18n/de/gen.php b/app/i18n/de/gen.php
index cce364d50..f24a52c2e 100644
--- a/app/i18n/de/gen.php
+++ b/app/i18n/de/gen.php
@@ -104,6 +104,7 @@ return array(
'should_be_activated' => 'JavaScript muss aktiviert sein',
),
'lang' => array(
+ 'cz' => 'Čeština',
'de' => 'Deutsch',
'en' => 'English',
'fr' => 'Français',
@@ -156,6 +157,7 @@ return array(
'damn' => 'Verdammt!',
'default_category' => 'Unkategorisiert',
'no' => 'Nein',
+ 'not_applicable' => 'Nicht verfügbar',
'ok' => 'OK!',
'or' => 'oder',
'yes' => 'Ja',
diff --git a/app/i18n/en/admin.php b/app/i18n/en/admin.php
index d2fcd3e82..155384afd 100644
--- a/app/i18n/en/admin.php
+++ b/app/i18n/en/admin.php
@@ -12,7 +12,7 @@ return array(
'title' => 'Authentication',
'title_reset' => 'Authentication reset',
'token' => 'Authentication token',
- 'token_help' => 'Allows to access RSS output of the default user without authentication:',
+ 'token_help' => 'Allows access to RSS output of the default user without authentication:',
'type' => 'Authentication method',
'unsafe_autologin' => 'Allow unsafe automatic login using the format: ',
),
diff --git a/app/i18n/en/conf.php b/app/i18n/en/conf.php
index 308c45d2c..42a06906d 100644
--- a/app/i18n/en/conf.php
+++ b/app/i18n/en/conf.php
@@ -5,9 +5,9 @@ return array(
'_' => 'Archiving',
'advanced' => 'Advanced',
'delete_after' => 'Remove articles after',
- 'help' => 'More options are available in the individual stream settings',
+ 'help' => 'More options are available in the individual feed settings',
'keep_history_by_feed' => 'Minimum number of articles to keep by feed',
- 'optimize' => 'Optimize database',
+ 'optimize' => 'Optimise database',
'optimize_help' => 'To do occasionally to reduce the size of the database',
'purge_now' => 'Purge now',
'title' => 'Archiving',
@@ -72,7 +72,7 @@ return array(
),
'profile' => array(
'_' => 'Profile management',
- 'email_persona' => 'Login mail address<br /><small>(for <a href="https://persona.org/" rel="external">Mozilla Persona</a>)</small>',
+ 'email_persona' => 'Login email address<br /><small>(for <a href="https://persona.org/" rel="external">Mozilla Persona</a>)</small>',
'password_api' => 'Password API<br /><small>(e.g., for mobile apps)</small>',
'password_form' => 'Password<br /><small>(for the Web-form login method)</small>',
'password_format' => 'At least 7 characters',
@@ -84,6 +84,7 @@ return array(
'articles_per_page' => 'Number of articles per page',
'auto_load_more' => 'Load next articles at the page bottom',
'auto_remove_article' => 'Hide articles after reading',
+ 'mark_updated_article_unread' => 'Mark updated articles as unread',
'confirm_enabled' => 'Display a confirmation dialog on “mark all as read” actions',
'display_articles_unfolded' => 'Show articles unfolded by default',
'display_categories_unfolded' => 'Show categories folded by default',
diff --git a/app/i18n/en/feedback.php b/app/i18n/en/feedback.php
index 19af81e5b..c9189c0d0 100644
--- a/app/i18n/en/feedback.php
+++ b/app/i18n/en/feedback.php
@@ -2,7 +2,7 @@
return array(
'admin' => array(
- 'optimization_complete' => 'Optimization complete',
+ 'optimization_complete' => 'Optimisation complete',
),
'access' => array(
'denied' => 'You don’t have permission to access this page',
@@ -52,7 +52,7 @@ return array(
'zip_error' => 'An error occured during Zip import.',
),
'sub' => array(
- 'actualize' => 'Actualize',
+ 'actualize' => 'Actualise',
'category' => array(
'created' => 'Category %s has been created.',
'deleted' => 'Category has been deleted.',
@@ -86,7 +86,7 @@ return array(
'purge_completed' => 'Purge completed (%d articles deleted)',
),
'update' => array(
- 'can_apply' => 'FreshRSS will be now updated to the <strong>version %s</strong>.',
+ 'can_apply' => 'FreshRSS will now be updated to the <strong>version %s</strong>.',
'error' => 'The update process has encountered an error: %s',
'file_is_nok' => 'Check permissions on <em>%s</em> directory. HTTP server must have rights to write into',
'finished' => 'Update completed!',
diff --git a/app/i18n/en/gen.php b/app/i18n/en/gen.php
index 2143822ed..6e47e0921 100644
--- a/app/i18n/en/gen.php
+++ b/app/i18n/en/gen.php
@@ -10,7 +10,7 @@ return array(
'empty' => 'Empty',
'enable' => 'Enable',
'export' => 'Export',
- 'filter' => 'Filtrer',
+ 'filter' => 'Filter',
'import' => 'Import',
'manage' => 'Manage',
'mark_read' => 'Mark as read',
@@ -104,6 +104,7 @@ return array(
'should_be_activated' => 'JavaScript must be enabled',
),
'lang' => array(
+ 'cz' => 'Čeština',
'de' => 'Deutsch',
'en' => 'English',
'fr' => 'Français',
@@ -149,13 +150,14 @@ return array(
'wallabag' => 'wallabag',
),
'short' => array(
- 'attention' => 'Attention!',
+ 'attention' => 'Warning!',
'blank_to_disable' => 'Leave blank to disable',
'by_author' => 'By <em>%s</em>',
'by_default' => 'By default',
'damn' => 'Damn!',
'default_category' => 'Uncategorized',
'no' => 'No',
+ 'not_applicable' => 'Not available',
'ok' => 'Ok!',
'or' => 'or',
'yes' => 'Yes',
diff --git a/app/i18n/en/install.php b/app/i18n/en/install.php
index 2bc6bd38f..d4137309a 100644
--- a/app/i18n/en/install.php
+++ b/app/i18n/en/install.php
@@ -3,11 +3,11 @@
return array(
'action' => array(
'finish' => 'Complete installation',
- 'fix_errors_before' => 'Fix errors before skip to the next step.',
+ 'fix_errors_before' => 'Please fix errors before skipping to the next step.',
'next_step' => 'Go to the next step',
),
'auth' => array(
- 'email_persona' => 'Login mail address<br /><small>(for <a href="https://persona.org/" rel="external">Mozilla Persona</a>)</small>',
+ 'email_persona' => 'Login email address<br /><small>(for <a href="https://persona.org/" rel="external">Mozilla Persona</a>)</small>',
'form' => 'Web form (traditional, requires JavaScript)',
'http' => 'HTTP (for advanced users with HTTPS)',
'none' => 'None (dangerous)',
@@ -91,7 +91,7 @@ return array(
'congratulations' => 'Congratulations!',
'default_user' => 'Username of the default user <small>(maximum 16 alphanumeric characters)</small>',
'delete_articles_after' => 'Remove articles after',
- 'fix_errors_before' => 'Fix errors before skip to the next step.',
+ 'fix_errors_before' => 'Please fix errors before skipping to the next step.',
'javascript_is_better' => 'FreshRSS is more pleasant with JavaScript enabled',
'language' => array(
'_' => 'Language',
diff --git a/app/i18n/en/sub.php b/app/i18n/en/sub.php
index 2b62e4775..8fe150bb2 100644
--- a/app/i18n/en/sub.php
+++ b/app/i18n/en/sub.php
@@ -18,7 +18,7 @@ return array(
'password' => 'HTTP password',
'username' => 'HTTP username',
),
- 'css_help' => 'Retrieves truncated RSS feeds (attention, requires more time!)',
+ 'css_help' => 'Retrieves truncated RSS feeds (caution, requires more time!)',
'css_path' => 'Articles CSS path on original website',
'description' => 'Description',
'empty' => 'This feed is empty. Please verify that it is still maintained.',
@@ -26,7 +26,7 @@ return array(
'in_main_stream' => 'Show in main stream',
'informations' => 'Information',
'keep_history' => 'Minimum number of articles to keep',
- 'moved_category_deleted' => 'When you delete a category, their feeds are automatically classified under <em>%s</em>.',
+ 'moved_category_deleted' => 'When you delete a category, its feeds are automatically classified under <em>%s</em>.',
'no_selected' => 'No feed selected.',
'number_entries' => '%d articles',
'stats' => 'Statistics',
diff --git a/app/i18n/fr/conf.php b/app/i18n/fr/conf.php
index d38445b99..87f9be290 100644
--- a/app/i18n/fr/conf.php
+++ b/app/i18n/fr/conf.php
@@ -84,6 +84,7 @@ return array(
'articles_per_page' => 'Nombre d’articles par page',
'auto_load_more' => 'Charger les articles suivants en bas de page',
'auto_remove_article' => 'Cacher les articles après lecture',
+ 'mark_updated_article_unread' => 'Marquer les articles mis à jour comme non-lus',
'confirm_enabled' => 'Afficher une confirmation lors des actions “marquer tout comme lu”',
'display_articles_unfolded' => 'Afficher les articles dépliés par défaut',
'display_categories_unfolded' => 'Afficher les catégories pliées par défaut',
diff --git a/app/i18n/fr/gen.php b/app/i18n/fr/gen.php
index 1cfec6969..c81e57bf7 100644
--- a/app/i18n/fr/gen.php
+++ b/app/i18n/fr/gen.php
@@ -104,6 +104,7 @@ return array(
'should_be_activated' => 'Le JavaScript doit être activé.',
),
'lang' => array(
+ 'cz' => 'Čeština',
'de' => 'Deutsch',
'en' => 'English',
'fr' => 'Français',
@@ -156,6 +157,7 @@ return array(
'damn' => 'Arf !',
'default_category' => 'Sans catégorie',
'no' => 'Non',
+ 'not_applicable' => 'Non disponible',
'ok' => 'Ok !',
'or' => 'ou',
'yes' => 'Oui',
diff --git a/app/install.php b/app/install.php
index 177173fdb..3cdd59d11 100644
--- a/app/install.php
+++ b/app/install.php
@@ -168,8 +168,10 @@ function saveStep3() {
$_SESSION['bd_prefix_user'] = $_SESSION['bd_prefix'] .(empty($_SESSION['default_user']) ? '' :($_SESSION['default_user'] . '_'));
}
+ //TODO: load `config.default.php` as default
$config_array = array(
'environment' => 'production',
+ 'simplepie_syslog_enabled' => true,
'salt' => $_SESSION['salt'],
'title' => $_SESSION['title'],
'default_user' => $_SESSION['default_user'],
@@ -741,6 +743,13 @@ function printStep3() {
<script>
function mySqlShowHide() {
document.getElementById('mysql').style.display = document.getElementById('type').value === 'mysql' ? 'block' : 'none';
+ if (document.getElementById('type').value !== 'mysql') {
+ document.getElementById('host').value = '';
+ document.getElementById('user').value = '';
+ document.getElementById('pass').value = '';
+ document.getElementById('base').value = '';
+ document.getElementById('prefix').value = '';
+ }
}
mySqlShowHide();
</script>
diff --git a/app/views/auth/index.phtml b/app/views/auth/index.phtml
index f7a862ac9..8e4df8c2c 100644
--- a/app/views/auth/index.phtml
+++ b/app/views/auth/index.phtml
@@ -9,7 +9,7 @@
<div class="form-group">
<label class="group-name" for="auth_type"><?php echo _t('admin.auth.type'); ?></label>
<div class="group-controls">
- <select id="auth_type" name="auth_type" required="required">
+ <select id="auth_type" name="auth_type" required="required" data-leave-validation="<?php echo FreshRSS_Context::$system_conf->auth_type; ?>">
<?php if (!in_array(FreshRSS_Context::$system_conf->auth_type, array('form', 'persona', 'http_auth', 'none'))) { ?>
<option selected="selected"></option>
<?php } ?>
@@ -25,7 +25,7 @@
<div class="group-controls">
<label class="checkbox" for="anon_access">
<input type="checkbox" name="anon_access" id="anon_access" value="1"<?php echo FreshRSS_Context::$system_conf->allow_anonymous ? ' checked="checked"' : '',
- FreshRSS_Auth::accessNeedsAction() ? '' : ' disabled="disabled"'; ?> />
+ FreshRSS_Auth::accessNeedsAction() ? '' : ' disabled="disabled"'; ?> data-leave-validation="<?php echo FreshRSS_Context::$system_conf->allow_anonymous; ?>"/>
<?php echo _t('admin.auth.allow_anonymous', FreshRSS_Context::$system_conf->default_user); ?>
</label>
</div>
@@ -35,7 +35,7 @@
<div class="group-controls">
<label class="checkbox" for="anon_refresh">
<input type="checkbox" name="anon_refresh" id="anon_refresh" value="1"<?php echo FreshRSS_Context::$system_conf->allow_anonymous_refresh ? ' checked="checked"' : '',
- FreshRSS_Auth::accessNeedsAction() ? '' : ' disabled="disabled"'; ?> />
+ FreshRSS_Auth::accessNeedsAction() ? '' : ' disabled="disabled"'; ?> data-leave-validation="<?php echo FreshRSS_Context::$system_conf->allow_anonymous_refresh; ?>"/>
<?php echo _t('admin.auth.allow_anonymous_refresh'); ?>
</label>
</div>
@@ -45,7 +45,7 @@
<div class="group-controls">
<label class="checkbox" for="unsafe_autologin">
<input type="checkbox" name="unsafe_autologin" id="unsafe_autologin" value="1"<?php echo FreshRSS_Context::$system_conf->unsafe_autologin_enabled ? ' checked="checked"' : '',
- FreshRSS_Auth::accessNeedsAction() ? '' : ' disabled="disabled"'; ?> />
+ FreshRSS_Auth::accessNeedsAction() ? '' : ' disabled="disabled"'; ?> data-leave-validation="<?php echo FreshRSS_Context::$system_conf->unsafe_autologin_enabled; ?>"/>
<?php echo _t('admin.auth.unsafe_autologin'); ?>
<kbd><?php echo Minz_Url::display(array('c' => 'auth', 'a' => 'login', 'params' => array('u' => 'alice', 'p' => '1234')), 'html', true); ?></kbd>
</label>
@@ -58,7 +58,7 @@
<?php $token = FreshRSS_Context::$user_conf->token; ?>
<div class="group-controls">
<input type="text" id="token" name="token" value="<?php echo $token; ?>" placeholder="<?php echo _t('gen.short.blank_to_disable'); ?>"<?php
- echo FreshRSS_Auth::accessNeedsAction() ? '' : ' disabled="disabled"'; ?> />
+ echo FreshRSS_Auth::accessNeedsAction() ? '' : ' disabled="disabled"'; ?> data-leave-validation="<?php echo $token; ?>"/>
<?php echo _i('help'); ?> <?php echo _t('admin.auth.token_help'); ?>
<kbd><?php echo Minz_Url::display(array('params' => array('output' => 'rss', 'token' => $token)), 'html', true); ?></kbd>
</div>
@@ -69,7 +69,7 @@
<div class="group-controls">
<label class="checkbox" for="api_enabled">
<input type="checkbox" name="api_enabled" id="api_enabled" value="1"<?php echo FreshRSS_Context::$system_conf->api_enabled ? ' checked="checked"' : '',
- FreshRSS_Auth::accessNeedsLogin() ? '' : ' disabled="disabled"'; ?> />
+ FreshRSS_Auth::accessNeedsLogin() ? '' : ' disabled="disabled"'; ?> data-leave-validation="<?php echo FreshRSS_Context::$system_conf->api_enabled; ?>"/>
<?php echo _t('admin.auth.api_enabled'); ?>
</label>
</div>
diff --git a/app/views/configure/archiving.phtml b/app/views/configure/archiving.phtml
index 875463137..52ee98a48 100644
--- a/app/views/configure/archiving.phtml
+++ b/app/views/configure/archiving.phtml
@@ -10,14 +10,14 @@
<div class="form-group">
<label class="group-name" for="old_entries"><?php echo _t('conf.archiving.delete_after'); ?></label>
<div class="group-controls">
- <input type="number" id="old_entries" name="old_entries" min="1" max="1200" value="<?php echo FreshRSS_Context::$user_conf->old_entries; ?>" /> <?php echo _t('gen.date.month'); ?>
+ <input type="number" id="old_entries" name="old_entries" min="1" max="1200" value="<?php echo FreshRSS_Context::$user_conf->old_entries; ?>" data-leave-validation="<?php echo FreshRSS_Context::$user_conf->old_entries; ?>"/> <?php echo _t('gen.date.month'); ?>
  <a class="btn confirm" href="<?php echo _url('entry', 'purge'); ?>"><?php echo _t('conf.archiving.purge_now'); ?></a>
</div>
</div>
<div class="form-group">
<label class="group-name" for="keep_history_default"><?php echo _t('conf.archiving.keep_history_by_feed'); ?></label>
<div class="group-controls">
- <select class="number" name="keep_history_default" id="keep_history_default" required="required"><?php
+ <select class="number" name="keep_history_default" id="keep_history_default" required="required" data-leave-validation="<?php echo FreshRSS_Context::$user_conf->keep_history_default; ?>"><?php
foreach (array('' => '', 0 => '0', 10 => '10', 50 => '50', 100 => '100', 500 => '500', 1000 => '1 000', 5000 => '5 000', 10000 => '10 000', -1 => '∞') as $v => $t) {
echo '<option value="' . $v . (FreshRSS_Context::$user_conf->keep_history_default == $v ? '" selected="selected' : '') . '">' . $t . ' </option>';
}
@@ -27,7 +27,7 @@
<div class="form-group">
<label class="group-name" for="ttl_default"><?php echo _t('conf.archiving.ttl'); ?></label>
<div class="group-controls">
- <select class="number" name="ttl_default" id="ttl_default" required="required"><?php
+ <select class="number" name="ttl_default" id="ttl_default" required="required" data-leave-validation="<?php echo FreshRSS_Context::$user_conf->ttl_default; ?>"><?php
$found = false;
foreach (array(1200 => '20min', 1500 => '25min', 1800 => '30min', 2700 => '45min',
3600 => '1h', 5400 => '1.5h', 7200 => '2h', 10800 => '3h', 14400 => '4h', 18800 => '5h', 21600 => '6h', 25200 => '7h', 28800 => '8h',
diff --git a/app/views/configure/display.phtml b/app/views/configure/display.phtml
index 02249bc55..91b0b8189 100644
--- a/app/views/configure/display.phtml
+++ b/app/views/configure/display.phtml
@@ -9,7 +9,7 @@
<div class="form-group">
<label class="group-name" for="language"><?php echo _t('conf.display.language'); ?></label>
<div class="group-controls">
- <select name="language" id="language">
+ <select name="language" id="language" data-leave-validation="<?php echo FreshRSS_Context::$user_conf->language; ?>">
<?php $languages = Minz_Translate::availableLanguages(); ?>
<?php foreach ($languages as $lang) { ?>
<option value="<?php echo $lang; ?>"<?php echo FreshRSS_Context::$user_conf->language === $lang ? ' selected="selected"' : ''; ?>><?php echo _t('gen.lang.' . $lang); ?></option>
@@ -24,7 +24,7 @@
<ul class="slides">
<?php $slides = count($this->themes); $i = 1; ?>
<?php foreach($this->themes as $theme) { ?>
- <input type="radio" name="theme" id="img-<?php echo $i ?>" <?php if (FreshRSS_Context::$user_conf->theme === $theme['id']) {echo "checked";}?> value="<?php echo $theme['id'] ?>"/>
+ <input type="radio" name="theme" id="img-<?php echo $i ?>" <?php if (FreshRSS_Context::$user_conf->theme === $theme['id']) {echo "checked";}?> value="<?php echo $theme['id'] ?>" data-leave-validation="<?php echo (FreshRSS_Context::$user_conf->theme === $theme['id']) ? 1 : 0; ?>"/>
<li class="slide-container">
<div class="slide">
<img src="<?php echo Minz_Url::display('/themes/' . $theme['id'] . '/thumbs/original.png')?>"/>
@@ -53,7 +53,7 @@
<div class="form-group">
<label class="group-name" for="content_width"><?php echo _t('conf.display.width.content'); ?></label>
<div class="group-controls">
- <select name="content_width" id="content_width" required="">
+ <select name="content_width" id="content_width" required="" data-leave-validation="<?php echo $width; ?>">
<option value="thin" <?php echo $width === 'thin'? 'selected="selected"' : ''; ?>>
<?php echo _t('conf.display.width.thin'); ?>
</option>
@@ -87,20 +87,20 @@
<tbody>
<tr>
<th><?php echo _t('conf.display.icon.top_line'); ?></th>
- <td><input type="checkbox" name="topline_read" value="1"<?php echo FreshRSS_Context::$user_conf->topline_read ? ' checked="checked"' : ''; ?> /></td>
- <td><input type="checkbox" name="topline_favorite" value="1"<?php echo FreshRSS_Context::$user_conf->topline_favorite ? ' checked="checked"' : ''; ?> /></td>
+ <td><input type="checkbox" name="topline_read" value="1"<?php echo FreshRSS_Context::$user_conf->topline_read ? ' checked="checked"' : ''; ?> data-leave-validation="<?php echo FreshRSS_Context::$user_conf->topline_read; ?>"/></td>
+ <td><input type="checkbox" name="topline_favorite" value="1"<?php echo FreshRSS_Context::$user_conf->topline_favorite ? ' checked="checked"' : ''; ?> data-leave-validation="<?php echo FreshRSS_Context::$user_conf->topline_favorite; ?>"/></td>
<td><input type="checkbox" disabled="disabled" /></td>
<td><input type="checkbox" disabled="disabled" /></td>
- <td><input type="checkbox" name="topline_date" value="1"<?php echo FreshRSS_Context::$user_conf->topline_date ? ' checked="checked"' : ''; ?> /></td>
- <td><input type="checkbox" name="topline_link" value="1"<?php echo FreshRSS_Context::$user_conf->topline_link ? ' checked="checked"' : ''; ?> /></td>
+ <td><input type="checkbox" name="topline_date" value="1"<?php echo FreshRSS_Context::$user_conf->topline_date ? ' checked="checked"' : ''; ?> data-leave-validation="<?php echo FreshRSS_Context::$user_conf->topline_date; ?>"/></td>
+ <td><input type="checkbox" name="topline_link" value="1"<?php echo FreshRSS_Context::$user_conf->topline_link ? ' checked="checked"' : ''; ?> data-leave-validation="<?php echo FreshRSS_Context::$user_conf->topline_link; ?>"/></td>
</tr><tr>
<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"' : ''; ?> /></td>
- <td><input type="checkbox" name="bottomline_favorite" value="1"<?php echo FreshRSS_Context::$user_conf->bottomline_favorite ? ' checked="checked"' : ''; ?> /></td>
- <td><input type="checkbox" name="bottomline_sharing" value="1"<?php echo FreshRSS_Context::$user_conf->bottomline_sharing ? ' checked="checked"' : ''; ?> /></td>
- <td><input type="checkbox" name="bottomline_tags" value="1"<?php echo FreshRSS_Context::$user_conf->bottomline_tags ? ' checked="checked"' : ''; ?> /></td>
- <td><input type="checkbox" name="bottomline_date" value="1"<?php echo FreshRSS_Context::$user_conf->bottomline_date ? ' checked="checked"' : ''; ?> /></td>
- <td><input type="checkbox" name="bottomline_link" value="1"<?php echo FreshRSS_Context::$user_conf->bottomline_link ? ' checked="checked"' : ''; ?> /></td>
+ <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_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>
</tbody>
</table><br />
@@ -109,7 +109,7 @@
<div class="form-group">
<label class="group-name" for="posts_per_page"><?php echo _t('conf.display.notif_html5.timeout'); ?></label>
<div class="group-controls">
- <input type="number" id="html5_notif_timeout" name="html5_notif_timeout" value="<?php echo FreshRSS_Context::$user_conf->html5_notif_timeout; ?>" /> <?php echo _t('conf.display.notif_html5.seconds'); ?>
+ <input type="number" id="html5_notif_timeout" name="html5_notif_timeout" value="<?php echo FreshRSS_Context::$user_conf->html5_notif_timeout; ?>" data-leave-validation="<?php echo FreshRSS_Context::$user_conf->html5_notif_timeout; ?>"/> <?php echo _t('conf.display.notif_html5.seconds'); ?>
</div>
</div>
diff --git a/app/views/configure/queries.phtml b/app/views/configure/queries.phtml
index 5f449deb3..50df4cfea 100644
--- a/app/views/configure/queries.phtml
+++ b/app/views/configure/queries.phtml
@@ -6,27 +6,29 @@
<form method="post" action="<?php echo _url('configure', 'queries'); ?>">
<legend><?php echo _t('conf.query'); ?></legend>
- <?php foreach (FreshRSS_Context::$user_conf->queries as $key => $query) { ?>
+ <?php foreach ($this->queries as $key => $query) { ?>
<div class="form-group" id="query-group-<?php echo $key; ?>">
<label class="group-name" for="queries_<?php echo $key; ?>_name">
<?php echo _t('conf.query.number', $key + 1); ?>
</label>
<div class="group-controls">
- <input type="hidden" id="queries_<?php echo $key; ?>_search" name="queries[<?php echo $key; ?>][search]" value="<?php echo isset($query['search']) ? $query['search'] : ""; ?>"/>
- <input type="hidden" id="queries_<?php echo $key; ?>_state" name="queries[<?php echo $key; ?>][state]" value="<?php echo isset($query['state']) ? $query['state'] : ""; ?>"/>
- <input type="hidden" id="queries_<?php echo $key; ?>_order" name="queries[<?php echo $key; ?>][order]" value="<?php echo isset($query['order']) ? $query['order'] : ""; ?>"/>
- <input type="hidden" id="queries_<?php echo $key; ?>_get" name="queries[<?php echo $key; ?>][get]" value="<?php echo isset($query['get']) ? $query['get'] : ""; ?>"/>
+ <input type="hidden" id="queries_<?php echo $key; ?>_search" name="queries[<?php echo $key; ?>][url]" value="<?php echo $query->getUrl(); ?>"/>
+ <input type="hidden" id="queries_<?php echo $key; ?>_search" name="queries[<?php echo $key; ?>][search]" value="<?php echo $query->getSearch(); ?>"/>
+ <input type="hidden" id="queries_<?php echo $key; ?>_state" name="queries[<?php echo $key; ?>][state]" value="<?php echo $query->getState(); ?>"/>
+ <input type="hidden" id="queries_<?php echo $key; ?>_order" name="queries[<?php echo $key; ?>][order]" value="<?php echo $query->getOrder(); ?>"/>
+ <input type="hidden" id="queries_<?php echo $key; ?>_get" name="queries[<?php echo $key; ?>][get]" value="<?php echo $query->getGet(); ?>"/>
<div class="stick">
<input class="extend"
type="text"
id="queries_<?php echo $key; ?>_name"
name="queries[<?php echo $key; ?>][name]"
- value="<?php echo $query['name']; ?>"
+ value="<?php echo $query->getName(); ?>"
+ data-leave-validation="<?php echo $query->getName(); ?>"
/>
- <a class="btn" href="<?php echo $query['url']; ?>">
+ <a class="btn" href="<?php echo $query->getUrl(); ?>">
<?php echo _i('link'); ?>
</a>
@@ -35,23 +37,11 @@
</a>
</div>
- <?php
- $exist = (isset($query['search']) ? 1 : 0)
- + (isset($query['state']) ? 1 : 0)
- + (isset($query['order']) ? 1 : 0)
- + (isset($query['get']) ? 1 : 0);
- // If the only filter is "all" articles, we consider there is no filter
- $exist = ($exist === 1 && isset($query['get']) && $query['get'] === 'a') ? 0 : $exist;
-
- $deprecated = (isset($this->query_get[$key]) &&
- $this->query_get[$key]['deprecated']);
- ?>
-
- <?php if ($exist === 0) { ?>
+ <?php if (!$query->hasParameters()) { ?>
<div class="alert alert-warn">
<div class="alert-head"><?php echo _t('conf.query.no_filter'); ?></div>
</div>
- <?php } elseif ($deprecated) { ?>
+ <?php } elseif ($query->isDeprecated()) { ?>
<div class="alert alert-error">
<div class="alert-head"><?php echo _t('conf.query.deprecated'); ?></div>
</div>
@@ -60,20 +50,20 @@
<div class="alert-head"><?php echo _t('conf.query.filter'); ?></div>
<ul>
- <?php if (isset($query['search'])) { ?>
- <li class="item"><?php echo _t('conf.query.search', $query['search']); ?></li>
+ <?php if ($query->hasSearch()) { ?>
+ <li class="item"><?php echo _t('conf.query.search', $query->getSearch()->getRawInput()); ?></li>
<?php } ?>
- <?php if (isset($query['state'])) { ?>
- <li class="item"><?php echo _t('conf.query.state_' . $query['state']); ?></li>
+ <?php if ($query->getState()) { ?>
+ <li class="item"><?php echo _t('conf.query.state_' . $query->getState()); ?></li>
<?php } ?>
- <?php if (isset($query['order'])) { ?>
- <li class="item"><?php echo _t('conf.query.order_' . strtolower($query['order'])); ?></li>
+ <?php if ($query->getOrder()) { ?>
+ <li class="item"><?php echo _t('conf.query.order_' . strtolower($query->getOrder())); ?></li>
<?php } ?>
- <?php if (isset($query['get'])) { ?>
- <li class="item"><?php echo _t('conf.query.get_' . $this->query_get[$key]['type'], $this->query_get[$key]['name']); ?></li>
+ <?php if ($query->getGet()) { ?>
+ <li class="item"><?php echo _t('conf.query.get_' . $query->getGetType(), $query->getGetName()); ?></li>
<?php } ?>
</ul>
</div>
diff --git a/app/views/configure/reading.phtml b/app/views/configure/reading.phtml
index 636671f14..1b7a101df 100644
--- a/app/views/configure/reading.phtml
+++ b/app/views/configure/reading.phtml
@@ -9,7 +9,7 @@
<div class="form-group">
<label class="group-name" for="posts_per_page"><?php echo _t('conf.reading.articles_per_page'); ?></label>
<div class="group-controls">
- <input type="number" id="posts_per_page" name="posts_per_page" value="<?php echo FreshRSS_Context::$user_conf->posts_per_page; ?>" min="5" max="50" />
+ <input type="number" id="posts_per_page" name="posts_per_page" value="<?php echo FreshRSS_Context::$user_conf->posts_per_page; ?>" min="5" max="50" data-leave-validation="<?php echo FreshRSS_Context::$user_conf->posts_per_page; ?>"/>
<?php echo _i('help'); ?> <?php echo _t('conf.reading.number_divided_when_reader'); ?>
</div>
</div>
@@ -17,7 +17,7 @@
<div class="form-group">
<label class="group-name" for="sort_order"><?php echo _t('conf.reading.sort'); ?></label>
<div class="group-controls">
- <select name="sort_order" id="sort_order">
+ <select name="sort_order" id="sort_order" data-leave-validation="<?php echo FreshRSS_Context::$user_conf->sort_order; ?>">
<option value="DESC"<?php echo FreshRSS_Context::$user_conf->sort_order === 'DESC' ? ' selected="selected"' : ''; ?>><?php echo _t('conf.reading.sort.newer_first'); ?></option>
<option value="ASC"<?php echo FreshRSS_Context::$user_conf->sort_order === 'ASC' ? ' selected="selected"' : ''; ?>><?php echo _t('conf.reading.sort.older_first'); ?></option>
</select>
@@ -27,7 +27,7 @@
<div class="form-group">
<label class="group-name" for="view_mode"><?php echo _t('conf.reading.view.default'); ?></label>
<div class="group-controls">
- <select name="view_mode" id="view_mode">
+ <select name="view_mode" id="view_mode" data-leave-validation="<?php echo FreshRSS_Context::$user_conf->view_mode; ?>">
<option value="normal"<?php echo FreshRSS_Context::$user_conf->view_mode === 'normal' ? ' selected="selected"' : ''; ?>><?php echo _t('conf.reading.view.normal'); ?></option>
<option value="reader"<?php echo FreshRSS_Context::$user_conf->view_mode === 'reader' ? ' selected="selected"' : ''; ?>><?php echo _t('conf.reading.view.reader'); ?></option>
<option value="global"<?php echo FreshRSS_Context::$user_conf->view_mode === 'global' ? ' selected="selected"' : ''; ?>><?php echo _t('conf.reading.view.global'); ?></option>
@@ -38,7 +38,7 @@
<div class="form-group">
<label class="group-name" for="view_mode"><?php echo _t('conf.reading.show'); ?></label>
<div class="group-controls">
- <select name="default_view" id="default_view">
+ <select name="default_view" id="default_view" data-leave-validation="<?php echo FreshRSS_Context::$user_conf->default_view; ?>">
<option value="adaptive"<?php echo FreshRSS_Context::$user_conf->default_view === 'adaptive' ? ' selected="selected"' : ''; ?>><?php echo _t('conf.reading.show.adaptive'); ?></option>
<option value="all"<?php echo FreshRSS_Context::$user_conf->default_view === 'all' ? ' selected="selected"' : ''; ?>><?php echo _t('conf.reading.show.all_articles'); ?></option>
<option value="unread"<?php echo FreshRSS_Context::$user_conf->default_view === 'unread' ? ' selected="selected"' : ''; ?>><?php echo _t('conf.reading.show.unread'); ?></option>
@@ -49,7 +49,7 @@
<div class="form-group">
<div class="group-controls">
<label class="checkbox" for="hide_read_feeds">
- <input type="checkbox" name="hide_read_feeds" id="hide_read_feeds" value="1"<?php echo FreshRSS_Context::$user_conf->hide_read_feeds ? ' checked="checked"' : ''; ?> />
+ <input type="checkbox" name="hide_read_feeds" id="hide_read_feeds" value="1"<?php echo FreshRSS_Context::$user_conf->hide_read_feeds ? ' checked="checked"' : ''; ?> data-leave-validation="<?php echo FreshRSS_Context::$user_conf->hide_read_feeds; ?>"/>
<?php echo _t('conf.reading.hide_read_feeds'); ?>
</label>
</div>
@@ -58,7 +58,7 @@
<div class="form-group">
<div class="group-controls">
<label class="checkbox" for="display_posts">
- <input type="checkbox" name="display_posts" id="display_posts" value="1"<?php echo FreshRSS_Context::$user_conf->display_posts ? ' checked="checked"' : ''; ?> />
+ <input type="checkbox" name="display_posts" id="display_posts" value="1"<?php echo FreshRSS_Context::$user_conf->display_posts ? ' checked="checked"' : ''; ?> data-leave-validation="<?php echo FreshRSS_Context::$user_conf->display_posts; ?>"/>
<?php echo _t('conf.reading.display_articles_unfolded'); ?>
<noscript> — <strong><?php echo _t('gen.js.should_be_activated'); ?></strong></noscript>
</label>
@@ -68,7 +68,7 @@
<div class="form-group">
<div class="group-controls">
<label class="checkbox" for="display_categories">
- <input type="checkbox" name="display_categories" id="display_categories" value="1"<?php echo FreshRSS_Context::$user_conf->display_categories ? ' checked="checked"' : ''; ?> />
+ <input type="checkbox" name="display_categories" id="display_categories" value="1"<?php echo FreshRSS_Context::$user_conf->display_categories ? ' checked="checked"' : ''; ?> data-leave-validation="<?php echo FreshRSS_Context::$user_conf->display_categories; ?>"/>
<?php echo _t('conf.reading.display_categories_unfolded'); ?>
<noscript> — <strong><?php echo _t('gen.js.should_be_activated'); ?></strong></noscript>
</label>
@@ -78,7 +78,7 @@
<div class="form-group">
<div class="group-controls">
<label class="checkbox" for="sticky_post">
- <input type="checkbox" name="sticky_post" id="sticky_post" value="1"<?php echo FreshRSS_Context::$user_conf->sticky_post ? ' checked="checked"' : ''; ?> />
+ <input type="checkbox" name="sticky_post" id="sticky_post" value="1"<?php echo FreshRSS_Context::$user_conf->sticky_post ? ' checked="checked"' : ''; ?> data-leave-validation="<?php echo FreshRSS_Context::$user_conf->sticky_post; ?>"/>
<?php echo _t('conf.reading.sticky_post'); ?>
<noscript> — <strong><?php echo _t('gen.js.should_be_activated'); ?></strong></noscript>
</label>
@@ -88,7 +88,7 @@
<div class="form-group">
<div class="group-controls">
<label class="checkbox" for="auto_load_more">
- <input type="checkbox" name="auto_load_more" id="auto_load_more" value="1"<?php echo FreshRSS_Context::$user_conf->auto_load_more ? ' checked="checked"' : ''; ?> />
+ <input type="checkbox" name="auto_load_more" id="auto_load_more" value="1"<?php echo FreshRSS_Context::$user_conf->auto_load_more ? ' checked="checked"' : ''; ?> data-leave-validation="<?php echo FreshRSS_Context::$user_conf->auto_load_more; ?>"/>
<?php echo _t('conf.reading.auto_load_more'); ?>
<noscript> — <strong><?php echo _t('gen.js.should_be_activated'); ?></strong></noscript>
</label>
@@ -98,7 +98,7 @@
<div class="form-group">
<div class="group-controls">
<label class="checkbox" for="lazyload">
- <input type="checkbox" name="lazyload" id="lazyload" value="1"<?php echo FreshRSS_Context::$user_conf->lazyload ? ' checked="checked"' : ''; ?> />
+ <input type="checkbox" name="lazyload" id="lazyload" value="1"<?php echo FreshRSS_Context::$user_conf->lazyload ? ' checked="checked"' : ''; ?> data-leave-validation="<?php echo FreshRSS_Context::$user_conf->lazyload; ?>"/>
<?php echo _t('conf.reading.img_with_lazyload'); ?>
<noscript> — <strong><?php echo _t('gen.js.should_be_activated'); ?></strong></noscript>
</label>
@@ -108,7 +108,7 @@
<div class="form-group">
<div class="group-controls">
<label class="checkbox" for="reading_confirm">
- <input type="checkbox" name="reading_confirm" id="reading_confirm" value="1"<?php echo FreshRSS_Context::$user_conf->reading_confirm ? ' checked="checked"' : ''; ?> />
+ <input type="checkbox" name="reading_confirm" id="reading_confirm" value="1"<?php echo FreshRSS_Context::$user_conf->reading_confirm ? ' checked="checked"' : ''; ?> data-leave-validation="<?php echo FreshRSS_Context::$user_conf->reading_confirm; ?>"/>
<?php echo _t('conf.reading.confirm_enabled'); ?>
<noscript> — <strong><?php echo _t('gen.js.should_be_activated'); ?></strong></noscript>
</label>
@@ -118,7 +118,7 @@
<div class="form-group">
<div class="group-controls">
<label class="checkbox" for="auto_remove_article">
- <input type="checkbox" name="auto_remove_article" id="auto_remove_article" value="1"<?php echo FreshRSS_Context::$user_conf->auto_remove_article ? ' checked="checked"' : ''; ?> />
+ <input type="checkbox" name="auto_remove_article" id="auto_remove_article" value="1"<?php echo FreshRSS_Context::$user_conf->auto_remove_article ? ' checked="checked"' : ''; ?> data-leave-validation="<?php echo FreshRSS_Context::$user_conf->auto_remove_article; ?>"/>
<?php echo _t('conf.reading.auto_remove_article'); ?>
<noscript> — <strong><?php echo _t('gen.js.should_be_activated'); ?></strong></noscript>
</label>
@@ -126,22 +126,31 @@
</div>
<div class="form-group">
+ <div class="group-controls">
+ <label class="checkbox" for="mark_updated_article_unread">
+ <input type="checkbox" name="mark_updated_article_unread" id="mark_updated_article_unread" value="1"<?php echo FreshRSS_Context::$user_conf->mark_updated_article_unread ? ' checked="checked"' : ''; ?> data-leave-validation="<?php echo FreshRSS_Context::$user_conf->mark_updated_article_unread; ?>"/>
+ <?php echo _t('conf.reading.mark_updated_article_unread'); ?>
+ </label>
+ </div>
+ </div>
+
+ <div class="form-group">
<label class="group-name"><?php echo _t('conf.reading.read.when'); ?></label>
<div class="group-controls">
<label class="checkbox" for="check_open_article">
- <input type="checkbox" name="mark_open_article" id="check_open_article" value="1"<?php echo FreshRSS_Context::$user_conf->mark_when['article'] ? ' checked="checked"' : ''; ?> />
+ <input type="checkbox" name="mark_open_article" id="check_open_article" value="1"<?php echo FreshRSS_Context::$user_conf->mark_when['article'] ? ' checked="checked"' : ''; ?> data-leave-validation="<?php echo FreshRSS_Context::$user_conf->mark_when['article']; ?>"/>
<?php echo _t('conf.reading.read.article_viewed'); ?>
</label>
<label class="checkbox" for="check_open_site">
- <input type="checkbox" name="mark_open_site" id="check_open_site" value="1"<?php echo FreshRSS_Context::$user_conf->mark_when['site'] ? ' checked="checked"' : ''; ?> />
+ <input type="checkbox" name="mark_open_site" id="check_open_site" value="1"<?php echo FreshRSS_Context::$user_conf->mark_when['site'] ? ' checked="checked"' : ''; ?> data-leave-validation="<?php echo FreshRSS_Context::$user_conf->mark_when['site']; ?>"/>
<?php echo _t('conf.reading.read.article_open_on_website'); ?>
</label>
<label class="checkbox" for="check_scroll">
- <input type="checkbox" name="mark_scroll" id="check_scroll" value="1"<?php echo FreshRSS_Context::$user_conf->mark_when['scroll'] ? ' checked="checked"' : ''; ?> />
+ <input type="checkbox" name="mark_scroll" id="check_scroll" value="1"<?php echo FreshRSS_Context::$user_conf->mark_when['scroll'] ? ' checked="checked"' : ''; ?> data-leave-validation="<?php echo FreshRSS_Context::$user_conf->mark_when['scroll']; ?>"/>
<?php echo _t('conf.reading.read.scroll'); ?>
</label>
<label class="checkbox" for="check_reception">
- <input type="checkbox" name="mark_upon_reception" id="check_reception" value="1"<?php echo FreshRSS_Context::$user_conf->mark_when['reception'] ? ' checked="checked"' : ''; ?> />
+ <input type="checkbox" name="mark_upon_reception" id="check_reception" value="1"<?php echo FreshRSS_Context::$user_conf->mark_when['reception'] ? ' checked="checked"' : ''; ?> data-leave-validation="<?php echo FreshRSS_Context::$user_conf->mark_when['reception']; ?>"/>
<?php echo _t('conf.reading.read.upon_reception'); ?>
</label>
</div>
@@ -151,7 +160,7 @@
<label class="group-name"><?php echo _t('conf.reading.after_onread'); ?></label>
<div class="group-controls">
<label class="checkbox" for="onread_jump_next">
- <input type="checkbox" name="onread_jump_next" id="onread_jump_next" value="1"<?php echo FreshRSS_Context::$user_conf->onread_jump_next ? ' checked="checked"' : ''; ?> />
+ <input type="checkbox" name="onread_jump_next" id="onread_jump_next" value="1"<?php echo FreshRSS_Context::$user_conf->onread_jump_next ? ' checked="checked"' : ''; ?> data-leave-validation="<?php echo FreshRSS_Context::$user_conf->onread_jump_next; ?>"/>
<?php echo _t('conf.reading.jump_next'); ?>
</label>
</div>
diff --git a/app/views/configure/sharing.phtml b/app/views/configure/sharing.phtml
index da7557480..7bf435777 100644
--- a/app/views/configure/sharing.phtml
+++ b/app/views/configure/sharing.phtml
@@ -4,7 +4,8 @@
<a href="<?php echo _url('index', 'index'); ?>"><?php echo _t('gen.action.back_to_rss_feeds'); ?></a>
<form method="post" action="<?php echo _url('configure', 'sharing'); ?>"
- data-simple='<div class="form-group" id="group-share-##key##"><label class="group-name">##label##</label><div class="group-controls"><a href="#" class="remove btn btn-attention" data-remove="group-share-##key##"><?php echo _i('close'); ?></a>
+ data-simple='<div class="form-group" id="group-share-##key##"><label class="group-name">##label##</label><div class="group-controls"><div class="stick"><input type="text" id="share_##key##_name" name="share[##key##][name]" class="extend" value="##label##" placeholder="<?php echo _t('conf.sharing.share_name'); ?>" size="64" />
+ <input type="url" id="share_##key##_url" name="share[##key##][url]" class="extend" value="" placeholder="<?php echo _t('gen.short.not_applicable'); ?>" size="64" disabled /><a href="#" class="remove btn btn-attention" data-remove="group-share-##key##"><?php echo _i('close'); ?></a></div>
<input type="hidden" id="share_##key##_type" name="share[##key##][type]" value="##type##" /></div></div>'
data-advanced='<div class="form-group" id="group-share-##key##"><label class="group-name">##label##</label><div class="group-controls">
<input type="hidden" id="share_##key##_type" name="share[##key##][type]" value="##type##" />
@@ -26,16 +27,17 @@
</label>
<div class="group-controls">
<input type='hidden' id='share_<?php echo $key; ?>_type' name="share[<?php echo $key; ?>][type]" value='<?php echo $share->type(); ?>' />
+ <div class="stick">
+ <input type="text" id="share_<?php echo $key; ?>_name" name="share[<?php echo $key; ?>][name]" class="extend" value="<?php echo $share->name(); ?>" placeholder="<?php echo _t('conf.sharing.share_name'); ?>" size="64" data-leave-validation="<?php echo $share->name(); ?>"/>
<?php if ($share->formType() === 'advanced') { ?>
- <div class="stick">
- <input type="text" id="share_<?php echo $key; ?>_name" name="share[<?php echo $key; ?>][name]" class="extend" value="<?php echo $share->name(); ?>" placeholder="<?php echo _t('conf.sharing.share_name'); ?>" size="64" />
- <input type="url" id="share_<?php echo $key; ?>_url" name="share[<?php echo $key; ?>][url]" class="extend" value="<?php echo $share->baseUrl(); ?>" placeholder="<?php echo _t('conf.sharing.share_url'); ?>" size="64" />
- <a href='#' class='remove btn btn-attention' data-remove="group-share-<?php echo $key; ?>"><?php echo _i('close'); ?></a>
- </div>
-
- <a target="_blank" class="btn" title="<?php echo _t('conf.sharing.more_information'); ?>" href="<?php echo $share->help(); ?>"><?php echo _i('help'); ?></a>
+ <input type="url" id="share_<?php echo $key; ?>_url" name="share[<?php echo $key; ?>][url]" class="extend" value="<?php echo $share->baseUrl(); ?>" placeholder="<?php echo _t('conf.sharing.share_url'); ?>" size="64" data-leave-validation="<?php echo $share->baseUrl(); ?>"/>
<?php } else { ?>
- <a href='#' class='remove btn btn-attention' data-remove="group-share-<?php echo $key; ?>"><?php echo _i('close'); ?></a>
+ <input type="url" id="share_<?php echo $key; ?>_url" name="share[<?php echo $key; ?>][url]" class="extend" value="<?php echo $share->baseUrl(); ?>" placeholder="<?php echo _t('gen.short.not_applicable'); ?>" size="64" disabled/>
+ <?php } ?>
+ <a href='#' class='remove btn btn-attention' data-remove="group-share-<?php echo $key; ?>"><?php echo _i('close'); ?></a>
+ </div>
+ <?php if ($share->formType() === 'advanced') { ?>
+ <a target="_blank" class="btn" title="<?php echo _t('conf.sharing.more_information'); ?>" href="<?php echo $share->help(); ?>"><?php echo _i('help'); ?></a>
<?php } ?>
</div>
</div>
diff --git a/app/views/configure/shortcut.phtml b/app/views/configure/shortcut.phtml
index f68091af9..264a5f805 100644
--- a/app/views/configure/shortcut.phtml
+++ b/app/views/configure/shortcut.phtml
@@ -23,28 +23,28 @@
<div class="form-group">
<label class="group-name" for="next_entry"><?php echo _t('conf.shortcut.next_article'); ?></label>
<div class="group-controls">
- <input type="text" id="next_entry" name="shortcuts[next_entry]" list="keys" value="<?php echo $s['next_entry']; ?>" />
+ <input type="text" id="next_entry" name="shortcuts[next_entry]" list="keys" value="<?php echo $s['next_entry']; ?>" data-leave-validation="<?php echo $s['next_entry']; ?>"/>
</div>
</div>
<div class="form-group">
<label class="group-name" for="prev_entry"><?php echo _t('conf.shortcut.previous_article'); ?></label>
<div class="group-controls">
- <input type="text" id="prev_entry" name="shortcuts[prev_entry]" list="keys" value="<?php echo $s['prev_entry']; ?>" />
+ <input type="text" id="prev_entry" name="shortcuts[prev_entry]" list="keys" value="<?php echo $s['prev_entry']; ?>" data-leave-validation="<?php echo $s['prev_entry']; ?>"/>
</div>
</div>
<div class="form-group">
<label class="group-name" for="first_entry"><?php echo _t('conf.shortcut.first_article'); ?></label>
<div class="group-controls">
- <input type="text" id="first_entry" name="shortcuts[first_entry]" list="keys" value="<?php echo $s['first_entry']; ?>" />
+ <input type="text" id="first_entry" name="shortcuts[first_entry]" list="keys" value="<?php echo $s['first_entry']; ?>" data-leave-validation="<?php echo $s['first_entry']; ?>"/>
</div>
</div>
<div class="form-group">
<label class="group-name" for="last_entry"><?php echo _t('conf.shortcut.last_article'); ?></label>
<div class="group-controls">
- <input type="text" id="last_entry" name="shortcuts[last_entry]" list="keys" value="<?php echo $s['last_entry']; ?>" />
+ <input type="text" id="last_entry" name="shortcuts[last_entry]" list="keys" value="<?php echo $s['last_entry']; ?>" data-leave-validation="<?php echo $s['last_entry']; ?>"/>
</div>
</div>
@@ -53,7 +53,7 @@
<div class="form-group">
<label class="group-name" for="mark_read"><?php echo _t('conf.shortcut.mark_read'); ?></label>
<div class="group-controls">
- <input type="text" id="mark_read" name="shortcuts[mark_read]" list="keys" value="<?php echo $s['mark_read']; ?>" />
+ <input type="text" id="mark_read" name="shortcuts[mark_read]" list="keys" value="<?php echo $s['mark_read']; ?>" data-leave-validation="<?php echo $s['mark_read']; ?>"/>
<?php echo _t('conf.shortcut.shift_for_all_read'); ?>
</div>
</div>
@@ -61,21 +61,21 @@
<div class="form-group">
<label class="group-name" for="mark_favorite"><?php echo _t('conf.shortcut.mark_favorite'); ?></label>
<div class="group-controls">
- <input type="text" id="mark_favorite" name="shortcuts[mark_favorite]" list="keys" value="<?php echo $s['mark_favorite']; ?>" />
+ <input type="text" id="mark_favorite" name="shortcuts[mark_favorite]" list="keys" value="<?php echo $s['mark_favorite']; ?>" data-leave-validation="<?php echo $s['mark_favorite']; ?>"/>
</div>
</div>
<div class="form-group">
<label class="group-name" for="go_website"><?php echo _t('conf.shortcut.see_on_website'); ?></label>
<div class="group-controls">
- <input type="text" id="go_website" name="shortcuts[go_website]" list="keys" value="<?php echo $s['go_website']; ?>" />
+ <input type="text" id="go_website" name="shortcuts[go_website]" list="keys" value="<?php echo $s['go_website']; ?>" data-leave-validation="<?php echo $s['go_website']; ?>"/>
</div>
</div>
<div class="form-group">
<label class="group-name" for="auto_share_shortcut"><?php echo _t('conf.shortcut.auto_share'); ?></label>
<div class="group-controls">
- <input type="text" id="auto_share_shortcut" name="shortcuts[auto_share]" list="keys" value="<?php echo $s['auto_share']; ?>" />
+ <input type="text" id="auto_share_shortcut" name="shortcuts[auto_share]" list="keys" value="<?php echo $s['auto_share']; ?>" data-leave-validation="<?php echo $s['auto_share']; ?>"/>
<?php echo _t('conf.shortcut.auto_share_help'); ?>
</div>
</div>
@@ -83,7 +83,7 @@
<div class="form-group">
<label class="group-name" for="collapse_entry"><?php echo _t('conf.shortcut.collapse_article'); ?></label>
<div class="group-controls">
- <input type="text" id="collapse_entry" name="shortcuts[collapse_entry]" list="keys" value="<?php echo $s['collapse_entry']; ?>" />
+ <input type="text" id="collapse_entry" name="shortcuts[collapse_entry]" list="keys" value="<?php echo $s['collapse_entry']; ?>" data-leave-validation="<?php echo $s['collapse_entry']; ?>"/>
</div>
</div>
@@ -92,21 +92,21 @@
<div class="form-group">
<label class="group-name" for="load_more_shortcut"><?php echo _t('conf.shortcut.load_more'); ?></label>
<div class="group-controls">
- <input type="text" id="load_more_shortcut" name="shortcuts[load_more]" list="keys" value="<?php echo $s['load_more']; ?>" />
+ <input type="text" id="load_more_shortcut" name="shortcuts[load_more]" list="keys" value="<?php echo $s['load_more']; ?>" data-leave-validation="<?php echo $s['load_more']; ?>"/>
</div>
</div>
<div class="form-group">
<label class="group-name" for="focus_search_shortcut"><?php echo _t('conf.shortcut.focus_search'); ?></label>
<div class="group-controls">
- <input type="text" id="focus_search_shortcut" name="shortcuts[focus_search]" list="keys" value="<?php echo $s['focus_search']; ?>" />
+ <input type="text" id="focus_search_shortcut" name="shortcuts[focus_search]" list="keys" value="<?php echo $s['focus_search']; ?>" data-leave-validation="<?php echo $s['focus_search']; ?>"/>
</div>
</div>
<div class="form-group">
<label class="group-name" for="user_filter_shortcut"><?php echo _t('conf.shortcut.user_filter'); ?></label>
<div class="group-controls">
- <input type="text" id="user_filter_shortcut" name="shortcuts[user_filter]" list="keys" value="<?php echo $s['user_filter']; ?>" />
+ <input type="text" id="user_filter_shortcut" name="shortcuts[user_filter]" list="keys" value="<?php echo $s['user_filter']; ?>" data-leave-validation="<?php echo $s['user_filter']; ?>"/>
<?php echo _t('conf.shortcut.user_filter_help'); ?>
</div>
</div>
@@ -114,14 +114,14 @@
<div class="form-group">
<label class="group-name" for="close_dropdown_shortcut"><?php echo _t('conf.shortcut.close_dropdown'); ?></label>
<div class="group-controls">
- <input type="text" id="close_dropdown" name="shortcuts[close_dropdown]" list="keys" value="<?php echo $s['close_dropdown']; ?>" />
+ <input type="text" id="close_dropdown" name="shortcuts[close_dropdown]" list="keys" value="<?php echo $s['close_dropdown']; ?>" data-leave-validation="<?php echo $s['close_dropdown']; ?>"/>
</div>
</div>
<div class="form-group">
<label class="group-name" for="help_shortcut"><?php echo _t('conf.shortcut.help'); ?></label>
<div class="group-controls">
- <input type="text" id="help_shortcut" name="shortcuts[help]" list="keys" value="<?php echo $s['help']; ?>" />
+ <input type="text" id="help_shortcut" name="shortcuts[help]" list="keys" value="<?php echo $s['help']; ?>" data-leave-validation="<?php echo $s['help']; ?>"/>
</div>
</div>