aboutsummaryrefslogtreecommitdiff
path: root/app
diff options
context:
space:
mode:
authorGravatar Alexandre Alapetite <alexandre@alapetite.fr> 2016-10-30 20:15:11 +0100
committerGravatar GitHub <noreply@github.com> 2016-10-30 20:15:11 +0100
commit1d3e5bdee069434fd65c2717ae8fcce8c54fe81d (patch)
tree39b0ae9ac0b0d1ed7fa11c747a0523cb3faa384b /app
parent17c8c039df675b3b0f8d88d14f7316a240eabe76 (diff)
parent29e1f048159b7a580bdf1bab184e928f11d104b4 (diff)
Merge pull request #1346 from FreshRSS/dev1.6.0
Merge 1.6.0-dev in master
Diffstat (limited to 'app')
-rw-r--r--app/Controllers/categoryController.php5
-rwxr-xr-xapp/Controllers/configureController.php2
-rwxr-xr-xapp/Controllers/feedController.php318
-rw-r--r--app/Controllers/importExportController.php379
-rwxr-xr-xapp/Controllers/indexController.php44
-rw-r--r--app/Controllers/statsController.php36
-rw-r--r--app/Controllers/userController.php161
-rw-r--r--app/Exceptions/AlreadySubscribedException.php14
-rw-r--r--app/Exceptions/FeedNotAddedException.php14
-rw-r--r--app/Exceptions/ZipException.php14
-rw-r--r--app/Exceptions/ZipMissingException.php4
-rw-r--r--app/FreshRSS.php2
-rw-r--r--app/Models/CategoryDAO.php21
-rw-r--r--app/Models/ConfigurationSetter.php1
-rw-r--r--app/Models/Context.php20
-rw-r--r--app/Models/DatabaseDAOPGSQL.php43
-rw-r--r--app/Models/EntryDAO.php168
-rw-r--r--app/Models/EntryDAOPGSQL.php31
-rw-r--r--app/Models/EntryDAOSQLite.php20
-rw-r--r--app/Models/Factory.php42
-rw-r--r--app/Models/Feed.php41
-rw-r--r--app/Models/FeedDAO.php57
-rw-r--r--app/Models/FeedDAOSQLite.php4
-rw-r--r--app/Models/StatsDAO.php102
-rw-r--r--app/Models/StatsDAOPGSQL.php67
-rw-r--r--app/Models/StatsDAOSQLite.php59
-rw-r--r--app/Models/UserDAO.php54
-rw-r--r--app/SQL/install.sql.mysql.php7
-rw-r--r--app/SQL/install.sql.pgsql.php63
-rw-r--r--app/SQL/install.sql.sqlite.php36
-rwxr-xr-xapp/actualize_script.php3
-rw-r--r--app/i18n/cz/admin.php8
-rw-r--r--app/i18n/cz/feedback.php6
-rw-r--r--app/i18n/cz/install.php6
-rw-r--r--app/i18n/cz/sub.php4
-rw-r--r--app/i18n/de/admin.php8
-rw-r--r--app/i18n/de/feedback.php6
-rw-r--r--app/i18n/de/install.php8
-rw-r--r--app/i18n/de/sub.php2
-rw-r--r--app/i18n/en/admin.php20
-rw-r--r--app/i18n/en/feedback.php6
-rw-r--r--app/i18n/en/install.php26
-rw-r--r--app/i18n/en/sub.php4
-rw-r--r--app/i18n/fr/admin.php28
-rw-r--r--app/i18n/fr/feedback.php6
-rw-r--r--app/i18n/fr/install.php26
-rw-r--r--app/i18n/fr/sub.php4
-rw-r--r--app/i18n/it/admin.php8
-rw-r--r--app/i18n/it/feedback.php6
-rw-r--r--app/i18n/it/install.php12
-rw-r--r--app/i18n/it/sub.php4
-rw-r--r--app/i18n/nl/admin.php8
-rw-r--r--app/i18n/nl/feedback.php6
-rw-r--r--app/i18n/nl/install.php10
-rw-r--r--app/i18n/nl/sub.php4
-rw-r--r--app/i18n/ru/admin.php8
-rw-r--r--app/i18n/ru/feedback.php6
-rw-r--r--app/i18n/ru/install.php10
-rw-r--r--app/i18n/ru/sub.php4
-rw-r--r--app/i18n/tr/admin.php8
-rw-r--r--app/i18n/tr/feedback.php6
-rw-r--r--app/i18n/tr/install.php10
-rw-r--r--app/i18n/tr/sub.php4
-rw-r--r--app/install.php196
-rw-r--r--app/layout/aside_feed.phtml4
-rw-r--r--app/layout/aside_subscription.phtml2
-rw-r--r--app/layout/layout.phtml3
-rw-r--r--app/layout/nav_menu.phtml7
-rw-r--r--app/views/auth/index.phtml2
-rw-r--r--app/views/configure/sharing.phtml4
-rw-r--r--app/views/feed/add.phtml6
-rw-r--r--app/views/helpers/feed/update.phtml6
-rw-r--r--app/views/helpers/index/normal/entry_bottom.phtml4
-rw-r--r--app/views/helpers/index/normal/entry_header.phtml4
-rwxr-xr-xapp/views/helpers/pagination.phtml3
-rw-r--r--app/views/importExport/index.phtml2
-rw-r--r--app/views/index/global.phtml5
-rw-r--r--app/views/index/normal.phtml2
-rw-r--r--app/views/stats/index.phtml12
-rw-r--r--app/views/stats/repartition.phtml8
-rw-r--r--app/views/user/manage.phtml4
81 files changed, 1403 insertions, 985 deletions
diff --git a/app/Controllers/categoryController.php b/app/Controllers/categoryController.php
index e65c146de..922f92844 100644
--- a/app/Controllers/categoryController.php
+++ b/app/Controllers/categoryController.php
@@ -117,7 +117,6 @@ class FreshRSS_category_Controller extends Minz_ActionController {
public function deleteAction() {
$feedDAO = FreshRSS_Factory::createFeedDao();
$catDAO = new FreshRSS_CategoryDAO();
- $default_category = $catDAO->getDefault();
$url_redirect = array('c' => 'subscription', 'a' => 'index');
if (Minz_Request::isPost()) {
@@ -128,11 +127,11 @@ class FreshRSS_category_Controller extends Minz_ActionController {
Minz_Request::bad(_t('feedback.sub.category.no_id'), $url_redirect);
}
- if ($id === $default_category->id()) {
+ if ($id === FreshRSS_CategoryDAO::defaultCategoryId) {
Minz_Request::bad(_t('feedback.sub.category.not_delete_default'), $url_redirect);
}
- if ($feedDAO->changeCategory($id, $default_category->id()) === false) {
+ if ($feedDAO->changeCategory($id, FreshRSS_CategoryDAO::defaultCategoryId) === false) {
Minz_Request::bad(_t('feedback.sub.category.error'), $url_redirect);
}
diff --git a/app/Controllers/configureController.php b/app/Controllers/configureController.php
index 147a2fe06..e73f106a6 100755
--- a/app/Controllers/configureController.php
+++ b/app/Controllers/configureController.php
@@ -139,7 +139,7 @@ class FreshRSS_configure_Controller extends Minz_ActionController {
*/
public function sharingAction() {
if (Minz_Request::isPost()) {
- $params = Minz_Request::fetchGET();
+ $params = Minz_Request::fetchPOST();
FreshRSS_Context::$user_conf->sharing = $params['share'];
FreshRSS_Context::$user_conf->save();
invalidateHttpCache();
diff --git a/app/Controllers/feedController.php b/app/Controllers/feedController.php
index ffda1450d..c4115584a 100755
--- a/app/Controllers/feedController.php
+++ b/app/Controllers/feedController.php
@@ -26,6 +26,62 @@ class FreshRSS_feed_Controller extends Minz_ActionController {
}
}
+ public static function addFeed($url, $title = '', $cat_id = 0, $new_cat_name = '', $http_auth = '') {
+ @set_time_limit(300);
+
+ $catDAO = new FreshRSS_CategoryDAO();
+
+ $cat = null;
+ if ($cat_id > 0) {
+ $cat = $catDAO->searchById($cat_id);
+ }
+ if ($cat == null && $new_cat_name != '') {
+ $cat = $catDAO->addCategory(array('name' => $new_cat_name));
+ }
+ if ($cat == null) {
+ $catDAO->checkDefault();
+ }
+ $cat_id = $cat == null ? FreshRSS_CategoryDAO::defaultCategoryId : $cat->id();
+
+ $feed = new FreshRSS_Feed($url); //Throws FreshRSS_BadUrl_Exception
+ $feed->_httpAuth($http_auth);
+ $feed->load(true); //Throws FreshRSS_Feed_Exception, Minz_FileNotExistException
+ $feed->_category($cat_id);
+
+ $feedDAO = FreshRSS_Factory::createFeedDao();
+ if ($feedDAO->searchByUrl($feed->url())) {
+ throw new FreshRSS_AlreadySubscribed_Exception($url, $feed->name());
+ }
+
+ // Call the extension hook
+ $feed = Minz_ExtensionManager::callHook('feed_before_insert', $feed);
+ if ($feed === null) {
+ throw new FreshRSS_FeedNotAdded_Exception($url, $feed->name());
+ }
+
+ $values = array(
+ 'url' => $feed->url(),
+ 'category' => $feed->category(),
+ 'name' => $title != '' ? $title : $feed->name(),
+ 'website' => $feed->website(),
+ 'description' => $feed->description(),
+ 'lastUpdate' => time(),
+ 'httpAuth' => $feed->httpAuth(),
+ );
+
+ $id = $feedDAO->addFeed($values);
+ if (!$id) {
+ // There was an error in database... we cannot say what here.
+ throw new FreshRSS_FeedNotAdded_Exception($url, $feed->name());
+ }
+ $feed->_id($id);
+
+ // Ok, feed has been added in database. Now we have to refresh entries.
+ self::actualizeFeed($id, $url, false, null, true);
+
+ return $feed;
+ }
+
/**
* This action subscribes to a feed.
*
@@ -59,7 +115,6 @@ class FreshRSS_feed_Controller extends Minz_ActionController {
}
$feedDAO = FreshRSS_Factory::createFeedDao();
- $this->catDAO = new FreshRSS_CategoryDAO();
$url_redirect = array(
'c' => 'subscription',
'a' => 'index',
@@ -74,26 +129,13 @@ class FreshRSS_feed_Controller extends Minz_ActionController {
}
if (Minz_Request::isPost()) {
- @set_time_limit(300);
-
$cat = Minz_Request::param('category');
+ $new_cat_name = '';
if ($cat === 'nc') {
// User want to create a new category, new_category parameter
// must exist
$new_cat = Minz_Request::param('new_category');
- if (empty($new_cat['name'])) {
- $cat = false;
- } else {
- $cat = $this->catDAO->addCategory($new_cat);
- }
- }
-
- if ($cat === false) {
- // If category was not given or if creating new category failed,
- // get the default category
- $this->catDAO->checkDefault();
- $def_cat = $this->catDAO->getDefault();
- $cat = $def_cat->id();
+ $new_cat_name = isset($new_cat['name']) ? $new_cat['name'] : '';
}
// HTTP information are useful if feed is protected behind a
@@ -105,103 +147,24 @@ class FreshRSS_feed_Controller extends Minz_ActionController {
$http_auth = $user . ':' . $pass;
}
- $transaction_started = false;
try {
- $feed = new FreshRSS_Feed($url);
+ $feed = self::addFeed($url, '', $cat, $new_cat_name, $http_auth);
} catch (FreshRSS_BadUrl_Exception $e) {
// Given url was not a valid url!
Minz_Log::warning($e->getMessage());
Minz_Request::bad(_t('feedback.sub.feed.invalid_url', $url), $url_redirect);
- }
-
- $feed->_httpAuth($http_auth);
-
- try {
- $feed->load(true);
} catch (FreshRSS_Feed_Exception $e) {
// Something went bad (timeout, server not found, etc.)
Minz_Log::warning($e->getMessage());
- Minz_Request::bad(
- _t('feedback.sub.feed.internal_problem', _url('index', 'logs')),
- $url_redirect
- );
+ Minz_Request::bad(_t('feedback.sub.feed.internal_problem', _url('index', 'logs')), $url_redirect);
} catch (Minz_FileNotExistException $e) {
// Cache directory doesn't exist!
Minz_Log::error($e->getMessage());
- Minz_Request::bad(
- _t('feedback.sub.feed.internal_problem', _url('index', 'logs')),
- $url_redirect
- );
- }
-
- if ($feedDAO->searchByUrl($feed->url())) {
- Minz_Request::bad(
- _t('feedback.sub.feed.already_subscribed', $feed->name()),
- $url_redirect
- );
- }
-
- $feed->_category($cat);
-
- // Call the extension hook
- $name = $feed->name();
- $feed = Minz_ExtensionManager::callHook('feed_before_insert', $feed);
- if ($feed === null) {
- Minz_Request::bad(_t('feedback.sub.feed.not_added', $name), $url_redirect);
- }
-
- $values = array(
- 'url' => $feed->url(),
- 'category' => $feed->category(),
- 'name' => $feed->name(),
- 'website' => $feed->website(),
- 'description' => $feed->description(),
- 'lastUpdate' => time(),
- 'httpAuth' => $feed->httpAuth(),
- );
-
- $id = $feedDAO->addFeed($values);
- if (!$id) {
- // There was an error in database... we cannot say what here.
- Minz_Request::bad(_t('feedback.sub.feed.not_added', $feed->name()), $url_redirect);
- }
-
- // Ok, feed has been added in database. Now we have to refresh entries.
- $feed->_id($id);
- $feed->faviconPrepare();
- //$feed->pubSubHubbubPrepare(); //TODO: prepare PubSubHubbub already when adding the feed
-
- $is_read = FreshRSS_Context::$user_conf->mark_when['reception'] ? 1 : 0;
-
- $entryDAO = FreshRSS_Factory::createEntryDao();
- // We want chronological order and SimplePie uses reverse order.
- $entries = array_reverse($feed->entries());
-
- // Calculate date of oldest entries we accept in DB.
- $nb_month_old = FreshRSS_Context::$user_conf->old_entries;
- $date_min = time() - (3600 * 24 * 30 * $nb_month_old);
-
- // Use a shared statement and a transaction to improve a LOT the
- // performances.
- $feedDAO->beginTransaction();
- foreach ($entries as $entry) {
- // Entries are added without any verification.
- $entry->_feed($feed->id());
- $entry->_id(min(time(), $entry->date(true)) . uSecString());
- $entry->_isRead($is_read);
-
- $entry = Minz_ExtensionManager::callHook('entry_before_insert', $entry);
- if ($entry === null) {
- // An extension has returned a null value, there is nothing to insert.
- continue;
- }
-
- $values = $entry->toArray();
- $entryDAO->addEntry($values);
- }
- $feedDAO->updateLastUpdate($feed->id());
- if ($feedDAO->inTransaction()) {
- $feedDAO->commit();
+ Minz_Request::bad(_t('feedback.sub.feed.internal_problem', _url('index', 'logs')), $url_redirect);
+ } catch (FreshRSS_AlreadySubscribed_Exception $e) {
+ Minz_Request::bad(_t('feedback.sub.feed.already_subscribed', $e->feedName()), $url_redirect);
+ } catch (FreshRSS_FeedNotAdded_Exception $e) {
+ Minz_Request::bad(_t('feedback.sub.feed.not_added', $e->feedName()), $url_redirect);
}
// Entries are in DB, we redirect to feed configuration page.
@@ -211,6 +174,7 @@ class FreshRSS_feed_Controller extends Minz_ActionController {
// GET request: we must ask confirmation to user before adding feed.
Minz_View::prependTitle(_t('sub.feed.title_add') . ' · ');
+ $this->catDAO = new FreshRSS_CategoryDAO();
$this->view->categories = $this->catDAO->listCategories(false);
$this->view->feed = new FreshRSS_Feed($url);
try {
@@ -261,38 +225,23 @@ class FreshRSS_feed_Controller extends Minz_ActionController {
}
}
- /**
- * This action actualizes entries from one or several feeds.
- *
- * Parameters are:
- * - id (default: false): Feed ID
- * - url (default: false): Feed URL
- * - force (default: false)
- * If id and url are not specified, all the feeds are actualized. But if force is
- * false, process stops at 10 feeds to avoid time execution problem.
- */
- public function actualizeAction($simplePiePush = null) {
+ public static function actualizeFeed($feed_id, $feed_url, $force, $simplePiePush = null, $isNewFeed = false) {
@set_time_limit(300);
$feedDAO = FreshRSS_Factory::createFeedDao();
$entryDAO = FreshRSS_Factory::createEntryDao();
- Minz_Session::_param('actualize_feeds', false);
- $id = Minz_Request::param('id');
- $url = Minz_Request::param('url');
- $force = Minz_Request::param('force');
-
// Create a list of feeds to actualize.
- // If id is set and valid, corresponding feed is added to the list but
+ // If feed_id is set and valid, corresponding feed is added to the list but
// alone in order to automatize further process.
$feeds = array();
- if ($id || $url) {
- $feed = $id ? $feedDAO->searchById($id) : $feedDAO->searchByUrl($url);
+ if ($feed_id > 0 || $feed_url) {
+ $feed = $feed_id > 0 ? $feedDAO->searchById($feed_id) : $feedDAO->searchByUrl($feed_url);
if ($feed) {
$feeds[] = $feed;
}
} else {
- $feeds = $feedDAO->listFeedsOrderUpdate(FreshRSS_Context::$user_conf->ttl_default);
+ $feeds = $feedDAO->listFeedsOrderUpdate(-1);
}
// Calculate date of oldest entries we accept in DB.
@@ -309,13 +258,29 @@ class FreshRSS_feed_Controller extends Minz_ActionController {
$url = $feed->url(); //For detection of HTTP 301
$pubSubHubbubEnabled = $pubsubhubbubEnabledGeneral && $feed->pubSubHubbubEnabled();
- if ((!$simplePiePush) && (!$id) && $pubSubHubbubEnabled && ($feed->lastUpdate() > $pshbMinAge)) {
+ if ((!$simplePiePush) && (!$feed_id) && $pubSubHubbubEnabled && ($feed->lastUpdate() > $pshbMinAge)) {
//$text = 'Skip pull of feed using PubSubHubbub: ' . $url;
//Minz_Log::debug($text);
//file_put_contents(USERS_PATH . '/_/log_pshb.txt', date('c') . "\t" . $text . "\n", FILE_APPEND);
continue; //When PubSubHubbub is used, do not pull refresh so often
}
+ $mtime = 0;
+ $ttl = $feed->ttl();
+ if ($ttl == -1) {
+ continue; //Feed refresh is disabled
+ }
+ if ((!$simplePiePush) && (!$feed_id) &&
+ ($feed->lastUpdate() + 10 >= time() - ($ttl == -2 ? FreshRSS_Context::$user_conf->ttl_default : $ttl))) {
+ //Too early to refresh from source, but check whether the feed was updated by another user
+ $mtime = $feed->cacheModifiedTime();
+ if ($feed->lastUpdate() + 10 >= $mtime) {
+ continue; //Nothing newer from other users
+ }
+ //Minz_Log::debug($feed->url() . ' was updated at ' . date('c', $mtime) . ' by another user');
+ //Will take advantage of the newer cache
+ }
+
if (!$feed->lock()) {
Minz_Log::notice('Feed already being actualized: ' . $feed->url());
continue;
@@ -325,7 +290,7 @@ class FreshRSS_feed_Controller extends Minz_ActionController {
if ($simplePiePush) {
$feed->loadEntries($simplePiePush); //Used by PubSubHubbub
} else {
- $feed->load(false);
+ $feed->load(false, $isNewFeed);
}
} catch (FreshRSS_Feed_Exception $e) {
Minz_Log::warning($e->getMessage());
@@ -335,7 +300,9 @@ class FreshRSS_feed_Controller extends Minz_ActionController {
}
$feed_history = $feed->keepHistory();
- if ($feed_history == -2) {
+ if ($isNewFeed) {
+ $feed_history = -1; //∞
+ } elseif ($feed_history == -2) {
// TODO: -2 must be a constant!
// -2 means we take the default value from configuration
$feed_history = FreshRSS_Context::$user_conf->keep_history_default;
@@ -346,7 +313,7 @@ class FreshRSS_feed_Controller extends Minz_ActionController {
if (count($entries) > 0) {
$newGuids = array();
foreach ($entries as $entry) {
- $newGuids[] = $entry->guid();
+ $newGuids[] = safe_ascii($entry->guid());
}
// For this feed, check existing GUIDs already in database.
$existingHashForGuids = $entryDAO->listHashForFeedGuids($feed->id(), $newGuids);
@@ -375,7 +342,9 @@ class FreshRSS_feed_Controller extends Minz_ActionController {
// This entry should not be added considering configuration and date.
$oldGuids[] = $entry->guid();
} else {
- if ($entry_date < $date_min) {
+ if ($isNewFeed) {
+ $id = min(time(), $entry_date) . uSecString();
+ } elseif ($entry_date < $date_min) {
$id = min(time(), $entry_date) . uSecString();
$entry->_isRead(true); //Old article that was not in database. Probably an error, so mark as read
} else {
@@ -404,7 +373,7 @@ class FreshRSS_feed_Controller extends Minz_ActionController {
$entryDAO->addEntry($entry->toArray());
}
}
- $entryDAO->updateLastSeen($feed->id(), $oldGuids);
+ $entryDAO->updateLastSeen($feed->id(), $oldGuids, $mtime);
}
if ($feed_history >= 0 && rand(0, 30) === 1) {
@@ -423,7 +392,7 @@ class FreshRSS_feed_Controller extends Minz_ActionController {
}
}
- $feedDAO->updateLastUpdate($feed->id(), 0, $entryDAO->inTransaction());
+ $feedDAO->updateLastUpdate($feed->id(), false, $entryDAO->inTransaction(), $mtime);
if ($entryDAO->inTransaction()) {
$entryDAO->commit();
}
@@ -464,6 +433,26 @@ class FreshRSS_feed_Controller extends Minz_ActionController {
break;
}
}
+ return array($updated_feeds, reset($feeds));
+ }
+
+ /**
+ * This action actualizes entries from one or several feeds.
+ *
+ * Parameters are:
+ * - id (default: false): Feed ID
+ * - url (default: false): Feed URL
+ * - force (default: false)
+ * If id and url are not specified, all the feeds are actualized. But if force is
+ * false, process stops at 10 feeds to avoid time execution problem.
+ */
+ public function actualizeAction() {
+ Minz_Session::_param('actualize_feeds', false);
+ $id = Minz_Request::param('id');
+ $url = Minz_Request::param('url');
+ $force = Minz_Request::param('force');
+
+ list($updated_feeds, $feed) = self::actualizeFeed($id, $url, $force);
if (Minz_Request::param('ajax')) {
// Most of the time, ajax request is for only one feed. But since
@@ -479,7 +468,6 @@ class FreshRSS_feed_Controller extends Minz_ActionController {
} else {
// Redirect to the main page with correct notification.
if ($updated_feeds === 1) {
- $feed = reset($feeds);
Minz_Request::good(_t('feedback.sub.feed.actualized', $feed->name()), array(
'params' => array('get' => 'f_' . $feed->id())
));
@@ -492,6 +480,36 @@ class FreshRSS_feed_Controller extends Minz_ActionController {
return $updated_feeds;
}
+ public static function renameFeed($feed_id, $feed_name) {
+ if ($feed_id <= 0 || $feed_name == '') {
+ return false;
+ }
+ $feedDAO = FreshRSS_Factory::createFeedDao();
+ return $feedDAO->updateFeed($feed_id, array('name' => $feed_name));
+ }
+
+ public static function moveFeed($feed_id, $cat_id, $new_cat_name = '') {
+ if ($feed_id <= 0 || ($cat_id <= 0 && $new_cat_name == '')) {
+ return false;
+ }
+
+ $catDAO = new FreshRSS_CategoryDAO();
+ if ($cat_id > 0) {
+ $cat = $catDAO->searchById($cat_id);
+ $cat_id = $cat == null ? 0 : $cat->id();
+ }
+ if ($cat_id <= 1 && $new_cat_name != '') {
+ $cat_id = $catDAO->addCategory(array('name' => $new_cat_name));
+ }
+ if ($cat_id <= 1) {
+ $catDAO->checkDefault();
+ $cat_id = FreshRSS_CategoryDAO::defaultCategoryId;
+ }
+
+ $feedDAO = FreshRSS_Factory::createFeedDao();
+ return $feedDAO->updateFeed($feed_id, array('category' => $cat_id));
+ }
+
/**
* This action changes the category of a feed.
*
@@ -512,20 +530,7 @@ class FreshRSS_feed_Controller extends Minz_ActionController {
$feed_id = Minz_Request::param('f_id');
$cat_id = Minz_Request::param('c_id');
- if ($cat_id === false) {
- // If category was not given get the default one.
- $catDAO = new FreshRSS_CategoryDAO();
- $catDAO->checkDefault();
- $def_cat = $catDAO->getDefault();
- $cat_id = $def_cat->id();
- }
-
- $feedDAO = FreshRSS_Factory::createFeedDao();
- $values = array('category' => $cat_id);
-
- $feed = $feedDAO->searchById($feed_id);
- if ($feed && ($feed->category() == $cat_id ||
- $feedDAO->updateFeed($feed_id, $values))) {
+ if (self::moveFeed($feed_id, $cat_id)) {
// TODO: return something useful
} else {
Minz_Log::warning('Cannot move feed `' . $feed_id . '` ' .
@@ -534,6 +539,21 @@ class FreshRSS_feed_Controller extends Minz_ActionController {
}
}
+ public static function deleteFeed($feed_id) {
+ $feedDAO = FreshRSS_Factory::createFeedDao();
+ if ($feedDAO->deleteFeed($feed_id)) {
+ // TODO: Delete old favicon
+
+ // Remove related queries
+ FreshRSS_Context::$user_conf->queries = remove_query_by_get(
+ 'f_' . $feed_id, FreshRSS_Context::$user_conf->queries);
+ FreshRSS_Context::$user_conf->save();
+
+ return true;
+ }
+ return false;
+ }
+
/**
* This action deletes a feed.
*
@@ -552,21 +572,13 @@ class FreshRSS_feed_Controller extends Minz_ActionController {
if (!$redirect_url) {
$redirect_url = array('c' => 'subscription', 'a' => 'index');
}
-
if (!Minz_Request::isPost()) {
Minz_Request::forward($redirect_url, true);
}
$id = Minz_Request::param('id');
- $feedDAO = FreshRSS_Factory::createFeedDao();
- if ($feedDAO->deleteFeed($id)) {
- // TODO: Delete old favicon
-
- // Remove related queries
- FreshRSS_Context::$user_conf->queries = remove_query_by_get(
- 'f_' . $id, FreshRSS_Context::$user_conf->queries);
- FreshRSS_Context::$user_conf->save();
+ if (self::deleteFeed($id)) {
Minz_Request::good(_t('feedback.sub.feed.deleted'), $redirect_url);
} else {
Minz_Request::bad(_t('feedback.sub.feed.error'), $redirect_url);
diff --git a/app/Controllers/importExportController.php b/app/Controllers/importExportController.php
index 60e467255..3ba91a243 100644
--- a/app/Controllers/importExportController.php
+++ b/app/Controllers/importExportController.php
@@ -29,32 +29,14 @@ class FreshRSS_importExport_Controller extends Minz_ActionController {
Minz_View::prependTitle(_t('sub.import_export.title') . ' · ');
}
- /**
- * This action handles import action.
- *
- * It must be reached by a POST request.
- *
- * Parameter is:
- * - file (default: nothing!)
- * Available file types are: zip, json or xml.
- */
- public function importAction() {
- if (!Minz_Request::isPost()) {
- Minz_Request::forward(array('c' => 'importExport', 'a' => 'index'), true);
- }
-
- $file = $_FILES['file'];
- $status_file = $file['error'];
-
- if ($status_file !== 0) {
- Minz_Log::warning('File cannot be uploaded. Error code: ' . $status_file);
- Minz_Request::bad(_t('feedback.import_export.file_cannot_be_uploaded'),
- array('c' => 'importExport', 'a' => 'index'));
- }
+ public function importFile($name, $path, $username = null) {
+ require_once(LIB_PATH . '/lib_opml.php');
- @set_time_limit(300);
+ $this->catDAO = new FreshRSS_CategoryDAO($username);
+ $this->entryDAO = FreshRSS_Factory::createEntryDao($username);
+ $this->feedDAO = FreshRSS_Factory::createFeedDao($username);
- $type_file = $this->guessFileType($file['name']);
+ $type_file = self::guessFileType($name);
$list_files = array(
'opml' => array(),
@@ -65,21 +47,17 @@ class FreshRSS_importExport_Controller extends Minz_ActionController {
// We try to list all files according to their type
$list = array();
if ($type_file === 'zip' && extension_loaded('zip')) {
- $zip = zip_open($file['tmp_name']);
-
+ $zip = zip_open($path);
if (!is_resource($zip)) {
// zip_open cannot open file: something is wrong
- Minz_Log::warning('Zip archive cannot be imported. Error code: ' . $zip);
- Minz_Request::bad(_t('feedback.import_export.zip_error'),
- array('c' => 'importExport', 'a' => 'index'));
+ throw new FreshRSS_Zip_Exception($zip);
}
-
while (($zipfile = zip_read($zip)) !== false) {
if (!is_resource($zipfile)) {
// zip_entry() can also return an error code!
- Minz_Log::warning('Zip file cannot be imported. Error code: ' . $zipfile);
+ throw new FreshRSS_Zip_Exception($zipfile);
} else {
- $type_zipfile = $this->guessFileType(zip_entry_name($zipfile));
+ $type_zipfile = self::guessFileType(zip_entry_name($zipfile));
if ($type_file !== 'unknown') {
$list_files[$type_zipfile][] = zip_entry_read(
$zipfile,
@@ -88,29 +66,88 @@ class FreshRSS_importExport_Controller extends Minz_ActionController {
}
}
}
-
zip_close($zip);
} elseif ($type_file === 'zip') {
- // Zip extension is not loaded
- Minz_Request::bad(_t('feedback.import_export.no_zip_extension'),
- array('c' => 'importExport', 'a' => 'index'));
+ // ZIP extension is not loaded
+ throw new FreshRSS_ZipMissing_Exception();
} elseif ($type_file !== 'unknown') {
- $list_files[$type_file][] = file_get_contents($file['tmp_name']);
+ $list_files[$type_file][] = file_get_contents($path);
}
// Import file contents.
// OPML first(so categories and feeds are imported)
// Starred articles then so the "favourite" status is already set
// And finally all other files.
- $error = false;
+ $ok = true;
foreach ($list_files['opml'] as $opml_file) {
- $error = $this->importOpml($opml_file);
+ if (!$this->importOpml($opml_file)) {
+ $ok = false;
+ if (FreshRSS_Context::$isCli) {
+ fwrite(STDERR, 'FreshRSS error during OPML import' . "\n");
+ } else {
+ Minz_Log::warning('Error during OPML import');
+ }
+ }
}
foreach ($list_files['json_starred'] as $article_file) {
- $error = $this->importJson($article_file, true);
+ if (!$this->importJson($article_file, true)) {
+ $ok = false;
+ if (FreshRSS_Context::$isCli) {
+ fwrite(STDERR, 'FreshRSS error during JSON stars import' . "\n");
+ } else {
+ Minz_Log::warning('Error during JSON stars import');
+ }
+ }
}
foreach ($list_files['json_feed'] as $article_file) {
- $error = $this->importJson($article_file);
+ if (!$this->importJson($article_file)) {
+ $ok = false;
+ if (FreshRSS_Context::$isCli) {
+ fwrite(STDERR, 'FreshRSS error during JSON feeds import' . "\n");
+ } else {
+ Minz_Log::warning('Error during JSON feeds import');
+ }
+ }
+ }
+
+ return $ok;
+ }
+
+ /**
+ * This action handles import action.
+ *
+ * It must be reached by a POST request.
+ *
+ * Parameter is:
+ * - file (default: nothing!)
+ * Available file types are: zip, json or xml.
+ */
+ public function importAction() {
+ if (!Minz_Request::isPost()) {
+ Minz_Request::forward(array('c' => 'importExport', 'a' => 'index'), true);
+ }
+
+ $file = $_FILES['file'];
+ $status_file = $file['error'];
+
+ if ($status_file !== 0) {
+ Minz_Log::warning('File cannot be uploaded. Error code: ' . $status_file);
+ Minz_Request::bad(_t('feedback.import_export.file_cannot_be_uploaded'),
+ array('c' => 'importExport', 'a' => 'index'));
+ }
+
+ @set_time_limit(300);
+
+ $error = false;
+ try {
+ $error = !$this->importFile($file['name'], $file['tmp_name']);
+ } catch (FreshRSS_ZipMissing_Exception $zme) {
+ Minz_Request::bad(_t('feedback.import_export.no_zip_extension'),
+ array('c' => 'importExport', 'a' => 'index'));
+ } catch (FreshRSS_Zip_Exception $ze) {
+ Minz_Log::warning('ZIP archive cannot be imported. Error code: ' . $ze->zipErrorCode());
+ Minz_Request::bad(_t('feedback.import_export.zip_error'),
+ array('c' => 'importExport', 'a' => 'index'));
}
// And finally, we get import status and redirect to the home page
@@ -126,7 +163,7 @@ class FreshRSS_importExport_Controller extends Minz_ActionController {
* Itis a *very* basic guess file type function. Only based on filename.
* That's could be improved but should be enough for what we have to do.
*/
- private function guessFileType($filename) {
+ private static function guessFileType($filename) {
if (substr_compare($filename, '.zip', -4) === 0) {
return 'zip';
} elseif (substr_compare($filename, '.opml', -5) === 0 ||
@@ -146,15 +183,19 @@ class FreshRSS_importExport_Controller extends Minz_ActionController {
* This method parses and imports an OPML file.
*
* @param string $opml_file the OPML file content.
- * @return boolean true if an error occured, false else.
+ * @return boolean false if an error occured, true otherwise.
*/
private function importOpml($opml_file) {
$opml_array = array();
try {
$opml_array = libopml_parse_string($opml_file, false);
} catch (LibOPML_Exception $e) {
- Minz_Log::warning($e->getMessage());
- return true;
+ if (FreshRSS_Context::$isCli) {
+ fwrite(STDERR, 'FreshRSS error during OPML parsing: ' . $e->getMessage() . "\n");
+ } else {
+ Minz_Log::warning($e->getMessage());
+ }
+ return false;
}
$this->catDAO->checkDefault();
@@ -167,51 +208,49 @@ class FreshRSS_importExport_Controller extends Minz_ActionController {
*
* @param array $opml_elements an OPML element (body or outline).
* @param string $parent_cat the name of the parent category.
- * @return boolean true if an error occured, false else.
+ * @return boolean false if an error occured, true otherwise.
*/
private function addOpmlElements($opml_elements, $parent_cat = null) {
- $error = false;
+ $ok = true;
$nb_feeds = count($this->feedDAO->listFeeds());
$nb_cats = count($this->catDAO->listCategories(false));
$limits = FreshRSS_Context::$system_conf->limits;
foreach ($opml_elements as $elt) {
- $is_error = false;
if (isset($elt['xmlUrl'])) {
// If xmlUrl exists, it means it is a feed
- if ($nb_feeds >= $limits['max_feeds']) {
+ if (FreshRSS_Context::$isCli && $nb_feeds >= $limits['max_feeds']) {
Minz_Log::warning(_t('feedback.sub.feed.over_max',
- $limits['max_feeds']));
- $is_error = true;
+ $limits['max_feeds']));
+ $ok = false;
continue;
}
- $is_error = $this->addFeedOpml($elt, $parent_cat);
- if (!$is_error) {
- $nb_feeds += 1;
+ if ($this->addFeedOpml($elt, $parent_cat)) {
+ $nb_feeds++;
+ } else {
+ $ok = false;
}
} else {
// No xmlUrl? It should be a category!
$limit_reached = ($nb_cats >= $limits['max_categories']);
- if ($limit_reached) {
+ if (!FreshRSS_Context::$isCli && $limit_reached) {
Minz_Log::warning(_t('feedback.sub.category.over_max',
- $limits['max_categories']));
+ $limits['max_categories']));
+ $ok = false;
+ continue;
}
- $is_error = $this->addCategoryOpml($elt, $parent_cat, $limit_reached);
- if (!$is_error) {
- $nb_cats += 1;
+ if ($this->addCategoryOpml($elt, $parent_cat, $limit_reached)) {
+ $nb_cats++;
+ } else {
+ $ok = false;
}
}
-
- if (!$error && $is_error) {
- // oops: there is at least one error!
- $error = $is_error;
- }
}
- return $error;
+ return $ok;
}
/**
@@ -219,21 +258,23 @@ class FreshRSS_importExport_Controller extends Minz_ActionController {
*
* @param array $feed_elt an OPML element (must be a feed element).
* @param string $parent_cat the name of the parent category.
- * @return boolean true if an error occured, false else.
+ * @return boolean false if an error occured, true otherwise.
*/
private function addFeedOpml($feed_elt, $parent_cat) {
- $default_cat = $this->catDAO->getDefault();
- if (is_null($parent_cat)) {
+ if ($parent_cat == null) {
// This feed has no parent category so we get the default one
+ $this->catDAO->checkDefault();
+ $default_cat = $this->catDAO->getDefault();
$parent_cat = $default_cat->name();
}
$cat = $this->catDAO->searchByName($parent_cat);
- if (is_null($cat)) {
+ if ($cat == null) {
// If there is not $cat, it means parent category does not exist in
// database.
// If it happens, take the default category.
- $cat = $default_cat;
+ $this->catDAO->checkDefault();
+ $cat = $this->catDAO->getDefault();
}
// We get different useful information
@@ -259,7 +300,7 @@ class FreshRSS_importExport_Controller extends Minz_ActionController {
// Call the extension hook
$feed = Minz_ExtensionManager::callHook('feed_before_insert', $feed);
- if (!is_null($feed)) {
+ if ($feed != null) {
// addFeedObject checks if feed is already in DB so nothing else to
// check here
$id = $this->feedDAO->addFeedObject($feed);
@@ -268,11 +309,23 @@ class FreshRSS_importExport_Controller extends Minz_ActionController {
$error = true;
}
} catch (FreshRSS_Feed_Exception $e) {
- Minz_Log::warning($e->getMessage());
+ if (FreshRSS_Context::$isCli) {
+ fwrite(STDERR, 'FreshRSS error during OPML feed import: ' . $e->getMessage() . "\n");
+ } else {
+ Minz_Log::warning($e->getMessage());
+ }
$error = true;
}
- return $error;
+ if ($error) {
+ if (FreshRSS_Context::$isCli) {
+ fwrite(STDERR, 'FreshRSS error during OPML feed import from URL: ' . $url . ' in category ' . $cat->id() . "\n");
+ } else {
+ Minz_Log::warning('Error during OPML feed import from URL: ' . $url . ' in category ' . $cat->id());
+ }
+ }
+
+ return !$error;
}
/**
@@ -282,29 +335,34 @@ class FreshRSS_importExport_Controller extends Minz_ActionController {
* @param string $parent_cat the name of the parent category.
* @param boolean $cat_limit_reached indicates if category limit has been reached.
* if yes, category is not added (but we try for feeds!)
- * @return boolean true if an error occured, false else.
+ * @return boolean false if an error occured, true otherwise.
*/
private function addCategoryOpml($cat_elt, $parent_cat, $cat_limit_reached) {
// Create a new Category object
- $cat = new FreshRSS_Category(Minz_Helper::htmlspecialchars_utf8($cat_elt['text']));
+ $catName = Minz_Helper::htmlspecialchars_utf8($cat_elt['text']);
+ $cat = new FreshRSS_Category($catName);
$error = true;
- if (!$cat_limit_reached) {
+ if (FreshRSS_Context::$isCli || !$cat_limit_reached) {
$id = $this->catDAO->addCategoryObject($cat);
$error = ($id === false);
}
+ if ($error) {
+ if (FreshRSS_Context::$isCli) {
+ fwrite(STDERR, 'FreshRSS error during OPML category import from URL: ' . $catName . "\n");
+ } else {
+ Minz_Log::warning('Error during OPML category import from URL: ' . $catName);
+ }
+ }
if (isset($cat_elt['@outlines'])) {
// Our cat_elt contains more categories or more feeds, so we
// add them recursively.
// Note: FreshRSS does not support yet category arborescence
- $res = $this->addOpmlElements($cat_elt['@outlines'], $cat->name());
- if (!$error && $res) {
- $error = true;
- }
+ $error &= !$this->addOpmlElements($cat_elt['@outlines'], $catName);
}
- return $error;
+ return !$error;
}
/**
@@ -312,13 +370,17 @@ class FreshRSS_importExport_Controller extends Minz_ActionController {
*
* @param string $article_file the JSON file content.
* @param boolean $starred true if articles from the file must be starred.
- * @return boolean true if an error occured, false else.
+ * @return boolean false if an error occured, true otherwise.
*/
private function importJson($article_file, $starred = false) {
$article_object = json_decode($article_file, true);
- if (is_null($article_object)) {
- Minz_Log::warning('Try to import a non-JSON file');
- return true;
+ if ($article_object == null) {
+ if (FreshRSS_Context::$isCli) {
+ fwrite(STDERR, 'FreshRSS error trying to import a non-JSON file' . "\n");
+ } else {
+ Minz_Log::warning('Try to import a non-JSON file');
+ }
+ return false;
}
$is_read = FreshRSS_Context::$user_conf->mark_when['reception'] ? 1 : 0;
@@ -337,29 +399,36 @@ class FreshRSS_importExport_Controller extends Minz_ActionController {
$feed = new FreshRSS_Feed($item['origin'][$key]);
$feed = $this->feedDAO->searchByUrl($feed->url());
- if (is_null($feed)) {
+ if ($feed == null) {
// Feed does not exist in DB,we should to try to add it.
- if ($nb_feeds >= $limits['max_feeds']) {
+ if ((!FreshRSS_Context::$isCli) && ($nb_feeds >= $limits['max_feeds'])) {
// Oops, no more place!
Minz_Log::warning(_t('feedback.sub.feed.over_max', $limits['max_feeds']));
} else {
$feed = $this->addFeedJson($item['origin'], $google_compliant);
}
- if (is_null($feed)) {
+ if ($feed == null) {
// Still null? It means something went wrong.
$error = true;
} else {
- // Nice! Increase the counter.
- $nb_feeds += 1;
+ $nb_feeds++;
}
}
- if (!is_null($feed)) {
+ if ($feed != null) {
$article_to_feed[$item['id']] = $feed->id();
}
}
+ $newGuids = array();
+ foreach ($article_object['items'] as $item) {
+ $newGuids[] = safe_ascii($item['id']);
+ }
+ // For this feed, check existing GUIDs already in database.
+ $existingHashForGuids = $this->entryDAO->listHashForFeedGuids($feed->id(), $newGuids);
+ unset($newGuids);
+
// Then, articles are imported.
$this->entryDAO->beginTransaction();
foreach ($article_object['items'] as $item) {
@@ -376,7 +445,7 @@ class FreshRSS_importExport_Controller extends Minz_ActionController {
if ($google_compliant) {
// Remove tags containing "/state/com.google" which are useless.
$tags = array_filter($tags, function($var) {
- return strpos($var, '/state/com.google') === false;
+ return strpos($var, '/state/com.google') !== false;
});
}
@@ -389,13 +458,17 @@ class FreshRSS_importExport_Controller extends Minz_ActionController {
$entry->_tags($tags);
$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();
- $id = $this->entryDAO->addEntry($values);
+ if (isset($existingHashForGuids[$entry->guid()])) {
+ $id = $this->entryDAO->updateEntry($values);
+ } else {
+ $id = $this->entryDAO->addEntry($values);
+ }
if (!$error && ($id === false)) {
$error = true;
@@ -403,7 +476,7 @@ class FreshRSS_importExport_Controller extends Minz_ActionController {
}
$this->entryDAO->commit();
- return $error;
+ return !$error;
}
/**
@@ -415,8 +488,6 @@ class FreshRSS_importExport_Controller extends Minz_ActionController {
* else null.
*/
private function addFeedJson($origin, $google_compliant) {
- $default_cat = $this->catDAO->getDefault();
-
$return = null;
$key = $google_compliant ? 'htmlUrl' : 'feedUrl';
$url = $origin[$key];
@@ -426,13 +497,13 @@ class FreshRSS_importExport_Controller extends Minz_ActionController {
try {
// Create a Feed object and add it in database.
$feed = new FreshRSS_Feed($url);
- $feed->_category($default_cat->id());
+ $feed->_category(FreshRSS_CategoryDAO::defaultCategoryId);
$feed->_name($name);
$feed->_website($website);
// Call the extension hook
$feed = Minz_ExtensionManager::callHook('feed_before_insert', $feed);
- if (!is_null($feed)) {
+ if ($feed != null) {
// addFeedObject checks if feed is already in DB so nothing else to
// check here.
$id = $this->feedDAO->addFeedObject($feed);
@@ -443,67 +514,98 @@ class FreshRSS_importExport_Controller extends Minz_ActionController {
}
}
} catch (FreshRSS_Feed_Exception $e) {
- Minz_Log::warning($e->getMessage());
+ if (FreshRSS_Context::$isCli) {
+ fwrite(STDERR, 'FreshRSS error during JSON feed import: ' . $e->getMessage() . "\n");
+ } else {
+ Minz_Log::warning($e->getMessage());
+ }
}
return $return;
}
- /**
- * This action handles export action.
- *
- * This action must be reached by a POST request.
- *
- * Parameters are:
- * - export_opml (default: false)
- * - export_starred (default: false)
- * - export_feeds (default: array()) a list of feed ids
- */
- public function exportAction() {
- if (!Minz_Request::isPost()) {
- Minz_Request::forward(array('c' => 'importExport', 'a' => 'index'), true);
- }
+ public function exportFile($export_opml = true, $export_starred = false, $export_feeds = array(), $maxFeedEntries = 50, $username = null) {
+ require_once(LIB_PATH . '/lib_opml.php');
- $this->view->_useLayout(false);
+ $this->catDAO = new FreshRSS_CategoryDAO($username);
+ $this->entryDAO = FreshRSS_Factory::createEntryDao($username);
+ $this->feedDAO = FreshRSS_Factory::createFeedDao($username);
+
+ if ($export_feeds === true) {
+ //All feeds
+ $export_feeds = $this->feedDAO->listFeedsIds();
+ }
+ if (!is_array($export_feeds)) {
+ $export_feeds = array();
+ }
- $export_opml = Minz_Request::param('export_opml', false);
- $export_starred = Minz_Request::param('export_starred', false);
- $export_feeds = Minz_Request::param('export_feeds', array());
+ $day = date('Y-m-d');
$export_files = array();
if ($export_opml) {
- $export_files['feeds.opml'] = $this->generateOpml();
+ $export_files["feeds_${day}.opml.xml"] = $this->generateOpml();
}
if ($export_starred) {
- $export_files['starred.json'] = $this->generateEntries('starred');
+ $export_files["starred_${day}.json"] = $this->generateEntries('starred');
}
foreach ($export_feeds as $feed_id) {
$feed = $this->feedDAO->searchById($feed_id);
if ($feed) {
- $filename = 'feed_' . $feed->category() . '_'
+ $filename = "feed_${day}_" . $feed->category() . '_'
. $feed->id() . '.json';
- $export_files[$filename] = $this->generateEntries('feed', $feed);
+ $export_files[$filename] = $this->generateEntries('feed', $feed, $maxFeedEntries);
}
}
$nb_files = count($export_files);
if ($nb_files > 1) {
- // If there are more than 1 file to export, we need a zip archive.
+ // If there are more than 1 file to export, we need a ZIP archive.
try {
- $this->exportZip($export_files);
+ $this->sendZip($export_files);
} catch (Exception $e) {
- # Oops, there is no Zip extension!
- Minz_Request::bad(_t('feedback.import_export.export_no_zip_extension'),
- array('c' => 'importExport', 'a' => 'index'));
+ throw new FreshRSS_ZipMissing_Exception($e);
}
} elseif ($nb_files === 1) {
// Only one file? Guess its type and export it.
$filename = key($export_files);
- $type = $this->guessFileType($filename);
- $this->exportFile('freshrss_' . $filename, $export_files[$filename], $type);
- } else {
+ $type = self::guessFileType($filename);
+ $this->sendFile('freshrss_' . $filename, $export_files[$filename], $type);
+ }
+ return $nb_files;
+ }
+
+ /**
+ * This action handles export action.
+ *
+ * This action must be reached by a POST request.
+ *
+ * Parameters are:
+ * - export_opml (default: false)
+ * - export_starred (default: false)
+ * - export_feeds (default: array()) a list of feed ids
+ */
+ public function exportAction() {
+ if (!Minz_Request::isPost()) {
+ Minz_Request::forward(array('c' => 'importExport', 'a' => 'index'), true);
+ }
+ $this->view->_useLayout(false);
+
+ $nb_files = 0;
+ try {
+ $nb_files = $this->exportFile(
+ Minz_Request::param('export_opml', false),
+ Minz_Request::param('export_starred', false),
+ Minz_Request::param('export_feeds', array())
+ );
+ } catch (FreshRSS_ZipMissing_Exception $zme) {
+ # Oops, there is no ZIP extension!
+ Minz_Request::bad(_t('feedback.import_export.export_no_zip_extension'),
+ array('c' => 'importExport', 'a' => 'index'));
+ }
+
+ if ($nb_files < 1) {
// Nothing to do...
Minz_Request::forward(array('c' => 'importExport', 'a' => 'index'), true);
}
@@ -532,7 +634,7 @@ class FreshRSS_importExport_Controller extends Minz_ActionController {
* @param FreshRSS_Feed $feed feed of which we want to get entries.
* @return string the JSON file content.
*/
- private function generateEntries($type, $feed = NULL) {
+ private function generateEntries($type, $feed = NULL, $maxFeedEntries = 50) {
$this->view->categories = $this->catDAO->listCategories();
if ($type == 'starred') {
@@ -542,12 +644,12 @@ class FreshRSS_importExport_Controller extends Minz_ActionController {
$this->view->entries = $this->entryDAO->listWhere(
's', '', FreshRSS_Entry::STATE_ALL, 'ASC', $unread_fav['all']
);
- } elseif ($type == 'feed' && !is_null($feed)) {
+ } elseif ($type === 'feed' && $feed != null) {
$this->view->list_title = _t('sub.import_export.feed_list', $feed->name());
$this->view->type = 'feed/' . $feed->id();
$this->view->entries = $this->entryDAO->listWhere(
'f', $feed->id(), FreshRSS_Entry::STATE_ALL, 'ASC',
- FreshRSS_Context::$user_conf->posts_per_page
+ $maxFeedEntries
);
$this->view->feed = $feed;
}
@@ -561,7 +663,7 @@ class FreshRSS_importExport_Controller extends Minz_ActionController {
* @param array $files list of files where key is filename and value the content.
* @throws Exception if Zip extension is not loaded.
*/
- private function exportZip($files) {
+ private function sendZip($files) {
if (!extension_loaded('zip')) {
throw new Exception();
}
@@ -579,7 +681,8 @@ class FreshRSS_importExport_Controller extends Minz_ActionController {
$zip->close();
header('Content-Type: application/zip');
header('Content-Length: ' . filesize($zip_file));
- header('Content-Disposition: attachment; filename="freshrss_export.zip"');
+ $day = date('Y-m-d');
+ header('Content-Disposition: attachment; filename="freshrss_' . $day . '_export.zip"');
readfile($zip_file);
unlink($zip_file);
}
@@ -592,16 +695,16 @@ class FreshRSS_importExport_Controller extends Minz_ActionController {
* @param string $type the file type (opml, json_feed or json_starred).
* If equals to unknown, nothing happens.
*/
- private function exportFile($filename, $content, $type) {
+ private function sendFile($filename, $content, $type) {
if ($type === 'unknown') {
return;
}
$content_type = '';
if ($type === 'opml') {
- $content_type = "text/opml";
+ $content_type = 'application/xml';
} elseif ($type === 'json_feed' || $type === 'json_starred') {
- $content_type = "text/json";
+ $content_type = 'application/json';
}
header('Content-Type: ' . $content_type . '; charset=utf-8');
diff --git a/app/Controllers/indexController.php b/app/Controllers/indexController.php
index 2332d225d..5ca147ff3 100755
--- a/app/Controllers/indexController.php
+++ b/app/Controllers/indexController.php
@@ -34,7 +34,9 @@ class FreshRSS_index_Controller extends Minz_ActionController {
$this->view->callbackBeforeContent = function($view) {
try {
+ FreshRSS_Context::$number++; //+1 for pagination
$entries = FreshRSS_index_Controller::listEntriesByContext();
+ FreshRSS_Context::$number--;
$nb_entries = count($entries);
if ($nb_entries > FreshRSS_Context::$number) {
@@ -154,8 +156,14 @@ class FreshRSS_index_Controller extends Minz_ActionController {
* - order (default: conf->sort_order)
* - nb (default: conf->posts_per_page)
* - next (default: empty string)
+ * - hours (default: 0)
*/
private function updateContext() {
+ if (empty(FreshRSS_Context::$categories)) {
+ $catDAO = new FreshRSS_CategoryDAO();
+ FreshRSS_Context::$categories = $catDAO->listCategories();
+ }
+
// Update number of read / unread variables.
$entryDAO = FreshRSS_Factory::createEntryDao();
FreshRSS_Context::$total_starred = $entryDAO->countUnreadReadFavorites();
@@ -180,10 +188,14 @@ class FreshRSS_index_Controller extends Minz_ActionController {
FreshRSS_Context::$order = Minz_Request::param(
'order', FreshRSS_Context::$user_conf->sort_order
);
- FreshRSS_Context::$number = Minz_Request::param(
- 'nb', FreshRSS_Context::$user_conf->posts_per_page
- );
+ FreshRSS_Context::$number = intval(Minz_Request::param('nb', FreshRSS_Context::$user_conf->posts_per_page));
+ if (FreshRSS_Context::$number > FreshRSS_Context::$user_conf->max_posts_per_rss) {
+ FreshRSS_Context::$number = max(
+ FreshRSS_Context::$user_conf->max_posts_per_rss,
+ FreshRSS_Context::$user_conf->posts_per_page);
+ }
FreshRSS_Context::$first_id = Minz_Request::param('next', '');
+ FreshRSS_Context::$sinceHours = intval(Minz_Request::param('hours', 0));
}
/**
@@ -201,11 +213,31 @@ class FreshRSS_index_Controller extends Minz_ActionController {
$id = '';
}
- return $entryDAO->listWhere(
+ $limit = FreshRSS_Context::$number;
+
+ $date_min = 0;
+ if (FreshRSS_Context::$sinceHours) {
+ $date_min = time() - (FreshRSS_Context::$sinceHours * 3600);
+ $limit = FreshRSS_Context::$user_conf->max_posts_per_rss;
+ }
+
+ $entries = $entryDAO->listWhere(
$type, $id, FreshRSS_Context::$state, FreshRSS_Context::$order,
- FreshRSS_Context::$number + 1, FreshRSS_Context::$first_id,
- FreshRSS_Context::$search
+ $limit, FreshRSS_Context::$first_id,
+ FreshRSS_Context::$search, $date_min
);
+
+ if (FreshRSS_Context::$sinceHours && (count($entries) < FreshRSS_Context::$user_conf->min_posts_per_rss)) {
+ $date_min = 0;
+ $limit = FreshRSS_Context::$user_conf->min_posts_per_rss;
+ $entries = $entryDAO->listWhere(
+ $type, $id, FreshRSS_Context::$state, FreshRSS_Context::$order,
+ $limit, FreshRSS_Context::$first_id,
+ FreshRSS_Context::$search, $date_min
+ );
+ }
+
+ return $entries;
}
/**
diff --git a/app/Controllers/statsController.php b/app/Controllers/statsController.php
index 4a597ae7d..5d1dee72c 100644
--- a/app/Controllers/statsController.php
+++ b/app/Controllers/statsController.php
@@ -18,6 +18,27 @@ class FreshRSS_stats_Controller extends Minz_ActionController {
Minz_View::prependTitle(_t('admin.stats.title') . ' · ');
}
+ private function convertToSerie($data) {
+ $serie = array();
+
+ foreach ($data as $key => $value) {
+ $serie[] = array($key, $value);
+ }
+
+ return $serie;
+ }
+
+ private function convertToPieSerie($data) {
+ $serie = array();
+
+ foreach ($data as $value) {
+ $value['data'] = array(array(0, (int) $value['data']));
+ $serie[] = $value;
+ }
+
+ return $serie;
+ }
+
/**
* This action handles the statistic main page.
*
@@ -33,10 +54,11 @@ class FreshRSS_stats_Controller extends Minz_ActionController {
$statsDAO = FreshRSS_Factory::createStatsDAO();
Minz_View::appendScript(Minz_Url::display('/scripts/flotr2.min.js?' . @filemtime(PUBLIC_PATH . '/scripts/flotr2.min.js')));
$this->view->repartition = $statsDAO->calculateEntryRepartition();
- $this->view->count = $statsDAO->calculateEntryCount();
- $this->view->average = $statsDAO->calculateEntryAverage();
- $this->view->feedByCategory = $statsDAO->calculateFeedByCategory();
- $this->view->entryByCategory = $statsDAO->calculateEntryByCategory();
+ $entryCount = $statsDAO->calculateEntryCount();
+ $this->view->count = $this->convertToSerie($entryCount);
+ $this->view->average = round(array_sum(array_values($entryCount)) / count($entryCount), 2);
+ $this->view->feedByCategory = $this->convertToPieSerie($statsDAO->calculateFeedByCategory());
+ $this->view->entryByCategory = $this->convertToPieSerie($statsDAO->calculateEntryByCategory());
$this->view->topFeed = $statsDAO->calculateTopFeed();
}
@@ -118,11 +140,11 @@ class FreshRSS_stats_Controller extends Minz_ActionController {
$this->view->days = $statsDAO->getDays();
$this->view->months = $statsDAO->getMonths();
$this->view->repartition = $statsDAO->calculateEntryRepartitionPerFeed($id);
- $this->view->repartitionHour = $statsDAO->calculateEntryRepartitionPerFeedPerHour($id);
+ $this->view->repartitionHour = $this->convertToSerie($statsDAO->calculateEntryRepartitionPerFeedPerHour($id));
$this->view->averageHour = $statsDAO->calculateEntryAveragePerFeedPerHour($id);
- $this->view->repartitionDayOfWeek = $statsDAO->calculateEntryRepartitionPerFeedPerDayOfWeek($id);
+ $this->view->repartitionDayOfWeek = $this->convertToSerie($statsDAO->calculateEntryRepartitionPerFeedPerDayOfWeek($id));
$this->view->averageDayOfWeek = $statsDAO->calculateEntryAveragePerFeedPerDayOfWeek($id);
- $this->view->repartitionMonth = $statsDAO->calculateEntryRepartitionPerFeedPerMonth($id);
+ $this->view->repartitionMonth = $this->convertToSerie($statsDAO->calculateEntryRepartitionPerFeedPerMonth($id));
$this->view->averageMonth = $statsDAO->calculateEntryAveragePerFeedPerMonth($id);
}
}
diff --git a/app/Controllers/userController.php b/app/Controllers/userController.php
index 0521bc008..9d6ae18e6 100644
--- a/app/Controllers/userController.php
+++ b/app/Controllers/userController.php
@@ -24,6 +24,16 @@ class FreshRSS_user_Controller extends Minz_ActionController {
}
}
+ public static function hashPassword($passwordPlain) {
+ if (!function_exists('password_hash')) {
+ include_once(LIB_PATH . '/password_compat.php');
+ }
+ $passwordHash = password_hash($passwordPlain, PASSWORD_BCRYPT, array('cost' => self::BCRYPT_COST));
+ $passwordPlain = '';
+ $passwordHash = preg_replace('/^\$2[xy]\$/', '\$2a\$', $passwordHash); //Compatibility with bcrypt.js
+ return $passwordHash == '' ? '' : $passwordHash;
+ }
+
/**
* This action displays the user profile page.
*/
@@ -41,12 +51,7 @@ class FreshRSS_user_Controller extends Minz_ActionController {
if ($passwordPlain != '') {
Minz_Request::_param('newPasswordPlain'); //Discard plain-text password ASAP
$_POST['newPasswordPlain'] = '';
- if (!function_exists('password_hash')) {
- include_once(LIB_PATH . '/password_compat.php');
- }
- $passwordHash = password_hash($passwordPlain, PASSWORD_BCRYPT, array('cost' => self::BCRYPT_COST));
- $passwordPlain = '';
- $passwordHash = preg_replace('/^\$2[xy]\$/', '\$2a\$', $passwordHash); //Compatibility with bcrypt.js
+ $passwordHash = self::hashPassword($passwordPlain);
$ok &= ($passwordHash != '');
FreshRSS_Context::$user_conf->passwordHash = $passwordHash;
}
@@ -54,12 +59,7 @@ class FreshRSS_user_Controller extends Minz_ActionController {
$passwordPlain = Minz_Request::param('apiPasswordPlain', '', true);
if ($passwordPlain != '') {
- if (!function_exists('password_hash')) {
- include_once(LIB_PATH . '/password_compat.php');
- }
- $passwordHash = password_hash($passwordPlain, PASSWORD_BCRYPT, array('cost' => self::BCRYPT_COST));
- $passwordPlain = '';
- $passwordHash = preg_replace('/^\$2[xy]\$/', '\$2a\$', $passwordHash); //Compatibility with bcrypt.js
+ $passwordHash = self::hashPassword($passwordPlain);
$ok &= ($passwordHash != '');
FreshRSS_Context::$user_conf->apiPasswordHash = $passwordHash;
}
@@ -99,6 +99,50 @@ class FreshRSS_user_Controller extends Minz_ActionController {
$this->view->size_user = $entryDAO->size();
}
+ public static function createUser($new_user_name, $passwordPlain, $apiPasswordPlain, $userConfig = array(), $insertDefaultFeeds = true) {
+ if (!is_array($userConfig)) {
+ $userConfig = array();
+ }
+
+ $ok = ($new_user_name != '') && ctype_alnum($new_user_name);
+
+ if ($ok) {
+ $languages = Minz_Translate::availableLanguages();
+ if (empty($userConfig['language']) || !in_array($userConfig['language'], $languages)) {
+ $userConfig['language'] = 'en';
+ }
+
+ $ok &= !in_array(strtoupper($new_user_name), array_map('strtoupper', listUsers())); //Not an existing user, case-insensitive
+
+ $configPath = join_path(DATA_PATH, 'users', $new_user_name, 'config.php');
+ $ok &= !file_exists($configPath);
+ }
+ if ($ok) {
+ $passwordHash = '';
+ if ($passwordPlain != '') {
+ $passwordHash = self::hashPassword($passwordPlain);
+ $ok &= ($passwordHash != '');
+ }
+
+ $apiPasswordHash = '';
+ if ($apiPasswordPlain != '') {
+ $apiPasswordHash = self::hashPassword($apiPasswordPlain);
+ $ok &= ($apiPasswordHash != '');
+ }
+ }
+ if ($ok) {
+ mkdir(join_path(DATA_PATH, 'users', $new_user_name));
+ $userConfig['passwordHash'] = $passwordHash;
+ $userConfig['apiPasswordHash'] = $apiPasswordHash;
+ $ok &= (file_put_contents($configPath, "<?php\n return " . var_export($userConfig, true) . ';') !== false);
+ }
+ if ($ok) {
+ $userDAO = new FreshRSS_UserDAO();
+ $ok &= $userDAO->createUser($new_user_name, $userConfig['language'], $insertDefaultFeeds);
+ }
+ return $ok;
+ }
+
/**
* This action creates a new user.
*
@@ -116,57 +160,13 @@ class FreshRSS_user_Controller extends Minz_ActionController {
FreshRSS_Auth::hasAccess('admin') ||
!max_registrations_reached()
)) {
- $db = FreshRSS_Context::$system_conf->db;
- require_once(APP_PATH . '/SQL/install.sql.' . $db['type'] . '.php');
-
- $new_user_language = Minz_Request::param('new_user_language', FreshRSS_Context::$user_conf->language);
- $languages = Minz_Translate::availableLanguages();
- if (!isset($languages[$new_user_language])) {
- $new_user_language = FreshRSS_Context::$user_conf->language;
- }
-
$new_user_name = Minz_Request::param('new_user_name');
- $ok = ($new_user_name != '') && ctype_alnum($new_user_name);
-
- if ($ok) {
- $default_user = FreshRSS_Context::$system_conf->default_user;
- $ok &= (strcasecmp($new_user_name, $default_user) !== 0); //It is forbidden to alter the default user
-
- $ok &= !in_array(strtoupper($new_user_name), array_map('strtoupper', listUsers())); //Not an existing user, case-insensitive
+ $passwordPlain = Minz_Request::param('new_user_passwordPlain', '', true);
+ $new_user_language = Minz_Request::param('new_user_language', FreshRSS_Context::$user_conf->language);
- $configPath = join_path(DATA_PATH, 'users', $new_user_name, 'config.php');
- $ok &= !file_exists($configPath);
- }
- if ($ok) {
- $passwordPlain = Minz_Request::param('new_user_passwordPlain', '', true);
- $passwordHash = '';
- if ($passwordPlain != '') {
- Minz_Request::_param('new_user_passwordPlain'); //Discard plain-text password ASAP
- $_POST['new_user_passwordPlain'] = '';
- if (!function_exists('password_hash')) {
- include_once(LIB_PATH . '/password_compat.php');
- }
- $passwordHash = password_hash($passwordPlain, PASSWORD_BCRYPT, array('cost' => self::BCRYPT_COST));
- $passwordPlain = '';
- $passwordHash = preg_replace('/^\$2[xy]\$/', '\$2a\$', $passwordHash); //Compatibility with bcrypt.js
- $ok &= ($passwordHash != '');
- }
- if (empty($passwordHash)) {
- $passwordHash = '';
- }
- }
- if ($ok) {
- mkdir(join_path(DATA_PATH, 'users', $new_user_name));
- $config_array = array(
- 'language' => $new_user_language,
- 'passwordHash' => $passwordHash,
- );
- $ok &= (file_put_contents($configPath, "<?php\n return " . var_export($config_array, true) . ';') !== false);
- }
- if ($ok) {
- $userDAO = new FreshRSS_UserDAO();
- $ok &= $userDAO->createUser($new_user_name);
- }
+ $ok = self::createUser($new_user_name, $passwordPlain, '', array('language' => $new_user_language));
+ Minz_Request::_param('new_user_passwordPlain'); //Discard plain-text password ASAP
+ $_POST['new_user_passwordPlain'] = '';
invalidateHttpCache();
$notif = array(
@@ -183,6 +183,27 @@ class FreshRSS_user_Controller extends Minz_ActionController {
Minz_Request::forward($redirect_url, true);
}
+ public static function deleteUser($username) {
+ $db = FreshRSS_Context::$system_conf->db;
+ require_once(APP_PATH . '/SQL/install.sql.' . $db['type'] . '.php');
+
+ $ok = ctype_alnum($username);
+ if ($ok) {
+ $default_user = FreshRSS_Context::$system_conf->default_user;
+ $ok &= (strcasecmp($username, $default_user) !== 0); //It is forbidden to delete the default user
+ }
+ $user_data = join_path(DATA_PATH, 'users', $username);
+ if ($ok) {
+ $ok &= is_dir($user_data);
+ }
+ if ($ok) {
+ $userDAO = new FreshRSS_UserDAO();
+ $ok &= $userDAO->deleteUser($username);
+ $ok &= recursive_unlink($user_data);
+ }
+ return $ok;
+ }
+
/**
* This action delete an existing user.
*
@@ -204,16 +225,7 @@ class FreshRSS_user_Controller extends Minz_ActionController {
FreshRSS_Auth::hasAccess('admin') ||
$self_deletion
)) {
- $db = FreshRSS_Context::$system_conf->db;
- require_once(APP_PATH . '/SQL/install.sql.' . $db['type'] . '.php');
-
- $ok = ctype_alnum($username);
- $user_data = join_path(DATA_PATH, 'users', $username);
-
- if ($ok) {
- $default_user = FreshRSS_Context::$system_conf->default_user;
- $ok &= (strcasecmp($username, $default_user) !== 0); //It is forbidden to delete the default user
- }
+ $ok = true;
if ($ok && $self_deletion) {
// We check the password if it's a self-destruction
$nonce = Minz_Session::param('nonce');
@@ -225,12 +237,7 @@ class FreshRSS_user_Controller extends Minz_ActionController {
);
}
if ($ok) {
- $ok &= is_dir($user_data);
- }
- if ($ok) {
- $userDAO = new FreshRSS_UserDAO();
- $ok &= $userDAO->deleteUser($username);
- $ok &= recursive_unlink($user_data);
+ $ok &= self::deleteUser($username);
}
if ($ok && $self_deletion) {
FreshRSS_Auth::removeAccess();
diff --git a/app/Exceptions/AlreadySubscribedException.php b/app/Exceptions/AlreadySubscribedException.php
new file mode 100644
index 000000000..33b9f9555
--- /dev/null
+++ b/app/Exceptions/AlreadySubscribedException.php
@@ -0,0 +1,14 @@
+<?php
+
+class FreshRSS_AlreadySubscribed_Exception extends Exception {
+ private $feedName = '';
+
+ public function __construct($url, $feedName) {
+ parent::__construct('Already subscribed! ' . $url, 2135);
+ $this->feedName = $feedName;
+ }
+
+ public function feedName() {
+ return $this->feedName;
+ }
+}
diff --git a/app/Exceptions/FeedNotAddedException.php b/app/Exceptions/FeedNotAddedException.php
new file mode 100644
index 000000000..350a17c56
--- /dev/null
+++ b/app/Exceptions/FeedNotAddedException.php
@@ -0,0 +1,14 @@
+<?php
+
+class FreshRSS_FeedNotAdded_Exception extends Exception {
+ private $feedName = '';
+
+ public function __construct($url, $feedName) {
+ parent::__construct('Feed not added! ' . $url, 2147);
+ $this->feedName = $feedName;
+ }
+
+ public function feedName() {
+ return $this->feedName;
+ }
+}
diff --git a/app/Exceptions/ZipException.php b/app/Exceptions/ZipException.php
new file mode 100644
index 000000000..8441daedf
--- /dev/null
+++ b/app/Exceptions/ZipException.php
@@ -0,0 +1,14 @@
+<?php
+
+class FreshRSS_Zip_Exception extends Exception {
+ private $zipErrorCode = 0;
+
+ public function __construct($zipErrorCode) {
+ parent::__construct('ZIP error! ' . $url, 2141);
+ $this->zipErrorCode = $zipErrorCode;
+ }
+
+ public function zipErrorCode() {
+ return $this->zipErrorCode;
+ }
+}
diff --git a/app/Exceptions/ZipMissingException.php b/app/Exceptions/ZipMissingException.php
new file mode 100644
index 000000000..864cc3991
--- /dev/null
+++ b/app/Exceptions/ZipMissingException.php
@@ -0,0 +1,4 @@
+<?php
+
+class FreshRSS_ZipMissing_Exception extends Exception {
+}
diff --git a/app/FreshRSS.php b/app/FreshRSS.php
index f9c371d27..e4caf23d1 100644
--- a/app/FreshRSS.php
+++ b/app/FreshRSS.php
@@ -49,7 +49,7 @@ class FreshRSS extends Minz_FrontController {
self::initI18n();
self::loadNotifications();
// Enable extensions for the current (logged) user.
- if (FreshRSS_Auth::hasAccess()) {
+ if (FreshRSS_Auth::hasAccess() || $system_conf->allow_anonymous) {
$ext_list = FreshRSS_Context::$user_conf->extensions_enabled;
Minz_ExtensionManager::enableByList($ext_list);
}
diff --git a/app/Models/CategoryDAO.php b/app/Models/CategoryDAO.php
index fc431553e..c2d57c241 100644
--- a/app/Models/CategoryDAO.php
+++ b/app/Models/CategoryDAO.php
@@ -1,6 +1,9 @@
<?php
class FreshRSS_CategoryDAO extends Minz_ModelPdo implements FreshRSS_Searchable {
+
+ const defaultCategoryId = 1;
+
public function addCategory($valuesTmp) {
$sql = 'INSERT INTO `' . $this->prefix . 'category`(name) VALUES(?)';
$stm = $this->bd->prepare($sql);
@@ -10,7 +13,7 @@ class FreshRSS_CategoryDAO extends Minz_ModelPdo implements FreshRSS_Searchable
);
if ($stm && $stm->execute($values)) {
- return $this->bd->lastInsertId();
+ return $this->bd->lastInsertId('"' . $this->prefix . 'category_id_seq"');
} else {
$info = $stm == null ? array(2 => 'syntax error') : $stm->errorInfo();
Minz_Log::error('SQL error addCategory: ' . $info[2]);
@@ -50,6 +53,9 @@ class FreshRSS_CategoryDAO extends Minz_ModelPdo implements FreshRSS_Searchable
}
public function deleteCategory($id) {
+ if ($id <= self::defaultCategoryId) {
+ return false;
+ }
$sql = 'DELETE FROM `' . $this->prefix . 'category` WHERE id=?';
$stm = $this->bd->prepare($sql);
@@ -100,7 +106,7 @@ class FreshRSS_CategoryDAO extends Minz_ModelPdo implements FreshRSS_Searchable
public function listCategories($prePopulateFeeds = true, $details = false) {
if ($prePopulateFeeds) {
$sql = 'SELECT c.id AS c_id, c.name AS c_name, '
- . ($details ? 'f.* ' : 'f.id, f.name, f.url, f.website, f.priority, f.error, f.cache_nbEntries, f.cache_nbUnreads ')
+ . ($details ? 'f.* ' : 'f.id, f.name, f.url, f.website, f.priority, f.error, f.`cache_nbEntries`, f.`cache_nbUnreads` ')
. 'FROM `' . $this->prefix . 'category` c '
. 'LEFT OUTER JOIN `' . $this->prefix . 'feed` f ON f.category=c.id '
. 'GROUP BY f.id, c_id '
@@ -117,7 +123,7 @@ class FreshRSS_CategoryDAO extends Minz_ModelPdo implements FreshRSS_Searchable
}
public function getDefault() {
- $sql = 'SELECT * FROM `' . $this->prefix . 'category` WHERE id=1';
+ $sql = 'SELECT * FROM `' . $this->prefix . 'category` WHERE id=' . self::defaultCategoryId;
$stm = $this->bd->prepare($sql);
$stm->execute();
@@ -131,11 +137,11 @@ class FreshRSS_CategoryDAO extends Minz_ModelPdo implements FreshRSS_Searchable
}
}
public function checkDefault() {
- $def_cat = $this->searchById(1);
+ $def_cat = $this->searchById(self::defaultCategoryId);
if ($def_cat == null) {
$cat = new FreshRSS_Category(_t('gen.short.default_category'));
- $cat->_id(1);
+ $cat->_id(self::defaultCategoryId);
$values = array(
'id' => $cat->id(),
@@ -207,12 +213,13 @@ class FreshRSS_CategoryDAO extends Minz_ModelPdo implements FreshRSS_Searchable
$previousLine = null;
$feedsDao = array();
+ $feedDao = FreshRSS_Factory::createFeedDAO();
foreach ($listDAO as $line) {
if ($previousLine['c_id'] != null && $line['c_id'] !== $previousLine['c_id']) {
// End of the current category, we add it to the $list
$cat = new FreshRSS_Category(
$previousLine['c_name'],
- FreshRSS_FeedDAO::daoToFeed($feedsDao, $previousLine['c_id'])
+ $feedDao->daoToFeed($feedsDao, $previousLine['c_id'])
);
$cat->_id($previousLine['c_id']);
$list[$previousLine['c_id']] = $cat;
@@ -228,7 +235,7 @@ class FreshRSS_CategoryDAO extends Minz_ModelPdo implements FreshRSS_Searchable
if ($previousLine != null) {
$cat = new FreshRSS_Category(
$previousLine['c_name'],
- FreshRSS_FeedDAO::daoToFeed($feedsDao, $previousLine['c_id'])
+ $feedDao->daoToFeed($feedsDao, $previousLine['c_id'])
);
$cat->_id($previousLine['c_id']);
$list[$previousLine['c_id']] = $cat;
diff --git a/app/Models/ConfigurationSetter.php b/app/Models/ConfigurationSetter.php
index 79bd0170b..046f54955 100644
--- a/app/Models/ConfigurationSetter.php
+++ b/app/Models/ConfigurationSetter.php
@@ -282,6 +282,7 @@ class FreshRSS_ConfigurationSetter {
switch ($value['type']) {
case 'mysql':
+ case 'pgsql':
if (empty($value['host']) ||
empty($value['user']) ||
empty($value['base']) ||
diff --git a/app/Models/Context.php b/app/Models/Context.php
index 2a58bd4ba..fd0e79fc1 100644
--- a/app/Models/Context.php
+++ b/app/Models/Context.php
@@ -35,6 +35,9 @@ class FreshRSS_Context {
public static $first_id = '';
public static $next_id = '';
public static $id_max = '';
+ public static $sinceHours = 0;
+
+ public static $isCli = false;
/**
* Initialize the context.
@@ -45,9 +48,6 @@ class FreshRSS_Context {
// Init configuration.
self::$system_conf = Minz_Configuration::get('system');
self::$user_conf = Minz_Configuration::get('user');
-
- $catDAO = new FreshRSS_CategoryDAO();
- self::$categories = $catDAO->listCategories();
}
/**
@@ -139,15 +139,22 @@ class FreshRSS_Context {
$id = substr($get, 2);
$nb_unread = 0;
+ if (empty(self::$categories)) {
+ $catDAO = new FreshRSS_CategoryDAO();
+ self::$categories = $catDAO->listCategories();
+ }
+
switch($type) {
case 'a':
self::$current_get['all'] = true;
self::$name = _t('index.feed.title');
+ self::$description = self::$system_conf->meta_description;
self::$get_unread = self::$total_unread;
break;
case 's':
self::$current_get['starred'] = true;
self::$name = _t('index.feed.title_fav');
+ self::$description = self::$system_conf->meta_description;
self::$get_unread = self::$total_starred['unread'];
// Update state if favorite is not yet enabled.
@@ -198,11 +205,16 @@ class FreshRSS_Context {
/**
* Set the value of $next_get attribute.
*/
- public static function _nextGet() {
+ private static function _nextGet() {
$get = self::currentGet();
// By default, $next_get == $get
self::$next_get = $get;
+ if (empty(self::$categories)) {
+ $catDAO = new FreshRSS_CategoryDAO();
+ self::$categories = $catDAO->listCategories();
+ }
+
if (self::$user_conf->onread_jump_next && strlen($get) > 2) {
$another_unread_id = '';
$found_current_get = false;
diff --git a/app/Models/DatabaseDAOPGSQL.php b/app/Models/DatabaseDAOPGSQL.php
new file mode 100644
index 000000000..a4edaa448
--- /dev/null
+++ b/app/Models/DatabaseDAOPGSQL.php
@@ -0,0 +1,43 @@
+<?php
+
+/**
+ * This class is used to test database is well-constructed.
+ */
+class FreshRSS_DatabaseDAOPGSQL extends FreshRSS_DatabaseDAO {
+ public function tablesAreCorrect() {
+ $db = FreshRSS_Context::$system_conf->db;
+ $dbowner = $db['user'];
+ $sql = 'SELECT * FROM pg_catalog.pg_tables where tableowner=?';
+ $stm = $this->bd->prepare($sql);
+ $values = array($dbowner);
+ $stm->execute($values);
+ $res = $stm->fetchAll(PDO::FETCH_ASSOC);
+
+ $tables = array(
+ $this->prefix . 'category' => false,
+ $this->prefix . 'feed' => false,
+ $this->prefix . 'entry' => false,
+ );
+ foreach ($res as $value) {
+ $tables[array_pop($value)] = true;
+ }
+
+ return count(array_keys($tables, true, true)) == count($tables);
+ }
+
+ public function getSchema($table) {
+ $sql = 'select column_name as field, data_type as type, column_default as default, is_nullable as null from INFORMATION_SCHEMA.COLUMNS where table_name = ?';
+ $stm = $this->bd->prepare($sql);
+ $stm->execute(array($this->prefix . $table));
+ return $this->listDaoToSchema($stm->fetchAll(PDO::FETCH_ASSOC));
+ }
+
+ public function daoToSchema($dao) {
+ return array(
+ 'name' => $dao['field'],
+ 'type' => strtolower($dao['type']),
+ 'notnull' => (bool)$dao['null'],
+ 'default' => $dao['default'],
+ );
+ }
+}
diff --git a/app/Models/EntryDAO.php b/app/Models/EntryDAO.php
index 8f64098e5..4c6a9ea20 100644
--- a/app/Models/EntryDAO.php
+++ b/app/Models/EntryDAO.php
@@ -3,13 +3,21 @@
class FreshRSS_EntryDAO extends Minz_ModelPdo implements FreshRSS_Searchable {
public function isCompressed() {
- return parent::$sharedDbType !== 'sqlite';
+ return parent::$sharedDbType === 'mysql';
}
public function hasNativeHex() {
return parent::$sharedDbType !== 'sqlite';
}
+ public function sqlHexDecode($x) {
+ return 'unhex(' . $x . ')';
+ }
+
+ public function sqlHexEncode($x) {
+ return 'hex(' . $x . ')';
+ }
+
protected function addColumn($name) {
Minz_Log::warning('FreshRSS_EntryDAO::addColumn: ' . $name);
$hasTransaction = false;
@@ -20,7 +28,7 @@ class FreshRSS_EntryDAO extends Minz_ModelPdo implements FreshRSS_Searchable {
$this->bd->beginTransaction();
$hasTransaction = true;
}
- $stm = $this->bd->prepare('ALTER TABLE `' . $this->prefix . 'entry` ADD COLUMN lastSeen INT(11) DEFAULT 0');
+ $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()) {
@@ -105,32 +113,45 @@ class FreshRSS_EntryDAO extends Minz_ModelPdo implements FreshRSS_Searchable {
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->hasNativeHex() ? 'X?' : '?')
- . ', ?, ?, ?, ?)';
+ . ', link, date, `lastSeen`, hash, is_read, is_favorite, id_feed, tags) '
+ . 'VALUES(:id, :guid, :title, :author, '
+ . ($this->isCompressed() ? 'COMPRESS(:content)' : ':content')
+ . ', :link, :date, :last_seen, '
+ . $this->sqlHexDecode(':hash')
+ . ', :is_read, :is_favorite, :id_feed, :tags)';
$this->addEntryPrepared = $this->bd->prepare($sql);
}
+ $this->addEntryPrepared->bindParam(':id', $valuesTmp['id']);
+ $valuesTmp['guid'] = substr($valuesTmp['guid'], 0, 760);
+ $valuesTmp['guid'] = safe_ascii($valuesTmp['guid']);
+ $this->addEntryPrepared->bindParam(':guid', $valuesTmp['guid']);
+ $valuesTmp['title'] = substr($valuesTmp['title'], 0, 255);
+ $this->addEntryPrepared->bindParam(':title', $valuesTmp['title']);
+ $valuesTmp['author'] = substr($valuesTmp['author'], 0, 255);
+ $this->addEntryPrepared->bindParam(':author', $valuesTmp['author']);
+ $this->addEntryPrepared->bindParam(':content', $valuesTmp['content']);
+ $valuesTmp['link'] = substr($valuesTmp['link'], 0, 1023);
+ $valuesTmp['link'] = safe_ascii($valuesTmp['link']);
+ $this->addEntryPrepared->bindParam(':link', $valuesTmp['link']);
+ $this->addEntryPrepared->bindParam(':date', $valuesTmp['date'], PDO::PARAM_INT);
+ $valuesTmp['lastSeen'] = time();
+ $this->addEntryPrepared->bindParam(':last_seen', $valuesTmp['lastSeen'], PDO::PARAM_INT);
+ $valuesTmp['is_read'] = $valuesTmp['is_read'] ? 1 : 0;
+ $this->addEntryPrepared->bindParam(':is_read', $valuesTmp['is_read'], PDO::PARAM_INT);
+ $valuesTmp['is_favorite'] = $valuesTmp['is_favorite'] ? 1 : 0;
+ $this->addEntryPrepared->bindParam(':is_favorite', $valuesTmp['is_favorite'], PDO::PARAM_INT);
+ $this->addEntryPrepared->bindParam(':id_feed', $valuesTmp['id_feed'], PDO::PARAM_INT);
+ $valuesTmp['tags'] = substr($valuesTmp['tags'], 0, 1023);
+ $this->addEntryPrepared->bindParam(':tags', $valuesTmp['tags']);
+
+ if ($this->hasNativeHex()) {
+ $this->addEntryPrepared->bindParam(':hash', $valuesTmp['hash']);
+ } else {
+ $valuesTmp['hashBin'] = pack('H*', $valuesTmp['hash']); //hex2bin() is PHP5.4+
+ $this->addEntryPrepared->bindParam(':hash', $valuesTmp['hashBin']);
+ }
- $values = array(
- $valuesTmp['id'],
- substr($valuesTmp['guid'], 0, 760),
- substr($valuesTmp['title'], 0, 255),
- substr($valuesTmp['author'], 0, 255),
- $valuesTmp['content'],
- substr($valuesTmp['link'], 0, 1023),
- $valuesTmp['date'],
- time(),
- $this->hasNativeHex() ? $valuesTmp['hash'] : pack('H*', $valuesTmp['hash']), // X'09AF' hexadecimal literals do not work with SQLite/PDO //hex2bin() is PHP5.4+
- $valuesTmp['is_read'] ? 1 : 0,
- $valuesTmp['is_favorite'] ? 1 : 0,
- $valuesTmp['id_feed'],
- substr($valuesTmp['tags'], 0, 1023),
- );
-
- if ($this->addEntryPrepared && $this->addEntryPrepared->execute($values)) {
+ if ($this->addEntryPrepared && $this->addEntryPrepared->execute()) {
return $this->bd->lastInsertId();
} else {
$info = $this->addEntryPrepared == null ? array(0 => '', 1 => '', 2 => 'syntax error') : $this->addEntryPrepared->errorInfo();
@@ -153,35 +174,44 @@ class FreshRSS_EntryDAO extends Minz_ModelPdo implements FreshRSS_Searchable {
if ($this->updateEntryPrepared === null) {
$sql = 'UPDATE `' . $this->prefix . 'entry` '
- . 'SET title=?, author=?, '
- . ($this->isCompressed() ? 'content_bin=COMPRESS(?)' : 'content=?')
- . ', link=?, date=?, lastSeen=?, hash='
- . ($this->hasNativeHex() ? 'X?' : '?')
- . ', ' . ($valuesTmp['is_read'] === null ? '' : 'is_read=?, ')
- . 'tags=? '
- . 'WHERE id_feed=? AND guid=?';
+ . 'SET title=:title, author=:author, '
+ . ($this->isCompressed() ? 'content_bin=COMPRESS(:content)' : 'content=:content')
+ . ', link=:link, date=:date, `lastSeen`=:last_seen, '
+ . 'hash=' . $this->sqlHexDecode(':hash')
+ . ', ' . ($valuesTmp['is_read'] === null ? '' : 'is_read=:is_read, ')
+ . 'tags=:tags '
+ . 'WHERE id_feed=:id_feed AND guid=:guid';
$this->updateEntryPrepared = $this->bd->prepare($sql);
}
- $values = array(
- substr($valuesTmp['title'], 0, 255),
- substr($valuesTmp['author'], 0, 255),
- $valuesTmp['content'],
- substr($valuesTmp['link'], 0, 1023),
- $valuesTmp['date'],
- time(),
- $this->hasNativeHex() ? $valuesTmp['hash'] : pack('H*', $valuesTmp['hash']),
- );
+ $valuesTmp['guid'] = substr($valuesTmp['guid'], 0, 760);
+ $this->updateEntryPrepared->bindParam(':guid', $valuesTmp['guid']);
+ $valuesTmp['title'] = substr($valuesTmp['title'], 0, 255);
+ $this->updateEntryPrepared->bindParam(':title', $valuesTmp['title']);
+ $valuesTmp['author'] = substr($valuesTmp['author'], 0, 255);
+ $this->updateEntryPrepared->bindParam(':author', $valuesTmp['author']);
+ $this->updateEntryPrepared->bindParam(':content', $valuesTmp['content']);
+ $valuesTmp['link'] = substr($valuesTmp['link'], 0, 1023);
+ $valuesTmp['link'] = safe_ascii($valuesTmp['link']);
+ $this->updateEntryPrepared->bindParam(':link', $valuesTmp['link']);
+ $this->updateEntryPrepared->bindParam(':date', $valuesTmp['date'], PDO::PARAM_INT);
+ $valuesTmp['lastSeen'] = time();
+ $this->updateEntryPrepared->bindParam(':last_seen', $valuesTmp['lastSeen'], PDO::PARAM_INT);
if ($valuesTmp['is_read'] !== null) {
- $values[] = $valuesTmp['is_read'] ? 1 : 0;
+ $this->updateEntryPrepared->bindParam(':is_read', $valuesTmp['is_read'] ? 1 : 0, PDO::PARAM_INT);
+ }
+ $this->updateEntryPrepared->bindParam(':id_feed', $valuesTmp['id_feed'], PDO::PARAM_INT);
+ $valuesTmp['tags'] = substr($valuesTmp['tags'], 0, 1023);
+ $this->updateEntryPrepared->bindParam(':tags', $valuesTmp['tags']);
+
+ if ($this->hasNativeHex()) {
+ $this->updateEntryPrepared->bindParam(':hash', $valuesTmp['hash']);
+ } else {
+ $valuesTmp['hashBin'] = pack('H*', $valuesTmp['hash']); //hex2bin() is PHP5.4+
+ $this->updateEntryPrepared->bindParam(':hash', $valuesTmp['hashBin']);
}
- $values = array_merge($values, array(
- substr($valuesTmp['tags'], 0, 1023),
- $valuesTmp['id_feed'],
- substr($valuesTmp['guid'], 0, 760),
- ));
- if ($this->updateEntryPrepared && $this->updateEntryPrepared->execute($values)) {
+ if ($this->updateEntryPrepared && $this->updateEntryPrepared->execute()) {
return $this->bd->lastInsertId();
} else {
$info = $this->updateEntryPrepared == null ? array(0 => '', 1 => '', 2 => 'syntax error') : $this->updateEntryPrepared->errorInfo();
@@ -246,15 +276,19 @@ class FreshRSS_EntryDAO extends Minz_ModelPdo implements FreshRSS_Searchable {
. 'WHERE e.is_read=0 '
. 'GROUP BY e.id_feed'
. ') x ON x.id_feed=f.id '
- . 'SET f.cache_nbUnreads=COALESCE(x.nbUnreads, 0) '
- . 'WHERE 1';
+ . 'SET f.`cache_nbUnreads`=COALESCE(x.nbUnreads, 0)';
+ $hasWhere = false;
$values = array();
if ($feedId !== false) {
- $sql .= ' AND f.id=?';
+ $sql .= $hasWhere ? ' AND' : ' WHERE';
+ $hasWhere = true;
+ $sql .= ' f.id=?';
$values[] = $id;
}
if ($catId !== false) {
- $sql .= ' AND f.category=?';
+ $sql .= $hasWhere ? ' AND' : ' WHERE';
+ $hasWhere = true;
+ $sql .= ' f.category=?';
$values[] = $catId;
}
$stm = $this->bd->prepare($sql);
@@ -309,7 +343,7 @@ class FreshRSS_EntryDAO extends Minz_ModelPdo implements FreshRSS_Searchable {
} else {
$sql = 'UPDATE `' . $this->prefix . 'entry` e INNER JOIN `' . $this->prefix . 'feed` f ON e.id_feed=f.id '
. 'SET e.is_read=?,'
- . 'f.cache_nbUnreads=f.cache_nbUnreads' . ($is_read ? '-' : '+') . '1 '
+ . 'f.`cache_nbUnreads`=f.`cache_nbUnreads`' . ($is_read ? '-' : '+') . '1 '
. 'WHERE e.id=? AND e.is_read=?';
$values = array($is_read ? 1 : 0, $ids, $is_read ? 0 : 1);
$stm = $this->bd->prepare($sql);
@@ -430,17 +464,17 @@ class FreshRSS_EntryDAO extends Minz_ModelPdo implements FreshRSS_Searchable {
}
$this->bd->beginTransaction();
- $sql = 'UPDATE `' . $this->prefix . 'entry` e '
- . 'SET e.is_read=1 '
- . 'WHERE e.id_feed=? AND e.is_read=0 AND e.id <= ?';
+ $sql = 'UPDATE `' . $this->prefix . 'entry` '
+ . 'SET is_read=1 '
+ . 'WHERE id_feed=? AND is_read=0 AND id <= ?';
$values = array($id_feed, $idMax);
- list($searchValues, $search) = $this->sqlListEntriesWhere('e.', $filter, $state);
+ list($searchValues, $search) = $this->sqlListEntriesWhere('', $filter, $state);
$stm = $this->bd->prepare($sql . $search);
if (!($stm && $stm->execute(array_merge($values, $searchValues)))) {
$info = $stm == null ? array(2 => 'syntax error') : $stm->errorInfo();
- Minz_Log::error('SQL error markReadFeed: ' . $info[2]);
+ Minz_Log::error('SQL error markReadFeed: ' . $info[2] . ' with SQL: ' . $sql . $search);
$this->bd->rollBack();
return false;
}
@@ -448,13 +482,13 @@ class FreshRSS_EntryDAO extends Minz_ModelPdo implements FreshRSS_Searchable {
if ($affected > 0) {
$sql = 'UPDATE `' . $this->prefix . 'feed` '
- . 'SET cache_nbUnreads=cache_nbUnreads-' . $affected
+ . 'SET `cache_nbUnreads`=`cache_nbUnreads`-' . $affected
. ' WHERE id=?';
$values = array($id_feed);
$stm = $this->bd->prepare($sql);
if (!($stm && $stm->execute($values))) {
$info = $stm == null ? array(2 => 'syntax error') : $stm->errorInfo();
- Minz_Log::error('SQL error markReadFeed: ' . $info[2]);
+ Minz_Log::error('SQL error markReadFeed cache: ' . $info[2]);
$this->bd->rollBack();
return false;
}
@@ -658,7 +692,8 @@ class FreshRSS_EntryDAO extends Minz_ModelPdo implements FreshRSS_Searchable {
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). '?)';
+ $guids = array_unique($guids);
+ $sql = 'SELECT guid, ' . $this->sqlHexEncode('hash') . ' AS hex_hash 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);
@@ -666,7 +701,7 @@ class FreshRSS_EntryDAO extends Minz_ModelPdo implements FreshRSS_Searchable {
$result = array();
$rows = $stm->fetchAll(PDO::FETCH_ASSOC);
foreach ($rows as $row) {
- $result[$row['guid']] = $row['hexHash'];
+ $result[$row['guid']] = $row['hex_hash'];
}
return $result;
} else {
@@ -680,13 +715,16 @@ class FreshRSS_EntryDAO extends Minz_ModelPdo implements FreshRSS_Searchable {
}
}
- public function updateLastSeen($id_feed, $guids) {
+ public function updateLastSeen($id_feed, $guids, $mtime = 0) {
if (count($guids) < 1) {
return 0;
}
- $sql = 'UPDATE `' . $this->prefix . 'entry` SET lastSeen=? WHERE id_feed=? AND guid IN (' . str_repeat('?,', count($guids) - 1). '?)';
+ $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(time(), $id_feed);
+ if ($mtime <= 0) {
+ $mtime = time();
+ }
+ $values = array($mtime, $id_feed);
$values = array_merge($values, $guids);
if ($stm && $stm->execute($values)) {
return $stm->rowCount();
diff --git a/app/Models/EntryDAOPGSQL.php b/app/Models/EntryDAOPGSQL.php
new file mode 100644
index 000000000..b96a62ebc
--- /dev/null
+++ b/app/Models/EntryDAOPGSQL.php
@@ -0,0 +1,31 @@
+<?php
+
+class FreshRSS_EntryDAOPGSQL extends FreshRSS_EntryDAOSQLite {
+
+ public function sqlHexDecode($x) {
+ return 'decode(' . $x . ", 'hex')";
+ }
+
+ public function sqlHexEncode($x) {
+ return 'encode(' . $x . ", 'hex')";
+ }
+
+ protected function autoUpdateDb($errorInfo) {
+ return false;
+ }
+
+ protected function addColumn($name) {
+ return false;
+ }
+
+ public function size($all = true) {
+ $db = FreshRSS_Context::$system_conf->db;
+ $sql = 'SELECT pg_size_pretty(pg_database_size(?))';
+ $values = array($db['base']);
+ $stm = $this->bd->prepare($sql);
+ $stm->execute($values);
+ $res = $stm->fetchAll(PDO::FETCH_COLUMN, 0);
+ return $res[0];
+ }
+
+}
diff --git a/app/Models/EntryDAOSQLite.php b/app/Models/EntryDAOSQLite.php
index dad34a93d..34e854608 100644
--- a/app/Models/EntryDAOSQLite.php
+++ b/app/Models/EntryDAOSQLite.php
@@ -2,6 +2,10 @@
class FreshRSS_EntryDAOSQLite extends FreshRSS_EntryDAO {
+ public function sqlHexDecode($x) {
+ return $x;
+ }
+
protected function autoUpdateDb($errorInfo) {
if (empty($errorInfo[0]) || $errorInfo[0] == '42S22') { //ER_BAD_FIELD_ERROR
//autoAddColumn
@@ -24,17 +28,21 @@ class FreshRSS_EntryDAOSQLite extends FreshRSS_EntryDAO {
protected function updateCacheUnreads($catId = false, $feedId = false) {
$sql = 'UPDATE `' . $this->prefix . 'feed` '
- . 'SET cache_nbUnreads=('
+ . 'SET `cache_nbUnreads`=('
. 'SELECT COUNT(*) AS nbUnreads FROM `' . $this->prefix . 'entry` e '
- . 'WHERE e.id_feed=`' . $this->prefix . 'feed`.id AND e.is_read=0) '
- . 'WHERE 1';
+ . 'WHERE e.id_feed=`' . $this->prefix . 'feed`.id AND e.is_read=0)';
+ $hasWhere = false;
$values = array();
if ($feedId !== false) {
- $sql .= ' AND id=?';
+ $sql .= $hasWhere ? ' AND' : ' WHERE';
+ $hasWhere = true;
+ $sql .= ' id=?';
$values[] = $feedId;
}
if ($catId !== false) {
- $sql .= ' AND category=?';
+ $sql .= $hasWhere ? ' AND' : ' WHERE';
+ $hasWhere = true;
+ $sql .= ' category=?';
$values[] = $catId;
}
$stm = $this->bd->prepare($sql);
@@ -82,7 +90,7 @@ class FreshRSS_EntryDAOSQLite extends FreshRSS_EntryDAO {
}
$affected = $stm->rowCount();
if ($affected > 0) {
- $sql = 'UPDATE `' . $this->prefix . 'feed` SET cache_nbUnreads=cache_nbUnreads' . ($is_read ? '-' : '+') . '1 '
+ $sql = 'UPDATE `' . $this->prefix . 'feed` SET `cache_nbUnreads`=`cache_nbUnreads`' . ($is_read ? '-' : '+') . '1 '
. 'WHERE id=(SELECT e.id_feed FROM `' . $this->prefix . 'entry` e WHERE e.id=?)';
$values = array($ids);
$stm = $this->bd->prepare($sql);
diff --git a/app/Models/Factory.php b/app/Models/Factory.php
index db09d155d..764987c46 100644
--- a/app/Models/Factory.php
+++ b/app/Models/Factory.php
@@ -4,37 +4,47 @@ class FreshRSS_Factory {
public static function createFeedDao($username = null) {
$conf = Minz_Configuration::get('system');
- if ($conf->db['type'] === 'sqlite') {
- return new FreshRSS_FeedDAOSQLite($username);
- } else {
- return new FreshRSS_FeedDAO($username);
+ switch ($conf->db['type']) {
+ case 'sqlite':
+ return new FreshRSS_FeedDAOSQLite($username);
+ default:
+ return new FreshRSS_FeedDAO($username);
}
}
public static function createEntryDao($username = null) {
$conf = Minz_Configuration::get('system');
- if ($conf->db['type'] === 'sqlite') {
- return new FreshRSS_EntryDAOSQLite($username);
- } else {
- return new FreshRSS_EntryDAO($username);
+ switch ($conf->db['type']) {
+ case 'sqlite':
+ return new FreshRSS_EntryDAOSQLite($username);
+ case 'pgsql':
+ return new FreshRSS_EntryDAOPGSQL($username);
+ default:
+ return new FreshRSS_EntryDAO($username);
}
}
public static function createStatsDAO($username = null) {
$conf = Minz_Configuration::get('system');
- if ($conf->db['type'] === 'sqlite') {
- return new FreshRSS_StatsDAOSQLite($username);
- } else {
- return new FreshRSS_StatsDAO($username);
+ switch ($conf->db['type']) {
+ case 'sqlite':
+ return new FreshRSS_StatsDAOSQLite($username);
+ case 'pgsql':
+ return new FreshRSS_StatsDAOPGSQL($username);
+ default:
+ return new FreshRSS_StatsDAO($username);
}
}
public static function createDatabaseDAO($username = null) {
$conf = Minz_Configuration::get('system');
- if ($conf->db['type'] === 'sqlite') {
- return new FreshRSS_DatabaseDAOSQLite($username);
- } else {
- return new FreshRSS_DatabaseDAO($username);
+ switch ($conf->db['type']) {
+ case 'sqlite':
+ return new FreshRSS_DatabaseDAOSQLite($username);
+ case 'pgsql':
+ return new FreshRSS_DatabaseDAOPGSQL($username);
+ default:
+ return new FreshRSS_DatabaseDAO($username);
}
}
diff --git a/app/Models/Feed.php b/app/Models/Feed.php
index 6104b1e31..97cb1c47e 100644
--- a/app/Models/Feed.php
+++ b/app/Models/Feed.php
@@ -131,13 +131,26 @@ class FreshRSS_Feed extends Minz_Model {
return $this->nbNotRead;
}
public function faviconPrepare() {
- $file = DATA_PATH . '/favicons/' . $this->hash() . '.txt';
- if (!file_exists($file)) {
- $t = $this->website;
- if ($t == '') {
- $t = $this->url;
+ global $favicons_dir;
+ require_once(LIB_PATH . '/favicons.php');
+ $url = $this->website;
+ if ($url == '') {
+ $url = $this->url;
+ }
+ $txt = $favicons_dir . $this->hash() . '.txt';
+ if (!file_exists($txt)) {
+ file_put_contents($txt, $url);
+ }
+ if (FreshRSS_Context::$isCli) {
+ $ico = $favicons_dir . $this->hash() . '.ico';
+ $ico_mtime = @filemtime($ico);
+ $txt_mtime = @filemtime($txt);
+ if ($txt_mtime != false &&
+ ($ico_mtime == false || $ico_mtime < $txt_mtime || ($ico_mtime < time() - (14 * 86400)))) {
+ // no ico file or we should download a new one.
+ $url = file_get_contents($txt);
+ download_favicon($url, $ico) || touch($ico);
}
- file_put_contents($file, $t);
}
}
public static function faviconDelete($hash) {
@@ -216,7 +229,7 @@ class FreshRSS_Feed extends Minz_Model {
$this->nbEntries = intval($value);
}
- public function load($loadDetails = false) {
+ public function load($loadDetails = false, $noCache = false) {
if ($this->url !== null) {
if (CACHE_PATH === false) {
throw new Minz_FileNotExistException(
@@ -268,7 +281,7 @@ class FreshRSS_Feed extends Minz_Model {
$this->_url($clean_url);
}
- if (($mtime === true) || ($mtime > $this->lastUpdate)) {
+ if (($mtime === true) || ($mtime > $this->lastUpdate) || $noCache) {
//Minz_Log::debug('FreshRSS no cache ' . $mtime . ' > ' . $this->lastUpdate . ' for ' . $clean_url);
$this->loadEntries($feed); // et on charge les articles du flux
} else {
@@ -309,11 +322,11 @@ class FreshRSS_Feed extends Minz_Model {
$elinks[$elink] = '1';
$mime = strtolower($enclosure->get_type());
if (strpos($mime, 'image/') === 0) {
- $content .= '<br /><img lazyload="" postpone="" src="' . $elink . '" alt="" />';
+ $content .= '<p class="enclosure"><img src="' . $elink . '" alt="" /></p>';
} elseif (strpos($mime, 'audio/') === 0) {
- $content .= '<br /><audio lazyload="" postpone="" preload="none" src="' . $elink . '" controls="controls" />';
+ $content .= '<p class="enclosure"><audio preload="none" src="' . $elink . '" controls="controls"></audio> <a download="" href="' . $elink . '">💾</a></p>';
} elseif (strpos($mime, 'video/') === 0) {
- $content .= '<br /><video lazyload="" postpone="" preload="none" src="' . $elink . '" controls="controls" />';
+ $content .= '<p class="enclosure"><video preload="none" src="' . $elink . '" controls="controls"></video> <a download="" href="' . $elink . '">💾</a></p>';
} else {
unset($elinks[$elink]);
}
@@ -340,6 +353,10 @@ class FreshRSS_Feed extends Minz_Model {
$this->entries = $entries;
}
+ function cacheModifiedTime() {
+ return @filemtime(CACHE_PATH . '/' . md5($this->url) . '.spc');
+ }
+
function lock() {
$this->lockPath = TMP_PATH . '/' . $this->hash() . '.freshrss.lock';
if (file_exists($this->lockPath) && ((time() - @filemtime($this->lockPath)) > 3600)) {
@@ -460,7 +477,7 @@ class FreshRSS_Feed extends Minz_Model {
CURLOPT_URL => $this->hubUrl,
CURLOPT_FOLLOWLOCATION => true,
CURLOPT_RETURNTRANSFER => true,
- CURLOPT_USERAGENT => _t('gen.freshrss') . '/' . FRESHRSS_VERSION . ' (' . PHP_OS . '; ' . FRESHRSS_WEBSITE . ')',
+ CURLOPT_USERAGENT => 'FreshRSS/' . FRESHRSS_VERSION . ' (' . PHP_OS . '; ' . FRESHRSS_WEBSITE . ')',
CURLOPT_POSTFIELDS => 'hub.verify=sync'
. '&hub.mode=' . ($state ? 'subscribe' : 'unsubscribe')
. '&hub.topic=' . urlencode($this->selfUrl)
diff --git a/app/Models/FeedDAO.php b/app/Models/FeedDAO.php
index 475d39286..68398efd5 100644
--- a/app/Models/FeedDAO.php
+++ b/app/Models/FeedDAO.php
@@ -2,9 +2,12 @@
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)';
+ $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);
+ $valuesTmp['url'] = safe_ascii($valuesTmp['url']);
+ $valuesTmp['website'] = safe_ascii($valuesTmp['website']);
+
$values = array(
substr($valuesTmp['url'], 0, 511),
$valuesTmp['category'],
@@ -16,7 +19,7 @@ class FreshRSS_FeedDAO extends Minz_ModelPdo implements FreshRSS_Searchable {
);
if ($stm && $stm->execute($values)) {
- return $this->bd->lastInsertId();
+ return $this->bd->lastInsertId('"' . $this->prefix . 'feed_id_seq"');
} else {
$info = $stm == null ? array(2 => 'syntax error') : $stm->errorInfo();
Minz_Log::error('SQL error addFeed: ' . $info[2]);
@@ -55,6 +58,13 @@ class FreshRSS_FeedDAO extends Minz_ModelPdo implements FreshRSS_Searchable {
}
public function updateFeed($id, $valuesTmp) {
+ if (isset($valuesTmp['url'])) {
+ $valuesTmp['url'] = safe_ascii($valuesTmp['url']);
+ }
+ if (isset($valuesTmp['website'])) {
+ $valuesTmp['website'] = safe_ascii($valuesTmp['website']);
+ }
+
$set = '';
foreach ($valuesTmp as $key => $v) {
$set .= $key . '=?, ';
@@ -82,22 +92,26 @@ class FreshRSS_FeedDAO extends Minz_ModelPdo implements FreshRSS_Searchable {
}
}
- public function updateLastUpdate($id, $inError = 0, $updateCache = true) {
+ public function updateLastUpdate($id, $inError = false, $updateCache = true, $mtime = 0) {
if ($updateCache) {
$sql = 'UPDATE `' . $this->prefix . 'feed` ' //2 sub-requests with FOREIGN KEY(e.id_feed), INDEX(e.is_read) faster than 1 request with GROUP BY or CASE
- . 'SET cache_nbEntries=(SELECT COUNT(e1.id) FROM `' . $this->prefix . 'entry` e1 WHERE e1.id_feed=`' . $this->prefix . 'feed`.id),'
- . 'cache_nbUnreads=(SELECT COUNT(e2.id) FROM `' . $this->prefix . 'entry` e2 WHERE e2.id_feed=`' . $this->prefix . 'feed`.id AND e2.is_read=0),'
- . 'lastUpdate=?, error=? '
+ . 'SET `cache_nbEntries`=(SELECT COUNT(e1.id) FROM `' . $this->prefix . 'entry` e1 WHERE e1.id_feed=`' . $this->prefix . 'feed`.id),'
+ . '`cache_nbUnreads`=(SELECT COUNT(e2.id) FROM `' . $this->prefix . 'entry` e2 WHERE e2.id_feed=`' . $this->prefix . 'feed`.id AND e2.is_read=0),'
+ . '`lastUpdate`=?, error=? '
. 'WHERE id=?';
} else {
$sql = 'UPDATE `' . $this->prefix . 'feed` '
- . 'SET lastUpdate=?, error=? '
+ . 'SET `lastUpdate`=?, error=? '
. 'WHERE id=?';
}
+ if ($mtime <= 0) {
+ $mtime = time();
+ }
+
$values = array(
- time(),
- $inError,
+ $mtime,
+ $inError ? 1 : 0,
$id,
);
@@ -198,6 +212,13 @@ class FreshRSS_FeedDAO extends Minz_ModelPdo implements FreshRSS_Searchable {
}
}
+ public function listFeedsIds() {
+ $sql = 'SELECT id FROM `' . $this->prefix . 'feed`';
+ $stm = $this->bd->prepare($sql);
+ $stm->execute();
+ return $stm->fetchAll(PDO::FETCH_COLUMN, 0);
+ }
+
public function listFeeds() {
$sql = 'SELECT * FROM `' . $this->prefix . 'feed` ORDER BY name';
$stm = $this->bd->prepare($sql);
@@ -222,14 +243,14 @@ class FreshRSS_FeedDAO extends Minz_ModelPdo implements FreshRSS_Searchable {
return $feedCategoryNames;
}
+ /**
+ * Use $defaultCacheDuration == -1 to return all feeds, without filtering them by TTL.
+ */
public function listFeedsOrderUpdate($defaultCacheDuration = 3600) {
- if ($defaultCacheDuration < 0) {
- $defaultCacheDuration = 2147483647;
- }
- $sql = 'SELECT id, url, name, website, lastUpdate, pathEntries, httpAuth, keep_history, ttl '
+ $sql = 'SELECT id, url, name, website, `lastUpdate`, `pathEntries`, `httpAuth`, keep_history, ttl '
. 'FROM `' . $this->prefix . 'feed` '
- . 'WHERE ttl <> -1 AND lastUpdate < (' . (time() + 60) . '-(CASE WHEN ttl=-2 THEN ' . intval($defaultCacheDuration) . ' ELSE ttl END)) '
- . 'ORDER BY lastUpdate';
+ . ($defaultCacheDuration < 0 ? '' : 'WHERE ttl <> -1 AND `lastUpdate` < (' . (time() + 60) . '-(CASE WHEN ttl=-2 THEN ' . intval($defaultCacheDuration) . ' ELSE ttl END)) ')
+ . 'ORDER BY `lastUpdate`';
$stm = $this->bd->prepare($sql);
if (!($stm && $stm->execute())) {
$sql2 = 'ALTER TABLE `' . $this->prefix . 'feed` ADD COLUMN ttl INT NOT NULL DEFAULT -2'; //v0.7.3
@@ -282,7 +303,7 @@ class FreshRSS_FeedDAO extends Minz_ModelPdo implements FreshRSS_Searchable {
. 'FROM `' . $this->prefix . 'entry` e '
. 'GROUP BY e.id_feed'
. ') x ON x.id_feed=f.id '
- . 'SET f.cache_nbEntries=x.nbEntries, f.cache_nbUnreads=x.nbUnreads';
+ . 'SET f.`cache_nbEntries`=x.nbEntries, f.`cache_nbUnreads`=x.nbUnreads';
$stm = $this->bd->prepare($sql);
if ($stm && $stm->execute()) {
@@ -308,7 +329,7 @@ class FreshRSS_FeedDAO extends Minz_ModelPdo implements FreshRSS_Searchable {
$affected = $stm->rowCount();
$sql = 'UPDATE `' . $this->prefix . 'feed` '
- . 'SET cache_nbEntries=0, cache_nbUnreads=0 WHERE id=?';
+ . 'SET `cache_nbEntries`=0, `cache_nbUnreads`=0 WHERE id=?';
$values = array($id);
$stm = $this->bd->prepare($sql);
if (!($stm && $stm->execute($values))) {
@@ -326,7 +347,7 @@ class FreshRSS_FeedDAO extends Minz_ModelPdo implements FreshRSS_Searchable {
$sql = 'DELETE FROM `' . $this->prefix . 'entry` '
. 'WHERE id_feed=:id_feed AND id<=:id_max '
. 'AND is_favorite=0 ' //Do not remove favourites
- . 'AND lastSeen < (SELECT maxLastSeen FROM (SELECT (MAX(e3.lastSeen)-99) AS maxLastSeen FROM `' . $this->prefix . 'entry` e3 WHERE e3.id_feed=:id_feed) recent) ' //Do not remove the most newly seen articles, plus a few seconds of tolerance
+ . 'AND `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);
diff --git a/app/Models/FeedDAOSQLite.php b/app/Models/FeedDAOSQLite.php
index 7599fda53..440ae74da 100644
--- a/app/Models/FeedDAOSQLite.php
+++ b/app/Models/FeedDAOSQLite.php
@@ -4,8 +4,8 @@ class FreshRSS_FeedDAOSQLite extends FreshRSS_FeedDAO {
public function updateCachedValues() { //For one single feed, call updateLastUpdate($id)
$sql = 'UPDATE `' . $this->prefix . 'feed` '
- . 'SET cache_nbEntries=(SELECT COUNT(e1.id) FROM `' . $this->prefix . 'entry` e1 WHERE e1.id_feed=`' . $this->prefix . 'feed`.id),'
- . 'cache_nbUnreads=(SELECT COUNT(e2.id) FROM `' . $this->prefix . 'entry` e2 WHERE e2.id_feed=`' . $this->prefix . 'feed`.id AND e2.is_read=0)';
+ . 'SET `cache_nbEntries`=(SELECT COUNT(e1.id) FROM `' . $this->prefix . 'entry` e1 WHERE e1.id_feed=`' . $this->prefix . 'feed`.id),'
+ . '`cache_nbUnreads`=(SELECT COUNT(e2.id) FROM `' . $this->prefix . 'entry` e2 WHERE e2.id_feed=`' . $this->prefix . 'feed`.id AND e2.is_read=0)';
$stm = $this->bd->prepare($sql);
if ($stm && $stm->execute()) {
return $stm->rowCount();
diff --git a/app/Models/StatsDAO.php b/app/Models/StatsDAO.php
index 4f83ff577..2ce4f2944 100644
--- a/app/Models/StatsDAO.php
+++ b/app/Models/StatsDAO.php
@@ -4,6 +4,10 @@ class FreshRSS_StatsDAO extends Minz_ModelPdo {
const ENTRY_COUNT_PERIOD = 30;
+ protected function sqlFloor($s) {
+ return "FLOOR($s)";
+ }
+
/**
* Calculates entry repartition for all feeds and for main stream.
*
@@ -37,12 +41,12 @@ class FreshRSS_StatsDAO extends Minz_ModelPdo {
$filter .= "AND e.id_feed = {$feed}";
}
$sql = <<<SQL
-SELECT COUNT(1) AS `total`,
-COUNT(1) - SUM(e.is_read) AS `unread`,
-SUM(e.is_read) AS `read`,
-SUM(e.is_favorite) AS `favorite`
-FROM {$this->prefix}entry AS e
-, {$this->prefix}feed AS f
+SELECT COUNT(1) AS total,
+COUNT(1) - SUM(e.is_read) AS count_unreads,
+SUM(e.is_read) AS count_reads,
+SUM(e.is_favorite) AS count_favorites
+FROM `{$this->prefix}entry` AS e
+, `{$this->prefix}feed` AS f
WHERE e.id_feed = f.id
{$filter}
SQL;
@@ -61,14 +65,16 @@ SQL;
*/
public function calculateEntryCount() {
$count = $this->initEntryCountArray();
- $period = self::ENTRY_COUNT_PERIOD;
+ $midnight = mktime(0, 0, 0);
+ $oldest = $midnight - (self::ENTRY_COUNT_PERIOD * 86400);
// Get stats per day for the last 30 days
+ $sqlDay = $this->sqlFloor("(date - $midnight) / 86400");
$sql = <<<SQL
-SELECT DATEDIFF(FROM_UNIXTIME(e.date), NOW()) AS day,
-COUNT(1) AS count
-FROM {$this->prefix}entry AS e
-WHERE FROM_UNIXTIME(e.date, '%Y%m%d') BETWEEN DATE_FORMAT(DATE_ADD(NOW(), INTERVAL -{$period} DAY), '%Y%m%d') AND DATE_FORMAT(DATE_ADD(NOW(), INTERVAL -1 DAY), '%Y%m%d')
+SELECT {$sqlDay} AS day,
+COUNT(*) as count
+FROM `{$this->prefix}entry`
+WHERE date >= {$oldest} AND date < {$midnight}
GROUP BY day
ORDER BY day ASC
SQL;
@@ -80,28 +86,7 @@ SQL;
$count[$value['day']] = (int) $value['count'];
}
- return $this->convertToSerie($count);
- }
-
- /**
- * Calculates entry average per day on a 30 days period.
- *
- * @return integer
- */
- public function calculateEntryAverage() {
- $period = self::ENTRY_COUNT_PERIOD;
-
- // Get stats per day for the last 30 days
- $sql = <<<SQL
-SELECT COUNT(1) / {$period} AS average
-FROM {$this->prefix}entry AS e
-WHERE FROM_UNIXTIME(e.date, '%Y%m%d') BETWEEN DATE_FORMAT(DATE_ADD(NOW(), INTERVAL -{$period} DAY), '%Y%m%d') AND DATE_FORMAT(DATE_ADD(NOW(), INTERVAL -1 DAY), '%Y%m%d')
-SQL;
- $stm = $this->bd->prepare($sql);
- $stm->execute();
- $res = $stm->fetch(PDO::FETCH_NAMED);
-
- return round($res['average'], 2);
+ return $count;
}
/**
@@ -158,7 +143,7 @@ SQL;
$sql = <<<SQL
SELECT DATE_FORMAT(FROM_UNIXTIME(e.date), '{$period}') AS period
, COUNT(1) AS count
-FROM {$this->prefix}entry AS e
+FROM `{$this->prefix}entry` AS e
{$restrict}
GROUP BY period
ORDER BY period ASC
@@ -173,7 +158,7 @@ SQL;
$repartition[(int) $value['period']] = (int) $value['count'];
}
- return $this->convertToSerie($repartition);
+ return $repartition;
}
/**
@@ -222,7 +207,7 @@ SQL;
SELECT COUNT(1) AS count
, MIN(date) AS date_min
, MAX(date) AS date_max
-FROM {$this->prefix}entry AS e
+FROM `{$this->prefix}entry` AS e
{$restrict}
SQL;
$stm = $this->bd->prepare($sql);
@@ -266,8 +251,8 @@ SQL;
$sql = <<<SQL
SELECT c.name AS label
, COUNT(f.id) AS data
-FROM {$this->prefix}category AS c,
-{$this->prefix}feed AS f
+FROM `{$this->prefix}category` AS c,
+`{$this->prefix}feed` AS f
WHERE c.id = f.category
GROUP BY label
ORDER BY data DESC
@@ -276,7 +261,7 @@ SQL;
$stm->execute();
$res = $stm->fetchAll(PDO::FETCH_ASSOC);
- return $this->convertToPieSerie($res);
+ return $res;
}
/**
@@ -289,9 +274,9 @@ SQL;
$sql = <<<SQL
SELECT c.name AS label
, COUNT(e.id) AS data
-FROM {$this->prefix}category AS c,
-{$this->prefix}feed AS f,
-{$this->prefix}entry AS e
+FROM `{$this->prefix}category` AS c,
+`{$this->prefix}feed` AS f,
+`{$this->prefix}entry` AS e
WHERE c.id = f.category
AND f.id = e.id_feed
GROUP BY label
@@ -301,7 +286,7 @@ SQL;
$stm->execute();
$res = $stm->fetchAll(PDO::FETCH_ASSOC);
- return $this->convertToPieSerie($res);
+ return $res;
}
/**
@@ -315,9 +300,9 @@ SELECT f.id AS id
, MAX(f.name) AS name
, MAX(c.name) AS category
, COUNT(e.id) AS count
-FROM {$this->prefix}category AS c,
-{$this->prefix}feed AS f,
-{$this->prefix}entry AS e
+FROM `{$this->prefix}category` AS c,
+`{$this->prefix}feed` AS f,
+`{$this->prefix}entry` AS e
WHERE c.id = f.category
AND f.id = e.id_feed
GROUP BY f.id
@@ -340,8 +325,8 @@ SELECT MAX(f.id) as id
, MAX(f.name) AS name
, MAX(date) AS last_date
, COUNT(*) AS nb_articles
-FROM {$this->prefix}feed AS f,
-{$this->prefix}entry AS e
+FROM `{$this->prefix}feed` AS f,
+`{$this->prefix}entry` AS e
WHERE f.id = e.id_feed
GROUP BY f.id
ORDER BY name
@@ -351,27 +336,6 @@ SQL;
return $stm->fetchAll(PDO::FETCH_ASSOC);
}
- protected function convertToSerie($data) {
- $serie = array();
-
- foreach ($data as $key => $value) {
- $serie[] = array($key, $value);
- }
-
- return $serie;
- }
-
- protected function convertToPieSerie($data) {
- $serie = array();
-
- foreach ($data as $value) {
- $value['data'] = array(array(0, (int) $value['data']));
- $serie[] = $value;
- }
-
- return $serie;
- }
-
/**
* Gets days ready for graphs
*
diff --git a/app/Models/StatsDAOPGSQL.php b/app/Models/StatsDAOPGSQL.php
new file mode 100644
index 000000000..1effbb64b
--- /dev/null
+++ b/app/Models/StatsDAOPGSQL.php
@@ -0,0 +1,67 @@
+<?php
+
+class FreshRSS_StatsDAOPGSQL extends FreshRSS_StatsDAO {
+
+ /**
+ * Calculates the number of article per hour of the day per feed
+ *
+ * @param integer $feed id
+ * @return string
+ */
+ public function calculateEntryRepartitionPerFeedPerHour($feed = null) {
+ return $this->calculateEntryRepartitionPerFeedPerPeriod('hour', $feed);
+ }
+
+ /**
+ * Calculates the number of article per day of week per feed
+ *
+ * @param integer $feed id
+ * @return string
+ */
+ public function calculateEntryRepartitionPerFeedPerDayOfWeek($feed = null) {
+ return $this->calculateEntryRepartitionPerFeedPerPeriod('day', $feed);
+ }
+
+ /**
+ * Calculates the number of article per month per feed
+ *
+ * @param integer $feed
+ * @return string
+ */
+ public function calculateEntryRepartitionPerFeedPerMonth($feed = null) {
+ return $this->calculateEntryRepartitionPerFeedPerPeriod('month', $feed);
+ }
+
+ /**
+ * Calculates the number of article per period per feed
+ *
+ * @param string $period format string to use for grouping
+ * @param integer $feed id
+ * @return string
+ */
+ protected function calculateEntryRepartitionPerFeedPerPeriod($period, $feed = null) {
+ $restrict = '';
+ if ($feed) {
+ $restrict = "WHERE e.id_feed = {$feed}";
+ }
+ $sql = <<<SQL
+SELECT extract( {$period} from to_timestamp(e.date)) AS period
+, COUNT(1) AS count
+FROM "{$this->prefix}entry" AS e
+{$restrict}
+GROUP BY period
+ORDER BY period ASC
+SQL;
+
+ $stm = $this->bd->prepare($sql);
+ $stm->execute();
+ $res = $stm->fetchAll(PDO::FETCH_NAMED);
+
+ foreach ($res as $value) {
+ $repartition[(int) $value['period']] = (int) $value['count'];
+ }
+
+ return $repartition;
+ }
+
+}
diff --git a/app/Models/StatsDAOSQLite.php b/app/Models/StatsDAOSQLite.php
index 9bfe8b20a..6cfc20463 100644
--- a/app/Models/StatsDAOSQLite.php
+++ b/app/Models/StatsDAOSQLite.php
@@ -2,59 +2,8 @@
class FreshRSS_StatsDAOSQLite extends FreshRSS_StatsDAO {
- /**
- * Calculates entry count per day on a 30 days period.
- * Returns the result as a JSON object.
- *
- * @return JSON object
- */
- public function calculateEntryCount() {
- $count = $this->initEntryCountArray();
- $period = parent::ENTRY_COUNT_PERIOD;
-
- // Get stats per day for the last 30 days
- $sql = <<<SQL
-SELECT round(julianday(e.date, 'unixepoch') - julianday('now')) AS day,
-COUNT(1) AS count
-FROM {$this->prefix}entry AS e
-WHERE strftime('%Y%m%d', e.date, 'unixepoch')
- BETWEEN strftime('%Y%m%d', 'now', '-{$period} days')
- AND strftime('%Y%m%d', 'now', '-1 day')
-GROUP BY day
-ORDER BY day ASC
-SQL;
- $stm = $this->bd->prepare($sql);
- $stm->execute();
- $res = $stm->fetchAll(PDO::FETCH_ASSOC);
-
- foreach ($res as $value) {
- $count[(int) $value['day']] = (int) $value['count'];
- }
-
- return $this->convertToSerie($count);
- }
-
- /**
- * Calculates entry average per day on a 30 days period.
- *
- * @return integer
- */
- public function calculateEntryAverage() {
- $period = self::ENTRY_COUNT_PERIOD;
-
- // Get stats per day for the last 30 days
- $sql = <<<SQL
-SELECT COUNT(1) / {$period} AS average
-FROM {$this->prefix}entry AS e
-WHERE strftime('%Y%m%d', e.date, 'unixepoch')
- BETWEEN strftime('%Y%m%d', 'now', '-{$period} days')
- AND strftime('%Y%m%d', 'now', '-1 day')
-SQL;
- $stm = $this->bd->prepare($sql);
- $stm->execute();
- $res = $stm->fetch(PDO::FETCH_NAMED);
-
- return round($res['average'], 2);
+ protected function sqlFloor($s) {
+ return "CAST(($s) AS INT)";
}
protected function calculateEntryRepartitionPerFeedPerPeriod($period, $feed = null) {
@@ -66,7 +15,7 @@ SQL;
$sql = <<<SQL
SELECT strftime('{$period}', e.date, 'unixepoch') AS period
, COUNT(1) AS count
-FROM {$this->prefix}entry AS e
+FROM `{$this->prefix}entry` AS e
{$restrict}
GROUP BY period
ORDER BY period ASC
@@ -81,7 +30,7 @@ SQL;
$repartition[(int) $value['period']] = (int) $value['count'];
}
- return $this->convertToSerie($repartition);
+ return $repartition;
}
}
diff --git a/app/Models/UserDAO.php b/app/Models/UserDAO.php
index b55766ab4..a95ee6bc4 100644
--- a/app/Models/UserDAO.php
+++ b/app/Models/UserDAO.php
@@ -1,34 +1,60 @@
<?php
class FreshRSS_UserDAO extends Minz_ModelPdo {
- public function createUser($username) {
+ public function createUser($username, $new_user_language, $insertDefaultFeeds = true) {
$db = FreshRSS_Context::$system_conf->db;
require_once(APP_PATH . '/SQL/install.sql.' . $db['type'] . '.php');
$userPDO = new Minz_ModelPdo($username);
- $ok = false;
- if (defined('SQL_CREATE_TABLES')) { //E.g. MySQL
- $sql = sprintf(SQL_CREATE_TABLES, $db['prefix'] . $username . '_', _t('gen.short.default_category'));
- $stm = $userPDO->bd->prepare($sql);
- $ok = $stm && $stm->execute();
- } else { //E.g. SQLite
- global $SQL_CREATE_TABLES;
- if (is_array($SQL_CREATE_TABLES)) {
- $ok = true;
- foreach ($SQL_CREATE_TABLES as $instruction) {
- $sql = sprintf($instruction, '', _t('gen.short.default_category'));
+ $currentLanguage = Minz_Translate::language();
+
+ try {
+ Minz_Translate::reset($new_user_language);
+ $ok = false;
+ $bd_prefix_user = $db['prefix'] . $username . '_';
+ if (defined('SQL_CREATE_TABLES')) { //E.g. MySQL
+ $sql = sprintf(SQL_CREATE_TABLES, $bd_prefix_user, _t('gen.short.default_category'));
+ $stm = $userPDO->bd->prepare($sql);
+ $ok = $stm && $stm->execute();
+ } else { //E.g. SQLite
+ global $SQL_CREATE_TABLES;
+ if (is_array($SQL_CREATE_TABLES)) {
+ $ok = true;
+ foreach ($SQL_CREATE_TABLES as $instruction) {
+ $sql = sprintf($instruction, $bd_prefix_user, _t('gen.short.default_category'));
+ $stm = $userPDO->bd->prepare($sql);
+ $ok &= ($stm && $stm->execute());
+ }
+ }
+ }
+ if ($insertDefaultFeeds) {
+ if (defined('SQL_INSERT_FEEDS')) { //E.g. MySQL
+ $sql = sprintf(SQL_INSERT_FEEDS, $bd_prefix_user);
$stm = $userPDO->bd->prepare($sql);
- $ok &= ($stm && $stm->execute());
+ $ok &= $stm && $stm->execute();
+ } else { //E.g. SQLite
+ global $SQL_INSERT_FEEDS;
+ if (is_array($SQL_INSERT_FEEDS)) {
+ foreach ($SQL_INSERT_FEEDS as $instruction) {
+ $sql = sprintf($instruction, $bd_prefix_user);
+ $stm = $userPDO->bd->prepare($sql);
+ $ok &= ($stm && $stm->execute());
+ }
+ }
}
}
+ } catch (Exception $e) {
+ Minz_Log::error('Error while creating user: ' . $e->getMessage());
}
+ Minz_Translate::reset($currentLanguage);
+
if ($ok) {
return true;
} else {
$info = empty($stm) ? array(2 => 'syntax error') : $stm->errorInfo();
- Minz_Log::error('SQL error : ' . $info[2]);
+ Minz_Log::error('SQL error: ' . $info[2]);
return false;
}
}
diff --git a/app/SQL/install.sql.mysql.php b/app/SQL/install.sql.mysql.php
index c78839ef7..a454829d5 100644
--- a/app/SQL/install.sql.mysql.php
+++ b/app/SQL/install.sql.mysql.php
@@ -1,4 +1,6 @@
<?php
+define('SQL_CREATE_DB', 'CREATE DATABASE IF NOT EXISTS %1$s DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;');
+
define('SQL_CREATE_TABLES', '
CREATE TABLE IF NOT EXISTS `%1$scategory` (
`id` SMALLINT NOT NULL AUTO_INCREMENT, -- v0.7
@@ -57,11 +59,14 @@ CREATE TABLE IF NOT EXISTS `%1$sentry` (
ENGINE = INNODB;
INSERT IGNORE INTO `%1$scategory` (id, name) VALUES(1, "%2$s");
+');
+
+define('SQL_INSERT_FEEDS', '
INSERT IGNORE INTO `%1$sfeed` (url, category, name, website, description, ttl) VALUES("http://freshrss.org/feeds/all.atom.xml", 1, "FreshRSS.org", "http://freshrss.org/", "FreshRSS, a free, self-hostable aggregator…", 86400);
INSERT IGNORE INTO `%1$sfeed` (url, category, name, website, description, ttl) VALUES("https://github.com/FreshRSS/FreshRSS/releases.atom", 1, "FreshRSS @ GitHub", "https://github.com/FreshRSS/FreshRSS/", "FreshRSS releases @ GitHub", 86400);
');
-define('SQL_DROP_TABLES', 'DROP TABLES %1$sentry, %1$sfeed, %1$scategory');
+define('SQL_DROP_TABLES', 'DROP TABLE IF EXISTS `%1$sentry`, `%1$sfeed`, `%1$scategory`');
define('SQL_UPDATE_UTF8MB4', '
ALTER DATABASE `%2$s` CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
diff --git a/app/SQL/install.sql.pgsql.php b/app/SQL/install.sql.pgsql.php
new file mode 100644
index 000000000..9f4240b98
--- /dev/null
+++ b/app/SQL/install.sql.pgsql.php
@@ -0,0 +1,63 @@
+<?php
+define('SQL_CREATE_DB', 'CREATE DATABASE %1$s ENCODING \'UTF8\';');
+
+global $SQL_CREATE_TABLES;
+$SQL_CREATE_TABLES = array(
+'CREATE TABLE IF NOT EXISTS "%1$scategory" (
+ "id" SERIAL PRIMARY KEY,
+ "name" VARCHAR(255) UNIQUE NOT NULL
+);',
+
+'CREATE TABLE IF NOT EXISTS "%1$sfeed" (
+ "id" SERIAL PRIMARY KEY,
+ "url" varchar(511) UNIQUE NOT NULL,
+ "category" SMALLINT DEFAULT 0,
+ "name" VARCHAR(255) NOT NULL,
+ "website" VARCHAR(255),
+ "description" text,
+ "lastUpdate" INT DEFAULT 0,
+ "priority" SMALLINT NOT NULL DEFAULT 10,
+ "pathEntries" VARCHAR(511) DEFAULT NULL,
+ "httpAuth" VARCHAR(511) DEFAULT NULL,
+ "error" smallint DEFAULT 0,
+ "keep_history" INT NOT NULL DEFAULT -2,
+ "ttl" INT NOT NULL DEFAULT -2,
+ "cache_nbEntries" INT DEFAULT 0,
+ "cache_nbUnreads" INT DEFAULT 0,
+ FOREIGN KEY ("category") REFERENCES "%1$scategory" ("id") ON DELETE SET NULL ON UPDATE CASCADE
+);',
+'CREATE INDEX %1$sname_index ON "%1$sfeed" ("name");',
+'CREATE INDEX %1$spriority_index ON "%1$sfeed" ("priority");',
+'CREATE INDEX %1$skeep_history_index ON "%1$sfeed" ("keep_history");',
+
+'CREATE TABLE IF NOT EXISTS "%1$sentry" (
+ "id" BIGINT NOT NULL PRIMARY KEY,
+ "guid" VARCHAR(760) UNIQUE NOT NULL,
+ "title" VARCHAR(255) NOT NULL,
+ "author" VARCHAR(255),
+ "content" TEXT,
+ "link" VARCHAR(1023) NOT NULL,
+ "date" INT,
+ "lastSeen" INT DEFAULT 0,
+ "hash" BYTEA,
+ "is_read" SMALLINT NOT NULL DEFAULT 0,
+ "is_favorite" SMALLINT NOT NULL DEFAULT 0,
+ "id_feed" SMALLINT,
+ "tags" VARCHAR(1023),
+ FOREIGN KEY ("id_feed") REFERENCES "%1$sfeed" ("id") ON DELETE CASCADE ON UPDATE CASCADE,
+ UNIQUE ("id_feed","guid")
+);',
+'CREATE INDEX %1$sis_favorite_index ON "%1$sentry" ("is_favorite");',
+'CREATE INDEX %1$sis_read_index ON "%1$sentry" ("is_read");',
+'CREATE INDEX %1$sentry_lastSeen_index ON "%1$sentry" ("lastSeen");',
+
+'INSERT INTO "%1$scategory" (name) SELECT \'%2$s\' WHERE NOT EXISTS (SELECT id FROM "%1$scategory" WHERE id = 1);',
+);
+
+global $SQL_INSERT_FEEDS;
+$SQL_INSERT_FEEDS = array(
+'INSERT INTO "%1$sfeed" (url, category, name, website, description, ttl) SELECT \'http://freshrss.org/feeds/all.atom.xml\', 1, \'FreshRSS.org\', \'http://freshrss.org/\', \'FreshRSS, a free, self-hostable aggregator…\', 86400 WHERE NOT EXISTS (SELECT id FROM "%1$sfeed" WHERE url = \'http://freshrss.org/feeds/all.atom.xml\');',
+'INSERT INTO "%1$sfeed" (url, category, name, website, description, ttl) SELECT \'https://github.com/FreshRSS/FreshRSS/releases.atom\', 1, \'FreshRSS @ GitHub\', \'https://github.com/FreshRSS/FreshRSS/\', \'FreshRSS releases @ GitHub\', 86400 WHERE NOT EXISTS (SELECT id FROM "%1$sfeed" WHERE url = \'https://github.com/FreshRSS/FreshRSS/releases.atom\');',
+);
+
+define('SQL_DROP_TABLES', 'DROP TABLE IF EXISTS "%1$sentry", "%1$sfeed", "%1$scategory"');
diff --git a/app/SQL/install.sql.sqlite.php b/app/SQL/install.sql.sqlite.php
index 87d5cf286..68d93ba92 100644
--- a/app/SQL/install.sql.sqlite.php
+++ b/app/SQL/install.sql.sqlite.php
@@ -1,16 +1,16 @@
<?php
global $SQL_CREATE_TABLES;
$SQL_CREATE_TABLES = array(
-'CREATE TABLE IF NOT EXISTS `%1$scategory` (
+'CREATE TABLE IF NOT EXISTS `category` (
`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL,
`name` varchar(255) NOT NULL,
UNIQUE (`name`)
);',
-'CREATE TABLE IF NOT EXISTS `%1$sfeed` (
+'CREATE TABLE IF NOT EXISTS `feed` (
`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL,
`url` varchar(511) NOT NULL,
- `%1$scategory` SMALLINT DEFAULT 0,
+ `category` SMALLINT DEFAULT 0,
`name` varchar(255) NOT NULL,
`website` varchar(255),
`description` text,
@@ -23,15 +23,15 @@ $SQL_CREATE_TABLES = array(
`ttl` INT NOT NULL DEFAULT -2,
`cache_nbEntries` int DEFAULT 0,
`cache_nbUnreads` int DEFAULT 0,
- FOREIGN KEY (`%1$scategory`) REFERENCES `%1$scategory`(`id`) ON DELETE SET NULL ON UPDATE CASCADE,
+ FOREIGN KEY (`category`) REFERENCES `category`(`id`) ON DELETE SET NULL ON UPDATE CASCADE,
UNIQUE (`url`)
);',
-'CREATE INDEX IF NOT EXISTS feed_name_index ON `%1$sfeed`(`name`);',
-'CREATE INDEX IF NOT EXISTS feed_priority_index ON `%1$sfeed`(`priority`);',
-'CREATE INDEX IF NOT EXISTS feed_keep_history_index ON `%1$sfeed`(`keep_history`);',
+'CREATE INDEX IF NOT EXISTS feed_name_index ON `feed`(`name`);',
+'CREATE INDEX IF NOT EXISTS feed_priority_index ON `feed`(`priority`);',
+'CREATE INDEX IF NOT EXISTS feed_keep_history_index ON `feed`(`keep_history`);',
-'CREATE TABLE IF NOT EXISTS `%1$sentry` (
+'CREATE TABLE IF NOT EXISTS `entry` (
`id` bigint NOT NULL,
`guid` varchar(760) NOT NULL,
`title` varchar(255) NOT NULL,
@@ -46,17 +46,21 @@ $SQL_CREATE_TABLES = array(
`id_feed` SMALLINT,
`tags` varchar(1023),
PRIMARY KEY (`id`),
- FOREIGN KEY (`id_feed`) REFERENCES `%1$sfeed`(`id`) ON DELETE CASCADE ON UPDATE CASCADE,
+ FOREIGN KEY (`id_feed`) REFERENCES `feed`(`id`) ON DELETE CASCADE ON UPDATE CASCADE,
UNIQUE (`id_feed`,`guid`)
);',
-'CREATE INDEX IF NOT EXISTS entry_is_favorite_index ON `%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.1.1
+'CREATE INDEX IF NOT EXISTS entry_is_favorite_index ON `entry`(`is_favorite`);',
+'CREATE INDEX IF NOT EXISTS entry_is_read_index ON `entry`(`is_read`);',
+'CREATE INDEX IF NOT EXISTS entry_lastSeen_index ON `entry`(`lastSeen`);', //v1.1.1
-'INSERT OR IGNORE INTO `%1$scategory` (id, name) VALUES(1, "%2$s");',
-'INSERT OR IGNORE INTO `%1$sfeed` (url, category, name, website, description, ttl) VALUES("http://freshrss.org/feeds/all.atom.xml", 1, "FreshRSS.org", "http://freshrss.org/", "FreshRSS, a free, self-hostable aggregator…", 86400);',
-'INSERT OR IGNORE INTO `%1$sfeed` (url, category, name, website, description, ttl) VALUES("https://github.com/FreshRSS/FreshRSS/releases.atom", 1, "FreshRSS releases", "https://github.com/FreshRSS/FreshRSS/", "FreshRSS releases @ GitHub", 86400);',
+'INSERT OR IGNORE INTO `category` (id, name) VALUES(1, "%2$s");',
);
-define('SQL_DROP_TABLES', 'DROP TABLES %1$sentry, %1$sfeed, %1$scategory');
+global $SQL_INSERT_FEEDS;
+$SQL_INSERT_FEEDS = array(
+'INSERT OR IGNORE INTO `feed` (url, category, name, website, description, ttl) VALUES("http://freshrss.org/feeds/all.atom.xml", 1, "FreshRSS.org", "http://freshrss.org/", "FreshRSS, a free, self-hostable aggregator…", 86400);',
+'INSERT OR IGNORE INTO `feed` (url, category, name, website, description, ttl) VALUES("https://github.com/FreshRSS/FreshRSS/releases.atom", 1, "FreshRSS releases", "https://github.com/FreshRSS/FreshRSS/", "FreshRSS releases @ GitHub", 86400);',
+);
+
+define('SQL_DROP_TABLES', 'DROP TABLE IF EXISTS entry, feed, category');
diff --git a/app/actualize_script.php b/app/actualize_script.php
index fc4f9bfbb..deaa1bf7c 100755
--- a/app/actualize_script.php
+++ b/app/actualize_script.php
@@ -28,12 +28,13 @@ $app = new FreshRSS();
$system_conf = Minz_Configuration::get('system');
$system_conf->auth_type = 'none'; // avoid necessity to be logged in (not saved!)
+FreshRSS_Context::$isCli = true;
// Create the list of users to actualize.
// Users are processed in a random order but always start with admin
$users = listUsers();
shuffle($users);
-if ($system_conf->default_user !== ''){
+if ($system_conf->default_user !== '') {
array_unshift($users, $system_conf->default_user);
$users = array_unique($users);
}
diff --git a/app/i18n/cz/admin.php b/app/i18n/cz/admin.php
index 881c02fc6..84bc58a17 100644
--- a/app/i18n/cz/admin.php
+++ b/app/i18n/cz/admin.php
@@ -33,7 +33,7 @@ return array(
'ok' => 'Máte požadovanou knihovnu pro ověřování znaků (ctype).',
),
'curl' => array(
- 'nok' => 'Nemáte cURL (balíček php5-curl).',
+ 'nok' => 'Nemáte cURL (balíček php-curl).',
'ok' => 'Máte rozšíření cURL.',
),
'data' => array(
@@ -71,8 +71,8 @@ return array(
'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).',
+ 'nok' => 'Nemáte PDO nebo některý z podporovaných ovladačů (pdo_mysql, pdo_sqlite, pdo_pgsql).',
+ 'ok' => 'Máte PDO a alespoň jeden z podporovaných ovladačů (pdo_mysql, pdo_sqlite, pdo_pgsql).',
),
'php' => array(
'_' => 'PHP instalace',
@@ -93,7 +93,7 @@ return array(
'ok' => 'Oprávnění adresáře users jsou v pořádku.',
),
'zip' => array(
- 'nok' => 'Nemáte rozšíření ZIP (balíček php5-zip).',
+ 'nok' => 'Nemáte rozšíření ZIP (balíček php-zip).',
'ok' => 'Máte rozšíření ZIP.',
),
),
diff --git a/app/i18n/cz/feedback.php b/app/i18n/cz/feedback.php
index 81302afca..f2bd87c77 100644
--- a/app/i18n/cz/feedback.php
+++ b/app/i18n/cz/feedback.php
@@ -43,12 +43,12 @@ return array(
'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.',
+ '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ě.',
+ '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',
diff --git a/app/i18n/cz/install.php b/app/i18n/cz/install.php
index 6b94c0d4b..c40ae46e2 100644
--- a/app/i18n/cz/install.php
+++ b/app/i18n/cz/install.php
@@ -41,7 +41,7 @@ return array(
'ok' => 'Je nainstalována požadovaná knihovna pro ověřování znaků (ctype).',
),
'curl' => array(
- 'nok' => 'Nemáte cURL (balíček php5-curl).',
+ 'nok' => 'Nemáte cURL (balíček php-curl).',
'ok' => 'Máte rozšíření cURL.',
),
'data' => array(
@@ -73,8 +73,8 @@ return array(
'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).',
+ 'nok' => 'Nemáte PDO nebo některý z podporovaných ovladačů (pdo_mysql, pdo_sqlite, pdo_pgsql).',
+ 'ok' => 'Máte PDO a alespoň jeden z podporovaných ovladačů (pdo_mysql, pdo_sqlite, pdo_pgsql).',
),
'php' => array(
'nok' => 'Vaše verze PHP je %s, ale FreshRSS vyžaduje alespoň verzi %s.',
diff --git a/app/i18n/cz/sub.php b/app/i18n/cz/sub.php
index cea0541e3..274cf16e1 100644
--- a/app/i18n/cz/sub.php
+++ b/app/i18n/cz/sub.php
@@ -44,8 +44,8 @@ return array(
'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)',
+ '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',
diff --git a/app/i18n/de/admin.php b/app/i18n/de/admin.php
index 7b75fe5f4..c12f32bc8 100644
--- a/app/i18n/de/admin.php
+++ b/app/i18n/de/admin.php
@@ -33,7 +33,7 @@ return array(
'ok' => 'Sie haben die benötigte Bibliothek für die Überprüfung von Zeichentypen (ctype).',
),
'curl' => array(
- 'nok' => 'Ihnen fehlt cURL (Paket php5-curl).',
+ 'nok' => 'Ihnen fehlt cURL (Paket php-curl).',
'ok' => 'Sie haben die cURL-Erweiterung.',
),
'data' => array(
@@ -71,8 +71,8 @@ return array(
'ok' => 'Sie haben die benötigte Bibliothek für reguläre Ausdrücke (PCRE).',
),
'pdo' => array(
- 'nok' => 'Ihnen fehlt PDO oder einer der unterstützten Treiber (pdo_mysql, pdo_sqlite).',
- 'ok' => 'Sie haben PDO und mindestens einen der unterstützten Treiber (pdo_mysql, pdo_sqlite).',
+ 'nok' => 'Ihnen fehlt PDO oder einer der unterstützten Treiber (pdo_mysql, pdo_sqlite, pdo_pgsql).',
+ 'ok' => 'Sie haben PDO und mindestens einen der unterstützten Treiber (pdo_mysql, pdo_sqlite, pdo_pgsql).',
),
'php' => array(
'_' => 'PHP-Installation',
@@ -93,7 +93,7 @@ return array(
'ok' => 'Die Berechtigungen des Verzeichnisses <em>./data/users</em> sind in Ordnung.',
),
'zip' => array(
- 'nok' => 'Ihnen fehlt die ZIP-Erweiterung (Paket php5-zip).',
+ 'nok' => 'Ihnen fehlt die ZIP-Erweiterung (Paket php-zip).',
'ok' => 'Sie haben die ZIP-Erweiterung.',
),
),
diff --git a/app/i18n/de/feedback.php b/app/i18n/de/feedback.php
index f93992982..195083b36 100644
--- a/app/i18n/de/feedback.php
+++ b/app/i18n/de/feedback.php
@@ -43,12 +43,12 @@ return array(
'not_found' => '%s existiert nicht',
),
'import_export' => array(
- 'export_no_zip_extension' => 'Die Zip-Erweiterung fehlt auf Ihrem Server. Bitte versuchen Sie die Dateien eine nach der anderen zu exportieren.',
+ 'export_no_zip_extension' => 'Die ZIP-Erweiterung fehlt auf Ihrem Server. Bitte versuchen Sie die Dateien eine nach der anderen zu exportieren.',
'feeds_imported' => 'Ihre Feeds sind importiert worden und werden jetzt aktualisiert',
'feeds_imported_with_errors' => 'Ihre Feeds sind importiert worden, aber es traten einige Fehler auf',
'file_cannot_be_uploaded' => 'Die Datei kann nicht hochgeladen werden!',
- 'no_zip_extension' => 'Die Zip-Erweiterung ist auf Ihrem Server nicht vorhanden.',
- 'zip_error' => 'Ein Fehler trat während des Zip-Imports auf.',
+ 'no_zip_extension' => 'Die ZIP-Erweiterung ist auf Ihrem Server nicht vorhanden.',
+ 'zip_error' => 'Ein Fehler trat während des ZIP-Imports auf.',
),
'sub' => array(
'actualize' => 'Aktualisieren',
diff --git a/app/i18n/de/install.php b/app/i18n/de/install.php
index a77822e7b..178e8ea4c 100644
--- a/app/i18n/de/install.php
+++ b/app/i18n/de/install.php
@@ -4,7 +4,7 @@ return array(
'action' => array(
'finish' => 'Installation fertigstellen',
'fix_errors_before' => 'Bitte Fehler korrigieren, bevor zum nächsten Schritt gesprungen wird.',
- 'keep_install' => 'Vorherige Installation beibehalten (Daten)',
+ 'keep_install' => 'Vorherige Konfiguration beibehalten',
'next_step' => 'Zum nächsten Schritt springen',
'reinstall' => 'Neuinstallation von FreshRSS',
),
@@ -41,7 +41,7 @@ return array(
'ok' => 'Sie haben die benötigte Bibliothek für die Überprüfung von Zeichentypen (ctype).',
),
'curl' => array(
- 'nok' => 'Ihnen fehlt cURL (Paket php5-curl).',
+ 'nok' => 'Ihnen fehlt cURL (Paket php-curl).',
'ok' => 'Sie haben die cURL-Erweiterung.',
),
'data' => array(
@@ -73,8 +73,8 @@ return array(
'ok' => 'Sie haben die benötigte Bibliothek für reguläre Ausdrücke (PCRE).',
),
'pdo' => array(
- 'nok' => 'Ihnen fehlt PDO oder einer der unterstützten Treiber (pdo_mysql, pdo_sqlite).',
- 'ok' => 'Sie haben PDO und mindestens einen der unterstützten Treiber (pdo_mysql, pdo_sqlite).',
+ 'nok' => 'Ihnen fehlt PDO oder einer der unterstützten Treiber (pdo_mysql, pdo_sqlite, pdo_pgsql).',
+ 'ok' => 'Sie haben PDO und mindestens einen der unterstützten Treiber (pdo_mysql, pdo_sqlite, pdo_pgsql).',
),
'php' => array(
'nok' => 'Ihre PHP-Version ist %s aber FreshRSS benötigt mindestens Version %s.',
diff --git a/app/i18n/de/sub.php b/app/i18n/de/sub.php
index 0f05a5635..cc98fd25c 100644
--- a/app/i18n/de/sub.php
+++ b/app/i18n/de/sub.php
@@ -44,7 +44,7 @@ return array(
'export_opml' => 'Liste der Feeds exportieren (OPML)',
'export_starred' => 'Ihre Favoriten exportieren',
'feed_list' => 'Liste von %s Artikeln',
- 'file_to_import' => 'Zu importierende Datei<br />(OPML, JSON oder Zip)',
+ 'file_to_import' => 'Zu importierende Datei<br />(OPML, JSON oder ZIP)',
'file_to_import_no_zip' => 'Zu importierende Datei<br />(OPML oder JSON)',
'import' => 'Importieren',
'starred_list' => 'Liste der Lieblingsartikel',
diff --git a/app/i18n/en/admin.php b/app/i18n/en/admin.php
index a88552087..e5286d948 100644
--- a/app/i18n/en/admin.php
+++ b/app/i18n/en/admin.php
@@ -29,12 +29,12 @@ return array(
'ok' => 'Connection to the database is ok.',
),
'ctype' => array(
- 'nok' => 'You lack a required library for character type checking (php-ctype).',
+ 'nok' => 'Cannot find a required library for character type checking (php-ctype).',
'ok' => 'You have the required library for character type checking (ctype).',
),
'curl' => array(
- 'nok' => 'You lack cURL (php5-curl package).',
- 'ok' => 'You have cURL extension.',
+ 'nok' => 'Cannot find the cURL library (php-curl package).',
+ 'ok' => 'You have the cURL library.',
),
'data' => array(
'nok' => 'Check permissions on <em>./data</em> directory. HTTP server must have rights to write into',
@@ -42,7 +42,7 @@ return array(
),
'database' => 'Database installation',
'dom' => array(
- 'nok' => 'You lack a required library to browse the DOM (php-xml package).',
+ 'nok' => 'Cannot find a required library to browse the DOM (php-xml package).',
'ok' => 'You have the required library to browse the DOM.',
),
'entries' => array(
@@ -59,20 +59,20 @@ return array(
),
'files' => 'File installation',
'json' => array(
- 'nok' => 'You lack JSON (php5-json package).',
+ 'nok' => 'Cannot find JSON (php5-json package).',
'ok' => 'You have JSON extension.',
),
'minz' => array(
- 'nok' => 'You lack the Minz framework.',
+ 'nok' => 'Cannot find the Minz framework.',
'ok' => 'You have the Minz framework.',
),
'pcre' => array(
- 'nok' => 'You lack a required library for regular expressions (php-pcre).',
+ 'nok' => 'Cannot find a required library for regular expressions (php-pcre).',
'ok' => 'You have the required library for regular expressions (PCRE).',
),
'pdo' => array(
- 'nok' => 'You lack PDO or one of the supported drivers (pdo_mysql, pdo_sqlite).',
- 'ok' => 'You have PDO and at least one of the supported drivers (pdo_mysql, pdo_sqlite).',
+ 'nok' => 'Cannot find PDO or one of the supported drivers (pdo_mysql, pdo_sqlite, pdo_pgsql).',
+ 'ok' => 'You have PDO and at least one of the supported drivers (pdo_mysql, pdo_sqlite, pdo_pgsql).',
),
'php' => array(
'_' => 'PHP installation',
@@ -93,7 +93,7 @@ return array(
'ok' => 'Permissions on users directory are good.',
),
'zip' => array(
- 'nok' => 'You lack ZIP extension (php5-zip package).',
+ 'nok' => 'Cannot find ZIP extension (php-zip package).',
'ok' => 'You have ZIP extension.',
),
),
diff --git a/app/i18n/en/feedback.php b/app/i18n/en/feedback.php
index 7ce2ae9cf..e7f6b9f85 100644
--- a/app/i18n/en/feedback.php
+++ b/app/i18n/en/feedback.php
@@ -43,12 +43,12 @@ return array(
'not_found' => '%s does not exist',
),
'import_export' => array(
- 'export_no_zip_extension' => 'Zip extension is not present on your server. Please try to export files one by one.',
+ 'export_no_zip_extension' => 'ZIP extension is not present on your server. Please try to export files one by one.',
'feeds_imported' => 'Your feeds have been imported and will now be updated',
'feeds_imported_with_errors' => 'Your feeds have been imported but some errors occurred',
'file_cannot_be_uploaded' => 'File cannot be uploaded!',
- 'no_zip_extension' => 'Zip extension is not present on your server.',
- 'zip_error' => 'An error occured during Zip import.',
+ 'no_zip_extension' => 'ZIP extension is not present on your server.',
+ 'zip_error' => 'An error occured during ZIP import.',
),
'sub' => array(
'actualize' => 'Actualise',
diff --git a/app/i18n/en/install.php b/app/i18n/en/install.php
index d1c5f37c8..63d31fe53 100644
--- a/app/i18n/en/install.php
+++ b/app/i18n/en/install.php
@@ -4,7 +4,7 @@ return array(
'action' => array(
'finish' => 'Complete installation',
'fix_errors_before' => 'Please fix errors before skipping to the next step.',
- 'keep_install' => 'Keep previous installation',
+ 'keep_install' => 'Keep previous configuration',
'next_step' => 'Go to the next step',
'reinstall' => 'Reinstall FreshRSS',
),
@@ -25,9 +25,9 @@ return array(
),
'host' => 'Host',
'prefix' => 'Table prefix',
- 'password' => 'HTTP password',
+ 'password' => 'Database password',
'type' => 'Type of database',
- 'username' => 'HTTP username',
+ 'username' => 'Database username',
),
'check' => array(
'_' => 'Checks',
@@ -37,19 +37,19 @@ return array(
'ok' => 'Permissions on cache directory are good.',
),
'ctype' => array(
- 'nok' => 'You lack a required library for character type checking (php-ctype).',
+ 'nok' => 'Cannot find a required library for character type checking (php-ctype).',
'ok' => 'You have the required library for character type checking (ctype).',
),
'curl' => array(
- 'nok' => 'You lack cURL (php5-curl package).',
- 'ok' => 'You have cURL extension.',
+ 'nok' => 'Cannot find the cURL library (php-curl package).',
+ 'ok' => 'You have the cURL library.',
),
'data' => array(
'nok' => 'Check permissions on <em>./data</em> directory. HTTP server must have rights to write into',
'ok' => 'Permissions on data directory are good.',
),
'dom' => array(
- 'nok' => 'You lack a required library to browse the DOM.',
+ 'nok' => 'Cannot find a required library to browse the DOM.',
'ok' => 'You have the required library to browse the DOM.',
),
'favicons' => array(
@@ -61,20 +61,20 @@ return array(
'ok' => 'Your HTTP REFERER is known and corresponds to your server.',
),
'json' => array(
- 'nok' => 'You lack a recommended library to parse JSON.',
+ 'nok' => 'Cannot find a recommended library to parse JSON.',
'ok' => 'You have a recommended library to parse JSON.',
),
'minz' => array(
- 'nok' => 'You lack the Minz framework.',
+ 'nok' => 'Cannot find the Minz framework.',
'ok' => 'You have the Minz framework.',
),
'pcre' => array(
- 'nok' => 'You lack a required library for regular expressions (php-pcre).',
+ 'nok' => 'Cannot find a required library for regular expressions (php-pcre).',
'ok' => 'You have the required library for regular expressions (PCRE).',
),
'pdo' => array(
- 'nok' => 'You lack PDO or one of the supported drivers (pdo_mysql, pdo_sqlite).',
- 'ok' => 'You have PDO and at least one of the supported drivers (pdo_mysql, pdo_sqlite).',
+ 'nok' => 'Cannot find PDO or one of the supported drivers (pdo_mysql, pdo_sqlite, pdo_pgsql).',
+ 'ok' => 'You have PDO and at least one of the supported drivers (pdo_mysql, pdo_sqlite, pdo_pgsql).',
),
'php' => array(
'nok' => 'Your PHP version is %s but FreshRSS requires at least version %s.',
@@ -85,7 +85,7 @@ return array(
'ok' => 'Permissions on users directory are good.',
),
'xml' => array(
- 'nok' => 'You lack the required library to parse XML.',
+ 'nok' => 'Cannot find the required library to parse XML.',
'ok' => 'You have the required library to parse XML.',
),
),
diff --git a/app/i18n/en/sub.php b/app/i18n/en/sub.php
index aaaa02827..789433ee6 100644
--- a/app/i18n/en/sub.php
+++ b/app/i18n/en/sub.php
@@ -44,8 +44,8 @@ return array(
'export_opml' => 'Export list of feeds (OPML)',
'export_starred' => 'Export your favourites',
'feed_list' => 'List of %s articles',
- 'file_to_import' => 'File to import<br />(OPML, Json or Zip)',
- 'file_to_import_no_zip' => 'File to import<br />(OPML or Json)',
+ 'file_to_import' => 'File to import<br />(OPML, JSON or ZIP)',
+ 'file_to_import_no_zip' => 'File to import<br />(OPML or JSON)',
'import' => 'Import',
'starred_list' => 'List of favourite articles',
'title' => 'Import / export',
diff --git a/app/i18n/fr/admin.php b/app/i18n/fr/admin.php
index c359e9d24..e9263a5e2 100644
--- a/app/i18n/fr/admin.php
+++ b/app/i18n/fr/admin.php
@@ -29,12 +29,12 @@ return array(
'ok' => 'La connexion à la base de données est bonne.',
),
'ctype' => array(
- 'nok' => 'Il manque une librairie pour la vérification des types de caractères (php-ctype).',
- 'ok' => 'Vous disposez du nécessaire pour la vérification des types de caractères (ctype).',
+ 'nok' => 'Impossible de trouver une librairie pour la vérification des types de caractères (php-ctype).',
+ 'ok' => 'Vous disposez de la librairie pour la vérification des types de caractères (ctype).',
),
'curl' => array(
- 'nok' => 'Vous ne disposez pas de cURL (paquet php5-curl).',
- 'ok' => 'Vous disposez de cURL.',
+ 'nok' => 'Impossible de trouver la librairie cURL (paquet php-curl).',
+ 'ok' => 'Vous disposez de la librairie cURL.',
),
'data' => array(
'nok' => 'Veuillez vérifier les droits sur le répertoire <em>./data</em>. Le serveur HTTP doit être capable d’écrire dedans',
@@ -42,8 +42,8 @@ return array(
),
'database' => 'Installation de la base de données',
'dom' => array(
- 'nok' => 'Il manque une librairie pour parcourir le DOM (paquet php-xml).',
- 'ok' => 'Vous disposez du nécessaire pour parcourir le DOM.',
+ 'nok' => 'Impossible de trouver une librairie pour parcourir le DOM (paquet php-xml).',
+ 'ok' => 'Vous disposez de la librairie pour parcourir le DOM.',
),
'entries' => array(
'nok' => 'La table entry est mal configurée.',
@@ -60,19 +60,19 @@ return array(
'files' => 'Installation des fichiers',
'json' => array(
'nok' => 'Vous ne disposez pas de JSON (paquet php5-json).',
- 'ok' => 'Vous disposez de l\'extension JSON.',
+ 'ok' => 'Vous disposez de l’extension JSON.',
),
'minz' => array(
'nok' => 'Vous ne disposez pas de la librairie Minz.',
'ok' => 'Vous disposez du framework Minz',
),
'pcre' => array(
- 'nok' => 'Il manque une librairie pour les expressions régulières (php-pcre).',
- 'ok' => 'Vous disposez du nécessaire pour les expressions régulières (PCRE).',
+ 'nok' => 'Impossible de trouver une librairie pour les expressions régulières (php-pcre).',
+ 'ok' => 'Vous disposez de la librairie pour les expressions régulières (PCRE).',
),
'pdo' => array(
- 'nok' => 'Vous ne disposez pas de PDO ou d’un des drivers supportés (pdo_mysql, pdo_sqlite).',
- 'ok' => 'Vous disposez de PDO et d’au moins un des drivers supportés (pdo_mysql, pdo_sqlite).',
+ 'nok' => 'Vous ne disposez pas de PDO ou d’un des drivers supportés (pdo_mysql, pdo_sqlite, pdo_pgsql).',
+ 'ok' => 'Vous disposez de PDO et d’au moins un des drivers supportés (pdo_mysql, pdo_sqlite, pdo_pgsql).',
),
'php' => array(
'_' => 'Installation de PHP',
@@ -80,7 +80,7 @@ return array(
'ok' => 'Votre version de PHP est la %s, qui est compatible avec FreshRSS.',
),
'tables' => array(
- 'nok' => 'Il manque une ou plusieurs tables en base de données.',
+ 'nok' => 'Impossible de trouver une ou plusieurs tables en base de données.',
'ok' => 'Les tables sont bien présentes en base de données.',
),
'title' => 'Vérification de l’installation',
@@ -93,8 +93,8 @@ return array(
'ok' => 'Les droits sur le répertoire des utilisateurs sont bons.',
),
'zip' => array(
- 'nok' => 'Vous ne disposez pas de l\'extension ZIP (paquet php5-zip).',
- 'ok' => 'Vous disposez de l\'extension ZIP.',
+ 'nok' => 'Vous ne disposez pas de l’extension ZIP (paquet php-zip).',
+ 'ok' => 'Vous disposez de l’extension ZIP.',
),
),
'extensions' => array(
diff --git a/app/i18n/fr/feedback.php b/app/i18n/fr/feedback.php
index 15f3ab859..5966fc3a7 100644
--- a/app/i18n/fr/feedback.php
+++ b/app/i18n/fr/feedback.php
@@ -43,12 +43,12 @@ return array(
'not_found' => '%s n’existe pas',
),
'import_export' => array(
- 'export_no_zip_extension' => 'L’extension Zip n’est pas présente sur votre serveur. Veuillez essayer d’exporter les fichiers un par un.',
+ 'export_no_zip_extension' => 'L’extension ZIP n’est pas présente sur votre serveur. Veuillez essayer d’exporter les fichiers un par un.',
'feeds_imported' => 'Vos flux ont été importés et vont maintenant être actualisés.',
'feeds_imported_with_errors' => 'Vos flux ont été importés mais des erreurs sont survenues.',
'file_cannot_be_uploaded' => 'Le fichier ne peut pas être téléchargé !',
- 'no_zip_extension' => 'L’extension Zip n’est pas présente sur votre serveur.',
- 'zip_error' => 'Une erreur est survenue durant l’import du fichier Zip.',
+ 'no_zip_extension' => 'L’extension ZIP n’est pas présente sur votre serveur.',
+ 'zip_error' => 'Une erreur est survenue durant l’import du fichier ZIP.',
),
'sub' => array(
'actualize' => 'Actualiser',
diff --git a/app/i18n/fr/install.php b/app/i18n/fr/install.php
index 946a210ee..c50807107 100644
--- a/app/i18n/fr/install.php
+++ b/app/i18n/fr/install.php
@@ -24,10 +24,10 @@ return array(
'ok' => 'La configuration de la base de données a été enregistrée.',
),
'host' => 'Hôte',
- 'password' => 'Mot de passe',
+ 'password' => 'Mot de passe pour base de données',
'prefix' => 'Préfixe des tables',
'type' => 'Type de base de données',
- 'username' => 'Nom d’utilisateur',
+ 'username' => 'Nom d’utilisateur pour base de données',
),
'check' => array(
'_' => 'Vérifications',
@@ -37,11 +37,11 @@ return array(
'ok' => 'Les droits sur le répertoire de cache sont bons.',
),
'ctype' => array(
- 'nok' => 'Il manque une librairie pour la vérification des types de caractères (php-ctype).',
- 'ok' => 'Vous disposez du nécessaire pour la vérification des types de caractères (ctype).',
+ 'nok' => 'Impossible de trouver une librairie pour la vérification des types de caractères (php-ctype).',
+ 'ok' => 'Vous disposez de la librairie pour la vérification des types de caractères (ctype).',
),
'curl' => array(
- 'nok' => 'Vous ne disposez pas de cURL (paquet php5-curl).',
+ 'nok' => 'Vous ne disposez pas de cURL (paquet php-curl).',
'ok' => 'Vous disposez de cURL.',
),
'data' => array(
@@ -49,8 +49,8 @@ return array(
'ok' => 'Les droits sur le répertoire de data sont bons.',
),
'dom' => array(
- 'nok' => 'Il manque une librairie pour parcourir le DOM.',
- 'ok' => 'Vous disposez du nécessaire pour parcourir le DOM.',
+ 'nok' => 'Impossible de trouver une librairie pour parcourir le DOM.',
+ 'ok' => 'Vous disposez de la librairie pour parcourir le DOM.',
),
'favicons' => array(
'nok' => 'Veuillez vérifier les droits sur le répertoire <em>./data/favicons</em>. Le serveur HTTP doit être capable d’écrire dedans',
@@ -61,7 +61,7 @@ return array(
'ok' => 'Le HTTP REFERER est connu et semble correspondre à votre serveur.',
),
'json' => array(
- 'nok' => 'Il manque une librairie recommandée pour JSON.',
+ 'nok' => 'Impossible de trouver une librairie recommandée pour JSON.',
'ok' => 'Vouz disposez de la librairie recommandée pour JSON.',
),
'minz' => array(
@@ -69,12 +69,12 @@ return array(
'ok' => 'Vous disposez du framework Minz',
),
'pcre' => array(
- 'nok' => 'Il manque une librairie pour les expressions régulières (php-pcre).',
- 'ok' => 'Vous disposez du nécessaire pour les expressions régulières (PCRE).',
+ 'nok' => 'Impossible de trouver une librairie pour les expressions régulières (php-pcre).',
+ 'ok' => 'Vous disposez de la librairie pour les expressions régulières (PCRE).',
),
'pdo' => array(
- 'nok' => 'Vous ne disposez pas de PDO ou d’un des drivers supportés (pdo_mysql, pdo_sqlite).',
- 'ok' => 'Vous disposez de PDO et d’au moins un des drivers supportés (pdo_mysql, pdo_sqlite).',
+ 'nok' => 'Vous ne disposez pas de PDO ou d’un des drivers supportés (pdo_mysql, pdo_sqlite, pdo_pgsql).',
+ 'ok' => 'Vous disposez de PDO et d’au moins un des drivers supportés (pdo_mysql, pdo_sqlite, pdo_pgsql).',
),
'php' => array(
'nok' => 'Votre version de PHP est la %s mais FreshRSS requiert au moins la version %s.',
@@ -85,7 +85,7 @@ return array(
'ok' => 'Les droits sur le répertoire des utilisateurs sont bons.',
),
'xml' => array(
- 'nok' => 'Il manque une librairie requise pour XML.',
+ 'nok' => 'Impossible de trouver une librairie requise pour XML.',
'ok' => 'Vouz disposez de la librairie requise pour XML.',
),
),
diff --git a/app/i18n/fr/sub.php b/app/i18n/fr/sub.php
index e3631eb8b..bb3f2fefc 100644
--- a/app/i18n/fr/sub.php
+++ b/app/i18n/fr/sub.php
@@ -44,8 +44,8 @@ return array(
'export_opml' => 'Exporter la liste des flux (OPML)',
'export_starred' => 'Exporter les favoris',
'feed_list' => 'Liste des articles de %s',
- 'file_to_import' => 'Fichier à importer<br />(OPML, Json ou Zip)',
- 'file_to_import_no_zip' => 'Fichier à importer<br />(OPML ou Json)',
+ 'file_to_import' => 'Fichier à importer<br />(OPML, JSON ou ZIP)',
+ 'file_to_import_no_zip' => 'Fichier à importer<br />(OPML ou JSON)',
'import' => 'Importer',
'starred_list' => 'Liste des articles favoris',
'title' => 'Importer / exporter',
diff --git a/app/i18n/it/admin.php b/app/i18n/it/admin.php
index 4eea158f6..c399bc8c8 100644
--- a/app/i18n/it/admin.php
+++ b/app/i18n/it/admin.php
@@ -33,7 +33,7 @@ return array(
'ok' => 'Libreria richiesta per il controllo dei caratteri presente (ctype).',
),
'curl' => array(
- 'nok' => 'Manca il supporto per cURL (pacchetto php5-curl).',
+ 'nok' => 'Manca il supporto per cURL (pacchetto php-curl).',
'ok' => 'Estensione cURL presente.',
),
'data' => array(
@@ -71,8 +71,8 @@ return array(
'ok' => 'Libreria richiesta per le regular expressions presente (PCRE).',
),
'pdo' => array(
- 'nok' => 'Manca PDO o uno degli altri driver supportati (pdo_mysql, pdo_sqlite).',
- 'ok' => 'PDO e altri driver supportati (pdo_mysql, pdo_sqlite).',
+ 'nok' => 'Manca PDO o uno degli altri driver supportati (pdo_mysql, pdo_sqlite, pdo_pgsql).',
+ 'ok' => 'PDO e altri driver supportati (pdo_mysql, pdo_sqlite, pdo_pgsql).',
),
'php' => array(
'_' => 'Installazione PHP',
@@ -93,7 +93,7 @@ return array(
'ok' => 'I permessi sulla cartella users sono corretti.',
),
'zip' => array(
- 'nok' => 'Manca estensione ZIP (pacchetto php5-zip).',
+ 'nok' => 'Manca estensione ZIP (pacchetto php-zip).',
'ok' => 'Estensione ZIP presente.',
),
),
diff --git a/app/i18n/it/feedback.php b/app/i18n/it/feedback.php
index f217586b0..5851cb2e6 100644
--- a/app/i18n/it/feedback.php
+++ b/app/i18n/it/feedback.php
@@ -43,12 +43,12 @@ return array(
'not_found' => '%s non disponibile',
),
'import_export' => array(
- 'export_no_zip_extension' => 'Estensione Zip non presente sul server. Per favore esporta i files singolarmente.',
+ 'export_no_zip_extension' => 'Estensione ZIP non presente sul server. Per favore esporta i files singolarmente.',
'feeds_imported' => 'I tuoi feed sono stati importati e saranno aggiornati',
'feeds_imported_with_errors' => 'I tuoi feeds sono stati importati ma si sono verificati alcuni errori',
'file_cannot_be_uploaded' => 'Il file non può essere caricato!',
- 'no_zip_extension' => 'Estensione Zip non presente sul server.',
- 'zip_error' => 'Si è verificato un errore importando il file Zip',
+ 'no_zip_extension' => 'Estensione ZIP non presente sul server.',
+ 'zip_error' => 'Si è verificato un errore importando il file ZIP',
),
'sub' => array(
'actualize' => 'Aggiorna',
diff --git a/app/i18n/it/install.php b/app/i18n/it/install.php
index a60dd4523..2fa298508 100644
--- a/app/i18n/it/install.php
+++ b/app/i18n/it/install.php
@@ -4,7 +4,7 @@ return array(
'action' => array(
'finish' => 'Installazione completata',
'fix_errors_before' => 'Per favore correggi gli errori prima di passare al passaggio successivo.',
- 'keep_install' => 'Mantieni installazione precedente',
+ 'keep_install' => 'Mantieni configurazione precedente',
'next_step' => 'Vai al prossimo passaggio',
'reinstall' => 'Reinstalla FreshRSS',
),
@@ -25,9 +25,9 @@ return array(
),
'host' => 'Host',
'prefix' => 'Prefisso tabella',
- 'password' => 'HTTP password',
+ 'password' => 'Password del database',
'type' => 'Tipo di database',
- 'username' => 'HTTP username',
+ 'username' => 'Nome utente del database',
),
'check' => array(
'_' => 'Controlli',
@@ -41,7 +41,7 @@ return array(
'ok' => 'Libreria richiesta per il controllo dei caratteri presente (ctype).',
),
'curl' => array(
- 'nok' => 'Manca il supporto per cURL (pacchetto php5-curl).',
+ 'nok' => 'Manca il supporto per cURL (pacchetto php-curl).',
'ok' => 'Estensione cURL presente.',
),
'data' => array(
@@ -73,8 +73,8 @@ return array(
'ok' => 'Libreria richiesta per le regular expressions presente (PCRE).',
),
'pdo' => array(
- 'nok' => 'Manca PDO o uno degli altri driver supportati (pdo_mysql, pdo_sqlite).',
- 'ok' => 'PDO e altri driver supportati (pdo_mysql, pdo_sqlite).',
+ 'nok' => 'Manca PDO o uno degli altri driver supportati (pdo_mysql, pdo_sqlite, pdo_pgsql).',
+ 'ok' => 'PDO e altri driver supportati (pdo_mysql, pdo_sqlite, pdo_pgsql).',
),
'php' => array(
'_' => 'Installazione PHP',
diff --git a/app/i18n/it/sub.php b/app/i18n/it/sub.php
index dfcee2ce3..758e322c5 100644
--- a/app/i18n/it/sub.php
+++ b/app/i18n/it/sub.php
@@ -44,8 +44,8 @@ return array(
'export_opml' => 'Esporta tutta la lista dei feed (OPML)',
'export_starred' => 'Esporta i tuoi preferiti',
'feed_list' => 'Elenco di %s articoli',
- 'file_to_import' => 'File da importare<br />(OPML, Json o Zip)',
- 'file_to_import_no_zip' => 'File da importare<br />(OPML o Json)',
+ 'file_to_import' => 'File da importare<br />(OPML, JSON o ZIP)',
+ 'file_to_import_no_zip' => 'File da importare<br />(OPML o JSON)',
'import' => 'Importa',
'starred_list' => 'Elenco articoli preferiti',
'title' => 'Importa / esporta',
diff --git a/app/i18n/nl/admin.php b/app/i18n/nl/admin.php
index 9f05d69b1..59637bfef 100644
--- a/app/i18n/nl/admin.php
+++ b/app/i18n/nl/admin.php
@@ -33,7 +33,7 @@ return array(
'ok' => 'U hebt de benodigde bibliotheek voor character type checking (ctype).',
),
'curl' => array(
- 'nok' => 'U mist de cURL (php5-curl package).',
+ 'nok' => 'U mist de cURL (php-curl package).',
'ok' => 'U hebt de cURL uitbreiding.',
),
'data' => array(
@@ -71,8 +71,8 @@ return array(
'ok' => 'U hebt de benodigde bibliotheek voor regular expressions (PCRE).',
),
'pdo' => array(
- 'nok' => 'U mist PDO of een van de ondersteunde drivers (pdo_mysql, pdo_sqlite).',
- 'ok' => 'U hebt PDO en ten minste één van de ondersteunde drivers (pdo_mysql, pdo_sqlite).',
+ 'nok' => 'U mist PDO of een van de ondersteunde drivers (pdo_mysql, pdo_sqlite, pdo_pgsql).',
+ 'ok' => 'U hebt PDO en ten minste één van de ondersteunde drivers (pdo_mysql, pdo_sqlite, pdo_pgsql).',
),
'php' => array(
'_' => 'PHP installatie',
@@ -93,7 +93,7 @@ return array(
'ok' => 'Permissies op de users map zijn goed.',
),
'zip' => array(
- 'nok' => 'U mist ZIP uitbreiding (php5-zip package).',
+ 'nok' => 'U mist ZIP uitbreiding (php-zip package).',
'ok' => 'U hebt ZIP uitbreiding.',
),
),
diff --git a/app/i18n/nl/feedback.php b/app/i18n/nl/feedback.php
index b703c43cf..386b8d415 100644
--- a/app/i18n/nl/feedback.php
+++ b/app/i18n/nl/feedback.php
@@ -43,12 +43,12 @@ return array(
'not_found' => '%s bestaat niet',
),
'import_export' => array(
- 'export_no_zip_extension' => 'Zip uitbreiding is niet aanwezig op uw server. Exporteer a.u.b. uw bestanden één voor één.',
+ 'export_no_zip_extension' => 'ZIP uitbreiding is niet aanwezig op uw server. Exporteer a.u.b. uw bestanden één voor één.',
'feeds_imported' => 'Uw feeds zijn geimporteerd en worden nu vernieuwd',
'feeds_imported_with_errors' => 'Uw feeds zijn geimporteerd maar er zijn enige fouten opgetreden',
'file_cannot_be_uploaded' => 'Bestand kan niet worden verzonden!',
- 'no_zip_extension' => 'Zip uitbreiding is niet aanwezig op uw server.',
- 'zip_error' => 'Er is een fout opgetreden tijdens het imporeren van het Zip bestand.',
+ 'no_zip_extension' => 'ZIP uitbreiding is niet aanwezig op uw server.',
+ 'zip_error' => 'Er is een fout opgetreden tijdens het imporeren van het ZIP bestand.',
),
'sub' => array(
'actualize' => 'Actualiseren',
diff --git a/app/i18n/nl/install.php b/app/i18n/nl/install.php
index 77783cd48..bd2ba0cce 100644
--- a/app/i18n/nl/install.php
+++ b/app/i18n/nl/install.php
@@ -25,9 +25,9 @@ return array(
),
'host' => 'Host',
'prefix' => 'Tabel voorvoegsel',
- 'password' => 'HTTP wachtwoord',
+ 'password' => 'Database wachtwoord',
'type' => 'Type database',
- 'username' => 'HTTP gebruikersnaam',
+ 'username' => 'Database gebruikersnaam',
),
'check' => array(
'_' => 'Controles',
@@ -41,7 +41,7 @@ return array(
'ok' => 'U hebt de benodigde bibliotheek voor character type checking (ctype).',
),
'curl' => array(
- 'nok' => 'U mist cURL (php5-curl package).',
+ 'nok' => 'U mist cURL (php-curl package).',
'ok' => 'U hebt de cURL uitbreiding.',
),
'data' => array(
@@ -73,8 +73,8 @@ return array(
'ok' => 'U hebt de benodigde bibliotheek voor regular expressions (PCRE).',
),
'pdo' => array(
- 'nok' => 'U mist PDO of één van de ondersteunde (pdo_mysql, pdo_sqlite).',
- 'ok' => 'U hebt PDO en ten minste één van de ondersteunde drivers (pdo_mysql, pdo_sqlite).',
+ 'nok' => 'U mist PDO of één van de ondersteunde (pdo_mysql, pdo_sqlite, pdo_pgsql).',
+ 'ok' => 'U hebt PDO en ten minste één van de ondersteunde drivers (pdo_mysql, pdo_sqlite, pdo_pgsql).',
),
'php' => array(
'nok' => 'Uw PHP versie is %s maar FreshRSS benodigd tenminste versie %s.',
diff --git a/app/i18n/nl/sub.php b/app/i18n/nl/sub.php
index 159a58b27..a1ba3f03d 100644
--- a/app/i18n/nl/sub.php
+++ b/app/i18n/nl/sub.php
@@ -44,8 +44,8 @@ return array(
'export_opml' => 'Exporteer lijst van feeds (OPML)',
'export_starred' => 'Exporteer je fovorieten',
'feed_list' => 'Lijst van %s artikelen',
- 'file_to_import' => 'Bestand om te importeren<br />(OPML, Json of Zip)',
- 'file_to_import_no_zip' => 'Bestand om te importeren<br />(OPML of Json)',
+ 'file_to_import' => 'Bestand om te importeren<br />(OPML, JSON of ZIP)',
+ 'file_to_import_no_zip' => 'Bestand om te importeren<br />(OPML of JSON)',
'import' => 'Importeer',
'starred_list' => 'Lijst van favoriete artikelen',
'title' => 'Importeren / exporteren',
diff --git a/app/i18n/ru/admin.php b/app/i18n/ru/admin.php
index caea627f3..355100689 100644
--- a/app/i18n/ru/admin.php
+++ b/app/i18n/ru/admin.php
@@ -33,7 +33,7 @@ return array(
'ok' => 'У вас не установлена библиотека для проверки типов символов (ctype).',
),
'curl' => array(
- 'nok' => 'У вас не установлено расширение cURL (пакет php5-curl).',
+ 'nok' => 'У вас не установлено расширение cURL (пакет php-curl).',
'ok' => 'У вас установлено расширение cURL.',
),
'data' => array(
@@ -71,8 +71,8 @@ return array(
'ok' => 'У вас установлена необходимая библиотека для работы с регулярными выражениями (PCRE).',
),
'pdo' => array(
- 'nok' => 'У вас не установлен PDO или один из необходимых драйверов (pdo_mysql, pdo_sqlite).',
- 'ok' => 'У вас установлен PDO и как минимум один из поддерживаемых драйверов (pdo_mysql, pdo_sqlite).',
+ 'nok' => 'У вас не установлен PDO или один из необходимых драйверов (pdo_mysql, pdo_sqlite, pdo_pgsql).',
+ 'ok' => 'У вас установлен PDO и как минимум один из поддерживаемых драйверов (pdo_mysql, pdo_sqlite, pdo_pgsql).',
),
'php' => array(
'_' => 'PHP installation',
@@ -93,7 +93,7 @@ return array(
'ok' => 'Права на папку users в порядке.',
),
'zip' => array(
- 'nok' => 'You lack ZIP extension (php5-zip package).',
+ 'nok' => 'You lack ZIP extension (php-zip package).',
'ok' => 'You have ZIP extension.',
),
),
diff --git a/app/i18n/ru/feedback.php b/app/i18n/ru/feedback.php
index 7ce2ae9cf..e7f6b9f85 100644
--- a/app/i18n/ru/feedback.php
+++ b/app/i18n/ru/feedback.php
@@ -43,12 +43,12 @@ return array(
'not_found' => '%s does not exist',
),
'import_export' => array(
- 'export_no_zip_extension' => 'Zip extension is not present on your server. Please try to export files one by one.',
+ 'export_no_zip_extension' => 'ZIP extension is not present on your server. Please try to export files one by one.',
'feeds_imported' => 'Your feeds have been imported and will now be updated',
'feeds_imported_with_errors' => 'Your feeds have been imported but some errors occurred',
'file_cannot_be_uploaded' => 'File cannot be uploaded!',
- 'no_zip_extension' => 'Zip extension is not present on your server.',
- 'zip_error' => 'An error occured during Zip import.',
+ 'no_zip_extension' => 'ZIP extension is not present on your server.',
+ 'zip_error' => 'An error occured during ZIP import.',
),
'sub' => array(
'actualize' => 'Actualise',
diff --git a/app/i18n/ru/install.php b/app/i18n/ru/install.php
index a52e2959b..bad59bbb3 100644
--- a/app/i18n/ru/install.php
+++ b/app/i18n/ru/install.php
@@ -25,9 +25,9 @@ return array(
),
'host' => 'Хост',
'prefix' => 'Префикс таблицы',
- 'password' => 'Пароль HTTP',
+ 'password' => 'Пароль базы данных',
'type' => 'Тип базы данных',
- 'username' => 'Имя пользователя HTTP',
+ 'username' => 'Имя пользователя базы данных',
),
'check' => array(
'_' => 'Проверки',
@@ -41,7 +41,7 @@ return array(
'ok' => 'У вас установлена необходимая библиотека для проверки типов символов (ctype).',
),
'curl' => array(
- 'nok' => 'У вас нет расширения cURL (пакет php5-curl).',
+ 'nok' => 'У вас нет расширения cURL (пакет php-curl).',
'ok' => 'У вас установлено расширение cURL.',
),
'data' => array(
@@ -69,8 +69,8 @@ return array(
'ok' => 'У вас установлена необходимая библиотека для работы с регулярными выражениями (PCRE).',
),
'pdo' => array(
- 'nok' => 'У вас не установлен PDO или один из необходимых драйверов (pdo_mysql, pdo_sqlite).',
- 'ok' => 'У вас установлен PDO и как минимум один из поддерживаемых драйверов (pdo_mysql, pdo_sqlite).',
+ 'nok' => 'У вас не установлен PDO или один из необходимых драйверов (pdo_mysql, pdo_sqlite, pdo_pgsql).',
+ 'ok' => 'У вас установлен PDO и как минимум один из поддерживаемых драйверов (pdo_mysql, pdo_sqlite, pdo_pgsql).',
),
'php' => array(
'nok' => 'У вас установлен PHP версии %s, но FreshRSS необходима версия не ниже %s.',
diff --git a/app/i18n/ru/sub.php b/app/i18n/ru/sub.php
index aaaa02827..789433ee6 100644
--- a/app/i18n/ru/sub.php
+++ b/app/i18n/ru/sub.php
@@ -44,8 +44,8 @@ return array(
'export_opml' => 'Export list of feeds (OPML)',
'export_starred' => 'Export your favourites',
'feed_list' => 'List of %s articles',
- 'file_to_import' => 'File to import<br />(OPML, Json or Zip)',
- 'file_to_import_no_zip' => 'File to import<br />(OPML or Json)',
+ 'file_to_import' => 'File to import<br />(OPML, JSON or ZIP)',
+ 'file_to_import_no_zip' => 'File to import<br />(OPML or JSON)',
'import' => 'Import',
'starred_list' => 'List of favourite articles',
'title' => 'Import / export',
diff --git a/app/i18n/tr/admin.php b/app/i18n/tr/admin.php
index 43f8e23c5..e0dbd288d 100644
--- a/app/i18n/tr/admin.php
+++ b/app/i18n/tr/admin.php
@@ -33,7 +33,7 @@ return array(
'ok' => 'Karakter yazım kontrolü için kütüphane sorunsuz (ctype).',
),
'curl' => array(
- 'nok' => 'cURL eksik (php5-curl package).',
+ 'nok' => 'cURL eksik (php-curl package).',
'ok' => 'cURL eklentisi sorunsuz.',
),
'data' => array(
@@ -71,8 +71,8 @@ return array(
'ok' => 'Düzenli ifadeler kütüphanesi sorunsuz (PCRE).',
),
'pdo' => array(
- 'nok' => 'PDO veya PDO destekli bir sürücü eksik (pdo_mysql, pdo_sqlite).',
- 'ok' => 'PDO sorunsuz (pdo_mysql, pdo_sqlite).',
+ 'nok' => 'PDO veya PDO destekli bir sürücü eksik (pdo_mysql, pdo_sqlite, pdo_pgsql).',
+ 'ok' => 'PDO sorunsuz (pdo_mysql, pdo_sqlite, pdo_pgsql).',
),
'php' => array(
'_' => 'PHP kurulumu',
@@ -93,7 +93,7 @@ return array(
'ok' => 'Kullanıcılar klasörü yetkileri sorunsuz.',
),
'zip' => array(
- 'nok' => 'ZIP eklentisi eksik (php5-zip package).',
+ 'nok' => 'ZIP eklentisi eksik (php-zip package).',
'ok' => 'ZIP eklentisi sorunsuz.',
),
),
diff --git a/app/i18n/tr/feedback.php b/app/i18n/tr/feedback.php
index a53316206..87361ff51 100644
--- a/app/i18n/tr/feedback.php
+++ b/app/i18n/tr/feedback.php
@@ -43,12 +43,12 @@ return array(
'not_found' => '%s bulunmamaktadır',
),
'import_export' => array(
- 'export_no_zip_extension' => 'Zip eklentisi mevcut sunucunuzda yer almıyor. Lütfen başka dosya formatında dışarı aktarmayı deneyin.',
+ 'export_no_zip_extension' => 'ZIP eklentisi mevcut sunucunuzda yer almıyor. Lütfen başka dosya formatında dışarı aktarmayı deneyin.',
'feeds_imported' => 'Akışlarınız içe aktarıldı ve şimdi güncellenecek',
'feeds_imported_with_errors' => 'Akışlarınız içeri aktarıldı ama bazı hatalar meydana geldi',
'file_cannot_be_uploaded' => 'Dosya yüklenemedi!',
- 'no_zip_extension' => 'Zip eklentisi mevcut sunucunuzda yer almıyor.',
- 'zip_error' => 'Zip içe aktarımı sırasında hata meydana geldi.',
+ 'no_zip_extension' => 'ZIP eklentisi mevcut sunucunuzda yer almıyor.',
+ 'zip_error' => 'ZIP içe aktarımı sırasında hata meydana geldi.',
),
'sub' => array(
'actualize' => 'Güncelleme',
diff --git a/app/i18n/tr/install.php b/app/i18n/tr/install.php
index 951a7c5fd..4daa45099 100644
--- a/app/i18n/tr/install.php
+++ b/app/i18n/tr/install.php
@@ -25,9 +25,9 @@ return array(
),
'host' => 'Sunucu',
'prefix' => 'Tablo ön eki',
- 'password' => 'HTTP şifre',
+ 'password' => 'Veritabanı şifresi',
'type' => 'Veritabanı türü',
- 'username' => 'HTTP kullanıcı adı',
+ 'username' => 'Veritabanı kullanıcı adı',
),
'check' => array(
'_' => 'Kontroller',
@@ -41,7 +41,7 @@ return array(
'ok' => 'Karakter yazım kontrolü için kütüphane sorunsuz (ctype).',
),
'curl' => array(
- 'nok' => 'cURL eksik (php5-curl package).',
+ 'nok' => 'cURL eksik (php-curl package).',
'ok' => 'cURL eklentisi sorunsuz.',
),
'data' => array(
@@ -73,8 +73,8 @@ return array(
'ok' => 'Düzenli ifadeler kütüphanesi sorunsuz (PCRE).',
),
'pdo' => array(
- 'nok' => 'PDO veya PDO destekli bir sürücü eksik (pdo_mysql, pdo_sqlite).',
- 'ok' => 'PDO sorunsuz (pdo_mysql, pdo_sqlite).',
+ 'nok' => 'PDO veya PDO destekli bir sürücü eksik (pdo_mysql, pdo_sqlite, pdo_pgsql).',
+ 'ok' => 'PDO sorunsuz (pdo_mysql, pdo_sqlite, pdo_pgsql).',
),
'php' => array(
'nok' => 'PHP versiyonunuz %s fakat FreshRSS için gerekli olan en düşük sürüm %s.',
diff --git a/app/i18n/tr/sub.php b/app/i18n/tr/sub.php
index 5ab367ebb..7592096d9 100644
--- a/app/i18n/tr/sub.php
+++ b/app/i18n/tr/sub.php
@@ -44,8 +44,8 @@ return array(
'export_opml' => 'Akış listesini dışarı aktar (OPML)',
'export_starred' => 'Favorileri dışarı aktar',
'feed_list' => '%s makalenin listesi',
- 'file_to_import' => 'Dosyadan içe aktar<br />(OPML, Json or Zip)',
- 'file_to_import_no_zip' => 'Dosyadan içe aktar<br />(OPML or Json)',
+ 'file_to_import' => 'Dosyadan içe aktar<br />(OPML, JSON or ZIP)',
+ 'file_to_import_no_zip' => 'Dosyadan içe aktar<br />(OPML or JSON)',
'import' => 'İçe aktar',
'starred_list' => 'Favori makaleleirn listesi',
'title' => 'İçe / dışa aktar',
diff --git a/app/install.php b/app/install.php
index 62695ceb6..fcc901713 100644
--- a/app/install.php
+++ b/app/install.php
@@ -4,23 +4,18 @@ if (function_exists('opcache_reset')) {
}
header("Content-Security-Policy: default-src 'self'");
-define('BCRYPT_COST', 9);
+require(LIB_PATH . '/lib_install.php');
session_name('FreshRSS');
session_set_cookie_params(0, dirname(empty($_SERVER['REQUEST_URI']) ? '/' : dirname($_SERVER['REQUEST_URI'])), null, false, true);
session_start();
-Minz_Configuration::register('default_system', join_path(DATA_PATH, 'config.default.php'));
-Minz_Configuration::register('default_user', join_path(USERS_PATH, '_', 'config.default.php'));
-
if (isset($_GET['step'])) {
define('STEP',(int)$_GET['step']);
} else {
define('STEP', 0);
}
-define('SQL_CREATE_DB', 'CREATE DATABASE IF NOT EXISTS %1$s DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;');
-
if (STEP === 3 && isset($_POST['type'])) {
$_SESSION['bd_type'] = $_POST['type'];
}
@@ -28,10 +23,13 @@ if (STEP === 3 && isset($_POST['type'])) {
if (isset($_SESSION['bd_type'])) {
switch ($_SESSION['bd_type']) {
case 'mysql':
- include(APP_PATH . '/SQL/install.sql.mysql.php');
+ include_once(APP_PATH . '/SQL/install.sql.mysql.php');
break;
case 'sqlite':
- include(APP_PATH . '/SQL/install.sql.sqlite.php');
+ include_once(APP_PATH . '/SQL/install.sql.sqlite.php');
+ break;
+ case 'pgsql':
+ include_once(APP_PATH . '/SQL/install.sql.pgsql.php');
break;
}
}
@@ -130,12 +128,7 @@ function saveStep2() {
$password_plain = param('passwordPlain', false);
if ($password_plain !== false && cryptAvailable()) {
- if (!function_exists('password_hash')) {
- include_once(LIB_PATH . '/password_compat.php');
- }
- $passwordHash = password_hash($password_plain, PASSWORD_BCRYPT, array('cost' => BCRYPT_COST));
- $passwordHash = preg_replace('/^\$2[xy]\$/', '\$2a\$', $passwordHash); //Compatibility with bcrypt.js
- $_SESSION['passwordHash'] = $passwordHash;
+ $_SESSION['passwordHash'] = FreshRSS_user_Controller::hashPassword($password_plain);
}
if (empty($_SESSION['old_entries']) ||
@@ -148,7 +141,7 @@ function saveStep2() {
return false;
}
- $_SESSION['salt'] = sha1(uniqid(mt_rand(), true).implode('', stat(__FILE__)));
+ $_SESSION['salt'] = generateSalt();
if ((!ctype_digit($_SESSION['old_entries'])) ||($_SESSION['old_entries'] < 1)) {
$_SESSION['old_entries'] = $user_default_config->old_entries;
}
@@ -170,7 +163,7 @@ function saveStep2() {
recursive_unlink($user_dir);
mkdir($user_dir);
- file_put_contents($user_config_path, "<?php\n return " . var_export($config_array, true) . ';');
+ file_put_contents($user_config_path, "<?php\n return " . var_export($config_array, true) . ";\n");
header('Location: index.php?step=3');
}
@@ -199,6 +192,9 @@ function saveStep3() {
$_SESSION['bd_prefix'] = substr($_POST['prefix'], 0, 16);
$_SESSION['bd_prefix_user'] = $_SESSION['bd_prefix'] . (empty($_SESSION['default_user']) ? '' : ($_SESSION['default_user'] . '_'));
}
+ if ($_SESSION['bd_type'] === 'pgsql') {
+ $_SESSION['bd_base'] = strtolower($_SESSION['bd_base']);
+ }
// We use dirname to remove the /i part
$base_url = dirname(Minz_Request::guessBaseUrl());
@@ -221,55 +217,30 @@ function saveStep3() {
);
@unlink(join_path(DATA_PATH, 'config.php')); //To avoid access-rights problems
- file_put_contents(join_path(DATA_PATH, 'config.php'), "<?php\n return " . var_export($config_array, true) . ';');
+ file_put_contents(join_path(DATA_PATH, 'config.php'), "<?php\n return " . var_export($config_array, true) . ";\n");
- $res = checkBD();
+ $config_array['db']['default_user'] = $config_array['default_user'];
+ $config_array['db']['prefix_user'] = $_SESSION['bd_prefix_user'];
+ $ok = checkDb($config_array['db']) && checkDbUser($config_array['db']);
+ if (!$ok) {
+ @unlink(join_path(DATA_PATH, 'config.php'));
+ }
- if ($res) {
+ if ($ok) {
$_SESSION['bd_error'] = '';
header('Location: index.php?step=4');
- } elseif (empty($_SESSION['bd_error'])) {
- $_SESSION['bd_error'] = 'Unknown error!';
+ } else {
+ $_SESSION['bd_error'] = empty($config_array['db']['bd_error']) ? 'Unknown error!' : $config_array['db']['bd_error'];
}
}
invalidateHttpCache();
}
-function newPdo() {
- switch ($_SESSION['bd_type']) {
- case 'mysql':
- $str = 'mysql:host=' . $_SESSION['bd_host'] . ';dbname=' . $_SESSION['bd_base'];
- $driver_options = array(
- PDO::MYSQL_ATTR_INIT_COMMAND => 'SET NAMES utf8mb4',
- );
- break;
- case 'sqlite':
- $str = 'sqlite:' . join_path(USERS_PATH, $_SESSION['default_user'], 'db.sqlite');
- $driver_options = array(
- PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
- );
- break;
- default:
- return false;
- }
- return new PDO($str, $_SESSION['bd_user'], $_SESSION['bd_password'], $driver_options);
-}
-
-function deleteInstall() {
- $res = unlink(join_path(DATA_PATH, 'do-install.txt'));
-
- if (!$res) {
- return false;
- }
-
- header('Location: index.php');
-}
-
/*** VÉRIFICATIONS ***/
function checkStep() {
$s0 = checkStep0();
- $s1 = checkStep1();
+ $s1 = checkRequirements();
$s2 = checkStep2();
$s3 = checkStep3();
if (STEP > 0 && $s0['all'] != 'ok') {
@@ -295,47 +266,6 @@ function checkStep0() {
);
}
-function checkStep1() {
- $php = version_compare(PHP_VERSION, '5.3.3') >= 0;
- $minz = file_exists(join_path(LIB_PATH, 'Minz'));
- $curl = extension_loaded('curl');
- $pdo_mysql = extension_loaded('pdo_mysql');
- $pdo_sqlite = extension_loaded('pdo_sqlite');
- $pdo = $pdo_mysql || $pdo_sqlite;
- $pcre = extension_loaded('pcre');
- $ctype = extension_loaded('ctype');
- $dom = class_exists('DOMDocument');
- $xml = function_exists('xml_parser_create');
- $json = function_exists('json_encode');
- $data = DATA_PATH && is_writable(DATA_PATH);
- $cache = CACHE_PATH && is_writable(CACHE_PATH);
- $users = USERS_PATH && is_writable(USERS_PATH);
- $favicons = is_writable(join_path(DATA_PATH, 'favicons'));
- $http_referer = is_referer_from_same_domain();
-
- return array(
- 'php' => $php ? 'ok' : 'ko',
- 'minz' => $minz ? 'ok' : 'ko',
- 'curl' => $curl ? 'ok' : 'ko',
- 'pdo-mysql' => $pdo_mysql ? 'ok' : 'ko',
- 'pdo-sqlite' => $pdo_sqlite ? 'ok' : 'ko',
- 'pdo' => $pdo ? 'ok' : 'ko',
- 'pcre' => $pcre ? 'ok' : 'ko',
- 'ctype' => $ctype ? 'ok' : 'ko',
- 'dom' => $dom ? 'ok' : 'ko',
- 'xml' => $xml ? 'ok' : 'ko',
- 'json' => $json ? 'ok' : 'ko',
- 'data' => $data ? 'ok' : 'ko',
- 'cache' => $cache ? 'ok' : 'ko',
- 'users' => $users ? 'ok' : 'ko',
- 'favicons' => $favicons ? 'ok' : 'ko',
- 'http_referer' => $http_referer ? 'ok' : 'ko',
- 'all' => $php && $minz && $curl && $pdo && $pcre && $ctype && $dom && $xml &&
- $data && $cache && $users && $favicons && $http_referer ?
- 'ok' : 'ko'
- );
-}
-
function freshrss_already_installed() {
$conf_path = join_path(DATA_PATH, 'config.php');
if (!file_exists($conf_path)) {
@@ -406,43 +336,15 @@ function checkStep3() {
);
}
-function checkBD() {
+function checkDbUser(&$dbOptions) {
$ok = false;
-
+ $str = $dbOptions['dsn'];
+ $driver_options = $dbOptions['options'];
try {
- $str = '';
- $driver_options = null;
- switch ($_SESSION['bd_type']) {
- case 'mysql':
- $driver_options = array(
- PDO::MYSQL_ATTR_INIT_COMMAND => 'SET NAMES utf8mb4'
- );
-
- try { // on ouvre une connexion juste pour créer la base si elle n'existe pas
- $str = 'mysql:host=' . $_SESSION['bd_host'] . ';';
- $c = new PDO($str, $_SESSION['bd_user'], $_SESSION['bd_password'], $driver_options);
- $sql = sprintf(SQL_CREATE_DB, $_SESSION['bd_base']);
- $res = $c->query($sql);
- } catch (PDOException $e) {
- }
-
- // on écrase la précédente connexion en sélectionnant la nouvelle BDD
- $str = 'mysql:host=' . $_SESSION['bd_host'] . ';dbname=' . $_SESSION['bd_base'];
- break;
- case 'sqlite':
- $str = 'sqlite:' . join_path(USERS_PATH, $_SESSION['default_user'], 'db.sqlite');
- $driver_options = array(
- PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
- );
- break;
- default:
- return false;
- }
-
- $c = new PDO($str, $_SESSION['bd_user'], $_SESSION['bd_password'], $driver_options);
+ $c = new PDO($str, $dbOptions['user'], $dbOptions['password'], $driver_options);
if (defined('SQL_CREATE_TABLES')) {
- $sql = sprintf(SQL_CREATE_TABLES, $_SESSION['bd_prefix_user'], _t('gen.short.default_category'));
+ $sql = sprintf(SQL_CREATE_TABLES, $dbOptions['prefix_user'], _t('gen.short.default_category'));
$stm = $c->prepare($sql);
$ok = $stm->execute();
} else {
@@ -450,7 +352,22 @@ function checkBD() {
if (is_array($SQL_CREATE_TABLES)) {
$ok = true;
foreach ($SQL_CREATE_TABLES as $instruction) {
- $sql = sprintf($instruction, $_SESSION['bd_prefix_user'], _t('gen.short.default_category'));
+ $sql = sprintf($instruction, $dbOptions['prefix_user'], _t('gen.short.default_category'));
+ $stm = $c->prepare($sql);
+ $ok &= $stm->execute();
+ }
+ }
+ }
+
+ if (defined('SQL_INSERT_FEEDS')) {
+ $sql = sprintf(SQL_INSERT_FEEDS, $dbOptions['prefix_user']);
+ $stm = $c->prepare($sql);
+ $ok &= $stm->execute();
+ } else {
+ global $SQL_INSERT_FEEDS;
+ if (is_array($SQL_INSERT_FEEDS)) {
+ foreach ($SQL_INSERT_FEEDS as $instruction) {
+ $sql = sprintf($instruction, $dbOptions['prefix_user']);
$stm = $c->prepare($sql);
$ok &= $stm->execute();
}
@@ -458,13 +375,8 @@ function checkBD() {
}
} catch (PDOException $e) {
$ok = false;
- $_SESSION['bd_error'] = $e->getMessage();
+ $dbOptions['bd_error'] = $e->getMessage();
}
-
- if (!$ok) {
- @unlink(join_path(DATA_PATH, 'config.php'));
- }
-
return $ok;
}
@@ -507,7 +419,7 @@ function printStep0() {
// @todo refactor this view with the check_install action
function printStep1() {
- $res = checkStep1();
+ $res = checkRequirements();
?>
<noscript><p class="alert alert-warn"><span class="alert-head"><?php echo _t('gen.short.attention'); ?></span> <?php echo _t('install.javascript_is_better'); ?></p></noscript>
@@ -690,7 +602,7 @@ function printStep3() {
<p class="alert alert-error"><span class="alert-head"><?php echo _t('gen.short.damn'); ?></span> <?php echo _t('install.bdd.conf.ko'),(empty($_SESSION['bd_error']) ? '' : ' : ' . $_SESSION['bd_error']); ?></p>
<?php } ?>
- <form action="index.php?step=3" method="post">
+ <form action="index.php?step=3" method="post" autocomplete="off">
<legend><?php echo _t('install.bdd.conf'); ?></legend>
<div class="form-group">
<label class="group-name" for="type"><?php echo _t('install.bdd.type'); ?></label>
@@ -708,6 +620,12 @@ function printStep3() {
SQLite
</option>
<?php }?>
+ <?php if (extension_loaded('pdo_pgsql')) {?>
+ <option value="pgsql"
+ <?php echo(isset($_SESSION['bd_type']) && $_SESSION['bd_type'] === 'pgsql') ? 'selected="selected"' : ''; ?>>
+ PostgreSQL (⚠️ experimental)
+ </option>
+ <?php }?>
</select>
</div>
</div>
@@ -716,7 +634,7 @@ function printStep3() {
<div class="form-group">
<label class="group-name" for="host"><?php echo _t('install.bdd.host'); ?></label>
<div class="group-controls">
- <input type="text" id="host" name="host" pattern="[0-9A-Za-z_.-]{1,64}" value="<?php echo isset($_SESSION['bd_host']) ? $_SESSION['bd_host'] : $system_default_config->db['host']; ?>" tabindex="2" />
+ <input type="text" id="host" name="host" pattern="[0-9A-Za-z_.-]{1,64}(:[0-9]{2,5})?" value="<?php echo isset($_SESSION['bd_host']) ? $_SESSION['bd_host'] : $system_default_config->db['host']; ?>" tabindex="2" />
</div>
</div>
@@ -730,7 +648,7 @@ function printStep3() {
<div class="form-group">
<label class="group-name" for="pass"><?php echo _t('install.bdd.password'); ?></label>
<div class="group-controls">
- <input type="password" id="pass" name="pass" value="<?php echo isset($_SESSION['bd_password']) ? $_SESSION['bd_password'] : ''; ?>" tabindex="4" />
+ <input type="password" id="pass" name="pass" value="<?php echo isset($_SESSION['bd_password']) ? $_SESSION['bd_password'] : ''; ?>" tabindex="4" autocomplete="off" />
</div>
</div>
@@ -796,7 +714,9 @@ case 3:
case 4:
break;
case 5:
- deleteInstall();
+ if (deleteInstall()) {
+ header('Location: index.php');
+ }
break;
}
?>
diff --git a/app/layout/aside_feed.phtml b/app/layout/aside_feed.phtml
index e8fdbf842..3e1ee44dd 100644
--- a/app/layout/aside_feed.phtml
+++ b/app/layout/aside_feed.phtml
@@ -79,13 +79,13 @@
<?php if (FreshRSS_Auth::hasAccess()) { ?>
<li class="item"><a href="<?php echo _url('stats', 'repartition', 'id', '------'); ?>"><?php echo _t('index.menu.stats'); ?></a></li>
<?php } ?>
- <li class="item"><a target="_blank" href="http://example.net/"><?php echo _t('gen.action.see_website'); ?></a></li>
+ <li class="item"><a target="_blank" rel="noreferrer" href="http://example.net/"><?php echo _t('gen.action.see_website'); ?></a></li>
<?php if (FreshRSS_Auth::hasAccess()) { ?>
<li class="separator"></li>
<li class="item"><a href="<?php echo _url('subscription', 'index', 'id', '------'); ?>"><?php echo _t('gen.action.manage'); ?></a></li>
<li class="item"><a href="<?php echo _url('feed', 'actualize', 'id', '------'); ?>"><?php echo _t('gen.action.actualize'); ?></a></li>
<li class="item">
- <?php $confirm = FreshRSS_Context::$user_conf->reading_confirm ? 'confirm' : ''; ?>
+ <?php $confirm = FreshRSS_Context::$user_conf->reading_confirm ? 'confirm" disabled="disabled' : ''; ?>
<button class="read_all as-link <?php echo $confirm; ?>"
form="mark-read-aside"
formaction="<?php echo _url('entry', 'read', 'get', 'f_------'); ?>"
diff --git a/app/layout/aside_subscription.phtml b/app/layout/aside_subscription.phtml
index fa10d63e8..e14afe2a7 100644
--- a/app/layout/aside_subscription.phtml
+++ b/app/layout/aside_subscription.phtml
@@ -10,7 +10,7 @@
</li>
<li class="item">
- <a class="bookmarkClick" href="javascript:(function(){var%20url%20=%20location.href;window.open('<?php echo Minz_Url::display(array('c' => 'feed', 'a' => 'add'), 'html', true); ?>&amp;url_rss='+encodeURIComponent(url), '_blank');})();">
+ <a class="bookmarkClick" href="javascript:(function(){var%20url%20=%20location.href;var%20otherWindow=window.open('about:blank','_blank');otherWindow.opener=null;otherWindow.location='<?php echo Minz_Url::display(array('c' => 'feed', 'a' => 'add'), 'html', true); ?>&amp;url_rss='+encodeURIComponent(url);})();">
<?php echo _t('sub.menu.bookmark'); ?>
</a>
</li>
diff --git a/app/layout/layout.phtml b/app/layout/layout.phtml
index 4d9ad5458..1f11e0af1 100644
--- a/app/layout/layout.phtml
+++ b/app/layout/layout.phtml
@@ -42,6 +42,9 @@
} if (isset($this->rss_title)) {
$url_rss = $url_base;
$url_rss['a'] = 'rss';
+ if (FreshRSS_Context::$user_conf->since_hours_posts_per_rss) {
+ $url_rss['params']['hours'] = FreshRSS_Context::$user_conf->since_hours_posts_per_rss;
+ }
?>
<link rel="alternate" type="application/rss+xml" title="<?php echo $this->rss_title; ?>" href="<?php echo Minz_Url::display($url_rss); ?>" />
<?php } if (FreshRSS_Context::$system_conf->allow_robots) { ?>
diff --git a/app/layout/nav_menu.phtml b/app/layout/nav_menu.phtml
index 23255f04f..f6d824d55 100644
--- a/app/layout/nav_menu.phtml
+++ b/app/layout/nav_menu.phtml
@@ -83,7 +83,7 @@
<div class="stick" id="nav_menu_read_all">
<form id="mark-read-menu" method="post">
- <?php $confirm = FreshRSS_Context::$user_conf->reading_confirm ? 'confirm' : ''; ?>
+ <?php $confirm = FreshRSS_Context::$user_conf->reading_confirm ? 'confirm" disabled="disabled' : ''; ?>
<button class="read_all btn <?php echo $confirm; ?>"
form="mark-read-menu"
formaction="<?php echo Minz_Url::display($mark_read_url); ?>"
@@ -151,8 +151,11 @@
if (FreshRSS_Context::$user_conf->token) {
$url_output['params']['token'] = FreshRSS_Context::$user_conf->token;
}
+ if (FreshRSS_Context::$user_conf->since_hours_posts_per_rss) {
+ $url_output['params']['hours'] = FreshRSS_Context::$user_conf->since_hours_posts_per_rss;
+ }
?>
- <a class="view_rss btn" target="_blank" title="<?php echo _t('index.menu.rss_view'); ?>" href="<?php echo Minz_Url::display($url_output); ?>">
+ <a class="view_rss btn" target="_blank" rel="noreferrer" title="<?php echo _t('index.menu.rss_view'); ?>" href="<?php echo Minz_Url::display($url_output); ?>">
<?php echo _i('rss'); ?>
</a>
</div>
diff --git a/app/views/auth/index.phtml b/app/views/auth/index.phtml
index 74e692ec5..010eae33f 100644
--- a/app/views/auth/index.phtml
+++ b/app/views/auth/index.phtml
@@ -60,7 +60,7 @@
<input type="text" id="token" name="token" value="<?php echo $token; ?>" placeholder="<?php echo _t('gen.short.blank_to_disable'); ?>"<?php
echo FreshRSS_Auth::accessNeedsAction() ? '' : ' disabled="disabled"'; ?> data-leave-validation="<?php echo $token; ?>"/>
<?php echo _i('help'); ?> <?php echo _t('admin.auth.token_help'); ?>
- <kbd><?php echo Minz_Url::display(array('params' => array('output' => 'rss', 'token' => $token)), 'html', true); ?></kbd>
+ <kbd><?php echo Minz_Url::display(array('a' => 'rss', 'params' => array('token' => $token, 'hours' => FreshRSS_Context::$user_conf->since_hours_posts_per_rss)), 'html', true); ?></kbd>
</div>
</div>
<?php } ?>
diff --git a/app/views/configure/sharing.phtml b/app/views/configure/sharing.phtml
index 0dad5bf6d..ffcfb8b29 100644
--- a/app/views/configure/sharing.phtml
+++ b/app/views/configure/sharing.phtml
@@ -13,7 +13,7 @@
<input type="text" id="share_##key##_name" name="share[##key##][name]" class="extend" value="" 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('conf.sharing.share_url'); ?>" size="64" />
<a href="#" class="remove btn btn-attention" data-remove="group-share-##key##"><?php echo _i('close'); ?></a></div>
- <a target="_blank" class="btn" title="<?php echo _t('conf.sharing.more_information'); ?>" href="##help##"><?php echo _i('help'); ?></a>
+ <a target="_blank" rel="noreferrer" class="btn" title="<?php echo _t('conf.sharing.more_information'); ?>" href="##help##"><?php echo _i('help'); ?></a>
</div></div>'>
<input type="hidden" name="_csrf" value="<?php echo FreshRSS_Auth::csrfToken(); ?>" />
<legend><?php echo _t('conf.sharing'); ?></legend>
@@ -38,7 +38,7 @@
<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>
+ <a target="_blank" rel="noreferrer" 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/feed/add.phtml b/app/views/feed/add.phtml
index caf685d79..5cd59d298 100644
--- a/app/views/feed/add.phtml
+++ b/app/views/feed/add.phtml
@@ -30,7 +30,7 @@
<label class="group-name"><?php echo _t('sub.feed.website'); ?></label>
<div class="group-controls">
<?php echo $this->feed->website(); ?>
- <a class="btn" target="_blank" href="<?php echo $this->feed->website(); ?>"><?php echo _i('link'); ?></a>
+ <a class="btn" target="_blank" rel="noreferrer" href="<?php echo $this->feed->website(); ?>"><?php echo _i('link'); ?></a>
</div>
</div>
<?php } ?>
@@ -40,9 +40,9 @@
<div class="group-controls">
<div class="stick">
<input type="text" name="url_rss" id="url" class="extend" value="<?php echo $this->feed->url(); ?>" />
- <a class="btn" target="_blank" href="<?php echo $this->feed->url(); ?>"><?php echo _i('link'); ?></a>
+ <a class="btn" target="_blank" rel="noreferrer" href="<?php echo $this->feed->url(); ?>"><?php echo _i('link'); ?></a>
</div>
- <a class="btn" target="_blank" href="http://validator.w3.org/feed/check.cgi?url=<?php echo $this->feed->url(); ?>"><?php echo _t('sub.feed.validator'); ?></a>
+ <a class="btn" target="_blank" rel="noreferrer" href="http://validator.w3.org/feed/check.cgi?url=<?php echo $this->feed->url(); ?>"><?php echo _t('sub.feed.validator'); ?></a>
</div>
</div>
<div class="form-group">
diff --git a/app/views/helpers/feed/update.phtml b/app/views/helpers/feed/update.phtml
index b7e8f68cd..bf87a255a 100644
--- a/app/views/helpers/feed/update.phtml
+++ b/app/views/helpers/feed/update.phtml
@@ -37,7 +37,7 @@
<div class="group-controls">
<div class="stick">
<input type="text" name="website" id="website" class="extend" value="<?php echo $this->feed->website(); ?>" />
- <a class="btn" target="_blank" href="<?php echo $this->feed->website(); ?>"><?php echo _i('link'); ?></a>
+ <a class="btn" target="_blank" rel="noreferrer" href="<?php echo $this->feed->website(); ?>"><?php echo _i('link'); ?></a>
</div>
</div>
</div>
@@ -46,10 +46,10 @@
<div class="group-controls">
<div class="stick">
<input type="text" name="url" id="url" class="extend" value="<?php echo $this->feed->url(); ?>" />
- <a class="btn" target="_blank" href="<?php echo $this->feed->url(); ?>"><?php echo _i('link'); ?></a>
+ <a class="btn" target="_blank" rel="noreferrer" href="<?php echo $this->feed->url(); ?>"><?php echo _i('link'); ?></a>
</div>
- <a class="btn" target="_blank" href="http://validator.w3.org/feed/check.cgi?url=<?php echo rawurlencode(htmlspecialchars_decode($this->feed->url(), ENT_QUOTES)); ?>"><?php echo _t('sub.feed.validator'); ?></a>
+ <a class="btn" target="_blank" rel="noreferrer" href="http://validator.w3.org/feed/check.cgi?url=<?php echo rawurlencode(htmlspecialchars_decode($this->feed->url(), ENT_QUOTES)); ?>"><?php echo _t('sub.feed.validator'); ?></a>
</div>
</div>
<div class="form-group">
diff --git a/app/views/helpers/index/normal/entry_bottom.phtml b/app/views/helpers/index/normal/entry_bottom.phtml
index 3af7436c3..a9d5a80ca 100644
--- a/app/views/helpers/index/normal/entry_bottom.phtml
+++ b/app/views/helpers/index/normal/entry_bottom.phtml
@@ -52,7 +52,7 @@
$share_options['title'] = $title;
$share->update($share_options);
?><li class="item share">
- <a target="_blank" href="<?php echo $share->url(); ?>"><?php echo $share->name(); ?></a>
+ <a target="_blank" rel="noreferrer" href="<?php echo $share->url(); ?>"><?php echo $share->name(); ?></a>
</li><?php
}
?></ul>
@@ -81,6 +81,6 @@
?><li class="item date"><?php echo $this->entry->date(); ?></li><?php
}
if ($bottomline_link) {
- ?><li class="item link"><a target="_blank" href="<?php echo $this->entry->link(); ?>"><?php echo _i('link'); ?></a></li><?php
+ ?><li class="item link"><a target="_blank" rel="noreferrer" href="<?php echo $this->entry->link(); ?>"><?php echo _i('link'); ?></a></li><?php
} ?>
</ul>
diff --git a/app/views/helpers/index/normal/entry_header.phtml b/app/views/helpers/index/normal/entry_header.phtml
index dc544298f..86298e59f 100644
--- a/app/views/helpers/index/normal/entry_header.phtml
+++ b/app/views/helpers/index/normal/entry_header.phtml
@@ -27,7 +27,7 @@
}
}
?><li class="item website"><a href="<?php echo _url('index', 'index', 'get', 'f_' . $this->feed->id()); ?>"><img class="favicon" src="<?php echo $this->feed->favicon(); ?>" alt="✇" /> <span><?php echo $this->feed->name(); ?></span></a></li>
- <li class="item title"><a target="_blank" href="<?php echo $this->entry->link(); ?>"><?php echo $this->entry->title(); ?></a></li>
+ <li class="item title"><a target="_blank" rel="noreferrer" href="<?php echo $this->entry->link(); ?>"><?php echo $this->entry->title(); ?></a></li>
<?php if ($topline_date) { ?><li class="item date"><?php echo $this->entry->date(); ?> </li><?php } ?>
- <?php if ($topline_link) { ?><li class="item link"><a target="_blank" href="<?php echo $this->entry->link(); ?>"><?php echo _i('link'); ?></a></li><?php } ?>
+ <?php if ($topline_link) { ?><li class="item link"><a target="_blank" rel="noreferrer" href="<?php echo $this->entry->link(); ?>"><?php echo _i('link'); ?></a></li><?php } ?>
</ul>
diff --git a/app/views/helpers/pagination.phtml b/app/views/helpers/pagination.phtml
index 20957fc67..893451af9 100755
--- a/app/views/helpers/pagination.phtml
+++ b/app/views/helpers/pagination.phtml
@@ -1,6 +1,7 @@
<?php
$url_next = Minz_Request::currentRequest();
$url_next['params']['next'] = FreshRSS_Context::$next_id;
+ $url_next['params']['state'] = FreshRSS_Context::$state;
$url_next['params']['ajax'] = 1;
$url_mark_read = array(
@@ -26,7 +27,7 @@
</a>
<?php } elseif ($url_mark_read) { ?>
<button id="bigMarkAsRead"
- class="as-link <?php echo FreshRSS_Context::$user_conf->reading_confirm ? 'confirm' : ''; ?>"
+ class="as-link <?php echo FreshRSS_Context::$user_conf->reading_confirm ? 'confirm" disabled="disabled' : ''; ?>"
form="mark-read-pagination"
formaction="<?php echo Minz_Url::display($url_mark_read); ?>"
type="submit">
diff --git a/app/views/importExport/index.phtml b/app/views/importExport/index.phtml
index c0bc412c3..c5049e3ea 100644
--- a/app/views/importExport/index.phtml
+++ b/app/views/importExport/index.phtml
@@ -44,7 +44,7 @@
$select_args = ' size="' . min(10, count($this->feeds)) .'" multiple="multiple"';
}
?>
- <select name="export_feeds[]"<?php echo $select_args; ?>>
+ <select name="export_feeds[]"<?php echo $select_args; ?> size="10">
<?php echo extension_loaded('zip') ? '' : '<option></option>'; ?>
<?php foreach ($this->feeds as $feed) { ?>
<option value="<?php echo $feed->id(); ?>"><?php echo $feed->name(); ?></option>
diff --git a/app/views/index/global.phtml b/app/views/index/global.phtml
index 1e53e4f8c..f35732c8f 100644
--- a/app/views/index/global.phtml
+++ b/app/views/index/global.phtml
@@ -11,10 +11,13 @@
<div id="stream" class="global<?php echo $class; ?>">
<?php
+ $params = Minz_Request::fetchGET();
+ unset($params['c']);
+ unset($params['a']);
$url_base = array(
'c' => 'index',
'a' => 'normal',
- 'params' => Minz_Request::fetchGET(),
+ 'params' => $params,
);
foreach ($this->categories as $cat) {
diff --git a/app/views/index/normal.phtml b/app/views/index/normal.phtml
index 91ebcebd3..6fda11ed9 100644
--- a/app/views/index/normal.phtml
+++ b/app/views/index/normal.phtml
@@ -66,7 +66,7 @@ if (!empty($this->entries)) {
?><div class="flux_content">
<div class="content <?php echo $content_width; ?>">
- <h1 class="title"><a target="_blank" href="<?php echo $this->entry->link(); ?>"><?php echo $this->entry->title(); ?></a></h1>
+ <h1 class="title"><a target="_blank" rel="noreferrer" href="<?php echo $this->entry->link(); ?>"><?php echo $this->entry->title(); ?></a></h1>
<?php
$author = $this->entry->author();
echo $author != '' ? '<div class="author">' . _t('gen.short.by_author', $author) . '</div>' : '',
diff --git a/app/views/stats/index.phtml b/app/views/stats/index.phtml
index 0a2fbdb10..a36f812a8 100644
--- a/app/views/stats/index.phtml
+++ b/app/views/stats/index.phtml
@@ -23,18 +23,18 @@
</tr>
<tr>
<th><?php echo _t('admin.stats.status_read'); ?></th>
- <td class="numeric"><?php echo format_number($this->repartition['main_stream']['read']); ?></td>
- <td class="numeric"><?php echo format_number($this->repartition['all_feeds']['read']); ?></td>
+ <td class="numeric"><?php echo format_number($this->repartition['main_stream']['count_reads']); ?></td>
+ <td class="numeric"><?php echo format_number($this->repartition['all_feeds']['count_reads']); ?></td>
</tr>
<tr>
<th><?php echo _t('admin.stats.status_unread'); ?></th>
- <td class="numeric"><?php echo format_number($this->repartition['main_stream']['unread']); ?></td>
- <td class="numeric"><?php echo format_number($this->repartition['all_feeds']['unread']); ?></td>
+ <td class="numeric"><?php echo format_number($this->repartition['main_stream']['count_unreads']); ?></td>
+ <td class="numeric"><?php echo format_number($this->repartition['all_feeds']['count_unreads']); ?></td>
</tr>
<tr>
<th><?php echo _t('admin.stats.status_favorites'); ?></th>
- <td class="numeric"><?php echo format_number($this->repartition['main_stream']['favorite']); ?></td>
- <td class="numeric"><?php echo format_number($this->repartition['all_feeds']['favorite']); ?></td>
+ <td class="numeric"><?php echo format_number($this->repartition['main_stream']['count_favorites']); ?></td>
+ <td class="numeric"><?php echo format_number($this->repartition['all_feeds']['count_favorites']); ?></td>
</tr>
</tbody>
</table>
diff --git a/app/views/stats/repartition.phtml b/app/views/stats/repartition.phtml
index ffb2c361e..5ebcdce5a 100644
--- a/app/views/stats/repartition.phtml
+++ b/app/views/stats/repartition.phtml
@@ -12,7 +12,7 @@
if (!empty($feeds)) {
echo '<optgroup label="', $category->name(), '">';
foreach ($feeds as $feed) {
- if ($this->feed && $feed->id() == $this->feed->id()){
+ if ($this->feed && $feed->id() == $this->feed->id()) {
echo '<option value="', $feed->id(), '" selected="selected" data-url="', _url('stats', 'repartition', 'id', $feed->id()), '">', $feed->name(), '</option>';
} else {
echo '<option value="', $feed->id(), '" data-url="', _url('stats', 'repartition', 'id', $feed->id()), '">', $feed->name(), '</option>';
@@ -39,9 +39,9 @@
</tr>
<tr>
<td class="numeric"><?php echo $this->repartition['total']; ?></td>
- <td class="numeric"><?php echo $this->repartition['read']; ?></td>
- <td class="numeric"><?php echo $this->repartition['unread']; ?></td>
- <td class="numeric"><?php echo $this->repartition['favorite']; ?></td>
+ <td class="numeric"><?php echo $this->repartition['count_reads']; ?></td>
+ <td class="numeric"><?php echo $this->repartition['count_unreads']; ?></td>
+ <td class="numeric"><?php echo $this->repartition['count_favorites']; ?></td>
</tr>
</table>
</div>
diff --git a/app/views/user/manage.phtml b/app/views/user/manage.phtml
index aab3aa4c4..a32247d14 100644
--- a/app/views/user/manage.phtml
+++ b/app/views/user/manage.phtml
@@ -3,7 +3,7 @@
<div class="post">
<a href="<?php echo _url('index', 'index'); ?>"><?php echo _t('gen.action.back_to_rss_feeds'); ?></a>
- <form method="post" action="<?php echo _url('user', 'create'); ?>">
+ <form method="post" action="<?php echo _url('user', 'create'); ?>" autocomplete="off">
<input type="hidden" name="_csrf" value="<?php echo FreshRSS_Auth::csrfToken(); ?>" />
<legend><?php echo _t('admin.user.create'); ?></legend>
@@ -30,7 +30,7 @@
<label class="group-name" for="new_user_passwordPlain"><?php echo _t('admin.user.password_form'); ?></label>
<div class="group-controls">
<div class="stick">
- <input type="password" id="new_user_passwordPlain" name="new_user_passwordPlain" autocomplete="off" pattern=".{7,}" />
+ <input type="password" id="new_user_passwordPlain" name="new_user_passwordPlain" autocomplete="new-password" pattern=".{7,}" />
<a class="btn toggle-password" data-toggle="new_user_passwordPlain"><?php echo _i('key'); ?></a>
</div>
<?php echo _i('help'); ?> <?php echo _t('admin.user.password_format'); ?>