aboutsummaryrefslogtreecommitdiff
path: root/app/Models
diff options
context:
space:
mode:
authorGravatar Alkarex <alexandre@alapetite.fr> 2013-12-23 13:22:50 +0100
committerGravatar Alkarex <alexandre@alapetite.fr> 2013-12-23 13:22:50 +0100
commit24d9d1628d673f68fa0cc7b98a3ef9a8d021b070 (patch)
tree2d794dc100d707e53b82a2de7d84b3d13103e461 /app/Models
parent75096e6a39fe5d34d3951991f296f616e62a9fd8 (diff)
parent9e46c1ee7fc7f9ad9e2c07f0cf826573dd4c9766 (diff)
Fusion 0.7-dev
Diffstat (limited to 'app/Models')
-rw-r--r--app/Models/Category.php85
-rw-r--r--app/Models/CategoryDAO.php250
-rw-r--r--app/Models/Configuration.php326
-rw-r--r--app/Models/ConfigurationDAO.php156
-rw-r--r--app/Models/Days.php7
-rw-r--r--app/Models/Entry.php192
-rw-r--r--app/Models/EntryDAO.php464
-rw-r--r--app/Models/Feed.php319
-rw-r--r--app/Models/FeedDAO.php339
-rw-r--r--app/Models/Log.php26
-rw-r--r--app/Models/LogDAO.php20
-rw-r--r--app/Models/Themes.php88
12 files changed, 2272 insertions, 0 deletions
diff --git a/app/Models/Category.php b/app/Models/Category.php
new file mode 100644
index 000000000..e70d1303f
--- /dev/null
+++ b/app/Models/Category.php
@@ -0,0 +1,85 @@
+<?php
+
+class FreshRSS_Category extends Minz_Model {
+ private $id = 0;
+ private $name;
+ private $color;
+ private $nbFeed = -1;
+ private $nbNotRead = -1;
+ private $feeds = null;
+
+ public function __construct ($name = '', $color = '#0062BE', $feeds = null) {
+ $this->_name ($name);
+ $this->_color ($color);
+ if (isset ($feeds)) {
+ $this->_feeds ($feeds);
+ $this->nbFeed = 0;
+ $this->nbNotRead = 0;
+ foreach ($feeds as $feed) {
+ $this->nbFeed++;
+ $this->nbNotRead += $feed->nbNotRead ();
+ }
+ }
+ }
+
+ public function id () {
+ return $this->id;
+ }
+ public function name () {
+ return $this->name;
+ }
+ public function color () {
+ return $this->color;
+ }
+ public function nbFeed () {
+ if ($this->nbFeed < 0) {
+ $catDAO = new FreshRSS_CategoryDAO ();
+ $this->nbFeed = $catDAO->countFeed ($this->id ());
+ }
+
+ return $this->nbFeed;
+ }
+ public function nbNotRead () {
+ if ($this->nbNotRead < 0) {
+ $catDAO = new FreshRSS_CategoryDAO ();
+ $this->nbNotRead = $catDAO->countNotRead ($this->id ());
+ }
+
+ return $this->nbNotRead;
+ }
+ public function feeds () {
+ if (is_null ($this->feeds)) {
+ $feedDAO = new FreshRSS_FeedDAO ();
+ $this->feeds = $feedDAO->listByCategory ($this->id ());
+ $this->nbFeed = 0;
+ $this->nbNotRead = 0;
+ foreach ($this->feeds as $feed) {
+ $this->nbFeed++;
+ $this->nbNotRead += $feed->nbNotRead ();
+ }
+ }
+
+ return $this->feeds;
+ }
+
+ public function _id ($value) {
+ $this->id = $value;
+ }
+ public function _name ($value) {
+ $this->name = $value;
+ }
+ public function _color ($value) {
+ if (preg_match ('/^#([0-9a-f]{3}|[0-9a-f]{6})$/i', $value)) {
+ $this->color = $value;
+ } else {
+ $this->color = '#0062BE';
+ }
+ }
+ public function _feeds ($values) {
+ if (!is_array ($values)) {
+ $values = array ($values);
+ }
+
+ $this->feeds = $values;
+ }
+}
diff --git a/app/Models/CategoryDAO.php b/app/Models/CategoryDAO.php
new file mode 100644
index 000000000..3a810e9f0
--- /dev/null
+++ b/app/Models/CategoryDAO.php
@@ -0,0 +1,250 @@
+<?php
+class FreshRSS_CategoryDAO extends Minz_ModelPdo {
+ public function addCategory ($valuesTmp) {
+ $sql = 'INSERT INTO `' . $this->prefix . 'category` (name, color) VALUES(?, ?)';
+ $stm = $this->bd->prepare ($sql);
+
+ $values = array (
+ substr($valuesTmp['name'], 0, 255),
+ substr($valuesTmp['color'], 0, 7),
+ );
+
+ if ($stm && $stm->execute ($values)) {
+ return $this->bd->lastInsertId();
+ } else {
+ $info = $stm->errorInfo();
+ Minz_Log::record ('SQL error : ' . $info[2], Minz_Log::ERROR);
+ return false;
+ }
+ }
+
+ public function updateCategory ($id, $valuesTmp) {
+ $sql = 'UPDATE `' . $this->prefix . 'category` SET name=?, color=? WHERE id=?';
+ $stm = $this->bd->prepare ($sql);
+
+ $values = array (
+ $valuesTmp['name'],
+ $valuesTmp['color'],
+ $id
+ );
+
+ if ($stm && $stm->execute ($values)) {
+ return $stm->rowCount();
+ } else {
+ $info = $stm->errorInfo();
+ Minz_Log::record ('SQL error : ' . $info[2], Minz_Log::ERROR);
+ return false;
+ }
+ }
+
+ public function deleteCategory ($id) {
+ $sql = 'DELETE FROM `' . $this->prefix . 'category` WHERE id=?';
+ $stm = $this->bd->prepare ($sql);
+
+ $values = array ($id);
+
+ if ($stm && $stm->execute ($values)) {
+ return $stm->rowCount();
+ } else {
+ $info = $stm->errorInfo();
+ Minz_Log::record ('SQL error : ' . $info[2], Minz_Log::ERROR);
+ return false;
+ }
+ }
+
+ public function searchById ($id) {
+ $sql = 'SELECT * FROM `' . $this->prefix . 'category` WHERE id=?';
+ $stm = $this->bd->prepare ($sql);
+
+ $values = array ($id);
+
+ $stm->execute ($values);
+ $res = $stm->fetchAll (PDO::FETCH_ASSOC);
+ $cat = self::daoToCategory ($res);
+
+ if (isset ($cat[0])) {
+ return $cat[0];
+ } else {
+ return false;
+ }
+ }
+ public function searchByName ($name) {
+ $sql = 'SELECT * FROM `' . $this->prefix . 'category` WHERE name=?';
+ $stm = $this->bd->prepare ($sql);
+
+ $values = array ($name);
+
+ $stm->execute ($values);
+ $res = $stm->fetchAll (PDO::FETCH_ASSOC);
+ $cat = self::daoToCategory ($res);
+
+ if (isset ($cat[0])) {
+ return $cat[0];
+ } else {
+ return false;
+ }
+ }
+
+ public function listCategories ($prePopulateFeeds = true, $details = false) {
+ if ($prePopulateFeeds) {
+ $sql = 'SELECT c.id AS c_id, c.name AS c_name, '
+ . ($details ? 'c.color AS c_color, ' : '')
+ . ($details ? 'f.* ' : 'f.id, f.name, f.website, f.priority, f.error, f.cache_nbEntries, f.cache_nbUnreads ')
+ . 'FROM `' . $this->prefix . 'category` c '
+ . 'LEFT OUTER JOIN `' . $this->prefix . 'feed` f ON f.category = c.id '
+ . 'GROUP BY f.id '
+ . 'ORDER BY c.name, f.name';
+ $stm = $this->bd->prepare ($sql);
+ $stm->execute ();
+ return self::daoToCategoryPrepopulated ($stm->fetchAll (PDO::FETCH_ASSOC));
+ } else {
+ $sql = 'SELECT * FROM `' . $this->prefix . 'category` ORDER BY name';
+ $stm = $this->bd->prepare ($sql);
+ $stm->execute ();
+ return self::daoToCategory ($stm->fetchAll (PDO::FETCH_ASSOC));
+ }
+ }
+
+ public function getDefault () {
+ $sql = 'SELECT * FROM `' . $this->prefix . 'category` WHERE id=1';
+ $stm = $this->bd->prepare ($sql);
+
+ $stm->execute ();
+ $res = $stm->fetchAll (PDO::FETCH_ASSOC);
+ $cat = self::daoToCategory ($res);
+
+ if (isset ($cat[0])) {
+ return $cat[0];
+ } else {
+ return false;
+ }
+ }
+ public function checkDefault () {
+ $def_cat = $this->searchById (1);
+
+ if ($def_cat === false) {
+ $cat = new FreshRSS_Category (Minz_Translate::t ('default_category'));
+ $cat->_id (1);
+
+ $values = array (
+ 'id' => $cat->id (),
+ 'name' => $cat->name (),
+ 'color' => $cat->color ()
+ );
+
+ $this->addCategory ($values);
+ }
+ }
+
+ public function count () {
+ $sql = 'SELECT COUNT(*) AS count FROM `' . $this->prefix . 'category`';
+ $stm = $this->bd->prepare ($sql);
+ $stm->execute ();
+ $res = $stm->fetchAll (PDO::FETCH_ASSOC);
+
+ return $res[0]['count'];
+ }
+
+ public function countFeed ($id) {
+ $sql = 'SELECT COUNT(*) AS count FROM `' . $this->prefix . 'feed` WHERE category=?';
+ $stm = $this->bd->prepare ($sql);
+ $values = array ($id);
+ $stm->execute ($values);
+ $res = $stm->fetchAll (PDO::FETCH_ASSOC);
+
+ return $res[0]['count'];
+ }
+
+ public function countNotRead ($id) {
+ $sql = 'SELECT COUNT(*) AS count FROM `' . $this->prefix . 'entry` e INNER JOIN `' . $this->prefix . 'feed` f ON e.id_feed = f.id WHERE category=? AND e.is_read=0';
+ $stm = $this->bd->prepare ($sql);
+ $values = array ($id);
+ $stm->execute ($values);
+ $res = $stm->fetchAll (PDO::FETCH_ASSOC);
+
+ return $res[0]['count'];
+ }
+
+ public static function findFeed($categories, $feed_id) {
+ foreach ($categories as $category) {
+ foreach ($category->feeds () as $feed) {
+ if ($feed->id () === $feed_id) {
+ return $feed;
+ }
+ }
+ }
+ return null;
+ }
+
+ public static function CountUnreads($categories, $minPriority = 0) {
+ $n = 0;
+ foreach ($categories as $category) {
+ foreach ($category->feeds () as $feed) {
+ if ($feed->priority () >= $minPriority) {
+ $n += $feed->nbNotRead();
+ }
+ }
+ }
+ return $n;
+ }
+
+ public static function daoToCategoryPrepopulated ($listDAO) {
+ $list = array ();
+
+ if (!is_array ($listDAO)) {
+ $listDAO = array ($listDAO);
+ }
+
+ $previousLine = null;
+ $feedsDao = array();
+ foreach ($listDAO as $line) {
+ if ($previousLine['c_id'] != null && $line['c_id'] !== $previousLine['c_id']) {
+ // End of the current category, we add it to the $list
+ $cat = new FreshRSS_Category (
+ $previousLine['c_name'],
+ isset($previousLine['c_color']) ? $previousLine['c_color'] : '',
+ FreshRSS_FeedDAO::daoToFeed ($feedsDao, $previousLine['c_id'])
+ );
+ $cat->_id ($previousLine['c_id']);
+ $list[$previousLine['c_id']] = $cat;
+
+ $feedsDao = array(); //Prepare for next category
+ }
+
+ $previousLine = $line;
+ $feedsDao[] = $line;
+ }
+
+ // add the last category
+ if ($previousLine != null) {
+ $cat = new FreshRSS_Category (
+ $previousLine['c_name'],
+ isset($previousLine['c_color']) ? $previousLine['c_color'] : '',
+ FreshRSS_FeedDAO::daoToFeed ($feedsDao, $previousLine['c_id'])
+ );
+ $cat->_id ($previousLine['c_id']);
+ $list[$previousLine['c_id']] = $cat;
+ }
+
+ return $list;
+ }
+
+ public static function daoToCategory ($listDAO) {
+ $list = array ();
+
+ if (!is_array ($listDAO)) {
+ $listDAO = array ($listDAO);
+ }
+
+ foreach ($listDAO as $key => $dao) {
+ $cat = new FreshRSS_Category (
+ $dao['name'],
+ $dao['color']
+ );
+ $cat->_id ($dao['id']);
+ $list[$key] = $cat;
+ }
+
+ return $list;
+ }
+}
diff --git a/app/Models/Configuration.php b/app/Models/Configuration.php
new file mode 100644
index 000000000..7ef76b522
--- /dev/null
+++ b/app/Models/Configuration.php
@@ -0,0 +1,326 @@
+<?php
+class FreshRSS_Configuration extends Minz_Model {
+ private $available_languages = array (
+ 'en' => 'English',
+ 'fr' => 'Français',
+ );
+ private $language;
+ private $posts_per_page;
+ private $view_mode;
+ private $default_view;
+ private $display_posts;
+ private $onread_jump_next;
+ private $lazyload;
+ private $sort_order;
+ private $old_entries;
+ private $shortcuts = array ();
+ private $mail_login = '';
+ private $mark_when = array ();
+ private $sharing = array ();
+ private $theme;
+ private $anon_access;
+ private $token;
+ private $auto_load_more;
+ private $topline_read;
+ private $topline_favorite;
+ private $topline_date;
+ private $topline_link;
+ private $bottomline_read;
+ private $bottomline_favorite;
+ private $bottomline_sharing;
+ private $bottomline_tags;
+ private $bottomline_date;
+ private $bottomline_link;
+
+ public function __construct () {
+ $confDAO = new FreshRSS_ConfigurationDAO ();
+ $this->_language ($confDAO->language);
+ $this->_postsPerPage ($confDAO->posts_per_page);
+ $this->_viewMode ($confDAO->view_mode);
+ $this->_defaultView ($confDAO->default_view);
+ $this->_displayPosts ($confDAO->display_posts);
+ $this->_onread_jump_next ($confDAO->onread_jump_next);
+ $this->_lazyload ($confDAO->lazyload);
+ $this->_sortOrder ($confDAO->sort_order);
+ $this->_oldEntries ($confDAO->old_entries);
+ $this->_shortcuts ($confDAO->shortcuts);
+ $this->_mailLogin ($confDAO->mail_login);
+ $this->_markWhen ($confDAO->mark_when);
+ $this->_sharing ($confDAO->sharing);
+ $this->_theme ($confDAO->theme);
+ FreshRSS_Themes::setThemeId ($confDAO->theme);
+ $this->_anonAccess ($confDAO->anon_access);
+ $this->_token ($confDAO->token);
+ $this->_autoLoadMore ($confDAO->auto_load_more);
+ $this->_topline_read ($confDAO->topline_read);
+ $this->_topline_favorite ($confDAO->topline_favorite);
+ $this->_topline_date ($confDAO->topline_date);
+ $this->_topline_link ($confDAO->topline_link);
+ $this->_bottomline_read ($confDAO->bottomline_read);
+ $this->_bottomline_favorite ($confDAO->bottomline_favorite);
+ $this->_bottomline_sharing ($confDAO->bottomline_sharing);
+ $this->_bottomline_tags ($confDAO->bottomline_tags);
+ $this->_bottomline_date ($confDAO->bottomline_date);
+ $this->_bottomline_link ($confDAO->bottomline_link);
+ }
+
+ public function availableLanguages () {
+ return $this->available_languages;
+ }
+ public function language () {
+ return $this->language;
+ }
+ public function postsPerPage () {
+ return $this->posts_per_page;
+ }
+ public function viewMode () {
+ return $this->view_mode;
+ }
+ public function defaultView () {
+ return $this->default_view;
+ }
+ public function displayPosts () {
+ return $this->display_posts;
+ }
+ public function onread_jump_next () {
+ return $this->onread_jump_next;
+ }
+ public function lazyload () {
+ return $this->lazyload;
+ }
+ public function sortOrder () {
+ return $this->sort_order;
+ }
+ public function oldEntries () {
+ return $this->old_entries;
+ }
+ public function shortcuts () {
+ return $this->shortcuts;
+ }
+ public function mailLogin () {
+ return $this->mail_login;
+ }
+ public function markWhen () {
+ return $this->mark_when;
+ }
+ public function markWhenArticle () {
+ return $this->mark_when['article'];
+ }
+ public function markWhenSite () {
+ return $this->mark_when['site'];
+ }
+ public function markWhenScroll () {
+ return $this->mark_when['scroll'];
+ }
+ public function markUponReception () {
+ return $this->mark_when['reception'];
+ }
+ public function sharing ($key = false) {
+ if ($key === false) {
+ return $this->sharing;
+ } elseif (isset ($this->sharing[$key])) {
+ return $this->sharing[$key];
+ }
+ return false;
+ }
+ public function theme () {
+ return $this->theme;
+ }
+ public function anonAccess () {
+ return $this->anon_access;
+ }
+ public function token () {
+ return $this->token;
+ }
+ public function autoLoadMore () {
+ return $this->auto_load_more;
+ }
+ public function toplineRead () {
+ return $this->topline_read;
+ }
+ public function toplineFavorite () {
+ return $this->topline_favorite;
+ }
+ public function toplineDate () {
+ return $this->topline_date;
+ }
+ public function toplineLink () {
+ return $this->topline_link;
+ }
+ public function bottomlineRead () {
+ return $this->bottomline_read;
+ }
+ public function bottomlineFavorite () {
+ return $this->bottomline_favorite;
+ }
+ public function bottomlineSharing () {
+ return $this->bottomline_sharing;
+ }
+ public function bottomlineTags () {
+ return $this->bottomline_tags;
+ }
+ public function bottomlineDate () {
+ return $this->bottomline_date;
+ }
+ public function bottomlineLink () {
+ return $this->bottomline_link;
+ }
+
+ public function _language ($value) {
+ if (!isset ($this->available_languages[$value])) {
+ $value = 'en';
+ }
+ $this->language = $value;
+ }
+ public function _postsPerPage ($value) {
+ $value = intval($value);
+ $this->posts_per_page = $value > 0 ? $value : 10;
+ }
+ public function _viewMode ($value) {
+ if ($value == 'global' || $value == 'reader') {
+ $this->view_mode = $value;
+ } else {
+ $this->view_mode = 'normal';
+ }
+ }
+ public function _defaultView ($value) {
+ if ($value == 'not_read') {
+ $this->default_view = 'not_read';
+ } else {
+ $this->default_view = 'all';
+ }
+ }
+ public function _displayPosts ($value) {
+ if ($value == 'yes') {
+ $this->display_posts = 'yes';
+ } else {
+ $this->display_posts = 'no';
+ }
+ }
+ public function _onread_jump_next ($value) {
+ if ($value == 'no') {
+ $this->onread_jump_next = 'no';
+ } else {
+ $this->onread_jump_next = 'yes';
+ }
+ }
+ public function _lazyload ($value) {
+ if ($value == 'no') {
+ $this->lazyload = 'no';
+ } else {
+ $this->lazyload = 'yes';
+ }
+ }
+ public function _sortOrder ($value) {
+ $this->sort_order = $value === 'ASC' ? 'ASC' : 'DESC';
+ }
+ public function _oldEntries ($value) {
+ if (ctype_digit ($value) && $value > 0) {
+ $this->old_entries = $value;
+ } else {
+ $this->old_entries = 3;
+ }
+ }
+ public function _shortcuts ($values) {
+ foreach ($values as $key => $value) {
+ $this->shortcuts[$key] = $value;
+ }
+ }
+ public function _mailLogin ($value) {
+ if (filter_var ($value, FILTER_VALIDATE_EMAIL)) {
+ $this->mail_login = $value;
+ } elseif ($value == false) {
+ $this->mail_login = false;
+ }
+ }
+ public function _markWhen ($values) {
+ if(!isset($values['article'])) {
+ $values['article'] = 'yes';
+ }
+ if(!isset($values['site'])) {
+ $values['site'] = 'yes';
+ }
+ if(!isset($values['scroll'])) {
+ $values['scroll'] = 'yes';
+ }
+ if(!isset($values['reception'])) {
+ $values['reception'] = 'no';
+ }
+
+ $this->mark_when['article'] = $values['article'];
+ $this->mark_when['site'] = $values['site'];
+ $this->mark_when['scroll'] = $values['scroll'];
+ $this->mark_when['reception'] = $values['reception'];
+ }
+ public function _sharing ($values) {
+ $are_url = array ('shaarli', 'poche', 'diaspora');
+ foreach ($values as $key => $value) {
+ if (in_array($key, $are_url)) {
+ $is_url = (
+ filter_var ($value, FILTER_VALIDATE_URL) ||
+ (version_compare(PHP_VERSION, '5.3.3', '<') &&
+ (strpos($value, '-') > 0) &&
+ ($value === filter_var($value, FILTER_SANITIZE_URL)))
+ ); //PHP bug #51192
+
+ if (!$is_url) {
+ $value = '';
+ }
+ } elseif(!is_bool ($value)) {
+ $value = true;
+ }
+
+ $this->sharing[$key] = $value;
+ }
+ }
+ public function _theme ($value) {
+ $this->theme = $value;
+ }
+ public function _anonAccess ($value) {
+ if ($value == 'yes') {
+ $this->anon_access = 'yes';
+ } else {
+ $this->anon_access = 'no';
+ }
+ }
+ public function _token ($value) {
+ $this->token = $value;
+ }
+ public function _autoLoadMore ($value) {
+ if ($value == 'yes') {
+ $this->auto_load_more = 'yes';
+ } else {
+ $this->auto_load_more = 'no';
+ }
+ }
+ public function _topline_read ($value) {
+ $this->topline_read = $value === 'yes';
+ }
+ public function _topline_favorite ($value) {
+ $this->topline_favorite = $value === 'yes';
+ }
+ public function _topline_date ($value) {
+ $this->topline_date = $value === 'yes';
+ }
+ public function _topline_link ($value) {
+ $this->topline_link = $value === 'yes';
+ }
+ public function _bottomline_read ($value) {
+ $this->bottomline_read = $value === 'yes';
+ }
+ public function _bottomline_favorite ($value) {
+ $this->bottomline_favorite = $value === 'yes';
+ }
+ public function _bottomline_sharing ($value) {
+ $this->bottomline_sharing = $value === 'yes';
+ }
+ public function _bottomline_tags ($value) {
+ $this->bottomline_tags = $value === 'yes';
+ }
+ public function _bottomline_date ($value) {
+ $this->bottomline_date = $value === 'yes';
+ }
+ public function _bottomline_link ($value) {
+ $this->bottomline_link = $value === 'yes';
+ }
+}
diff --git a/app/Models/ConfigurationDAO.php b/app/Models/ConfigurationDAO.php
new file mode 100644
index 000000000..57fc98047
--- /dev/null
+++ b/app/Models/ConfigurationDAO.php
@@ -0,0 +1,156 @@
+<?php
+class FreshRSS_ConfigurationDAO extends Minz_ModelArray {
+ public $language = 'en';
+ public $posts_per_page = 20;
+ public $view_mode = 'normal';
+ public $default_view = 'not_read';
+ public $display_posts = 'no';
+ public $onread_jump_next = 'yes';
+ public $lazyload = 'yes';
+ public $sort_order = 'DESC';
+ public $old_entries = 3;
+ public $shortcuts = array (
+ 'mark_read' => 'r',
+ 'mark_favorite' => 'f',
+ 'go_website' => 'space',
+ 'next_entry' => 'j',
+ 'prev_entry' => 'k',
+ 'collapse_entry' => 'c',
+ 'load_more' => 'm'
+ );
+ public $mail_login = '';
+ public $mark_when = array (
+ 'article' => 'yes',
+ 'site' => 'yes',
+ 'scroll' => 'no',
+ 'reception' => 'no'
+ );
+ public $sharing = array (
+ 'shaarli' => '',
+ 'poche' => '',
+ 'diaspora' => '',
+ 'twitter' => true,
+ 'g+' => true,
+ 'facebook' => true,
+ 'email' => true,
+ 'print' => true
+ );
+ public $theme = 'default';
+ public $anon_access = 'no';
+ public $token = '';
+ public $auto_load_more = 'yes';
+ public $topline_read = 'yes';
+ public $topline_favorite = 'yes';
+ public $topline_date = 'yes';
+ public $topline_link = 'yes';
+ public $bottomline_read = 'yes';
+ public $bottomline_favorite = 'yes';
+ public $bottomline_sharing = 'yes';
+ public $bottomline_tags = 'yes';
+ public $bottomline_date = 'yes';
+ public $bottomline_link = 'yes';
+
+ public function __construct ($nameFile = '') {
+ if (empty($nameFile)) {
+ $nameFile = DATA_PATH . '/' . Minz_Configuration::currentUser () . '_user.php';
+ }
+ parent::__construct ($nameFile);
+
+ // TODO : simplifier ce code, une boucle for() devrait suffire !
+ if (isset ($this->array['language'])) {
+ $this->language = $this->array['language'];
+ }
+ if (isset ($this->array['posts_per_page'])) {
+ $this->posts_per_page = $this->array['posts_per_page'];
+ }
+ if (isset ($this->array['view_mode'])) {
+ $this->view_mode = $this->array['view_mode'];
+ }
+ if (isset ($this->array['default_view'])) {
+ $this->default_view = $this->array['default_view'];
+ }
+ if (isset ($this->array['display_posts'])) {
+ $this->display_posts = $this->array['display_posts'];
+ }
+ if (isset ($this->array['onread_jump_next'])) {
+ $this->onread_jump_next = $this->array['onread_jump_next'];
+ }
+ if (isset ($this->array['lazyload'])) {
+ $this->lazyload = $this->array['lazyload'];
+ }
+ if (isset ($this->array['sort_order'])) {
+ $this->sort_order = $this->array['sort_order'];
+ }
+ if (isset ($this->array['old_entries'])) {
+ $this->old_entries = $this->array['old_entries'];
+ }
+ if (isset ($this->array['shortcuts'])) {
+ $this->shortcuts = array_merge (
+ $this->shortcuts, $this->array['shortcuts']
+ );
+ }
+ if (isset ($this->array['mail_login'])) {
+ $this->mail_login = $this->array['mail_login'];
+ }
+ if (isset ($this->array['mark_when'])) {
+ $this->mark_when = $this->array['mark_when'];
+ }
+ if (isset ($this->array['sharing'])) {
+ $this->sharing = array_merge (
+ $this->sharing, $this->array['sharing']
+ );
+ }
+ if (isset ($this->array['theme'])) {
+ $this->theme = $this->array['theme'];
+ }
+ if (isset ($this->array['anon_access'])) {
+ $this->anon_access = $this->array['anon_access'];
+ }
+ if (isset ($this->array['token'])) {
+ $this->token = $this->array['token'];
+ }
+ if (isset ($this->array['auto_load_more'])) {
+ $this->auto_load_more = $this->array['auto_load_more'];
+ }
+
+ if (isset ($this->array['topline_read'])) {
+ $this->topline_read = $this->array['topline_read'];
+ }
+ if (isset ($this->array['topline_favorite'])) {
+ $this->topline_favorite = $this->array['topline_favorite'];
+ }
+ if (isset ($this->array['topline_date'])) {
+ $this->topline_date = $this->array['topline_date'];
+ }
+ if (isset ($this->array['topline_link'])) {
+ $this->topline_link = $this->array['topline_link'];
+ }
+ if (isset ($this->array['bottomline_read'])) {
+ $this->bottomline_read = $this->array['bottomline_read'];
+ }
+ if (isset ($this->array['bottomline_favorite'])) {
+ $this->bottomline_favorite = $this->array['bottomline_favorite'];
+ }
+ if (isset ($this->array['bottomline_sharing'])) {
+ $this->bottomline_sharing = $this->array['bottomline_sharing'];
+ }
+ if (isset ($this->array['bottomline_tags'])) {
+ $this->bottomline_tags = $this->array['bottomline_tags'];
+ }
+ if (isset ($this->array['bottomline_date'])) {
+ $this->bottomline_date = $this->array['bottomline_date'];
+ }
+ if (isset ($this->array['bottomline_link'])) {
+ $this->bottomline_link = $this->array['bottomline_link'];
+ }
+ }
+
+ public function update ($values) {
+ foreach ($values as $key => $value) {
+ $this->array[$key] = $value;
+ }
+
+ $this->writeFile($this->array);
+ invalidateHttpCache();
+ }
+}
diff --git a/app/Models/Days.php b/app/Models/Days.php
new file mode 100644
index 000000000..2d770c30b
--- /dev/null
+++ b/app/Models/Days.php
@@ -0,0 +1,7 @@
+<?php
+
+class FreshRSS_Days {
+ const TODAY = 0;
+ const YESTERDAY = 1;
+ const BEFORE_YESTERDAY = 2;
+}
diff --git a/app/Models/Entry.php b/app/Models/Entry.php
new file mode 100644
index 000000000..983f94727
--- /dev/null
+++ b/app/Models/Entry.php
@@ -0,0 +1,192 @@
+<?php
+class FreshRSS_Entry extends Minz_Model {
+
+ private $id = 0;
+ private $guid;
+ private $title;
+ private $author;
+ private $content;
+ private $link;
+ private $date;
+ private $is_read;
+ private $is_favorite;
+ private $feed;
+ private $tags;
+
+ public function __construct ($feed = '', $guid = '', $title = '', $author = '', $content = '',
+ $link = '', $pubdate = 0, $is_read = false, $is_favorite = false, $tags = '') {
+ $this->_guid ($guid);
+ $this->_title ($title);
+ $this->_author ($author);
+ $this->_content ($content);
+ $this->_link ($link);
+ $this->_date ($pubdate);
+ $this->_isRead ($is_read);
+ $this->_isFavorite ($is_favorite);
+ $this->_feed ($feed);
+ $this->_tags (preg_split('/[\s#]/', $tags));
+ }
+
+ public function id () {
+ return $this->id;
+ }
+ public function guid () {
+ return $this->guid;
+ }
+ public function title () {
+ return $this->title;
+ }
+ public function author () {
+ if (is_null ($this->author)) {
+ return '';
+ } else {
+ return $this->author;
+ }
+ }
+ public function content () {
+ return $this->content;
+ }
+ public function link () {
+ return $this->link;
+ }
+ public function date ($raw = false) {
+ if ($raw) {
+ return $this->date;
+ } else {
+ return timestamptodate ($this->date);
+ }
+ }
+ public function dateAdded ($raw = false) {
+ $date = intval(substr($this->id, 0, -6));
+ if ($raw) {
+ return $date;
+ } else {
+ return timestamptodate ($date);
+ }
+ }
+ public function isRead () {
+ return $this->is_read;
+ }
+ public function isFavorite () {
+ return $this->is_favorite;
+ }
+ public function feed ($object = false) {
+ if ($object) {
+ $feedDAO = new FreshRSS_FeedDAO ();
+ return $feedDAO->searchById ($this->feed);
+ } else {
+ return $this->feed;
+ }
+ }
+ public function tags ($inString = false) {
+ if ($inString) {
+ return empty ($this->tags) ? '' : '#' . implode(' #', $this->tags);
+ } else {
+ return $this->tags;
+ }
+ }
+
+ public function _id ($value) {
+ $this->id = $value;
+ }
+ public function _guid ($value) {
+ $this->guid = $value;
+ }
+ public function _title ($value) {
+ $this->title = $value;
+ }
+ public function _author ($value) {
+ $this->author = $value;
+ }
+ public function _content ($value) {
+ $this->content = $value;
+ }
+ public function _link ($value) {
+ $this->link = $value;
+ }
+ public function _date ($value) {
+ if (ctype_digit ($value)) {
+ $this->date = intval ($value);
+ } else {
+ $this->date = time ();
+ }
+ }
+ public function _isRead ($value) {
+ $this->is_read = $value;
+ }
+ public function _isFavorite ($value) {
+ $this->is_favorite = $value;
+ }
+ public function _feed ($value) {
+ $this->feed = $value;
+ }
+ public function _tags ($value) {
+ if (!is_array ($value)) {
+ $value = array ($value);
+ }
+
+ foreach ($value as $key => $t) {
+ if (!$t) {
+ unset ($value[$key]);
+ }
+ }
+
+ $this->tags = $value;
+ }
+
+ public function isDay ($day, $today) {
+ $date = $this->dateAdded(true);
+ switch ($day) {
+ case FreshRSS_Days::TODAY:
+ $tomorrow = $today + 86400;
+ return $date >= $today && $date < $tomorrow;
+ case FreshRSS_Days::YESTERDAY:
+ $yesterday = $today - 86400;
+ return $date >= $yesterday && $date < $today;
+ case FreshRSS_Days::BEFORE_YESTERDAY:
+ $yesterday = $today - 86400;
+ return $date < $yesterday;
+ default:
+ return false;
+ }
+ }
+
+ public function loadCompleteContent($pathEntries) {
+ // Gestion du contenu
+ // On cherche à récupérer les articles en entier... même si le flux ne le propose pas
+ if ($pathEntries) {
+ $entryDAO = new FreshRSS_EntryDAO();
+ $entry = $entryDAO->searchByGuid($this->feed, $this->guid);
+
+ if($entry) {
+ // l'article existe déjà en BDD, en se contente de recharger ce contenu
+ $this->content = $entry->content();
+ } else {
+ try {
+ // l'article n'est pas en BDD, on va le chercher sur le site
+ $this->content = get_content_by_parsing(
+ $this->link(), $pathEntries
+ );
+ } catch (Exception $e) {
+ // rien à faire, on garde l'ancien contenu (requête a échoué)
+ }
+ }
+ }
+ }
+
+ public function toArray () {
+ return array (
+ 'id' => $this->id (),
+ 'guid' => $this->guid (),
+ 'title' => $this->title (),
+ 'author' => $this->author (),
+ 'content' => $this->content (),
+ 'link' => $this->link (),
+ 'date' => $this->date (true),
+ 'is_read' => $this->isRead (),
+ 'is_favorite' => $this->isFavorite (),
+ 'id_feed' => $this->feed (),
+ 'tags' => $this->tags (true),
+ );
+ }
+}
diff --git a/app/Models/EntryDAO.php b/app/Models/EntryDAO.php
new file mode 100644
index 000000000..d8bc869ae
--- /dev/null
+++ b/app/Models/EntryDAO.php
@@ -0,0 +1,464 @@
+<?php
+class FreshRSS_EntryDAO extends Minz_ModelPdo {
+ public function addEntry ($valuesTmp) {
+ $sql = 'INSERT INTO `' . $this->prefix . 'entry`(id, guid, title, author, content_bin, link, date, is_read, is_favorite, id_feed, tags) '
+ . 'VALUES(?, ?, ?, ?, COMPRESS(?), ?, ?, ?, ?, ?, ?)';
+ $stm = $this->bd->prepare ($sql);
+
+ $values = array (
+ $valuesTmp['id'],
+ substr($valuesTmp['guid'], 0, 760),
+ substr($valuesTmp['title'], 0, 255),
+ substr($valuesTmp['author'], 0, 255),
+ $valuesTmp['content'],
+ substr($valuesTmp['link'], 0, 1023),
+ $valuesTmp['date'],
+ $valuesTmp['is_read'],
+ $valuesTmp['is_favorite'],
+ $valuesTmp['id_feed'],
+ substr($valuesTmp['tags'], 0, 1023),
+ );
+
+ if ($stm && $stm->execute ($values)) {
+ return $this->bd->lastInsertId();
+ } else {
+ $info = $stm->errorInfo();
+ if ((int)($info[0] / 1000) !== 23) { //Filter out "SQLSTATE Class code 23: Constraint Violation" because of expected duplicate entries
+ Minz_Log::record ('SQL error ' . $info[0] . ': ' . $info[1] . ' ' . $info[2]
+ . ' while adding entry in feed ' . $valuesTmp['id_feed'] . ' with title: ' . $valuesTmp['title'], Minz_Log::ERROR);
+ } /*else {
+ Minz_Log::record ('SQL error ' . $info[0] . ': ' . $info[1] . ' ' . $info[2]
+ . ' while adding entry in feed ' . $valuesTmp['id_feed'] . ' with title: ' . $valuesTmp['title'], Minz_Log::DEBUG);
+ }*/
+ return false;
+ }
+ }
+
+ public function markFavorite ($id, $is_favorite = true) {
+ $sql = 'UPDATE `' . $this->prefix . 'entry` e '
+ . 'SET e.is_favorite = ? '
+ . 'WHERE e.id=?';
+ $values = array ($is_favorite ? 1 : 0, $id);
+ $stm = $this->bd->prepare ($sql);
+ if ($stm && $stm->execute ($values)) {
+ return $stm->rowCount();
+ } else {
+ $info = $stm->errorInfo();
+ Minz_Log::record ('SQL error : ' . $info[2], Minz_Log::ERROR);
+ return false;
+ }
+ }
+ public function markRead ($id, $is_read = true) {
+ $sql = 'UPDATE `' . $this->prefix . 'entry` e INNER JOIN `' . $this->prefix . 'feed` f ON e.id_feed = f.id '
+ . 'SET e.is_read = ?,'
+ . 'f.cache_nbUnreads=f.cache_nbUnreads' . ($is_read ? '-' : '+') . '1 '
+ . 'WHERE e.id=?';
+ $values = array ($is_read ? 1 : 0, $id);
+ $stm = $this->bd->prepare ($sql);
+ if ($stm && $stm->execute ($values)) {
+ return $stm->rowCount();
+ } else {
+ $info = $stm->errorInfo();
+ Minz_Log::record ('SQL error : ' . $info[2], Minz_Log::ERROR);
+ return false;
+ }
+ }
+ public function markReadEntries ($idMax = 0, $favorites = false) {
+ if ($idMax === 0) {
+ $sql = 'UPDATE `' . $this->prefix . 'entry` e INNER JOIN `' . $this->prefix . 'feed` f ON e.id_feed = f.id '
+ . 'SET e.is_read = 1, f.cache_nbUnreads=0 '
+ . 'WHERE e.is_read = 0 AND ';
+ if ($favorites) {
+ $sql .= 'e.is_favorite = 1';
+ } else {
+ $sql .= 'f.priority > 0';
+ }
+ $stm = $this->bd->prepare ($sql);
+ if ($stm && $stm->execute ()) {
+ return $stm->rowCount();
+ } else {
+ $info = $stm->errorInfo();
+ Minz_Log::record ('SQL error : ' . $info[2], Minz_Log::ERROR);
+ return false;
+ }
+ } else {
+ $this->bd->beginTransaction ();
+
+ $sql = 'UPDATE `' . $this->prefix . 'entry` e INNER JOIN `' . $this->prefix . 'feed` f ON e.id_feed = f.id '
+ . 'SET e.is_read = 1 '
+ . 'WHERE e.is_read = 0 AND e.id <= ? AND ';
+ if ($favorites) {
+ $sql .= 'e.is_favorite = 1';
+ } else {
+ $sql .= 'f.priority > 0';
+ }
+ $values = array ($idMax);
+ $stm = $this->bd->prepare ($sql);
+ if (!($stm && $stm->execute ($values))) {
+ $info = $stm->errorInfo();
+ Minz_Log::record ('SQL error : ' . $info[2], Minz_Log::ERROR);
+ $this->bd->rollBack ();
+ return false;
+ }
+ $affected = $stm->rowCount();
+
+ if ($affected > 0) {
+ $sql = 'UPDATE `' . $this->prefix . 'feed` f '
+ . 'LEFT OUTER JOIN ('
+ . 'SELECT e.id_feed, '
+ . 'COUNT(*) AS nbUnreads '
+ . 'FROM `' . $this->prefix . 'entry` e '
+ . 'WHERE e.is_read = 0 '
+ . 'GROUP BY e.id_feed'
+ . ') x ON x.id_feed=f.id '
+ . 'SET f.cache_nbUnreads=COALESCE(x.nbUnreads, 0)';
+ $stm = $this->bd->prepare ($sql);
+ if (!($stm && $stm->execute ())) {
+ $info = $stm->errorInfo();
+ Minz_Log::record ('SQL error : ' . $info[2], Minz_Log::ERROR);
+ $this->bd->rollBack ();
+ return false;
+ }
+ }
+
+ $this->bd->commit ();
+ return $affected;
+ }
+ }
+ public function markReadCat ($id, $idMax = 0) {
+ if ($idMax === 0) {
+ $sql = 'UPDATE `' . $this->prefix . 'entry` e INNER JOIN `' . $this->prefix . 'feed` f ON e.id_feed = f.id '
+ . 'SET e.is_read = 1, f.cache_nbUnreads=0 '
+ . 'WHERE f.category = ? AND e.is_read = 0';
+ $values = array ($id);
+ $stm = $this->bd->prepare ($sql);
+ if ($stm && $stm->execute ($values)) {
+ return $stm->rowCount();
+ } else {
+ $info = $stm->errorInfo();
+ Minz_Log::record ('SQL error : ' . $info[2], Minz_Log::ERROR);
+ return false;
+ }
+ } else {
+ $this->bd->beginTransaction ();
+
+ $sql = 'UPDATE `' . $this->prefix . 'entry` e INNER JOIN `' . $this->prefix . 'feed` f ON e.id_feed = f.id '
+ . 'SET e.is_read = 1 '
+ . 'WHERE f.category = ? AND e.is_read = 0 AND e.id <= ?';
+ $values = array ($id, $idMax);
+ $stm = $this->bd->prepare ($sql);
+ if (!($stm && $stm->execute ($values))) {
+ $info = $stm->errorInfo();
+ Minz_Log::record ('SQL error : ' . $info[2], Minz_Log::ERROR);
+ $this->bd->rollBack ();
+ return false;
+ }
+ $affected = $stm->rowCount();
+
+ if ($affected > 0) {
+ $sql = 'UPDATE `' . $this->prefix . 'feed` f '
+ . 'LEFT OUTER JOIN ('
+ . 'SELECT e.id_feed, '
+ . 'COUNT(*) AS nbUnreads '
+ . 'FROM `' . $this->prefix . 'entry` e '
+ . 'WHERE e.is_read = 0 '
+ . 'GROUP BY e.id_feed'
+ . ') x ON x.id_feed=f.id '
+ . 'SET f.cache_nbUnreads=COALESCE(x.nbUnreads, 0) '
+ . 'WHERE f.category = ?';
+ $values = array ($id);
+ $stm = $this->bd->prepare ($sql);
+ if (!($stm && $stm->execute ($values))) {
+ $info = $stm->errorInfo();
+ Minz_Log::record ('SQL error : ' . $info[2], Minz_Log::ERROR);
+ $this->bd->rollBack ();
+ return false;
+ }
+ }
+
+ $this->bd->commit ();
+ return $affected;
+ }
+ }
+ public function markReadFeed ($id, $idMax = 0) {
+ if ($idMax === 0) {
+ $sql = 'UPDATE `' . $this->prefix . 'entry` e INNER JOIN `' . $this->prefix . 'feed` f ON e.id_feed = f.id '
+ . 'SET e.is_read = 1, f.cache_nbUnreads=0 '
+ . 'WHERE f.id=? AND e.is_read = 0';
+ $values = array ($id);
+ $stm = $this->bd->prepare ($sql);
+ if ($stm && $stm->execute ($values)) {
+ return $stm->rowCount();
+ } else {
+ $info = $stm->errorInfo();
+ Minz_Log::record ('SQL error : ' . $info[2], Minz_Log::ERROR);
+ return false;
+ }
+ } else {
+ $this->bd->beginTransaction ();
+
+ $sql = 'UPDATE `' . $this->prefix . 'entry` e INNER JOIN `' . $this->prefix . 'feed` f ON e.id_feed = f.id '
+ . 'SET e.is_read = 1 '
+ . 'WHERE f.id=? AND e.is_read = 0 AND e.id <= ?';
+ $values = array ($id, $idMax);
+ $stm = $this->bd->prepare ($sql);
+ if (!($stm && $stm->execute ($values))) {
+ $info = $stm->errorInfo();
+ Minz_Log::record ('SQL error : ' . $info[2], Minz_Log::ERROR);
+ $this->bd->rollBack ();
+ return false;
+ }
+ $affected = $stm->rowCount();
+
+ if ($affected > 0) {
+ $sql = 'UPDATE `' . $this->prefix . 'feed` f '
+ . 'SET f.cache_nbUnreads=f.cache_nbUnreads-' . $affected
+ . ' WHERE f.id=?';
+ $values = array ($id);
+ $stm = $this->bd->prepare ($sql);
+ if (!($stm && $stm->execute ($values))) {
+ $info = $stm->errorInfo();
+ Minz_Log::record ('SQL error : ' . $info[2], Minz_Log::ERROR);
+ $this->bd->rollBack ();
+ return false;
+ }
+ }
+
+ $this->bd->commit ();
+ return $affected;
+ }
+ }
+
+ public function searchByGuid ($feed_id, $id) {
+ // un guid est unique pour un flux donné
+ $sql = 'SELECT id, guid, title, author, UNCOMPRESS(content_bin) AS content, link, date, is_read, is_favorite, id_feed, tags '
+ . 'FROM `' . $this->prefix . 'entry` WHERE id_feed=? AND guid=?';
+ $stm = $this->bd->prepare ($sql);
+
+ $values = array (
+ $feed_id,
+ $id
+ );
+
+ $stm->execute ($values);
+ $res = $stm->fetchAll (PDO::FETCH_ASSOC);
+ $entries = self::daoToEntry ($res);
+ return isset ($entries[0]) ? $entries[0] : false;
+ }
+
+ public function searchById ($id) {
+ $sql = 'SELECT id, guid, title, author, UNCOMPRESS(content_bin) AS content, link, date, is_read, is_favorite, id_feed, tags '
+ . 'FROM `' . $this->prefix . 'entry` WHERE id=?';
+ $stm = $this->bd->prepare ($sql);
+
+ $values = array ($id);
+
+ $stm->execute ($values);
+ $res = $stm->fetchAll (PDO::FETCH_ASSOC);
+ $entries = self::daoToEntry ($res);
+ return isset ($entries[0]) ? $entries[0] : false;
+ }
+
+ public function listWhere($type = 'a', $id = '', $state = 'all', $order = 'DESC', $limit = 1, $firstId = '', $filter = '', $date_min = 0) {
+ $where = '';
+ $joinFeed = false;
+ $values = array();
+ switch ($type) {
+ case 'a':
+ $where .= 'f.priority > 0 ';
+ $joinFeed = true;
+ break;
+ case 's':
+ $where .= 'e1.is_favorite = 1 ';
+ break;
+ case 'c':
+ $where .= 'f.category = ? ';
+ $values[] = intval($id);
+ $joinFeed = true;
+ break;
+ case 'f':
+ $where .= 'e1.id_feed = ? ';
+ $values[] = intval($id);
+ break;
+ default:
+ throw new FreshRSS_EntriesGetter_Exception ('Bad type in Entry->listByType: [' . $type . ']!');
+ }
+ switch ($state) {
+ case 'all':
+ break;
+ case 'not_read':
+ $where .= 'AND e1.is_read = 0 ';
+ break;
+ case 'read':
+ $where .= 'AND e1.is_read = 1 ';
+ break;
+ default:
+ throw new FreshRSS_EntriesGetter_Exception ('Bad state in Entry->listByType: [' . $state . ']!');
+ }
+ switch ($order) {
+ case 'DESC':
+ case 'ASC':
+ break;
+ default:
+ throw new FreshRSS_EntriesGetter_Exception ('Bad order in Entry->listByType: [' . $order . ']!');
+ }
+ if ($firstId !== '') {
+ $where .= 'AND e1.id ' . ($order === 'DESC' ? '<=' : '>=') . $firstId . ' ';
+ }
+ if (($date_min > 0) && ($type !== 's')) {
+ $where .= 'AND (e1.id >= ' . $date_min . '000000 OR e1.is_favorite = 1 OR f.keep_history = 1) ';
+ $joinFeed = true;
+ }
+ $search = '';
+ if ($filter !== '') {
+ $filter = trim($filter);
+ $filter = addcslashes($filter, '\\%_');
+ if (stripos($filter, 'intitle:') === 0) {
+ $filter = substr($filter, strlen('intitle:'));
+ $intitle = true;
+ } else {
+ $intitle = false;
+ }
+ if (stripos($filter, 'inurl:') === 0) {
+ $filter = substr($filter, strlen('inurl:'));
+ $inurl = true;
+ } else {
+ $inurl = false;
+ }
+ if (stripos($filter, 'author:') === 0) {
+ $filter = substr($filter, strlen('author:'));
+ $author = true;
+ } else {
+ $author = false;
+ }
+ $terms = array_unique(explode(' ', $filter));
+ sort($terms); //Put #tags first
+ foreach ($terms as $word) {
+ $word = trim($word);
+ if (strlen($word) > 0) {
+ if ($intitle) {
+ $search .= 'AND e1.title LIKE ? ';
+ $values[] = '%' . $word .'%';
+ } elseif ($inurl) {
+ $search .= 'AND CONCAT(e1.link, e1.guid) LIKE ? ';
+ $values[] = '%' . $word .'%';
+ } elseif ($author) {
+ $search .= 'AND e1.author LIKE ? ';
+ $values[] = '%' . $word .'%';
+ } else {
+ if ($word[0] === '#' && isset($word[1])) {
+ $search .= 'AND e1.tags LIKE ? ';
+ $values[] = '%' . $word .'%';
+ } else {
+ $search .= 'AND CONCAT(e1.title, UNCOMPRESS(e1.content_bin)) LIKE ? ';
+ $values[] = '%' . $word .'%';
+ }
+ }
+ }
+ }
+ }
+
+ $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 '
+ . 'FROM `' . $this->prefix . 'entry` e '
+ . 'INNER JOIN (SELECT e1.id FROM `' . $this->prefix . 'entry` e1 '
+ . ($joinFeed ? 'INNER JOIN `' . $this->prefix . 'feed` f ON e1.id_feed = f.id ' : '')
+ . 'WHERE ' . $where
+ . $search
+ . 'ORDER BY e1.id ' . $order
+ . ($limit > 0 ? ' LIMIT ' . $limit : '') //TODO: See http://explainextended.com/2009/10/23/mysql-order-by-limit-performance-late-row-lookups/
+ . ') e2 ON e2.id = e.id '
+ . 'ORDER BY e.id ' . $order;
+
+ $stm = $this->bd->prepare ($sql);
+ $stm->execute ($values);
+
+ return self::daoToEntry ($stm->fetchAll (PDO::FETCH_ASSOC));
+ }
+
+ public function listLastGuidsByFeed($id, $n) {
+ $sql = 'SELECT guid FROM `' . $this->prefix . 'entry` WHERE id_feed=? ORDER BY id DESC LIMIT ' . intval($n);
+ $stm = $this->bd->prepare ($sql);
+ $values = array ($id);
+ $stm->execute ($values);
+ return $stm->fetchAll (PDO::FETCH_COLUMN, 0);
+ }
+
+ public function countUnreadRead () {
+ $sql = 'SELECT COUNT(e.id) AS count FROM `' . $this->prefix . 'entry` e INNER JOIN `' . $this->prefix . 'feed` f ON e.id_feed = f.id WHERE priority > 0'
+ . ' UNION SELECT COUNT(e.id) AS count FROM `' . $this->prefix . 'entry` e INNER JOIN `' . $this->prefix . 'feed` f ON e.id_feed = f.id WHERE priority > 0 AND is_read = 0';
+ $stm = $this->bd->prepare ($sql);
+ $stm->execute ();
+ $res = $stm->fetchAll (PDO::FETCH_COLUMN, 0);
+ $all = empty($res[0]) ? 0 : $res[0];
+ $unread = empty($res[1]) ? 0 : $res[1];
+ return array('all' => $all, 'unread' => $unread, 'read' => $all - $unread);
+ }
+ public function count ($minPriority = null) {
+ $sql = 'SELECT COUNT(e.id) AS count FROM `' . $this->prefix . 'entry` e INNER JOIN `' . $this->prefix . 'feed` f ON e.id_feed = f.id';
+ if ($minPriority !== null) {
+ $sql = ' WHERE priority > ' . intval($minPriority);
+ }
+ $stm = $this->bd->prepare ($sql);
+ $stm->execute ();
+ $res = $stm->fetchAll (PDO::FETCH_COLUMN, 0);
+ return $res[0];
+ }
+ public function countNotRead ($minPriority = null) {
+ $sql = 'SELECT COUNT(e.id) AS count FROM `' . $this->prefix . 'entry` e INNER JOIN `' . $this->prefix . 'feed` f ON e.id_feed = f.id WHERE is_read = 0';
+ if ($minPriority !== null) {
+ $sql = ' AND priority > ' . intval($minPriority);
+ }
+ $stm = $this->bd->prepare ($sql);
+ $stm->execute ();
+ $res = $stm->fetchAll (PDO::FETCH_COLUMN, 0);
+ return $res[0];
+ }
+
+ public function countUnreadReadFavorites () {
+ $sql = 'SELECT COUNT(id) FROM `' . $this->prefix . 'entry` WHERE is_favorite=1'
+ . ' UNION SELECT COUNT(id) FROM `' . $this->prefix . 'entry` WHERE is_favorite=1 AND is_read = 0';
+ $stm = $this->bd->prepare ($sql);
+ $stm->execute ();
+ $res = $stm->fetchAll (PDO::FETCH_COLUMN, 0);
+ $all = empty($res[0]) ? 0 : $res[0];
+ $unread = empty($res[1]) ? 0 : $res[1];
+ return array('all' => $all, 'unread' => $unread, 'read' => $all - $unread);
+ }
+
+ public function optimizeTable() {
+ $sql = 'OPTIMIZE TABLE `' . $this->prefix . 'entry`';
+ $stm = $this->bd->prepare ($sql);
+ $stm->execute ();
+ }
+
+ public static function daoToEntry ($listDAO) {
+ $list = array ();
+
+ if (!is_array ($listDAO)) {
+ $listDAO = array ($listDAO);
+ }
+
+ foreach ($listDAO as $key => $dao) {
+ $entry = new FreshRSS_Entry (
+ $dao['id_feed'],
+ $dao['guid'],
+ $dao['title'],
+ $dao['author'],
+ $dao['content'],
+ $dao['link'],
+ $dao['date'],
+ $dao['is_read'],
+ $dao['is_favorite'],
+ $dao['tags']
+ );
+ if (isset ($dao['id'])) {
+ $entry->_id ($dao['id']);
+ }
+ $list[] = $entry;
+ }
+
+ unset ($listDAO);
+
+ return $list;
+ }
+}
diff --git a/app/Models/Feed.php b/app/Models/Feed.php
new file mode 100644
index 000000000..e63ac8c7a
--- /dev/null
+++ b/app/Models/Feed.php
@@ -0,0 +1,319 @@
+<?php
+class FreshRSS_Feed extends Minz_Model {
+ private $id = 0;
+ private $url;
+ private $category = 1;
+ private $nbEntries = -1;
+ private $nbNotRead = -1;
+ private $entries = null;
+ private $name = '';
+ private $website = '';
+ private $description = '';
+ private $lastUpdate = 0;
+ private $priority = 10;
+ private $pathEntries = '';
+ private $httpAuth = '';
+ private $error = false;
+ private $keep_history = false;
+
+ public function __construct ($url, $validate=true) {
+ if ($validate) {
+ $this->_url ($url);
+ } else {
+ $this->url = $url;
+ }
+ }
+
+ public function id () {
+ return $this->id;
+ }
+ public function url () {
+ return $this->url;
+ }
+ public function category () {
+ return $this->category;
+ }
+ public function entries () {
+ if (!is_null ($this->entries)) {
+ return $this->entries;
+ } else {
+ return array ();
+ }
+ }
+ public function name () {
+ return $this->name;
+ }
+ public function website () {
+ return $this->website;
+ }
+ public function description () {
+ return $this->description;
+ }
+ public function lastUpdate () {
+ return $this->lastUpdate;
+ }
+ public function priority () {
+ return $this->priority;
+ }
+ public function pathEntries () {
+ return $this->pathEntries;
+ }
+ public function httpAuth ($raw = true) {
+ if ($raw) {
+ return $this->httpAuth;
+ } else {
+ $pos_colon = strpos ($this->httpAuth, ':');
+ $user = substr ($this->httpAuth, 0, $pos_colon);
+ $pass = substr ($this->httpAuth, $pos_colon + 1);
+
+ return array (
+ 'username' => $user,
+ 'password' => $pass
+ );
+ }
+ }
+ public function inError () {
+ return $this->error;
+ }
+ public function keepHistory () {
+ return $this->keep_history;
+ }
+ public function nbEntries () {
+ if ($this->nbEntries < 0) {
+ $feedDAO = new FreshRSS_FeedDAO ();
+ $this->nbEntries = $feedDAO->countEntries ($this->id ());
+ }
+
+ return $this->nbEntries;
+ }
+ public function nbNotRead () {
+ if ($this->nbNotRead < 0) {
+ $feedDAO = new FreshRSS_FeedDAO ();
+ $this->nbNotRead = $feedDAO->countNotRead ($this->id ());
+ }
+
+ return $this->nbNotRead;
+ }
+ public function faviconPrepare() {
+ $file = DATA_PATH . '/favicons/' . $this->id () . '.txt';
+ if (!file_exists ($file)) {
+ $t = $this->website;
+ if (empty($t)) {
+ $t = $this->url;
+ }
+ file_put_contents($file, $t);
+ }
+ }
+ public static function faviconDelete($id) {
+ $path = DATA_PATH . '/favicons/' . $id;
+ @unlink($path . '.ico');
+ @unlink($path . '.txt');
+ }
+ public function favicon () {
+ return Minz_Url::display ('/f.php?' . $this->id ());
+ }
+
+ public function _id ($value) {
+ $this->id = $value;
+ }
+ public function _url ($value, $validate=true) {
+ if ($validate) {
+ $value = checkUrl($value);
+ }
+ if (empty ($value)) {
+ throw new FreshRSS_BadUrl_Exception ($value);
+ }
+ $this->url = $value;
+ }
+ public function _category ($value) {
+ $this->category = $value;
+ }
+ public function _name ($value) {
+ if (is_null ($value)) {
+ $value = '';
+ }
+ $this->name = $value;
+ }
+ public function _website ($value, $validate=true) {
+ if ($validate) {
+ $value = checkUrl($value);
+ }
+ if (empty ($value)) {
+ $value = '';
+ }
+ $this->website = $value;
+ }
+ public function _description ($value) {
+ if (is_null ($value)) {
+ $value = '';
+ }
+ $this->description = $value;
+ }
+ public function _lastUpdate ($value) {
+ $this->lastUpdate = $value;
+ }
+ public function _priority ($value) {
+ $this->priority = ctype_digit ($value) ? intval ($value) : 10;
+ }
+ public function _pathEntries ($value) {
+ $this->pathEntries = $value;
+ }
+ public function _httpAuth ($value) {
+ $this->httpAuth = $value;
+ }
+ public function _error ($value) {
+ if ($value) {
+ $value = true;
+ } else {
+ $value = false;
+ }
+ $this->error = $value;
+ }
+ public function _keepHistory ($value) {
+ if ($value) {
+ $value = true;
+ } else {
+ $value = false;
+ }
+ $this->keep_history = $value;
+ }
+ public function _nbNotRead ($value) {
+ $this->nbNotRead = ctype_digit ($value) ? intval ($value) : -1;
+ }
+ public function _nbEntries ($value) {
+ $this->nbEntries = ctype_digit ($value) ? intval ($value) : -1;
+ }
+
+ public function load () {
+ if (!is_null ($this->url)) {
+ if (CACHE_PATH === false) {
+ throw new Minz_FileNotExistException (
+ 'CACHE_PATH',
+ Minz_Exception::ERROR
+ );
+ } else {
+ $feed = new SimplePie ();
+ $feed->set_useragent(Minz_Translate::t ('freshrss') . '/' . FRESHRSS_VERSION . ' (' . PHP_OS . '; ' . FRESHRSS_WEBSITE . ') ' . SIMPLEPIE_NAME . '/' . SIMPLEPIE_VERSION);
+ $url = htmlspecialchars_decode ($this->url, ENT_QUOTES);
+ if ($this->httpAuth != '') {
+ $url = preg_replace ('#((.+)://)(.+)#', '${1}' . $this->httpAuth . '@${3}', $url);
+ }
+
+ $feed->set_feed_url ($url);
+ $feed->set_cache_location (CACHE_PATH);
+ $feed->set_cache_duration(1500);
+ $feed->strip_htmltags (array (
+ 'base', 'blink', 'body', 'doctype', 'embed',
+ 'font', 'form', 'frame', 'frameset', 'html',
+ 'input', 'marquee', 'meta', 'noscript',
+ 'object', 'param', 'plaintext', 'script', 'style',
+ ));
+ $feed->strip_attributes(array_merge($feed->strip_attributes, array(
+ 'autoplay', 'onload', 'onunload', 'onclick', 'ondblclick', 'onmousedown', 'onmouseup',
+ 'onmouseover', 'onmousemove', 'onmouseout', 'onfocus', 'onblur',
+ 'onkeypress', 'onkeydown', 'onkeyup', 'onselect', 'onchange', 'seamless')));
+ $feed->add_attributes(array(
+ 'img' => array('lazyload' => ''), //http://www.w3.org/TR/resource-priorities/
+ 'audio' => array('preload' => 'none'),
+ 'iframe' => array('postpone' => '', 'sandbox' => 'allow-scripts allow-same-origin'),
+ 'video' => array('postpone' => '', 'preload' => 'none'),
+ ));
+ $feed->set_url_replacements(array(
+ 'a' => 'href',
+ 'area' => 'href',
+ 'audio' => 'src',
+ 'blockquote' => 'cite',
+ 'del' => 'cite',
+ 'form' => 'action',
+ 'iframe' => 'src',
+ 'img' => array(
+ 'longdesc',
+ 'src'
+ ),
+ 'input' => 'src',
+ 'ins' => 'cite',
+ 'q' => 'cite',
+ 'source' => 'src',
+ 'track' => 'src',
+ 'video' => array(
+ 'poster',
+ 'src',
+ ),
+ ));
+ $feed->init ();
+
+ if ($feed->error ()) {
+ throw new FreshRSS_Feed_Exception ($feed->error . ' [' . $url . ']');
+ }
+
+ // si on a utilisé l'auto-discover, notre url va avoir changé
+ $subscribe_url = $feed->subscribe_url ();
+ if (!is_null ($subscribe_url) && $subscribe_url != $this->url) {
+ if ($this->httpAuth != '') {
+ // on enlève les id si authentification HTTP
+ $subscribe_url = preg_replace ('#((.+)://)((.+)@)(.+)#', '${1}${5}', $subscribe_url);
+ }
+ $this->_url ($subscribe_url);
+ }
+
+ $title = $feed->get_title ();
+ $this->_name (!is_null ($title) ? $title : $this->url);
+
+ $this->_website ($feed->get_link ());
+ $this->_description ($feed->get_description ());
+
+ // et on charge les articles du flux
+ $this->loadEntries ($feed);
+ }
+ }
+ }
+ private function loadEntries ($feed) {
+ $entries = array ();
+
+ foreach ($feed->get_items () as $item) {
+ $title = html_only_entity_decode (strip_tags ($item->get_title ()));
+ $author = $item->get_author ();
+ $link = $item->get_permalink ();
+ $date = @strtotime ($item->get_date ());
+
+ // gestion des tags (catégorie == tag)
+ $tags_tmp = $item->get_categories ();
+ $tags = array ();
+ if (!is_null ($tags_tmp)) {
+ foreach ($tags_tmp as $tag) {
+ $tags[] = html_only_entity_decode ($tag->get_label ());
+ }
+ }
+
+ $content = html_only_entity_decode ($item->get_content ());
+
+ $elinks = array();
+ foreach ($item->get_enclosures() as $enclosure) {
+ $elink = $enclosure->get_link();
+ if (array_key_exists($elink, $elinks)) continue;
+ $elinks[$elink] = '1';
+ $mime = strtolower($enclosure->get_type());
+ if (strpos($mime, 'image/') === 0) {
+ $content .= '<br /><img src="' . $elink . '" alt="" />';
+ }
+ }
+
+ $entry = new FreshRSS_Entry (
+ $this->id (),
+ $item->get_id (),
+ !is_null ($title) ? $title : '',
+ !is_null ($author) ? html_only_entity_decode ($author->name) : '',
+ !is_null ($content) ? $content : '',
+ !is_null ($link) ? $link : '',
+ $date ? $date : time ()
+ );
+ $entry->_tags ($tags);
+ // permet de récupérer le contenu des flux tronqués
+ $entry->loadCompleteContent($this->pathEntries());
+
+ $entries[] = $entry;
+ }
+
+ $this->entries = $entries;
+ }
+}
diff --git a/app/Models/FeedDAO.php b/app/Models/FeedDAO.php
new file mode 100644
index 000000000..9ebea4a47
--- /dev/null
+++ b/app/Models/FeedDAO.php
@@ -0,0 +1,339 @@
+<?php
+class FreshRSS_FeedDAO extends Minz_ModelPdo {
+ public function addFeed ($valuesTmp) {
+ $sql = 'INSERT INTO `' . $this->prefix . 'feed` (url, category, name, website, description, lastUpdate, priority, httpAuth, error, keep_history) VALUES(?, ?, ?, ?, ?, ?, 10, ?, 0, 0)';
+ $stm = $this->bd->prepare ($sql);
+
+ $values = array (
+ substr($valuesTmp['url'], 0, 511),
+ $valuesTmp['category'],
+ substr($valuesTmp['name'], 0, 255),
+ substr($valuesTmp['website'], 0, 255),
+ substr($valuesTmp['description'], 0, 1023),
+ $valuesTmp['lastUpdate'],
+ base64_encode ($valuesTmp['httpAuth']),
+ );
+
+ if ($stm && $stm->execute ($values)) {
+ return $this->bd->lastInsertId();
+ } else {
+ $info = $stm->errorInfo();
+ Minz_Log::record ('SQL error : ' . $info[2], Minz_Log::ERROR);
+ return false;
+ }
+ }
+
+ public function updateFeed ($id, $valuesTmp) {
+ $set = '';
+ foreach ($valuesTmp as $key => $v) {
+ $set .= $key . '=?, ';
+
+ if ($key == 'httpAuth') {
+ $valuesTmp[$key] = base64_encode ($v);
+ }
+ }
+ $set = substr ($set, 0, -2);
+
+ $sql = 'UPDATE `' . $this->prefix . 'feed` SET ' . $set . ' WHERE id=?';
+ $stm = $this->bd->prepare ($sql);
+
+ foreach ($valuesTmp as $v) {
+ $values[] = $v;
+ }
+ $values[] = $id;
+
+ if ($stm && $stm->execute ($values)) {
+ return $stm->rowCount();
+ } else {
+ $info = $stm->errorInfo();
+ Minz_Log::record ('SQL error : ' . $info[2], Minz_Log::ERROR);
+ return false;
+ }
+ }
+
+ public function updateLastUpdate ($id, $inError = 0) {
+ $sql = 'UPDATE `' . $this->prefix . 'feed` f ' //2 sub-requests with FOREIGN KEY(e.id_feed), INDEX(e.is_read) faster than 1 request with GROUP BY or CASE
+ . 'SET f.cache_nbEntries=(SELECT COUNT(e1.id) FROM `' . $this->prefix . 'entry` e1 WHERE e1.id_feed=f.id),'
+ . 'f.cache_nbUnreads=(SELECT COUNT(e2.id) FROM `' . $this->prefix . 'entry` e2 WHERE e2.id_feed=f.id AND e2.is_read=0),'
+ . 'lastUpdate=?, error=? '
+ . 'WHERE f.id=?';
+
+ $stm = $this->bd->prepare ($sql);
+
+ $values = array (
+ time (),
+ $inError,
+ $id,
+ );
+
+ if ($stm && $stm->execute ($values)) {
+ return $stm->rowCount();
+ } else {
+ $info = $stm->errorInfo();
+ Minz_Log::record ('SQL error : ' . $info[2], Minz_Log::ERROR);
+ return false;
+ }
+ }
+
+ public function changeCategory ($idOldCat, $idNewCat) {
+ $catDAO = new FreshRSS_CategoryDAO ();
+ $newCat = $catDAO->searchById ($idNewCat);
+ if (!$newCat) {
+ $newCat = $catDAO->getDefault ();
+ }
+
+ $sql = 'UPDATE `' . $this->prefix . 'feed` SET category=? WHERE category=?';
+ $stm = $this->bd->prepare ($sql);
+
+ $values = array (
+ $newCat->id (),
+ $idOldCat
+ );
+
+ if ($stm && $stm->execute ($values)) {
+ return $stm->rowCount();
+ } else {
+ $info = $stm->errorInfo();
+ Minz_Log::record ('SQL error : ' . $info[2], Minz_Log::ERROR);
+ return false;
+ }
+ }
+
+ public function deleteFeed ($id) {
+ /*//For MYISAM (MySQL 5.5-) without FOREIGN KEY
+ $sql = 'DELETE FROM `' . $this->prefix . 'entry` WHERE id_feed=?';
+ $stm = $this->bd->prepare ($sql);
+ $values = array ($id);
+ if (!($stm && $stm->execute ($values))) {
+ $info = $stm->errorInfo();
+ Minz_Log::record ('SQL error : ' . $info[2], Minz_Log::ERROR);
+ return false;
+ }*/
+
+ $sql = 'DELETE FROM `' . $this->prefix . 'feed` WHERE id=?';
+ $stm = $this->bd->prepare ($sql);
+
+ $values = array ($id);
+
+ if ($stm && $stm->execute ($values)) {
+ return $stm->rowCount();
+ } else {
+ $info = $stm->errorInfo();
+ Minz_Log::record ('SQL error : ' . $info[2], Minz_Log::ERROR);
+ return false;
+ }
+ }
+ public function deleteFeedByCategory ($id) {
+ /*//For MYISAM (MySQL 5.5-) without FOREIGN KEY
+ $sql = 'DELETE FROM `' . $this->prefix . 'entry` e '
+ . 'INNER JOIN `' . $this->prefix . 'feed` f ON e.id_feed = f.id '
+ . 'WHERE f.category=?';
+ $stm = $this->bd->prepare ($sql);
+ $values = array ($id);
+ if (!($stm && $stm->execute ($values))) {
+ $info = $stm->errorInfo();
+ Minz_Log::record ('SQL error : ' . $info[2], Minz_Log::ERROR);
+ return false;
+ }*/
+
+ $sql = 'DELETE FROM `' . $this->prefix . 'feed` WHERE category=?';
+ $stm = $this->bd->prepare ($sql);
+
+ $values = array ($id);
+
+ if ($stm && $stm->execute ($values)) {
+ return $stm->rowCount();
+ } else {
+ $info = $stm->errorInfo();
+ Minz_Log::record ('SQL error : ' . $info[2], Minz_Log::ERROR);
+ return false;
+ }
+ }
+
+ public function searchById ($id) {
+ $sql = 'SELECT * FROM `' . $this->prefix . 'feed` WHERE id=?';
+ $stm = $this->bd->prepare ($sql);
+
+ $values = array ($id);
+
+ $stm->execute ($values);
+ $res = $stm->fetchAll (PDO::FETCH_ASSOC);
+ $feed = self::daoToFeed ($res);
+
+ if (isset ($feed[$id])) {
+ return $feed[$id];
+ } else {
+ return false;
+ }
+ }
+ public function searchByUrl ($url) {
+ $sql = 'SELECT * FROM `' . $this->prefix . 'feed` WHERE url=?';
+ $stm = $this->bd->prepare ($sql);
+
+ $values = array ($url);
+
+ $stm->execute ($values);
+ $res = $stm->fetchAll (PDO::FETCH_ASSOC);
+ $feed = current (self::daoToFeed ($res));
+
+ if (isset ($feed)) {
+ return $feed;
+ } else {
+ return false;
+ }
+ }
+
+ public function listFeeds () {
+ $sql = 'SELECT * FROM `' . $this->prefix . 'feed` ORDER BY name';
+ $stm = $this->bd->prepare ($sql);
+ $stm->execute ();
+
+ return self::daoToFeed ($stm->fetchAll (PDO::FETCH_ASSOC));
+ }
+
+ public function listFeedsOrderUpdate () {
+ $sql = 'SELECT * FROM `' . $this->prefix . 'feed` ORDER BY lastUpdate';
+ $stm = $this->bd->prepare ($sql);
+ $stm->execute ();
+
+ return self::daoToFeed ($stm->fetchAll (PDO::FETCH_ASSOC));
+ }
+
+ public function listByCategory ($cat) {
+ $sql = 'SELECT * FROM `' . $this->prefix . 'feed` WHERE category=? ORDER BY name';
+ $stm = $this->bd->prepare ($sql);
+
+ $values = array ($cat);
+
+ $stm->execute ($values);
+
+ return self::daoToFeed ($stm->fetchAll (PDO::FETCH_ASSOC));
+ }
+
+ public function countEntries ($id) {
+ $sql = 'SELECT COUNT(*) AS count FROM `' . $this->prefix . 'entry` WHERE id_feed=?';
+ $stm = $this->bd->prepare ($sql);
+ $values = array ($id);
+ $stm->execute ($values);
+ $res = $stm->fetchAll (PDO::FETCH_ASSOC);
+
+ return $res[0]['count'];
+ }
+ public function countNotRead ($id) {
+ $sql = 'SELECT COUNT(*) AS count FROM `' . $this->prefix . 'entry` WHERE id_feed=? AND is_read=0';
+ $stm = $this->bd->prepare ($sql);
+ $values = array ($id);
+ $stm->execute ($values);
+ $res = $stm->fetchAll (PDO::FETCH_ASSOC);
+
+ return $res[0]['count'];
+ }
+ public function updateCachedValues () { //For one single feed, call updateLastUpdate($id)
+ $sql = 'UPDATE `' . $this->prefix . 'feed` f '
+ . 'INNER JOIN ('
+ . 'SELECT e.id_feed, '
+ . 'COUNT(CASE WHEN e.is_read = 0 THEN 1 END) AS nbUnreads, '
+ . 'COUNT(e.id) AS nbEntries '
+ . 'FROM `' . $this->prefix . 'entry` e '
+ . 'GROUP BY e.id_feed'
+ . ') x ON x.id_feed=f.id '
+ . 'SET f.cache_nbEntries=x.nbEntries, f.cache_nbUnreads=x.nbUnreads';
+ $stm = $this->bd->prepare ($sql);
+
+ $values = array ($feed_id);
+
+ if ($stm && $stm->execute ($values)) {
+ return $stm->rowCount();
+ } else {
+ $info = $stm->errorInfo();
+ Minz_Log::record ('SQL error : ' . $info[2], Minz_Log::ERROR);
+ return false;
+ }
+ }
+
+ public function truncate ($id) {
+ $sql = 'DELETE e.* FROM `' . $this->prefix . 'entry` e WHERE e.id_feed=?';
+ $stm = $this->bd->prepare($sql);
+ $values = array($id);
+ $this->bd->beginTransaction ();
+ if (!($stm && $stm->execute ($values))) {
+ $info = $stm->errorInfo();
+ Minz_Log::record ('SQL error : ' . $info[2], Minz_Log::ERROR);
+ $this->bd->rollBack ();
+ return false;
+ }
+ $affected = $stm->rowCount();
+
+ $sql = 'UPDATE `' . $this->prefix . 'feed` f '
+ . 'SET f.cache_nbEntries=0, f.cache_nbUnreads=0 WHERE f.id=?';
+ $values = array ($id);
+ $stm = $this->bd->prepare ($sql);
+ if (!($stm && $stm->execute ($values))) {
+ $info = $stm->errorInfo();
+ Minz_Log::record ('SQL error : ' . $info[2], Minz_Log::ERROR);
+ $this->bd->rollBack ();
+ return false;
+ }
+
+ $this->bd->commit ();
+ return $affected;
+ }
+
+ public function cleanOldEntries ($id, $date_min, $keep = 15) { //Remember to call updateLastUpdate($id) just after
+ $sql = 'DELETE e.* FROM `' . $this->prefix . 'entry` e '
+ . 'WHERE e.id_feed = :id_feed AND e.id <= :id_max AND e.is_favorite = 0 AND e.id NOT IN '
+ . '(SELECT id FROM (SELECT e2.id FROM `' . $this->prefix . 'entry` e2 WHERE e2.id_feed = :id_feed ORDER BY id DESC LIMIT :keep) keep)'; //Double select because of: MySQL doesn't yet support 'LIMIT & IN/ALL/ANY/SOME subquery'
+ $stm = $this->bd->prepare ($sql);
+
+ $id_max = intval($date_min) . '000000';
+
+ $stm->bindParam(':id_feed', $id, PDO::PARAM_INT);
+ $stm->bindParam(':id_max', $id_max, PDO::PARAM_INT);
+ $stm->bindParam(':keep', $keep, PDO::PARAM_INT);
+
+ if ($stm && $stm->execute ()) {
+ return $stm->rowCount();
+ } else {
+ $info = $stm->errorInfo();
+ Minz_Log::record ('SQL error : ' . $info[2], Minz_Log::ERROR);
+ return false;
+ }
+ }
+
+ public static function daoToFeed ($listDAO, $catID = null) {
+ $list = array ();
+
+ if (!is_array ($listDAO)) {
+ $listDAO = array ($listDAO);
+ }
+
+ foreach ($listDAO as $key => $dao) {
+ if (!isset ($dao['name'])) {
+ continue;
+ }
+ if (isset ($dao['id'])) {
+ $key = $dao['id'];
+ }
+
+ $myFeed = new FreshRSS_Feed (isset($dao['url']) ? $dao['url'] : '', false);
+ $myFeed->_category ($catID === null ? $dao['category'] : $catID);
+ $myFeed->_name ($dao['name']);
+ $myFeed->_website ($dao['website'], false);
+ $myFeed->_description (isset($dao['description']) ? $dao['description'] : '');
+ $myFeed->_lastUpdate (isset($dao['lastUpdate']) ? $dao['lastUpdate'] : 0);
+ $myFeed->_priority ($dao['priority']);
+ $myFeed->_pathEntries (isset($dao['pathEntries']) ? $dao['pathEntries'] : '');
+ $myFeed->_httpAuth (isset($dao['httpAuth']) ? base64_decode ($dao['httpAuth']) : '');
+ $myFeed->_error ($dao['error']);
+ $myFeed->_keepHistory (isset($dao['keep_history']) ? $dao['keep_history'] : '');
+ $myFeed->_nbNotRead ($dao['cache_nbUnreads']);
+ $myFeed->_nbEntries ($dao['cache_nbEntries']);
+ if (isset ($dao['id'])) {
+ $myFeed->_id ($dao['id']);
+ }
+ $list[$key] = $myFeed;
+ }
+
+ return $list;
+ }
+}
diff --git a/app/Models/Log.php b/app/Models/Log.php
new file mode 100644
index 000000000..d2794458b
--- /dev/null
+++ b/app/Models/Log.php
@@ -0,0 +1,26 @@
+<?php
+
+class FreshRSS_Log extends Minz_Model {
+ private $date;
+ private $level;
+ private $information;
+
+ public function date () {
+ return $this->date;
+ }
+ public function level () {
+ return $this->level;
+ }
+ public function info () {
+ return $this->information;
+ }
+ public function _date ($date) {
+ $this->date = $date;
+ }
+ public function _level ($level) {
+ $this->level = $level;
+ }
+ public function _info ($information) {
+ $this->information = $information;
+ }
+}
diff --git a/app/Models/LogDAO.php b/app/Models/LogDAO.php
new file mode 100644
index 000000000..bf043fd6d
--- /dev/null
+++ b/app/Models/LogDAO.php
@@ -0,0 +1,20 @@
+<?php
+class FreshRSS_LogDAO extends Minz_ModelTxt {
+ public function __construct () {
+ parent::__construct (LOG_PATH . '/application.log', 'r+');
+ }
+
+ public function lister () {
+ $logs = array ();
+ while (($line = $this->readLine ()) !== false) {
+ if (preg_match ('/^\[([^\[]+)\] \[([^\[]+)\] --- (.*)$/', $line, $matches)) {
+ $myLog = new FreshRSS_Log ();
+ $myLog->_date ($matches[1]);
+ $myLog->_level ($matches[2]);
+ $myLog->_info ($matches[3]);
+ $logs[] = $myLog;
+ }
+ }
+ return $logs;
+ }
+}
diff --git a/app/Models/Themes.php b/app/Models/Themes.php
new file mode 100644
index 000000000..a52812339
--- /dev/null
+++ b/app/Models/Themes.php
@@ -0,0 +1,88 @@
+<?php
+
+class FreshRSS_Themes extends Minz_Model {
+ private static $themesUrl = '/themes/';
+ private static $defaultIconsUrl = '/themes/icons/';
+
+ public static function get() {
+ $themes_list = array_diff(
+ scandir(PUBLIC_PATH . self::$themesUrl),
+ array('..', '.')
+ );
+
+ $list = array();
+ foreach ($themes_list as $theme_dir) {
+ $theme = self::get_infos($theme_dir);
+ if ($theme) {
+ $list[$theme_dir] = $theme;
+ }
+ }
+ return $list;
+ }
+
+ public static function get_infos($theme_id) {
+ $theme_dir = PUBLIC_PATH . self::$themesUrl . $theme_id ;
+ if (is_dir($theme_dir)) {
+ $json_filename = $theme_dir . '/metadata.json';
+ if (file_exists($json_filename)) {
+ $content = file_get_contents($json_filename);
+ $res = json_decode($content, true);
+ if ($res && isset($res['files']) && is_array($res['files'])) {
+ $res['path'] = $theme_id;
+ return $res;
+ }
+ }
+ }
+ return false;
+ }
+
+ private static $themeIconsUrl;
+ private static $themeIcons;
+
+ public static function setThemeId($theme_id) {
+ self::$themeIconsUrl = self::$themesUrl . $theme_id . '/icons/';
+ self::$themeIcons = is_dir(PUBLIC_PATH . self::$themeIconsUrl) ? array_fill_keys(array_diff(
+ scandir(PUBLIC_PATH . self::$themeIconsUrl),
+ array('..', '.')
+ ), 1) : array();
+ }
+
+ public static function icon($name, $urlOnly = false) {
+ static $alts = array(
+ 'add' => '✚',
+ 'all' => '☰',
+ 'bookmark' => '★',
+ 'category' => '☷',
+ 'category-white' => '☷',
+ 'close' => '❌',
+ 'configure' => '⚙',
+ 'down' => '▽',
+ 'favorite' => '★',
+ 'help' => 'ⓘ',
+ 'link' => '↗',
+ 'login' => '🔒',
+ 'logout' => '🔓',
+ 'next' => '⏩',
+ 'non-starred' => '☆',
+ 'prev' => '⏪',
+ 'read' => '☑',
+ 'unread' => '☐',
+ 'refresh' => '🔃', //↻
+ 'search' => '🔍',
+ 'share' => '♺',
+ 'starred' => '★',
+ 'tag' => '⚐',
+ 'up' => '△',
+ );
+ if (!isset($alts[$name])) {
+ return '';
+ }
+
+ $url = $name . '.svg';
+ $url = isset(self::$themeIcons[$url]) ? (self::$themeIconsUrl . $url) :
+ (self::$defaultIconsUrl . $url);
+
+ return $urlOnly ? Minz_Url::display($url) :
+ '<img class="icon" src="' . Minz_Url::display($url) . '" alt="' . $alts[$name] . '" />';
+ }
+}