aboutsummaryrefslogtreecommitdiff
path: root/app/Controllers
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/Controllers
parent17c8c039df675b3b0f8d88d14f7316a240eabe76 (diff)
parent29e1f048159b7a580bdf1bab184e928f11d104b4 (diff)
Merge pull request #1346 from FreshRSS/dev1.6.0
Merge 1.6.0-dev in master
Diffstat (limited to 'app/Controllers')
-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
7 files changed, 560 insertions, 385 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();