From dbdda1d0c19b48d06b30879e8fe78679f79cc0c4 Mon Sep 17 00:00:00 2001 From: Marien Fressinaud Date: Sun, 16 Mar 2014 19:34:04 +0100 Subject: Move import/export operations into an independant class - import and export are now two methods of importExportController - "opml" has been removed from the title --- app/Controllers/importExportController.php | 77 ++++++++++++++++++++++++++++++ 1 file changed, 77 insertions(+) create mode 100644 app/Controllers/importExportController.php (limited to 'app/Controllers/importExportController.php') diff --git a/app/Controllers/importExportController.php b/app/Controllers/importExportController.php new file mode 100644 index 000000000..7ed142b33 --- /dev/null +++ b/app/Controllers/importExportController.php @@ -0,0 +1,77 @@ +view->loginOk) { + Minz_Error::error ( + 403, + array ('error' => array (Minz_Translate::t ('access_denied'))) + ); + } + + require_once(LIB_PATH . '/lib_opml.php'); + } + + public function indexAction() { + $catDAO = new FreshRSS_CategoryDAO (); + $this->view->categories = $catDAO->listCategories (); + + $feedDAO = new FreshRSS_FeedDAO (); + $this->view->feeds = $feedDAO->listFeeds (); + + // au niveau de la vue, permet de ne pas voir un flux sélectionné dans la liste + $this->view->flux = false; + + Minz_View::prependTitle (Minz_Translate::t ('import_export') . ' · '); + } + + public function importAction() { + if (Minz_Request::isPost() && $_FILES['file']['error'] == 0) { + invalidateHttpCache(); + // on parse le fichier OPML pour récupérer les catégories et les flux associés + try { + list ($categories, $feeds) = opml_import ( + file_get_contents ($_FILES['file']['tmp_name']) + ); + + // On redirige vers le controller feed qui va se charger d'insérer les flux en BDD + // les flux sont mis au préalable dans des variables de Request + Minz_Request::_param ('categories', $categories); + Minz_Request::_param ('feeds', $feeds); + Minz_Request::forward (array ('c' => 'feed', 'a' => 'massiveImport')); + } catch (FreshRSS_Opml_Exception $e) { + Minz_Log::record ($e->getMessage (), Minz_Log::WARNING); + + $notif = array ( + 'type' => 'bad', + 'content' => Minz_Translate::t ('bad_opml_file') + ); + Minz_Session::_param ('notification', $notif); + + Minz_Request::forward (array ( + 'c' => 'configure', + 'a' => 'importExport' + ), true); + } + } + } + + public function exportAction() { + Minz_View::_title ('freshrss_feeds.opml'); + + $this->view->_useLayout (false); + header('Content-Type: application/xml; charset=utf-8'); + header('Content-disposition: attachment; filename=freshrss_feeds.opml'); + + $feedDAO = new FreshRSS_FeedDAO (); + $catDAO = new FreshRSS_CategoryDAO (); + + $list = array (); + foreach ($catDAO->listCategories () as $key => $cat) { + $list[$key]['name'] = $cat->name (); + $list[$key]['feeds'] = $feedDAO->listByCategory ($cat->id ()); + } + + $this->view->categories = $list; + } +} -- cgit v1.2.3 From d1c5db7461ecb69c529149536718baf9b73a1f2c Mon Sep 17 00:00:00 2001 From: Marien Fressinaud Date: Tue, 18 Mar 2014 22:42:47 +0100 Subject: New version to export articles + opml It does not work yet! A lot of work has still to be done. Next versions should fix TODOs - OPML export works fine but can be improved - a framework has been created for articles export --- app/Controllers/importExportController.php | 48 +++++++++++++++++++++++++++--- app/views/helpers/export/articles.phtml | 30 +++++++++++++++++++ app/views/helpers/export/opml.phtml | 15 ++++++++++ app/views/importExport/export.phtml | 15 ---------- app/views/importExport/index.phtml | 33 ++++++++++++++++++-- 5 files changed, 119 insertions(+), 22 deletions(-) create mode 100644 app/views/helpers/export/articles.phtml create mode 100644 app/views/helpers/export/opml.phtml delete mode 100644 app/views/importExport/export.phtml (limited to 'app/Controllers/importExportController.php') diff --git a/app/Controllers/importExportController.php b/app/Controllers/importExportController.php index 7ed142b33..458814676 100644 --- a/app/Controllers/importExportController.php +++ b/app/Controllers/importExportController.php @@ -57,12 +57,40 @@ class FreshRSS_importExport_Controller extends Minz_ActionController { } public function exportAction() { - Minz_View::_title ('freshrss_feeds.opml'); + if (Minz_Request::isPost()) { + $this->view->_useLayout (false); - $this->view->_useLayout (false); - header('Content-Type: application/xml; charset=utf-8'); - header('Content-disposition: attachment; filename=freshrss_feeds.opml'); + $export_opml = Minz_Request::param('export_opml', false); + $export_starred = Minz_Request::param('export_starred', false); + $export_all = Minz_Request::param('export_all', false); + // code from https://stackoverflow.com/questions/1061710/php-zip-files-on-the-fly + $file = tempnam("tmp", "zip"); + $zip = new ZipArchive(); + $zip->open($file, ZipArchive::OVERWRITE); + + // Stuff with content + if ($export_opml) { + $zip->addFromString('feeds.opml', $this->generate_opml()); + } + if ($export_starred) { + $zip->addFromString('starred.json', $this->generate_articles('starred')); + } + if ($export_all) { + $zip->addFromString('all.json', $this->generate_articles('all')); + } + + // Close and send to users + $zip->close(); + header('Content-Type: application/zip'); + header('Content-Length: ' . filesize($file)); + header('Content-Disposition: attachment; filename="freshrss_export.zip"'); + readfile($file); + unlink($file); + } + } + + private function generate_opml() { $feedDAO = new FreshRSS_FeedDAO (); $catDAO = new FreshRSS_CategoryDAO (); @@ -73,5 +101,17 @@ class FreshRSS_importExport_Controller extends Minz_ActionController { } $this->view->categories = $list; + + // TODO: add a parameter to renderHelper in order to get a variable + ob_start(); + $this->view->renderHelper('export/opml'); + return ob_get_clean(); + } + + private function generate_articles($type) { + // TODO: same here + we should get articles according to $type + ob_start(); + $this->view->renderHelper('export/articles'); + return ob_get_clean(); } } diff --git a/app/views/helpers/export/articles.phtml b/app/views/helpers/export/articles.phtml new file mode 100644 index 000000000..b7df58caf --- /dev/null +++ b/app/views/helpers/export/articles.phtml @@ -0,0 +1,30 @@ +{ + "id": "user//state/org.freshrss/", + "title": "", + "author": "", + "items": [ + 1 ? ', ': ''; ?>{ + "id": "id(); ?>", + "categories": [], + "title": "title()); ?>", + "published": date(true); ?>, + "updated": date(true); ?>, + "content": "content()); ?>", + "origin": { + + "streamId": "", + "title": "", + "htmlUrl": "", + "feedUrl": "" + } + } + + ] +} \ No newline at end of file diff --git a/app/views/helpers/export/opml.phtml b/app/views/helpers/export/opml.phtml new file mode 100644 index 000000000..2e66e5054 --- /dev/null +++ b/app/views/helpers/export/opml.phtml @@ -0,0 +1,15 @@ +'; +?> + + + + <?php echo Minz_Configuration::title (); ?> OPML Feed + + + +categories); ?> + + diff --git a/app/views/importExport/export.phtml b/app/views/importExport/export.phtml deleted file mode 100644 index 2e66e5054..000000000 --- a/app/views/importExport/export.phtml +++ /dev/null @@ -1,15 +0,0 @@ -'; -?> - - - - <?php echo Minz_Configuration::title (); ?> OPML Feed - - - -categories); ?> - - diff --git a/app/views/importExport/index.phtml b/app/views/importExport/index.phtml index c7db752a7..d8c76543e 100644 --- a/app/views/importExport/index.phtml +++ b/app/views/importExport/index.phtml @@ -4,7 +4,7 @@
- +
@@ -15,8 +15,35 @@
- - +
+
+ + +
+ +
+
+ + + + + +
+
+ +
+
+
-- cgit v1.2.3 From 5081ffaf39699398f83be97e47b72444e5bcd5d1 Mon Sep 17 00:00:00 2001 From: Alexandre Alapetite Date: Sat, 22 Mar 2014 17:56:07 +0100 Subject: Minz: remove one layer of ob_ (experimental) https://github.com/marienfressinaud/FreshRSS/issues/303#issuecomment-38351311 https://github.com/marienfressinaud/FreshRSS/issues/163 * Remove Minz_Response (not needed anymore) * Move Minz_Request::reseted to Minz_Dispatcher::reset() --- app/Controllers/importExportController.php | 14 ++---- app/actualize_script.php | 1 - lib/Minz/Dispatcher.php | 73 +++++++++++++----------------- lib/Minz/Error.php | 32 +++++++++---- lib/Minz/FrontController.php | 16 +------ lib/Minz/Request.php | 5 +- lib/Minz/Response.php | 60 ------------------------ lib/Minz/View.php | 10 ++++ lib/lib_rss.php | 2 +- 9 files changed, 73 insertions(+), 140 deletions(-) delete mode 100644 lib/Minz/Response.php (limited to 'app/Controllers/importExportController.php') diff --git a/app/Controllers/importExportController.php b/app/Controllers/importExportController.php index 458814676..f697f4c9e 100644 --- a/app/Controllers/importExportController.php +++ b/app/Controllers/importExportController.php @@ -65,7 +65,7 @@ class FreshRSS_importExport_Controller extends Minz_ActionController { $export_all = Minz_Request::param('export_all', false); // code from https://stackoverflow.com/questions/1061710/php-zip-files-on-the-fly - $file = tempnam("tmp", "zip"); + $file = tempnam('tmp', 'zip'); $zip = new ZipArchive(); $zip->open($file, ZipArchive::OVERWRITE); @@ -101,17 +101,11 @@ class FreshRSS_importExport_Controller extends Minz_ActionController { } $this->view->categories = $list; - - // TODO: add a parameter to renderHelper in order to get a variable - ob_start(); - $this->view->renderHelper('export/opml'); - return ob_get_clean(); + return $this->view->helperToString('export/opml'); } private function generate_articles($type) { - // TODO: same here + we should get articles according to $type - ob_start(); - $this->view->renderHelper('export/articles'); - return ob_get_clean(); + // TODO: we should get articles according to $type + return $this->view->helperToString('export/articles'); } } diff --git a/app/actualize_script.php b/app/actualize_script.php index 8d81e0189..4c306b8da 100755 --- a/app/actualize_script.php +++ b/app/actualize_script.php @@ -28,7 +28,6 @@ foreach ($users as $myUser) { $_SERVER['HTTP_HOST'] = ''; $freshRSS = new FreshRSS(); - $freshRSS->_useOb(false); Minz_Configuration::_authType('none'); diff --git a/lib/Minz/Dispatcher.php b/lib/Minz/Dispatcher.php index 819f4cd5c..ca1fd1f5c 100644 --- a/lib/Minz/Dispatcher.php +++ b/lib/Minz/Dispatcher.php @@ -14,6 +14,7 @@ class Minz_Dispatcher { /* singleton */ private static $instance = null; + private static $needsReset; private $router; private $controller; @@ -40,44 +41,36 @@ class Minz_Dispatcher { * Remplit le body de Response à partir de la Vue * @exception Minz_Exception */ - public function run ($ob = true) { - // Le ob_start est dupliqué : sans ça il y a un bug sous Firefox - // ici on l'appelle avec 'ob_gzhandler', après sans. - // Vraisemblablement la compression fonctionne mais c'est sale - // J'ignore les effets de bord :( - if ($ob) { - ob_start ('ob_gzhandler'); - } - - $text = ''; //TODO: Clean this code - while (Minz_Request::$reseted) { - Minz_Request::$reseted = false; + public function run () { + do { + self::$needsReset = false; try { $this->createController ('FreshRSS_' . Minz_Request::controllerName () . '_Controller'); $this->controller->init (); $this->controller->firstAction (); - $this->launchAction ( - Minz_Request::actionName () - . 'Action' - ); + if (!self::$needsReset) { + $this->launchAction ( + Minz_Request::actionName () + . 'Action' + ); + } $this->controller->lastAction (); - if (!Minz_Request::$reseted) { - if ($ob) { - ob_start (); - } - $this->controller->view ()->build (); - if ($ob) { - $text = ob_get_clean(); - } + if (!self::$needsReset) { + echo $this->controller->view ()->build (); } } catch (Minz_Exception $e) { throw $e; } - } + } while (self::$needsReset); + } - Minz_Response::setBody ($text); + /** + * Informe le contrôleur qu'il doit recommancer car la requête a été modifiée + */ + public static function reset() { + self::$needsReset = true; } /** @@ -114,21 +107,19 @@ class Minz_Dispatcher { * le controller */ private function launchAction ($action_name) { - if (!Minz_Request::$reseted) { - if (!is_callable (array ( - $this->controller, - $action_name - ))) { - throw new Minz_ActionException ( - get_class ($this->controller), - $action_name, - Minz_Exception::ERROR - ); - } - call_user_func (array ( - $this->controller, - $action_name - )); + if (!is_callable (array ( + $this->controller, + $action_name + ))) { + throw new Minz_ActionException ( + get_class ($this->controller), + $action_name, + Minz_Exception::ERROR + ); } + call_user_func (array ( + $this->controller, + $action_name + )); } } diff --git a/lib/Minz/Error.php b/lib/Minz/Error.php index 337ab6c0a..c8222a430 100644 --- a/lib/Minz/Error.php +++ b/lib/Minz/Error.php @@ -23,13 +23,32 @@ class Minz_Error { $logs = self::processLogs ($logs); $error_filename = APP_PATH . '/Controllers/errorController.php'; + switch ($code) { + case 200 : + header('HTTP/1.1 200 OK'); + break; + case 403 : + header('HTTP/1.1 403 Forbidden'); + break; + case 404 : + header('HTTP/1.1 404 Not Found'); + break; + case 500 : + header('HTTP/1.1 500 Internal Server Error'); + break; + case 503 : + header('HTTP/1.1 503 Service Unavailable'); + break; + default : + header('HTTP/1.1 500 Internal Server Error'); + } + if (file_exists ($error_filename)) { $params = array ( 'code' => $code, 'logs' => $logs ); - Minz_Response::setHeader ($code); if ($redirect) { Minz_Request::forward (array ( 'c' => 'error' @@ -41,19 +60,16 @@ class Minz_Error { ), false); } } else { - $text = '

An error occured

'."\n"; + echo '

An error occured

' . "\n"; if (!empty ($logs)) { - $text .= '
    '."\n"; + echo '
      ' . "\n"; foreach ($logs as $log) { - $text .= '
    • ' . $log . '
    • '."\n"; + echo '
    • ' . $log . '
    • ' . "\n"; } - $text .= '
    '."\n"; + echo '
' . "\n"; } - Minz_Response::setHeader ($code); - Minz_Response::setBody ($text); - Minz_Response::send (); exit (); } } diff --git a/lib/Minz/FrontController.php b/lib/Minz/FrontController.php index 80eda8877..3e50db1cf 100644 --- a/lib/Minz/FrontController.php +++ b/lib/Minz/FrontController.php @@ -26,8 +26,6 @@ class Minz_FrontController { protected $dispatcher; protected $router; - private $useOb = true; - /** * Constructeur * Initialise le router et le dispatcher @@ -63,8 +61,7 @@ class Minz_FrontController { */ public function run () { try { - $this->dispatcher->run ($this->useOb); - Minz_Response::send (); + $this->dispatcher->run(); } catch (Minz_Exception $e) { try { Minz_Log::record ($e->getMessage (), Minz_Log::ERROR); @@ -96,15 +93,4 @@ class Minz_FrontController { } exit ('### Application problem ###
'."\n".$txt); } - - public function useOb() { - return $this->useOb; - } - - /** - * Use ob_start('ob_gzhandler') or not. - */ - public function _useOb($ob) { - return $this->useOb = (bool)$ob; - } } diff --git a/lib/Minz/Request.php b/lib/Minz/Request.php index 282d47a77..7e3c59990 100644 --- a/lib/Minz/Request.php +++ b/lib/Minz/Request.php @@ -15,8 +15,6 @@ class Minz_Request { private static $default_controller_name = 'index'; private static $default_action_name = 'index'; - public static $reseted = true; - /** * Getteurs */ @@ -137,14 +135,13 @@ class Minz_Request { header ('Location: ' . Minz_Url::display ($url, 'php')); exit (); } else { - self::$reseted = true; - self::_controllerName ($url['c']); self::_actionName ($url['a']); self::_params (array_merge ( self::$params, $url['params'] )); + Minz_Dispatcher::reset(); } } diff --git a/lib/Minz/Response.php b/lib/Minz/Response.php deleted file mode 100644 index f8ea3d946..000000000 --- a/lib/Minz/Response.php +++ /dev/null @@ -1,60 +0,0 @@ - -*/ - -/** - * Response représente la requête http renvoyée à l'utilisateur - */ -class Minz_Response { - private static $header = 'HTTP/1.0 200 OK'; - private static $body = ''; - - /** - * Mets à jour le body de la Response - * @param $text le texte à incorporer dans le body - */ - public static function setBody ($text) { - self::$body = $text; - } - - /** - * Mets à jour le header de la Response - * @param $code le code HTTP, valeurs possibles - * - 200 (OK) - * - 403 (Forbidden) - * - 404 (Forbidden) - * - 500 (Forbidden) -> par défaut si $code erroné - * - 503 (Forbidden) - */ - public static function setHeader ($code) { - switch ($code) { - case 200 : - self::$header = 'HTTP/1.0 200 OK'; - break; - case 403 : - self::$header = 'HTTP/1.0 403 Forbidden'; - break; - case 404 : - self::$header = 'HTTP/1.0 404 Not Found'; - break; - case 500 : - self::$header = 'HTTP/1.0 500 Internal Server Error'; - break; - case 503 : - self::$header = 'HTTP/1.0 503 Service Unavailable'; - break; - default : - self::$header = 'HTTP/1.0 500 Internal Server Error'; - } - } - - /** - * Envoie la Response à l'utilisateur - */ - public static function send () { - header (self::$header); - echo self::$body; - } -} diff --git a/lib/Minz/View.php b/lib/Minz/View.php index e170bd406..00d9a1a6d 100644 --- a/lib/Minz/View.php +++ b/lib/Minz/View.php @@ -102,6 +102,16 @@ class Minz_View { } } + /** + * Retourne renderHelper() dans une chaîne + * @param $helper l'élément à traîter + */ + public function helperToString($helper) { + ob_start(); + renderHelper($helper); + return ob_get_clean(); + } + /** * Permet de choisir si on souhaite utiliser le layout * @param $use true si on souhaite utiliser le layout, false sinon diff --git a/lib/lib_rss.php b/lib/lib_rss.php index 83edbf015..2077fe63f 100644 --- a/lib/lib_rss.php +++ b/lib/lib_rss.php @@ -27,7 +27,7 @@ function classAutoloader($class) { include(APP_PATH . '/Models/' . $components[1] . '.php'); return; case 3: //Controllers, Exceptions - include(APP_PATH . '/' . $components[2] . 's/' . $components[1] . $components[2] . '.php'); + @include(APP_PATH . '/' . $components[2] . 's/' . $components[1] . $components[2] . '.php'); return; } } elseif (strpos($class, 'Minz') === 0) { -- cgit v1.2.3 From 9d87f2f0aa8306c3e07a79d1a100b4d41ea8bc72 Mon Sep 17 00:00:00 2001 From: Marien Fressinaud Date: Thu, 27 Mar 2014 20:36:51 +0100 Subject: Export is fully implemented - Export list of feeds (OPML) - Export list of favourites (JSON) - Export list of articles per feed (JSON) --- app/Controllers/importExportController.php | 47 +++++++++++++++++----- app/i18n/en.php | 6 ++- app/i18n/fr.php | 6 ++- app/layout/aside_feed.phtml | 2 +- app/views/helpers/export/articles.phtml | 64 +++++++++++++++++------------- app/views/importExport/index.phtml | 10 ++--- lib/Minz/View.php | 2 +- 7 files changed, 90 insertions(+), 47 deletions(-) (limited to 'app/Controllers/importExportController.php') diff --git a/app/Controllers/importExportController.php b/app/Controllers/importExportController.php index f697f4c9e..040cf3f7b 100644 --- a/app/Controllers/importExportController.php +++ b/app/Controllers/importExportController.php @@ -13,16 +13,16 @@ class FreshRSS_importExport_Controller extends Minz_ActionController { } public function indexAction() { - $catDAO = new FreshRSS_CategoryDAO (); - $this->view->categories = $catDAO->listCategories (); + $catDAO = new FreshRSS_CategoryDAO(); + $this->view->categories = $catDAO->listCategories(); - $feedDAO = new FreshRSS_FeedDAO (); - $this->view->feeds = $feedDAO->listFeeds (); + $feedDAO = new FreshRSS_FeedDAO(); + $this->view->feeds = $feedDAO->listFeeds(); // au niveau de la vue, permet de ne pas voir un flux sélectionné dans la liste $this->view->flux = false; - Minz_View::prependTitle (Minz_Translate::t ('import_export') . ' · '); + Minz_View::prependTitle(Minz_Translate::t('import_export') . ' · '); } public function importAction() { @@ -62,7 +62,7 @@ class FreshRSS_importExport_Controller extends Minz_ActionController { $export_opml = Minz_Request::param('export_opml', false); $export_starred = Minz_Request::param('export_starred', false); - $export_all = Minz_Request::param('export_all', false); + $export_feeds = Minz_Request::param('export_feeds', false); // code from https://stackoverflow.com/questions/1061710/php-zip-files-on-the-fly $file = tempnam('tmp', 'zip'); @@ -76,11 +76,16 @@ class FreshRSS_importExport_Controller extends Minz_ActionController { if ($export_starred) { $zip->addFromString('starred.json', $this->generate_articles('starred')); } - if ($export_all) { - $zip->addFromString('all.json', $this->generate_articles('all')); + $feedDAO = new FreshRSS_FeedDAO (); + foreach ($export_feeds as $feed_id) { + $feed = $feedDAO->searchById($feed_id); + $zip->addFromString( + 'feed_' . $feed->category() . '_' . $feed->id() . '.json', + $this->generate_articles('feed', $feed) + ); } - // Close and send to users + // Close and send to user $zip->close(); header('Content-Type: application/zip'); header('Content-Length: ' . filesize($file)); @@ -104,8 +109,28 @@ class FreshRSS_importExport_Controller extends Minz_ActionController { return $this->view->helperToString('export/opml'); } - private function generate_articles($type) { - // TODO: we should get articles according to $type + private function generate_articles($type, $feed = NULL) { + $entryDAO = new FreshRSS_EntryDAO(); + + $catDAO = new FreshRSS_CategoryDAO(); + $this->view->categories = $catDAO->listCategories(); + + if ($type == 'starred') { + $this->view->list_title = Minz_Translate::t("starred_list"); + $this->view->type = 'starred'; + $this->view->entries = $entryDAO->listWhere( + 's', '', 'all', 'ASC', + $entryDAO->countUnreadReadFavorites()['all'] + ); + } elseif ($type == 'feed' && !is_null($feed)) { + $this->view->list_title = Minz_Translate::t("feed_list", $feed->name()); + $this->view->type = 'feed/' . $feed->id(); + $this->view->entries = $entryDAO->listWhere( + 'f', $feed->id(), 'all', 'ASC', + $this->view->conf->posts_per_page + ); + $this->view->feed = $feed; + } return $this->view->helperToString('export/articles'); } } diff --git a/app/i18n/en.php b/app/i18n/en.php index e9bed39a7..1a0c80249 100644 --- a/app/i18n/en.php +++ b/app/i18n/en.php @@ -137,9 +137,13 @@ return array ( 'auto_share' => 'Share', 'auto_share_help' => 'If there is only one sharing mode, it is used. Else modes are accessible by their number.', - 'file_to_import' => 'File to import', + 'file_to_import' => 'File to import (OPML)', 'import' => 'Import', 'export' => 'Export', + 'export_opml' => 'Export list of feeds (OPML)', + 'export_starred' => 'Export your favourites', + 'starred_list' => 'List of favourite articles', + 'feed_list' => 'List of %s articles', 'or' => 'or', 'informations' => 'Information', diff --git a/app/i18n/fr.php b/app/i18n/fr.php index 24f3741c8..0cc835142 100644 --- a/app/i18n/fr.php +++ b/app/i18n/fr.php @@ -137,9 +137,13 @@ return array ( 'auto_share' => 'Partager', 'auto_share_help' => 'Si il n’y a qu’un mode de partage, celui ci est utilisé automatiquement. Sinon ils sont accessibles par leur numéro.', - 'file_to_import' => 'Fichier à importer', + 'file_to_import' => 'Fichier à importer (OPML)', 'import' => 'Importer', 'export' => 'Exporter', + 'export_opml' => 'Exporter la liste des flux (OPML)', + 'export_starred' => 'Exporter les favoris', + 'starred_list' => 'Liste des articles favoris', + 'feed_list' => 'Liste des articles de %s', 'or' => 'ou', 'informations' => 'Informations', diff --git a/app/layout/aside_feed.phtml b/app/layout/aside_feed.phtml index 899cafe02..ae16efd96 100644 --- a/app/layout/aside_feed.phtml +++ b/app/layout/aside_feed.phtml @@ -43,7 +43,7 @@
-
  • +
  • diff --git a/app/views/helpers/export/articles.phtml b/app/views/helpers/export/articles.phtml index b7df58caf..71ac22f44 100644 --- a/app/views/helpers/export/articles.phtml +++ b/app/views/helpers/export/articles.phtml @@ -1,30 +1,40 @@ { - "id": "user//state/org.freshrss/", - "title": "", - "author": "", - "items": [ - 1 ? ', ': ''; ?>{ - "id": "id(); ?>", - "categories": [], - "title": "title()); ?>", - "published": date(true); ?>, - "updated": date(true); ?>, - "content": "content()); ?>", - "origin": { - - "streamId": "", - "title": "", - "htmlUrl": "", - "feedUrl": "" - } + + $articles = array( + 'id' => 'user/' . str_replace('/', '', $username) . '/state/org.freshrss/' . $this->type, + 'title' => $this->list_title, + 'author' => $username, + 'items' => array() + ); + + foreach ($this->entries as $entry) { + if (!isset($this->feed)) { + $feed = FreshRSS_CategoryDAO::findFeed($this->categories, $entry->feed ()); + } else { + $feed = $this->feed; } - - ] -} \ No newline at end of file + + $articles['items'][] = array( + 'id' => $entry->id(), + 'categories' => array_values($entry->tags()), + 'title' => $entry->title(), + 'published' => $entry->date(true), + 'updated' => $entry->date(true), + 'content' => $entry->content(), + 'origin' => array( + 'streamId' => $feed->id(), + 'title' => $feed->name(), + 'htmlUrl' => $feed->website(), + 'feedUrl' => $feed->url() + ) + ); + } + + $options = 0; + if (version_compare(PHP_VERSION, '5.4.0') >= 0) { + $options = JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE; + } + + echo json_encode($articles, $options); +?> diff --git a/app/views/importExport/index.phtml b/app/views/importExport/index.phtml index d8c76543e..b82e0d26b 100644 --- a/app/views/importExport/index.phtml +++ b/app/views/importExport/index.phtml @@ -33,11 +33,11 @@ - +
    diff --git a/lib/Minz/View.php b/lib/Minz/View.php index 00d9a1a6d..f034ab10b 100644 --- a/lib/Minz/View.php +++ b/lib/Minz/View.php @@ -108,7 +108,7 @@ class Minz_View { */ public function helperToString($helper) { ob_start(); - renderHelper($helper); + $this->renderHelper($helper); return ob_get_clean(); } -- cgit v1.2.3 From 05cc97a8f43d779fbea52f5ef47fe6a71c3ab692 Mon Sep 17 00:00:00 2001 From: Marien Fressinaud Date: Fri, 28 Mar 2014 18:44:52 +0100 Subject: Export form is not displayed if there is nothing to export --- app/Controllers/importExportController.php | 1 + app/views/importExport/index.phtml | 2 ++ 2 files changed, 3 insertions(+) (limited to 'app/Controllers/importExportController.php') diff --git a/app/Controllers/importExportController.php b/app/Controllers/importExportController.php index 040cf3f7b..51f0708fd 100644 --- a/app/Controllers/importExportController.php +++ b/app/Controllers/importExportController.php @@ -131,6 +131,7 @@ class FreshRSS_importExport_Controller extends Minz_ActionController { ); $this->view->feed = $feed; } + return $this->view->helperToString('export/articles'); } } diff --git a/app/views/importExport/index.phtml b/app/views/importExport/index.phtml index b82e0d26b..386c88648 100644 --- a/app/views/importExport/index.phtml +++ b/app/views/importExport/index.phtml @@ -19,6 +19,7 @@ + feeds) > 0) { ?>
    @@ -47,4 +48,5 @@
    + -- cgit v1.2.3 From d464833922e9cc00948698aed0a2af1f0b55e101 Mon Sep 17 00:00:00 2001 From: Marien Fressinaud Date: Fri, 28 Mar 2014 22:13:42 +0100 Subject: Improve import / export functionnality It is not finished yet and does not even work at all!! - ZIP archive can be uploaded - Entries are imported from starred.json and feed*.json but not added in DB for the moment. - Fix export (add author, id -> guid, content -> content.content and add alternate) See https://github.com/marienfressinaud/FreshRSS/issues/163 --- app/Controllers/feedController.php | 2 + app/Controllers/importExportController.php | 260 ++++++++++++++++++++++++----- app/views/helpers/export/articles.phtml | 11 +- 3 files changed, 228 insertions(+), 45 deletions(-) (limited to 'app/Controllers/importExportController.php') diff --git a/app/Controllers/feedController.php b/app/Controllers/feedController.php index 16516ad39..43435d99d 100755 --- a/app/Controllers/feedController.php +++ b/app/Controllers/feedController.php @@ -328,6 +328,8 @@ class FreshRSS_feed_Controller extends Minz_ActionController { } public function massiveImportAction () { + # TODO: this function has moved to importExportController.php + # I keep it for the moment but should be deleted in a near future. @set_time_limit(300); $this->catDAO = new FreshRSS_CategoryDAO (); diff --git a/app/Controllers/importExportController.php b/app/Controllers/importExportController.php index 51f0708fd..6194ce214 100644 --- a/app/Controllers/importExportController.php +++ b/app/Controllers/importExportController.php @@ -3,21 +3,22 @@ class FreshRSS_importExport_Controller extends Minz_ActionController { public function firstAction() { if (!$this->view->loginOk) { - Minz_Error::error ( + Minz_Error::error( 403, - array ('error' => array (Minz_Translate::t ('access_denied'))) + array('error' => array(Minz_Translate::t('access_denied'))) ); } require_once(LIB_PATH . '/lib_opml.php'); + + $this->catDAO = new FreshRSS_CategoryDAO(); + $this->entryDAO = new FreshRSS_EntryDAO(); + $this->feedDAO = new FreshRSS_FeedDAO(); } public function indexAction() { - $catDAO = new FreshRSS_CategoryDAO(); - $this->view->categories = $catDAO->listCategories(); - - $feedDAO = new FreshRSS_FeedDAO(); - $this->view->feeds = $feedDAO->listFeeds(); + $this->view->categories = $this->catDAO->listCategories(); + $this->view->feeds = $this->feedDAO->listFeeds(); // au niveau de la vue, permet de ne pas voir un flux sélectionné dans la liste $this->view->flux = false; @@ -27,38 +28,218 @@ class FreshRSS_importExport_Controller extends Minz_ActionController { public function importAction() { if (Minz_Request::isPost() && $_FILES['file']['error'] == 0) { - invalidateHttpCache(); - // on parse le fichier OPML pour récupérer les catégories et les flux associés + @set_time_limit(300); + + $file = $_FILES['file']; + $type_file = $this->guess_file_type($file['name']); + + $list_files = array( + 'opml' => array(), + 'json_starred' => array(), + 'json_feed' => array() + ); + + // We try to list all files according to their type + // A zip file is first opened and then its files are listed + $list = array(); + if ($type_file === 'zip') { + $zip = zip_open($file['tmp_name']); + + while (($zipfile = zip_read($zip)) !== false) { + $type_zipfile = $this->guess_file_type(zip_entry_name($zipfile)); + + if ($type_file !== 'unknown') { + $list_files[$type_zipfile][] = zip_entry_read( + $zipfile, + zip_entry_filesize($zipfile) + ); + } + } + + zip_close($zip); + } elseif ($type_file !== 'unknown') { + $list_files[$type_file][] = file_get_contents($file['tmp_name']); + } + + // Import different files. + // 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; + foreach ($list_files['opml'] as $opml_file) { + $error = $this->import_opml($opml_file); + } + foreach ($list_files['json_starred'] as $article_file) { + $error = $this->import_articles($article_file, true); + } + foreach ($list_files['json_feed'] as $article_file) { + $error = $this->import_articles($article_file); + } + + // And finally, we get import status and redirect to the home page + $notif = null; + if ($error === true) { + $notif = array( + 'type' => 'good', + 'content' => Minz_Translate::t('feeds_imported_with_errors') + ); + } else { + $notif = array( + 'type' => 'good', + 'content' => Minz_Translate::t('feeds_imported') + ); + } + + Minz_Session::_param('notification', $notif); + Minz_Session::_param('actualize_feeds', true); + + Minz_Request::forward(array( + 'c' => 'index', + 'a' => 'index' + ), true); + } + + // What are you doing? you have to call this controller + // with a POST request! + Minz_Request::forward(array( + 'c' => 'importExport', + 'a' => 'index' + )); + } + + private function guess_file_type($filename) { + // A *very* basic guess file type function. Only based on filename + // That's could be improved but should be enough, at least for a first + // implementation. + // TODO: improve this function? + + if (substr_compare($filename, '.zip', -4) === 0) { + return 'zip'; + } elseif (substr_compare($filename, '.opml', -5) === 0) { + return 'opml'; + } elseif (strcmp($filename, 'starred.json') === 0) { + return 'json_starred'; + } elseif (substr_compare($filename, '.json', -5) === 0 && + strpos($filename, 'feed_') === 0) { + return 'json_feed'; + } else { + return 'unknown'; + } + } + + private function import_opml($opml_file) { + $categories = array(); + $feeds = array(); + try { + list($categories, $feeds) = opml_import($opml_file); + } catch (FreshRSS_Opml_Exception $e) { + Minz_Log::warning($e->getMessage()); + return true; + } + + $this->catDAO->checkDefault(); + + // on ajoute les catégories en masse dans une fonction à part + $this->addCategories($categories); + + // on calcule la date des articles les plus anciens qu'on accepte + $nb_month_old = $this->view->conf->old_entries; + $date_min = time() -(3600 * 24 * 30 * $nb_month_old); + + // la variable $error permet de savoir si une erreur est survenue + // Le but est de ne pas arrêter l'import même en cas d'erreur + // L'utilisateur sera mis au courant s'il y a eu des erreurs, mais + // ne connaîtra pas les détails. Ceux-ci seront toutefois logguées + $error = false; + foreach ($feeds as $feed) { try { - list ($categories, $feeds) = opml_import ( - file_get_contents ($_FILES['file']['tmp_name']) + $values = array( + 'id' => $feed->id(), + 'url' => $feed->url(), + 'category' => $feed->category(), + 'name' => $feed->name(), + 'website' => $feed->website(), + 'description' => $feed->description(), + 'lastUpdate' => 0, + 'httpAuth' => $feed->httpAuth() ); - // On redirige vers le controller feed qui va se charger d'insérer les flux en BDD - // les flux sont mis au préalable dans des variables de Request - Minz_Request::_param ('categories', $categories); - Minz_Request::_param ('feeds', $feeds); - Minz_Request::forward (array ('c' => 'feed', 'a' => 'massiveImport')); - } catch (FreshRSS_Opml_Exception $e) { - Minz_Log::record ($e->getMessage (), Minz_Log::WARNING); - - $notif = array ( - 'type' => 'bad', - 'content' => Minz_Translate::t ('bad_opml_file') + // ajout du flux que s'il n'est pas déjà en BDD + if (!$this->feedDAO->searchByUrl($values['url'])) { + $id = $this->feedDAO->addFeed($values); + if ($id) { + $feed->_id($id); + $feed->faviconPrepare(); + } else { + $error = true; + } + } + } catch (FreshRSS_Feed_Exception $e) { + $error = true; + Minz_Log::record($e->getMessage(), Minz_Log::WARNING); + } + } + + return $error; + } + + private function addCategories($categories) { + foreach ($categories as $cat) { + if (!$this->catDAO->searchByName($cat->name())) { + $values = array( + 'id' => $cat->id(), + 'name' => $cat->name(), ); - Minz_Session::_param ('notification', $notif); + $this->catDAO->addCategory($values); + } + } + } + + private function import_articles($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; + } + + $google_compliant = (strpos($article_object['id'], 'com.google') !== false); + + foreach ($article_object['items'] as $item) { + $key = $google_compliant ? 'htmlUrl' : 'feedUrl'; + $feed = $this->feedDAO->searchByUrl($item['origin'][$key]); + if (is_null($feed)) { + $feed = new FreshRSS_Feed($item['origin'][$key]); + $feed->_name ($item['origin']['title']); + $feed->_website ($item['origin']['htmlUrl']); + + $error = $this->addFeed($feed); // TODO - Minz_Request::forward (array ( - 'c' => 'configure', - 'a' => 'importExport' - ), true); + if ($error) { + continue; + } } + + $author = isset($item['author']) ? $item['author'] : ''; + $key_content = $google_compliant && !isset($item['content']) ? 'summary' : 'content'; + $tags = $item['categories']; + if ($google_compliant) { + $tags = array_filter($tags, function($var) { + return strpos($var, '/state/com.google') === false; + }); + } + $entry = new FreshRSS_Entry( + $feed->id(), $item['id'], $item['title'], $author, + $item[$key_content]['content'], $item['alternate'][0]['href'], + $item['published'], false, $starred, $tags + ); + + Minz_Log::debug(print_r($entry, true)); // TODO } } public function exportAction() { if (Minz_Request::isPost()) { - $this->view->_useLayout (false); + $this->view->_useLayout(false); $export_opml = Minz_Request::param('export_opml', false); $export_starred = Minz_Request::param('export_starred', false); @@ -76,9 +257,8 @@ class FreshRSS_importExport_Controller extends Minz_ActionController { if ($export_starred) { $zip->addFromString('starred.json', $this->generate_articles('starred')); } - $feedDAO = new FreshRSS_FeedDAO (); foreach ($export_feeds as $feed_id) { - $feed = $feedDAO->searchById($feed_id); + $feed = $this->feedDAO->searchById($feed_id); $zip->addFromString( 'feed_' . $feed->category() . '_' . $feed->id() . '.json', $this->generate_articles('feed', $feed) @@ -96,13 +276,10 @@ class FreshRSS_importExport_Controller extends Minz_ActionController { } private function generate_opml() { - $feedDAO = new FreshRSS_FeedDAO (); - $catDAO = new FreshRSS_CategoryDAO (); - - $list = array (); - foreach ($catDAO->listCategories () as $key => $cat) { - $list[$key]['name'] = $cat->name (); - $list[$key]['feeds'] = $feedDAO->listByCategory ($cat->id ()); + $list = array(); + foreach ($this->catDAO->listCategories() as $key => $cat) { + $list[$key]['name'] = $cat->name(); + $list[$key]['feeds'] = $this->feedDAO->listByCategory($cat->id()); } $this->view->categories = $list; @@ -110,22 +287,19 @@ class FreshRSS_importExport_Controller extends Minz_ActionController { } private function generate_articles($type, $feed = NULL) { - $entryDAO = new FreshRSS_EntryDAO(); - - $catDAO = new FreshRSS_CategoryDAO(); - $this->view->categories = $catDAO->listCategories(); + $this->view->categories = $this->catDAO->listCategories(); if ($type == 'starred') { $this->view->list_title = Minz_Translate::t("starred_list"); $this->view->type = 'starred'; - $this->view->entries = $entryDAO->listWhere( + $this->view->entries = $this->entryDAO->listWhere( 's', '', 'all', 'ASC', $entryDAO->countUnreadReadFavorites()['all'] ); } elseif ($type == 'feed' && !is_null($feed)) { $this->view->list_title = Minz_Translate::t("feed_list", $feed->name()); $this->view->type = 'feed/' . $feed->id(); - $this->view->entries = $entryDAO->listWhere( + $this->view->entries = $this->entryDAO->listWhere( 'f', $feed->id(), 'all', 'ASC', $this->view->conf->posts_per_page ); diff --git a/app/views/helpers/export/articles.phtml b/app/views/helpers/export/articles.phtml index 71ac22f44..ffdca1daa 100644 --- a/app/views/helpers/export/articles.phtml +++ b/app/views/helpers/export/articles.phtml @@ -16,12 +16,19 @@ } $articles['items'][] = array( - 'id' => $entry->id(), + 'id' => $entry->guid(), 'categories' => array_values($entry->tags()), 'title' => $entry->title(), + 'author' => $entry->author(), 'published' => $entry->date(true), 'updated' => $entry->date(true), - 'content' => $entry->content(), + 'alternate' => array(array( + 'href' => $entry->link(), + 'type' => 'text/html' + )), + 'content' => array( + 'content' => $entry->content() + ), 'origin' => array( 'streamId' => $feed->id(), 'title' => $feed->name(), -- cgit v1.2.3 From 7676a197a4767f735dabae6ad9cf40ef65e91aa7 Mon Sep 17 00:00:00 2001 From: Marien Fressinaud Date: Sat, 29 Mar 2014 19:10:18 +0100 Subject: Fix typo + improve detection OPML file --- app/Controllers/importExportController.php | 7 ++++--- app/views/importExport/index.phtml | 2 +- 2 files changed, 5 insertions(+), 4 deletions(-) (limited to 'app/Controllers/importExportController.php') diff --git a/app/Controllers/importExportController.php b/app/Controllers/importExportController.php index 6194ce214..cbadeb6ca 100644 --- a/app/Controllers/importExportController.php +++ b/app/Controllers/importExportController.php @@ -115,7 +115,8 @@ class FreshRSS_importExport_Controller extends Minz_ActionController { if (substr_compare($filename, '.zip', -4) === 0) { return 'zip'; - } elseif (substr_compare($filename, '.opml', -5) === 0) { + } elseif (substr_compare($filename, '.opml', -5) === 0 || + substr_compare($filename, '.xml', -4) === 0) { return 'opml'; } elseif (strcmp($filename, 'starred.json') === 0) { return 'json_starred'; @@ -144,7 +145,7 @@ class FreshRSS_importExport_Controller extends Minz_ActionController { // on calcule la date des articles les plus anciens qu'on accepte $nb_month_old = $this->view->conf->old_entries; - $date_min = time() -(3600 * 24 * 30 * $nb_month_old); + $date_min = time() - (3600 * 24 * 30 * $nb_month_old); // la variable $error permet de savoir si une erreur est survenue // Le but est de ne pas arrêter l'import même en cas d'erreur @@ -294,7 +295,7 @@ class FreshRSS_importExport_Controller extends Minz_ActionController { $this->view->type = 'starred'; $this->view->entries = $this->entryDAO->listWhere( 's', '', 'all', 'ASC', - $entryDAO->countUnreadReadFavorites()['all'] + $this->entryDAO->countUnreadReadFavorites()['all'] ); } elseif ($type == 'feed' && !is_null($feed)) { $this->view->list_title = Minz_Translate::t("feed_list", $feed->name()); diff --git a/app/views/importExport/index.phtml b/app/views/importExport/index.phtml index 386c88648..309058959 100644 --- a/app/views/importExport/index.phtml +++ b/app/views/importExport/index.phtml @@ -34,7 +34,7 @@ - feeds as $feed) { ?> -- cgit v1.2.3 From 9ea3819402746d8425d4a608f2d5f3c0f5bc29fb Mon Sep 17 00:00:00 2001 From: Marien Fressinaud Date: Sat, 29 Mar 2014 20:18:57 +0100 Subject: Better OPML import / export - use a new OPML library (https://github.com/marienfressinaud/lib_opml) - import has been completely rewritten (far better!) - introduce addFeedObject and addCategoryObject (in DAO for the moment). Permit to add easily feeds and categories (check if they already exist in DB) - introduce html_chars_utf8 (wrap htmlspecialchars for UTF-8) --- app/Controllers/importExportController.php | 124 ++++++++----- app/Exceptions/OpmlException.php | 6 - app/Models/CategoryDAO.php | 12 ++ app/Models/FeedDAO.php | 29 +++ app/views/helpers/export/opml.phtml | 43 +++-- lib/lib_opml.php | 277 ++++++++++++++++++++--------- lib/lib_rss.php | 4 + 7 files changed, 345 insertions(+), 150 deletions(-) delete mode 100644 app/Exceptions/OpmlException.php (limited to 'app/Controllers/importExportController.php') diff --git a/app/Controllers/importExportController.php b/app/Controllers/importExportController.php index cbadeb6ca..b6b4d0fed 100644 --- a/app/Controllers/importExportController.php +++ b/app/Controllers/importExportController.php @@ -129,71 +129,101 @@ class FreshRSS_importExport_Controller extends Minz_ActionController { } private function import_opml($opml_file) { - $categories = array(); - $feeds = array(); + $opml_array = array(); try { - list($categories, $feeds) = opml_import($opml_file); - } catch (FreshRSS_Opml_Exception $e) { + $opml_array = libopml_parse_string($opml_file); + } catch (LibOPML_Exception $e) { Minz_Log::warning($e->getMessage()); return true; } $this->catDAO->checkDefault(); - // on ajoute les catégories en masse dans une fonction à part - $this->addCategories($categories); - - // on calcule la date des articles les plus anciens qu'on accepte - $nb_month_old = $this->view->conf->old_entries; - $date_min = time() - (3600 * 24 * 30 * $nb_month_old); + return $this->addOpmlElements($opml_array['body']); + } - // la variable $error permet de savoir si une erreur est survenue - // Le but est de ne pas arrêter l'import même en cas d'erreur - // L'utilisateur sera mis au courant s'il y a eu des erreurs, mais - // ne connaîtra pas les détails. Ceux-ci seront toutefois logguées + private function addOpmlElements($opml_elements, $parent_cat = null) { $error = false; - foreach ($feeds as $feed) { - try { - $values = array( - 'id' => $feed->id(), - 'url' => $feed->url(), - 'category' => $feed->category(), - 'name' => $feed->name(), - 'website' => $feed->website(), - 'description' => $feed->description(), - 'lastUpdate' => 0, - 'httpAuth' => $feed->httpAuth() - ); + foreach ($opml_elements as $elt) { + $res = false; + if (isset($elt['xmlUrl'])) { + $res = $this->addFeedOpml($elt, $parent_cat); + } else { + $res = $this->addCategoryOpml($elt, $parent_cat); + } - // ajout du flux que s'il n'est pas déjà en BDD - if (!$this->feedDAO->searchByUrl($values['url'])) { - $id = $this->feedDAO->addFeed($values); - if ($id) { - $feed->_id($id); - $feed->faviconPrepare(); - } else { - $error = true; - } - } - } catch (FreshRSS_Feed_Exception $e) { - $error = true; - Minz_Log::record($e->getMessage(), Minz_Log::WARNING); + if (!$error && $res) { + // oops: there is at least one error! + $error = $res; } } return $error; } - private function addCategories($categories) { - foreach ($categories as $cat) { - if (!$this->catDAO->searchByName($cat->name())) { - $values = array( - 'id' => $cat->id(), - 'name' => $cat->name(), - ); - $this->catDAO->addCategory($values); + private function addFeedOpml($feed_elt, $parent_cat) { + if (is_null($parent_cat)) { + // This feed has no parent category so we get the default one + $parent_cat = $catDAO->getDefault()->name(); + } + + $cat = $this->catDAO->searchByName($parent_cat); + + if (!$cat) { + return true; + } + + // We get different useful information + $url = html_chars_utf8($feed_elt['xmlUrl']); + $name = html_chars_utf8($feed_elt['text']); + $website = ''; + if (isset($feed_elt['htmlUrl'])) { + $website = html_chars_utf8($feed_elt['htmlUrl']); + } + $description = ''; + if (isset($feed_elt['description'])) { + $description = html_chars_utf8($feed_elt['description']); + } + + $error = false; + try { + // Create a Feed object and add it in DB + $feed = new FreshRSS_Feed($url); + $feed->_category($cat->id()); + $feed->_name($name); + $feed->_website($website); + $feed->_description($description); + + // addFeedObject checks if feed is already in DB so nothing else to + // check here + $id = $this->feedDAO->addFeedObject($feed); + $error = ($id === false); + } catch (FreshRSS_Feed_Exception $e) { + Minz_Log::record($e->getMessage(), Minz_Log::WARNING); + $error = true; + } + + return $error; + } + + private function addCategoryOpml($cat_elt, $parent_cat) { + // Create a new Category object + $cat = new FreshRSS_Category(html_chars_utf8($cat_elt['text'])); + + $id = $this->catDAO->addCategoryObject($cat); + $error = ($id === false); + + 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; } } + + return $error; } private function import_articles($article_file, $starred = false) { diff --git a/app/Exceptions/OpmlException.php b/app/Exceptions/OpmlException.php deleted file mode 100644 index e0ea3e493..000000000 --- a/app/Exceptions/OpmlException.php +++ /dev/null @@ -1,6 +0,0 @@ -searchByName($category->name())) { + // Category does not exist yet in DB so we add it before continue + $values = array( + 'name' => $category->name(), + ); + return $this->addCategory($values); + } + + return false; + } + public function updateCategory ($id, $valuesTmp) { $sql = 'UPDATE `' . $this->prefix . 'category` SET name=? WHERE id=?'; $stm = $this->bd->prepare ($sql); diff --git a/app/Models/FeedDAO.php b/app/Models/FeedDAO.php index ca25c3aeb..eac21df7e 100644 --- a/app/Models/FeedDAO.php +++ b/app/Models/FeedDAO.php @@ -24,6 +24,35 @@ class FreshRSS_FeedDAO extends Minz_ModelPdo { } } + public function addFeedObject($feed) { + // TODO: not sure if we should write this method in DAO since DAO + // should not be aware about feed class + + // Add feed only if we don't find it in DB + if (!$this->searchByUrl($feed->url())) { + $values = array( + 'id' => $feed->id(), + 'url' => $feed->url(), + 'category' => $feed->category(), + 'name' => $feed->name(), + 'website' => $feed->website(), + 'description' => $feed->description(), + 'lastUpdate' => 0, + 'httpAuth' => $feed->httpAuth() + ); + + $id = $this->addFeed($values); + if ($id) { + $feed->_id($id); + $feed->faviconPrepare(); + } + + return $id; + } + + return false; + } + public function updateFeed ($id, $valuesTmp) { $set = ''; foreach ($valuesTmp as $key => $v) { diff --git a/app/views/helpers/export/opml.phtml b/app/views/helpers/export/opml.phtml index 2e66e5054..adbac904d 100644 --- a/app/views/helpers/export/opml.phtml +++ b/app/views/helpers/export/opml.phtml @@ -1,15 +1,30 @@ '; -?> - - - - <?php echo Minz_Configuration::title (); ?> OPML Feed - - - -categories); ?> - - + +$opml_array = array( + 'head' => array( + 'title' => Minz_Configuration::title(), + 'dateCreated' => date('D, d M Y H:i:s') + ), + 'body' => array() +); + +foreach ($this->categories as $key => $cat) { + $opml_array['body'][$key] = array( + 'text' => $cat['name'], + '@outlines' => array() + ); + + foreach ($cat['feeds'] as $feed) { + $opml_array['body'][$key]['@outlines'][] = array( + 'text' => $feed->name(), + 'type' => 'rss', + 'xmlUrl' => $feed->url(), + 'htmlUrl' => $feed->website(), + 'description' => htmlspecialchars( + $feed->description(), ENT_COMPAT, 'UTF-8' + ) + ); + } +} + +echo libopml_render($opml_array); diff --git a/lib/lib_opml.php b/lib/lib_opml.php index 05e54d85e..16a9921ea 100644 --- a/lib/lib_opml.php +++ b/lib/lib_opml.php @@ -1,23 +1,86 @@ ' . "\n"; - - foreach ($cat['feeds'] as $feed) { - $txt .= "\t" . '' . "\n"; +/* * + * lib_opml is a free library to manage OPML format in PHP. + * It takes in consideration only version 2.0 (http://dev.opml.org/spec2.html). + * Basically it means "text" attribute for outline elements is required. + * + * lib_opml requires SimpleXML (http://php.net/manual/en/book.simplexml.php) + * + * Usages: + * > include('lib_opml.php'); + * > $filename = 'my_opml_file.xml'; + * > $opml_array = libopml_parse_file($filename); + * > print_r($opml_array); + * + * > $opml_string = [...]; + * > $opml_array = libopml_parse_string($opml_string); + * > print_r($opml_array); + * + * > $opml_array = [...]; + * > $opml_string = libopml_render($opml_array); + * > $opml_object = libopml_render($opml_array, true); + * > echo $opml_string; + * > print_r($opml_object); + * + * If parsing fails for any reason (e.g. not an XML string, does not match with + * the specifications), a LibOPML_Exception is raised. + * + * Author: Marien Fressinaud + * Url: https://github.com/marienfressinaud/lib_opml + * Version: 0.1 + * Date: 2014-03-29 + * License: public domain + * + * */ + +class LibOPML_Exception extends Exception {} + + +// These elements are optional +define('HEAD_ELEMENTS', serialize(array( + 'title', 'dateCreated', 'dateModified', 'ownerName', 'ownerEmail', + 'ownerId', 'docs', 'expansionState', 'vertScrollState', 'windowTop', + 'windowLeft', 'windowBottom', 'windowRight' +))); + + +function libopml_parse_outline($outline_xml) { + $outline = array(); + + // An outline may contain any kind of attributes but "text" attribute is + // required ! + $text_is_present = false; + foreach ($outline_xml->attributes() as $key => $value) { + $outline[$key] = (string)$value; + + if ($key === 'text') { + $text_is_present = true; } + } - $txt .= '' . "\n"; + if (!$text_is_present) { + throw new LibOPML_Exception( + 'Outline does not contain any text attribute' + ); } - return $txt; + foreach ($outline_xml->children() as $key => $value) { + // An outline may contain any number of outline children + if ($key === 'outline') { + $outline['@outlines'][] = libopml_parse_outline($value); + } else { + throw new LibOPML_Exception( + 'Body can contain only outline elements' + ); + } + } + + return $outline; } -function opml_import ($xml) { - $xml = html_only_entity_decode($xml); //!\ Assume UTF-8 +function libopml_parse_string($xml) { $dom = new DOMDocument(); $dom->recover = true; $dom->strictErrorChecking = false; @@ -27,94 +90,142 @@ function opml_import ($xml) { $opml = simplexml_import_dom($dom); if (!$opml) { - throw new FreshRSS_Opml_Exception (); + throw new LibOPML_Exception(); } - $catDAO = new FreshRSS_CategoryDAO(); - $catDAO->checkDefault(); - $defCat = $catDAO->getDefault(); + $array = array( + 'version' => (string)$opml['version'], + 'head' => array(), + 'body' => array() + ); + + // First, we get all "head" elements. Head is required but its sub-elements + // are optional. + foreach ($opml->head->children() as $key => $value) { + if (in_array($key, unserialize(HEAD_ELEMENTS), true)) { + $array['head'][$key] = (string)$value; + } else { + throw new LibOPML_Exception( + $key . 'is not part of OPML format' + ); + } + } - $categories = array (); - $feeds = array (); + // Then, we get body oulines. Body must contain at least one outline + // element. + $at_least_one_outline = false; + foreach ($opml->body->children() as $key => $value) { + if ($key === 'outline') { + $at_least_one_outline = true; + $array['body'][] = libopml_parse_outline($value); + } else { + throw new LibOPML_Exception( + 'Body can contain only outline elements' + ); + } + } + + if (!$at_least_one_outline) { + throw new LibOPML_Exception( + 'Body must contain at least one outline element' + ); + } - foreach ($opml->body->outline as $outline) { - if (!isset ($outline['xmlUrl'])) { - // Catégorie - $title = ''; + return $array; +} - if (isset ($outline['text'])) { - $title = (string) $outline['text']; - } elseif (isset ($outline['title'])) { - $title = (string) $outline['title']; - } - if ($title) { - // Permet d'éviter les soucis au niveau des id : - // ceux-ci sont générés en fonction de la date, - // un flux pourrait être dans une catégorie X avec l'id Y - // alors qu'il existe déjà la catégorie X mais avec l'id Z - // Y ne sera pas ajouté et le flux non plus vu que l'id - // de sa catégorie n'exisera pas - $title = htmlspecialchars($title, ENT_COMPAT, 'UTF-8'); - $catDAO = new FreshRSS_CategoryDAO (); - $cat = $catDAO->searchByName ($title); - if ($cat == null) { - $cat = new FreshRSS_Category ($title); - $values = array ( - 'name' => $cat->name () - ); - $cat->_id ($catDAO->addCategory ($values)); - } - - $feeds = array_merge ($feeds, getFeedsOutline ($outline, $cat->id ())); +function libopml_parse_file($filename) { + $file_content = file_get_contents($filename); + + if ($file_content === false) { + throw new LibOPML_Exception( + $filename . ' cannot be found' + ); + } + + return libopml_parse_string($file_content); +} + + +function libopml_render_outline($parent_elt, $outline) { + // Outline MUST be an array! + if (!is_array($outline)) { + throw new LibOPML_Exception( + 'Outline element must be defined as array' + ); + } + + $outline_elt = $parent_elt->addChild('outline'); + $text_is_present = false; + foreach ($outline as $key => $value) { + // Only outlines can be an array and so we consider children are also + // outline elements. + if ($key === '@outlines' && is_array($value)) { + foreach ($value as $outline_child) { + libopml_render_outline($outline_elt, $outline_child); } + } elseif (is_array($value)) { + throw new LibOPML_Exception( + 'Type of outline elements cannot be array: ' . $key + ); } else { - // Flux rss sans catégorie, on récupère l'ajoute dans la catégorie par défaut - $feeds[] = getFeed ($outline, $defCat->id()); + // Detect text attribute is present, that's good :) + if ($key === 'text') { + $text_is_present = true; + } + + $outline_elt->addAttribute($key, $value); } } - return array ($categories, $feeds); + if (!$text_is_present) { + throw new LibOPML_Exception( + 'You must define at least a text element for all outlines' + ); + } } -/** - * import all feeds of a given outline tag - */ -function getFeedsOutline ($outline, $cat_id) { - $feeds = array (); - foreach ($outline->children () as $child) { - if (isset ($child['xmlUrl'])) { - $feeds[] = getFeed ($child, $cat_id); - } else { - $feeds = array_merge( - $feeds, - getFeedsOutline ($child, $cat_id) - ); +function libopml_render($array, $as_xml_object = false) { + $opml = new SimpleXMLElement(''); + + // Create head element. $array['head'] is optional but head element will + // exist in the final XML object. + $head = $opml->addChild('head'); + if (isset($array['head'])) { + foreach ($array['head'] as $key => $value) { + if (in_array($key, unserialize(HEAD_ELEMENTS), true)) { + $head->addChild($key, $value); + } } } - return $feeds; -} + // Check body is set and contains at least one element + if (!isset($array['body'])) { + throw new LibOPML_Exception( + '$array must contain a body element' + ); + } + if (count($array['body']) <= 0) { + throw new LibOPML_Exception( + 'Body element must contain at least one element (array)' + ); + } -function getFeed ($outline, $cat_id) { - $url = (string) $outline['xmlUrl']; - $url = htmlspecialchars($url, ENT_COMPAT, 'UTF-8'); - $title = ''; - if (isset ($outline['text'])) { - $title = (string) $outline['text']; - } elseif (isset ($outline['title'])) { - $title = (string) $outline['title']; - } - $title = htmlspecialchars($title, ENT_COMPAT, 'UTF-8'); - $feed = new FreshRSS_Feed ($url); - $feed->_category ($cat_id); - $feed->_name ($title); - if (isset($outline['htmlUrl'])) { - $feed->_website(htmlspecialchars((string)$outline['htmlUrl'], ENT_COMPAT, 'UTF-8')); - } - if (isset($outline['description'])) { - $feed->_description(sanitizeHTML((string)$outline['description'])); - } - return $feed; + // Create outline elements + $body = $opml->addChild('body'); + foreach ($array['body'] as $outline) { + libopml_render_outline($body, $outline); + } + + // And return the final result + if ($as_xml_object) { + return $opml; + } else { + $dom = dom_import_simplexml($opml)->ownerDocument; + $dom->formatOutput = true; + $dom->encoding = 'UTF-8'; + return $dom->saveXML(); + } } diff --git a/lib/lib_rss.php b/lib/lib_rss.php index 2077fe63f..0f8161129 100644 --- a/lib/lib_rss.php +++ b/lib/lib_rss.php @@ -244,3 +244,7 @@ function cryptAvailable() { } return false; } + +function html_chars_utf8($str) { + return htmlspecialchars($str, ENT_COMPAT, 'UTF-8'); +} -- cgit v1.2.3 From 779afe9c4ee00f8099a423e1fceee40197a90cfa Mon Sep 17 00:00:00 2001 From: Marien Fressinaud Date: Sun, 30 Mar 2014 14:28:13 +0200 Subject: Import of articles is implemented! - Remove massiveImportAction and addCategories from FeedController - Fix typo for some methods (camelCase) - addCategoryObject and addFeedObject return id if corresponding object already exists in DB - introduce addEntryObject. Return -1 if Entry already exist (in order to keep quite good performances) - Complete importArticles method Need some more tests + better performance --- app/Controllers/feedController.php | 88 ---------------------------- app/Controllers/importExportController.php | 92 +++++++++++++++++++++--------- app/Models/CategoryDAO.php | 5 +- app/Models/EntryDAO.php | 30 ++++++++++ app/Models/FeedDAO.php | 5 +- 5 files changed, 100 insertions(+), 120 deletions(-) (limited to 'app/Controllers/importExportController.php') diff --git a/app/Controllers/feedController.php b/app/Controllers/feedController.php index 43435d99d..95c9c1e9c 100755 --- a/app/Controllers/feedController.php +++ b/app/Controllers/feedController.php @@ -327,82 +327,6 @@ class FreshRSS_feed_Controller extends Minz_ActionController { } } - public function massiveImportAction () { - # TODO: this function has moved to importExportController.php - # I keep it for the moment but should be deleted in a near future. - @set_time_limit(300); - - $this->catDAO = new FreshRSS_CategoryDAO (); - $this->catDAO->checkDefault (); - - $entryDAO = new FreshRSS_EntryDAO (); - $feedDAO = new FreshRSS_FeedDAO (); - - $categories = Minz_Request::param ('categories', array (), true); - $feeds = Minz_Request::param ('feeds', array (), true); - - // on ajoute les catégories en masse dans une fonction à part - $this->addCategories ($categories); - - // on calcule la date des articles les plus anciens qu'on accepte - $nb_month_old = $this->view->conf->old_entries; - $date_min = time () - (3600 * 24 * 30 * $nb_month_old); - - // la variable $error permet de savoir si une erreur est survenue - // Le but est de ne pas arrêter l'import même en cas d'erreur - // L'utilisateur sera mis au courant s'il y a eu des erreurs, mais - // ne connaîtra pas les détails. Ceux-ci seront toutefois logguées - $error = false; - $i = 0; - foreach ($feeds as $feed) { - try { - $values = array ( - 'id' => $feed->id (), - 'url' => $feed->url (), - 'category' => $feed->category (), - 'name' => $feed->name (), - 'website' => $feed->website (), - 'description' => $feed->description (), - 'lastUpdate' => 0, - 'httpAuth' => $feed->httpAuth () - ); - - // ajout du flux que s'il n'est pas déjà en BDD - if (!$feedDAO->searchByUrl ($values['url'])) { - $id = $feedDAO->addFeed ($values); - if ($id) { - $feed->_id ($id); - $feed->faviconPrepare(); - } else { - $error = true; - } - } - } catch (FreshRSS_Feed_Exception $e) { - $error = true; - Minz_Log::record ($e->getMessage (), Minz_Log::WARNING); - } - } - - if ($error) { - $res = Minz_Translate::t ('feeds_imported_with_errors'); - } else { - $res = Minz_Translate::t ('feeds_imported'); - } - - $notif = array ( - 'type' => 'good', - 'content' => $res - ); - Minz_Session::_param ('notification', $notif); - Minz_Session::_param ('actualize_feeds', true); - - // et on redirige vers la page d'accueil - Minz_Request::forward (array ( - 'c' => 'index', - 'a' => 'index' - ), true); - } - public function deleteAction () { if (Minz_Request::isPost ()) { $type = Minz_Request::param ('type', 'feed'); @@ -446,16 +370,4 @@ class FreshRSS_feed_Controller extends Minz_ActionController { } } } - - private function addCategories ($categories) { - foreach ($categories as $cat) { - if (!$this->catDAO->searchByName ($cat->name ())) { - $values = array ( - 'id' => $cat->id (), - 'name' => $cat->name (), - ); - $catDAO->addCategory ($values); - } - } - } } diff --git a/app/Controllers/importExportController.php b/app/Controllers/importExportController.php index b6b4d0fed..3b6bf2c5c 100644 --- a/app/Controllers/importExportController.php +++ b/app/Controllers/importExportController.php @@ -31,7 +31,7 @@ class FreshRSS_importExport_Controller extends Minz_ActionController { @set_time_limit(300); $file = $_FILES['file']; - $type_file = $this->guess_file_type($file['name']); + $type_file = $this->guessFileType($file['name']); $list_files = array( 'opml' => array(), @@ -46,7 +46,7 @@ class FreshRSS_importExport_Controller extends Minz_ActionController { $zip = zip_open($file['tmp_name']); while (($zipfile = zip_read($zip)) !== false) { - $type_zipfile = $this->guess_file_type(zip_entry_name($zipfile)); + $type_zipfile = $this->guessFileType(zip_entry_name($zipfile)); if ($type_file !== 'unknown') { $list_files[$type_zipfile][] = zip_entry_read( @@ -67,13 +67,13 @@ class FreshRSS_importExport_Controller extends Minz_ActionController { // And finally all other files. $error = false; foreach ($list_files['opml'] as $opml_file) { - $error = $this->import_opml($opml_file); + $error = $this->importOpml($opml_file); } foreach ($list_files['json_starred'] as $article_file) { - $error = $this->import_articles($article_file, true); + $error = $this->importArticles($article_file, true); } foreach ($list_files['json_feed'] as $article_file) { - $error = $this->import_articles($article_file); + $error = $this->importArticles($article_file); } // And finally, we get import status and redirect to the home page @@ -107,7 +107,7 @@ class FreshRSS_importExport_Controller extends Minz_ActionController { )); } - private function guess_file_type($filename) { + private function guessFileType($filename) { // A *very* basic guess file type function. Only based on filename // That's could be improved but should be enough, at least for a first // implementation. @@ -128,7 +128,7 @@ class FreshRSS_importExport_Controller extends Minz_ActionController { } } - private function import_opml($opml_file) { + private function importOpml($opml_file) { $opml_array = array(); try { $opml_array = libopml_parse_string($opml_file); @@ -164,7 +164,7 @@ class FreshRSS_importExport_Controller extends Minz_ActionController { private function addFeedOpml($feed_elt, $parent_cat) { if (is_null($parent_cat)) { // This feed has no parent category so we get the default one - $parent_cat = $catDAO->getDefault()->name(); + $parent_cat = $this->catDAO->getDefault()->name(); } $cat = $this->catDAO->searchByName($parent_cat); @@ -199,7 +199,7 @@ class FreshRSS_importExport_Controller extends Minz_ActionController { $id = $this->feedDAO->addFeedObject($feed); $error = ($id === false); } catch (FreshRSS_Feed_Exception $e) { - Minz_Log::record($e->getMessage(), Minz_Log::WARNING); + Minz_Log::warning($e->getMessage()); $error = true; } @@ -226,28 +226,23 @@ class FreshRSS_importExport_Controller extends Minz_ActionController { return $error; } - private function import_articles($article_file, $starred = false) { + private function importArticles($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; } + $is_read = $this->view->conf->mark_when['reception'] ? 1 : 0; + $google_compliant = (strpos($article_object['id'], 'com.google') !== false); + $error = false; foreach ($article_object['items'] as $item) { - $key = $google_compliant ? 'htmlUrl' : 'feedUrl'; - $feed = $this->feedDAO->searchByUrl($item['origin'][$key]); + $feed = $this->addFeedArticles($item['origin'], $google_compliant); if (is_null($feed)) { - $feed = new FreshRSS_Feed($item['origin'][$key]); - $feed->_name ($item['origin']['title']); - $feed->_website ($item['origin']['htmlUrl']); - - $error = $this->addFeed($feed); // TODO - - if ($error) { - continue; - } + $error = true; + continue; } $author = isset($item['author']) ? $item['author'] : ''; @@ -258,14 +253,55 @@ class FreshRSS_importExport_Controller extends Minz_ActionController { return strpos($var, '/state/com.google') === false; }); } + $entry = new FreshRSS_Entry( $feed->id(), $item['id'], $item['title'], $author, $item[$key_content]['content'], $item['alternate'][0]['href'], - $item['published'], false, $starred, $tags + $item['published'], $is_read, $starred ); + $entry->_tags($tags); - Minz_Log::debug(print_r($entry, true)); // TODO + $id = $this->entryDAO->addEntryObject( + $entry, $this->view->conf, $feed->keepHistory() + ); + + if (!$error && ($id === false)) { + $error = true; + } } + + return $error; + } + + private function addFeedArticles($origin, $google_compliant) { + $default_cat = $this->catDAO->getDefault(); + + $return = null; + $key = $google_compliant ? 'htmlUrl' : 'feedUrl'; + $url = $origin[$key]; + $name = $origin['title']; + $website = $origin['htmlUrl']; + $error = false; + try { + // Create a Feed object and add it in DB + $feed = new FreshRSS_Feed($url); + $feed->_category($default_cat->id()); + $feed->_name($name); + $feed->_website($website); + + // addFeedObject checks if feed is already in DB so nothing else to + // check here + $id = $this->feedDAO->addFeedObject($feed); + + if ($id !== false) { + $feed->_id($id); + $return = $feed; + } + } catch (FreshRSS_Feed_Exception $e) { + Minz_Log::warning($e->getMessage()); + } + + return $return; } public function exportAction() { @@ -283,16 +319,16 @@ class FreshRSS_importExport_Controller extends Minz_ActionController { // Stuff with content if ($export_opml) { - $zip->addFromString('feeds.opml', $this->generate_opml()); + $zip->addFromString('feeds.opml', $this->generateOpml()); } if ($export_starred) { - $zip->addFromString('starred.json', $this->generate_articles('starred')); + $zip->addFromString('starred.json', $this->generateArticles('starred')); } foreach ($export_feeds as $feed_id) { $feed = $this->feedDAO->searchById($feed_id); $zip->addFromString( 'feed_' . $feed->category() . '_' . $feed->id() . '.json', - $this->generate_articles('feed', $feed) + $this->generateArticles('feed', $feed) ); } @@ -306,7 +342,7 @@ class FreshRSS_importExport_Controller extends Minz_ActionController { } } - private function generate_opml() { + private function generateOpml() { $list = array(); foreach ($this->catDAO->listCategories() as $key => $cat) { $list[$key]['name'] = $cat->name(); @@ -317,7 +353,7 @@ class FreshRSS_importExport_Controller extends Minz_ActionController { return $this->view->helperToString('export/opml'); } - private function generate_articles($type, $feed = NULL) { + private function generateArticles($type, $feed = NULL) { $this->view->categories = $this->catDAO->listCategories(); if ($type == 'starred') { diff --git a/app/Models/CategoryDAO.php b/app/Models/CategoryDAO.php index 8be732b98..6a9b839b9 100644 --- a/app/Models/CategoryDAO.php +++ b/app/Models/CategoryDAO.php @@ -19,7 +19,8 @@ class FreshRSS_CategoryDAO extends Minz_ModelPdo { } public function addCategoryObject($category) { - if (!$this->searchByName($category->name())) { + $cat = $this->searchByName($category->name()); + if (!$cat) { // Category does not exist yet in DB so we add it before continue $values = array( 'name' => $category->name(), @@ -27,7 +28,7 @@ class FreshRSS_CategoryDAO extends Minz_ModelPdo { return $this->addCategory($values); } - return false; + return $cat->id(); } public function updateCategory ($id, $valuesTmp) { diff --git a/app/Models/EntryDAO.php b/app/Models/EntryDAO.php index e4cf128ea..6d00967fc 100644 --- a/app/Models/EntryDAO.php +++ b/app/Models/EntryDAO.php @@ -35,6 +35,36 @@ class FreshRSS_EntryDAO extends Minz_ModelPdo { } } + public function addEntryObject($entry, $conf, $feedHistory) { + $existingGuids = array_fill_keys( + $this->listLastGuidsByFeed($entry->feed(), 20), 1 + ); + + $nb_month_old = max($conf->old_entries, 1); + $date_min = time() - (3600 * 24 * 30 * $nb_month_old); + + $eDate = $entry->date(true); + + if ($feedHistory == -2) { + $feedHistory = $conf->keep_history_default; + } + + if (!isset($existingGuids[$entry->guid()]) && + ($feedHistory != 0 || $eDate >= $date_min)) { + $values = $entry->toArray(); + + $useDeclaredDate = empty($existingGuids); + $values['id'] = ($useDeclaredDate || $eDate < $date_min) ? + min(time(), $eDate) . uSecString() : + uTimeString(); + + return $this->addEntry($values); + } + + // We don't return Entry object to avoid a research in DB + return -1; + } + public function markFavorite($ids, $is_favorite = true) { if (!is_array($ids)) { $ids = array($ids); diff --git a/app/Models/FeedDAO.php b/app/Models/FeedDAO.php index eac21df7e..b65ff4af0 100644 --- a/app/Models/FeedDAO.php +++ b/app/Models/FeedDAO.php @@ -29,7 +29,8 @@ class FreshRSS_FeedDAO extends Minz_ModelPdo { // should not be aware about feed class // Add feed only if we don't find it in DB - if (!$this->searchByUrl($feed->url())) { + $feed_search = $this->searchByUrl($feed->url()); + if (!$feed_search) { $values = array( 'id' => $feed->id(), 'url' => $feed->url(), @@ -50,7 +51,7 @@ class FreshRSS_FeedDAO extends Minz_ModelPdo { return $id; } - return false; + return $feed_search->id(); } public function updateFeed ($id, $valuesTmp) { -- cgit v1.2.3 From 34b17b748efb996f0202815dcf095a45d28e38da Mon Sep 17 00:00:00 2001 From: Marien Fressinaud Date: Sun, 30 Mar 2014 14:55:29 +0200 Subject: Fix coding style --- app/Controllers/importExportController.php | 49 ++++++++++++++++++------------ app/layout/aside_feed.phtml | 2 +- 2 files changed, 31 insertions(+), 20 deletions(-) (limited to 'app/Controllers/importExportController.php') diff --git a/app/Controllers/importExportController.php b/app/Controllers/importExportController.php index 3b6bf2c5c..02d62c890 100644 --- a/app/Controllers/importExportController.php +++ b/app/Controllers/importExportController.php @@ -20,9 +20,6 @@ class FreshRSS_importExport_Controller extends Minz_ActionController { $this->view->categories = $this->catDAO->listCategories(); $this->view->feeds = $this->feedDAO->listFeeds(); - // au niveau de la vue, permet de ne pas voir un flux sélectionné dans la liste - $this->view->flux = false; - Minz_View::prependTitle(Minz_Translate::t('import_export') . ' · '); } @@ -46,7 +43,9 @@ class FreshRSS_importExport_Controller extends Minz_ActionController { $zip = zip_open($file['tmp_name']); while (($zipfile = zip_read($zip)) !== false) { - $type_zipfile = $this->guessFileType(zip_entry_name($zipfile)); + $type_zipfile = $this->guessFileType( + zip_entry_name($zipfile) + ); if ($type_file !== 'unknown') { $list_files[$type_zipfile][] = zip_entry_read( @@ -58,7 +57,9 @@ class FreshRSS_importExport_Controller extends Minz_ActionController { zip_close($zip); } elseif ($type_file !== 'unknown') { - $list_files[$type_file][] = file_get_contents($file['tmp_name']); + $list_files[$type_file][] = file_get_contents( + $file['tmp_name'] + ); } // Import different files. @@ -79,18 +80,19 @@ class FreshRSS_importExport_Controller extends Minz_ActionController { // And finally, we get import status and redirect to the home page $notif = null; if ($error === true) { - $notif = array( - 'type' => 'good', - 'content' => Minz_Translate::t('feeds_imported_with_errors') + $content_notif = Minz_Translate::t( + 'feeds_imported_with_errors' ); } else { - $notif = array( - 'type' => 'good', - 'content' => Minz_Translate::t('feeds_imported') + $content_notif = Minz_Translate::t( + 'feeds_imported' ); } - Minz_Session::_param('notification', $notif); + Minz_Session::_param('notification', array( + 'type' => 'good', + 'content' => $content_notif + )); Minz_Session::_param('actualize_feeds', true); Minz_Request::forward(array( @@ -235,7 +237,9 @@ class FreshRSS_importExport_Controller extends Minz_ActionController { $is_read = $this->view->conf->mark_when['reception'] ? 1 : 0; - $google_compliant = (strpos($article_object['id'], 'com.google') !== false); + $google_compliant = ( + strpos($article_object['id'], 'com.google') !== false + ); $error = false; foreach ($article_object['items'] as $item) { @@ -246,7 +250,8 @@ class FreshRSS_importExport_Controller extends Minz_ActionController { } $author = isset($item['author']) ? $item['author'] : ''; - $key_content = $google_compliant && !isset($item['content']) ? 'summary' : 'content'; + $key_content = ($google_compliant && !isset($item['content'])) ? + 'summary' : 'content'; $tags = $item['categories']; if ($google_compliant) { $tags = array_filter($tags, function($var) { @@ -312,17 +317,21 @@ class FreshRSS_importExport_Controller extends Minz_ActionController { $export_starred = Minz_Request::param('export_starred', false); $export_feeds = Minz_Request::param('export_feeds', false); - // code from https://stackoverflow.com/questions/1061710/php-zip-files-on-the-fly + // From https://stackoverflow.com/questions/1061710/php-zip-files-on-the-fly $file = tempnam('tmp', 'zip'); $zip = new ZipArchive(); $zip->open($file, ZipArchive::OVERWRITE); // Stuff with content if ($export_opml) { - $zip->addFromString('feeds.opml', $this->generateOpml()); + $zip->addFromString( + 'feeds.opml', $this->generateOpml() + ); } if ($export_starred) { - $zip->addFromString('starred.json', $this->generateArticles('starred')); + $zip->addFromString( + 'starred.json', $this->generateArticles('starred') + ); } foreach ($export_feeds as $feed_id) { $feed = $this->feedDAO->searchById($feed_id); @@ -357,14 +366,16 @@ class FreshRSS_importExport_Controller extends Minz_ActionController { $this->view->categories = $this->catDAO->listCategories(); if ($type == 'starred') { - $this->view->list_title = Minz_Translate::t("starred_list"); + $this->view->list_title = Minz_Translate::t('starred_list'); $this->view->type = 'starred'; $this->view->entries = $this->entryDAO->listWhere( 's', '', 'all', 'ASC', $this->entryDAO->countUnreadReadFavorites()['all'] ); } elseif ($type == 'feed' && !is_null($feed)) { - $this->view->list_title = Minz_Translate::t("feed_list", $feed->name()); + $this->view->list_title = Minz_Translate::t( + 'feed_list', $feed->name() + ); $this->view->type = 'feed/' . $feed->id(); $this->view->entries = $this->entryDAO->listWhere( 'f', $feed->id(), 'all', 'ASC', diff --git a/app/layout/aside_feed.phtml b/app/layout/aside_feed.phtml index ae16efd96..1fad60af5 100644 --- a/app/layout/aside_feed.phtml +++ b/app/layout/aside_feed.phtml @@ -56,7 +56,7 @@ feeds)) { ?> feeds as $feed) { ?> nbEntries (); ?> -
  • +
  • ✇ name (); ?> -- cgit v1.2.3 From 8ef32a10c6fd8e4ace44a5e797f6176f80b9560e Mon Sep 17 00:00:00 2001 From: Marien Fressinaud Date: Sun, 30 Mar 2014 20:47:00 +0200 Subject: Improve compatibility PHP 5.3 --- app/Controllers/importExportController.php | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) (limited to 'app/Controllers/importExportController.php') diff --git a/app/Controllers/importExportController.php b/app/Controllers/importExportController.php index 02d62c890..a9b103a34 100644 --- a/app/Controllers/importExportController.php +++ b/app/Controllers/importExportController.php @@ -368,9 +368,10 @@ class FreshRSS_importExport_Controller extends Minz_ActionController { if ($type == 'starred') { $this->view->list_title = Minz_Translate::t('starred_list'); $this->view->type = 'starred'; + $unread_fav = $this->entryDAO->countUnreadReadFavorites(); $this->view->entries = $this->entryDAO->listWhere( 's', '', 'all', 'ASC', - $this->entryDAO->countUnreadReadFavorites()['all'] + $unread_fav['all'] ); } elseif ($type == 'feed' && !is_null($feed)) { $this->view->list_title = Minz_Translate::t( -- cgit v1.2.3 From 86066b1659e33eb5fdfbcae5fb7f0bd93604d442 Mon Sep 17 00:00:00 2001 From: Alexis Degrugillier Date: Sun, 13 Apr 2014 07:28:41 -0400 Subject: Add a new status for 'ALL' I made the conversion in every file I can think of. It should not have any reference to the string 'all' for the state context --- app/Controllers/importExportController.php | 4 ++-- app/Controllers/indexController.php | 8 ++++---- app/Models/Configuration.php | 3 ++- app/Models/EntryDAO.php | 9 ++++++--- app/views/configure/reading.phtml | 2 +- p/api/greader.php | 8 ++++---- 6 files changed, 19 insertions(+), 15 deletions(-) (limited to 'app/Controllers/importExportController.php') diff --git a/app/Controllers/importExportController.php b/app/Controllers/importExportController.php index a9b103a34..b8253b7bd 100644 --- a/app/Controllers/importExportController.php +++ b/app/Controllers/importExportController.php @@ -370,7 +370,7 @@ class FreshRSS_importExport_Controller extends Minz_ActionController { $this->view->type = 'starred'; $unread_fav = $this->entryDAO->countUnreadReadFavorites(); $this->view->entries = $this->entryDAO->listWhere( - 's', '', 'all', 'ASC', + 's', '', FreshRSS_Configuration::STATE_ALL, 'ASC', $unread_fav['all'] ); } elseif ($type == 'feed' && !is_null($feed)) { @@ -379,7 +379,7 @@ class FreshRSS_importExport_Controller extends Minz_ActionController { ); $this->view->type = 'feed/' . $feed->id(); $this->view->entries = $this->entryDAO->listWhere( - 'f', $feed->id(), 'all', 'ASC', + 'f', $feed->id(), FreshRSS_Configuration::STATE_ALL, 'ASC', $this->view->conf->posts_per_page ); $this->view->feed = $feed; diff --git a/app/Controllers/indexController.php b/app/Controllers/indexController.php index 243d887ac..48471b480 100755 --- a/app/Controllers/indexController.php +++ b/app/Controllers/indexController.php @@ -85,7 +85,7 @@ class FreshRSS_index_Controller extends Minz_ActionController { $state_param = Minz_Request::param ('state', null); $filter = Minz_Request::param ('search', ''); if (!empty($filter)) { - $state = 'all'; //Search always in read and unread articles + $state = FreshRSS_Configuration::STATE_ALL; //Search always in read and unread articles } $this->view->order = $order = Minz_Request::param ('order', $this->view->conf->sort_order); $nb = Minz_Request::param ('nb', $this->view->conf->posts_per_page); @@ -108,7 +108,7 @@ class FreshRSS_index_Controller extends Minz_ActionController { break; } if (!$hasUnread && ($state_param === null)) { - $this->view->state = $state = 'all'; + $this->view->state = $state = FreshRSS_Configuration::STATE_ALL; } } @@ -127,8 +127,8 @@ class FreshRSS_index_Controller extends Minz_ActionController { // on essaye de récupérer tous les articles if ($state === FreshRSS_Configuration::STATE_NOT_READ && empty($entries) && ($state_param === null)) { Minz_Log::record ('Conflicting information about nbNotRead!', Minz_Log::DEBUG); - $this->view->state = 'all'; - $entries = $entryDAO->listWhere($getType, $getId, 'all', $order, $nb, $first, $filter, $date_min, true, $keepHistoryDefault); + $this->view->state = FreshRSS_Configuration::STATE_ALL; + $entries = $entryDAO->listWhere($getType, $getId, $this->view->state, $order, $nb, $first, $filter, $date_min, true, $keepHistoryDefault); } if (count($entries) <= $nb) { diff --git a/app/Models/Configuration.php b/app/Models/Configuration.php index f9ea47be6..e693542e0 100644 --- a/app/Models/Configuration.php +++ b/app/Models/Configuration.php @@ -1,6 +1,7 @@ data['default_view'] = $value === 'all' ? 'all' : self::STATE_NOT_READ; + $this->data['default_view'] = $value === self::STATE_ALL ? self::STATE_ALL : self::STATE_NOT_READ; } public function _display_posts ($value) { $this->data['display_posts'] = ((bool)$value) && $value !== 'no'; diff --git a/app/Models/EntryDAO.php b/app/Models/EntryDAO.php index 2f5a9e1f6..b9cbfd584 100644 --- a/app/Models/EntryDAO.php +++ b/app/Models/EntryDAO.php @@ -406,7 +406,10 @@ class FreshRSS_EntryDAO extends Minz_ModelPdo { return isset ($entries[0]) ? $entries[0] : null; } - private function sqlListWhere($type = 'a', $id = '', $state = 'all', $order = 'DESC', $limit = 1, $firstId = '', $filter = '', $date_min = 0, $showOlderUnreadsorFavorites = false, $keepHistoryDefault = 0) { + private function sqlListWhere($type = 'a', $id = '', $state = null , $order = 'DESC', $limit = 1, $firstId = '', $filter = '', $date_min = 0, $showOlderUnreadsorFavorites = false, $keepHistoryDefault = 0) { + if (!$state) { + $state = FreshRSS_Configuration::STATE_ALL; + } $where = ''; $joinFeed = false; $values = array(); @@ -532,7 +535,7 @@ class FreshRSS_EntryDAO extends Minz_ModelPdo { . ($limit > 0 ? ' LIMIT ' . $limit : '')); //TODO: See http://explainextended.com/2009/10/23/mysql-order-by-limit-performance-late-row-lookups/ } - public function listWhere($type = 'a', $id = '', $state = 'all', $order = 'DESC', $limit = 1, $firstId = '', $filter = '', $date_min = 0, $showOlderUnreadsorFavorites = false, $keepHistoryDefault = 0) { + public function listWhere($type = 'a', $id = '', $state = null, $order = 'DESC', $limit = 1, $firstId = '', $filter = '', $date_min = 0, $showOlderUnreadsorFavorites = false, $keepHistoryDefault = 0) { list($values, $sql) = $this->sqlListWhere($type, $id, $state, $order, $limit, $firstId, $filter, $date_min, $showOlderUnreadsorFavorites, $keepHistoryDefault); $sql = 'SELECT e.id, e.guid, e.title, e.author, UNCOMPRESS(e.content_bin) AS content, e.link, e.date, e.is_read, e.is_favorite, e.id_feed, e.tags ' @@ -548,7 +551,7 @@ class FreshRSS_EntryDAO extends Minz_ModelPdo { return self::daoToEntry ($stm->fetchAll (PDO::FETCH_ASSOC)); } - public function listIdsWhere($type = 'a', $id = '', $state = 'all', $order = 'DESC', $limit = 1, $firstId = '', $filter = '', $date_min = 0, $showOlderUnreadsorFavorites = false, $keepHistoryDefault = 0) { //For API + public function listIdsWhere($type = 'a', $id = '', $state = null, $order = 'DESC', $limit = 1, $firstId = '', $filter = '', $date_min = 0, $showOlderUnreadsorFavorites = false, $keepHistoryDefault = 0) { //For API list($values, $sql) = $this->sqlListWhere($type, $id, $state, $order, $limit, $firstId, $filter, $date_min, $showOlderUnreadsorFavorites, $keepHistoryDefault); $stm = $this->bd->prepare($sql); diff --git a/app/views/configure/reading.phtml b/app/views/configure/reading.phtml index b778a4d22..b277397b1 100644 --- a/app/views/configure/reading.phtml +++ b/app/views/configure/reading.phtml @@ -32,7 +32,7 @@ state & FreshRSS_Configuration::STATE_NOT_READ) { - $url_state['params']['state'] = $this->state - FreshRSS_Configuration::STATE_NOT_READ; + if ($this->state & FreshRSS_Entry::STATE_NOT_READ) { + $url_state['params']['state'] = $this->state - FreshRSS_Entry::STATE_NOT_READ; $checked = 'true'; } else { - $url_state['params']['state'] = $this->state + FreshRSS_Configuration::STATE_NOT_READ; + $url_state['params']['state'] = $this->state + FreshRSS_Entry::STATE_NOT_READ; $checked = 'false'; } ?> @@ -40,11 +40,11 @@ state & FreshRSS_Configuration::STATE_FAVORITE) { - $url_state['params']['state'] = $this->state - FreshRSS_Configuration::STATE_FAVORITE; + if ($this->state & FreshRSS_Entry::STATE_FAVORITE) { + $url_state['params']['state'] = $this->state - FreshRSS_Entry::STATE_FAVORITE; $checked = 'true'; } else { - $url_state['params']['state'] = $this->state + FreshRSS_Configuration::STATE_FAVORITE; + $url_state['params']['state'] = $this->state + FreshRSS_Entry::STATE_FAVORITE; $checked = 'false'; } ?> @@ -56,11 +56,11 @@ state & FreshRSS_Configuration::STATE_NOT_FAVORITE) { - $url_state['params']['state'] = $this->state - FreshRSS_Configuration::STATE_NOT_FAVORITE; + if ($this->state & FreshRSS_Entry::STATE_NOT_FAVORITE) { + $url_state['params']['state'] = $this->state - FreshRSS_Entry::STATE_NOT_FAVORITE; $checked = 'true'; } else { - $url_state['params']['state'] = $this->state + FreshRSS_Configuration::STATE_NOT_FAVORITE; + $url_state['params']['state'] = $this->state + FreshRSS_Entry::STATE_NOT_FAVORITE; $checked = 'false'; } ?> diff --git a/app/views/configure/reading.phtml b/app/views/configure/reading.phtml index b277397b1..456ab9522 100644 --- a/app/views/configure/reading.phtml +++ b/app/views/configure/reading.phtml @@ -32,11 +32,11 @@ diff --git a/p/api/greader.php b/p/api/greader.php index 9836f2f02..8a8623966 100644 --- a/p/api/greader.php +++ b/p/api/greader.php @@ -353,10 +353,10 @@ function streamContents($path, $include_target, $start_time, $count, $order, $ex switch ($exclude_target) { case 'user/-/state/com.google/read': - $state = FreshRSS_Configuration::STATE_NOT_READ; + $state = FreshRSS_Entry::STATE_NOT_READ; break; default: - $state = FreshRSS_Configuration::STATE_ALL; + $state = FreshRSS_Entry::STATE_ALL; break; } @@ -451,10 +451,10 @@ function streamContentsItemsIds($streamId, $start_time, $count, $order, $exclude switch ($exclude_target) { case 'user/-/state/com.google/read': - $state = FreshRSS_Configuration::STATE_NOT_READ; + $state = FreshRSS_Entry::STATE_NOT_READ; break; default: - $state = FreshRSS_Configuration::STATE_ALL; + $state = FreshRSS_Entry::STATE_ALL; break; } -- cgit v1.2.3