aboutsummaryrefslogtreecommitdiff
path: root/app/Models
diff options
context:
space:
mode:
Diffstat (limited to 'app/Models')
-rw-r--r--app/Models/Category.php14
-rw-r--r--app/Models/CategoryDAO.php32
-rw-r--r--app/Models/Configuration.php89
-rw-r--r--app/Models/Entry.php5
-rw-r--r--app/Models/EntryDAO.php358
-rw-r--r--app/Models/Feed.php57
-rw-r--r--app/Models/FeedDAO.php87
-rw-r--r--app/Models/Share.php44
-rw-r--r--app/Models/Themes.php5
9 files changed, 514 insertions, 177 deletions
diff --git a/app/Models/Category.php b/app/Models/Category.php
index 8e1e44ef8..328bae799 100644
--- a/app/Models/Category.php
+++ b/app/Models/Category.php
@@ -3,14 +3,12 @@
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) {
+ public function __construct ($name = '', $feeds = null) {
$this->_name ($name);
- $this->_color ($color);
if (isset ($feeds)) {
$this->_feeds ($feeds);
$this->nbFeed = 0;
@@ -28,9 +26,6 @@ class FreshRSS_Category extends Minz_Model {
public function name () {
return $this->name;
}
- public function color () {
- return $this->color;
- }
public function nbFeed () {
if ($this->nbFeed < 0) {
$catDAO = new FreshRSS_CategoryDAO ();
@@ -68,13 +63,6 @@ class FreshRSS_Category extends Minz_Model {
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);
diff --git a/app/Models/CategoryDAO.php b/app/Models/CategoryDAO.php
index 1cc616ac0..6a9b839b9 100644
--- a/app/Models/CategoryDAO.php
+++ b/app/Models/CategoryDAO.php
@@ -2,12 +2,11 @@
class FreshRSS_CategoryDAO extends Minz_ModelPdo {
public function addCategory ($valuesTmp) {
- $sql = 'INSERT INTO `' . $this->prefix . 'category` (name, color) VALUES(?, ?)';
+ $sql = 'INSERT INTO `' . $this->prefix . 'category` (name) VALUES(?)';
$stm = $this->bd->prepare ($sql);
$values = array (
substr($valuesTmp['name'], 0, 255),
- substr($valuesTmp['color'], 0, 7),
);
if ($stm && $stm->execute ($values)) {
@@ -19,13 +18,25 @@ class FreshRSS_CategoryDAO extends Minz_ModelPdo {
}
}
+ public function addCategoryObject($category) {
+ $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(),
+ );
+ return $this->addCategory($values);
+ }
+
+ return $cat->id();
+ }
+
public function updateCategory ($id, $valuesTmp) {
- $sql = 'UPDATE `' . $this->prefix . 'category` SET name=?, color=? WHERE id=?';
+ $sql = 'UPDATE `' . $this->prefix . 'category` SET name=? WHERE id=?';
$stm = $this->bd->prepare ($sql);
$values = array (
$valuesTmp['name'],
- $valuesTmp['color'],
$id
);
@@ -66,7 +77,7 @@ class FreshRSS_CategoryDAO extends Minz_ModelPdo {
if (isset ($cat[0])) {
return $cat[0];
} else {
- return false;
+ return null;
}
}
public function searchByName ($name) {
@@ -82,14 +93,13 @@ class FreshRSS_CategoryDAO extends Minz_ModelPdo {
if (isset ($cat[0])) {
return $cat[0];
} else {
- return false;
+ return null;
}
}
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.url, f.website, f.priority, f.error, f.cache_nbEntries, f.cache_nbUnreads ')
. 'FROM `' . $this->prefix . 'category` c '
. 'LEFT OUTER JOIN `' . $this->prefix . 'feed` f ON f.category = c.id '
@@ -123,14 +133,13 @@ class FreshRSS_CategoryDAO extends Minz_ModelPdo {
public function checkDefault () {
$def_cat = $this->searchById (1);
- if ($def_cat === false) {
+ if ($def_cat == null) {
$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);
@@ -203,7 +212,6 @@ class FreshRSS_CategoryDAO extends Minz_ModelPdo {
// 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']);
@@ -220,7 +228,6 @@ class FreshRSS_CategoryDAO extends Minz_ModelPdo {
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']);
@@ -239,8 +246,7 @@ class FreshRSS_CategoryDAO extends Minz_ModelPdo {
foreach ($listDAO as $key => $dao) {
$cat = new FreshRSS_Category (
- $dao['name'],
- $dao['color']
+ $dao['name']
);
$cat->_id ($dao['id']);
$list[$key] = $cat;
diff --git a/app/Models/Configuration.php b/app/Models/Configuration.php
index 2a7fe95aa..0d6666297 100644
--- a/app/Models/Configuration.php
+++ b/app/Models/Configuration.php
@@ -10,13 +10,15 @@ class FreshRSS_Configuration {
'mail_login' => '',
'token' => '',
'passwordHash' => '', //CRYPT_BLOWFISH
+ 'apiPasswordHash' => '', //CRYPT_BLOWFISH
'posts_per_page' => 20,
'view_mode' => 'normal',
- 'default_view' => 'not_read',
+ 'default_view' => FreshRSS_Entry::STATE_NOT_READ,
'auto_load_more' => true,
'display_posts' => false,
'onread_jump_next' => true,
'lazyload' => true,
+ 'sticky_post' => true,
'sort_order' => 'DESC',
'anon_access' => false,
'mark_when' => array(
@@ -26,15 +28,19 @@ class FreshRSS_Configuration {
'reception' => false,
),
'theme' => 'Origine',
+ 'content_width' => 'thin',
'shortcuts' => array(
'mark_read' => 'r',
'mark_favorite' => 'f',
'go_website' => 'space',
'next_entry' => 'j',
'prev_entry' => 'k',
+ 'first_entry' => 'home',
+ 'last_entry' => 'end',
'collapse_entry' => 'c',
'load_more' => 'm',
'auto_share' => 's',
+ 'focus_search' => 'a',
),
'topline_read' => true,
'topline_favorite' => true,
@@ -46,16 +52,7 @@ class FreshRSS_Configuration {
'bottomline_tags' => true,
'bottomline_date' => true,
'bottomline_link' => true,
- 'sharing' => array(
- 'shaarli' => '',
- 'wallabag' => '',
- 'diaspora' => '',
- 'twitter' => true,
- 'g+' => true,
- 'facebook' => true,
- 'email' => true,
- 'print' => true,
- ),
+ 'sharing' => array(),
);
private $available_languages = array(
@@ -63,8 +60,10 @@ class FreshRSS_Configuration {
'fr' => 'Français',
);
- public function __construct ($user) {
- $this->filename = DATA_PATH . '/' . $user . '_user.php';
+ private $shares;
+
+ public function __construct($user) {
+ $this->filename = DATA_PATH . DIRECTORY_SEPARATOR . $user . '_user.php';
$data = @include($this->filename);
if (!is_array($data)) {
@@ -78,10 +77,20 @@ class FreshRSS_Configuration {
}
}
$this->data['user'] = $user;
+
+ $this->shares = DATA_PATH . DIRECTORY_SEPARATOR . 'shares.php';
+
+ $shares = @include($this->shares);
+ if (!is_array($shares)) {
+ throw new Minz_PermissionDeniedException($this->shares);
+ }
+
+ $this->data['shares'] = $shares;
}
public function save() {
@rename($this->filename, $this->filename . '.bak.php');
+ unset($this->data['shares']); // Remove shares because it is not intended to be stored in user configuration
if (file_put_contents($this->filename, "<?php\n return " . var_export($this->data, true) . ';', LOCK_EX) === false) {
throw new Minz_PermissionDeniedException($this->filename);
}
@@ -102,16 +111,6 @@ class FreshRSS_Configuration {
}
}
- public function sharing($key = false) {
- if ($key === false) {
- return $this->data['sharing'];
- }
- if (isset($this->data['sharing'][$key])) {
- return $this->data['sharing'][$key];
- }
- return false;
- }
-
public function availableLanguages() {
return $this->available_languages;
}
@@ -134,7 +133,7 @@ class FreshRSS_Configuration {
}
}
public function _default_view ($value) {
- $this->data['default_view'] = $value === 'all' ? 'all' : 'not_read';
+ $this->data['default_view'] = $value === FreshRSS_Entry::STATE_ALL ? FreshRSS_Entry::STATE_ALL : FreshRSS_Entry::STATE_NOT_READ;
}
public function _display_posts ($value) {
$this->data['display_posts'] = ((bool)$value) && $value !== 'no';
@@ -145,6 +144,9 @@ class FreshRSS_Configuration {
public function _lazyload ($value) {
$this->data['lazyload'] = ((bool)$value) && $value !== 'no';
}
+ public function _sticky_post($value) {
+ $this->data['sticky_post'] = ((bool)$value) && $value !== 'no';
+ }
public function _sort_order ($value) {
$this->data['sort_order'] = $value === 'ASC' ? 'ASC' : 'DESC';
}
@@ -166,6 +168,9 @@ class FreshRSS_Configuration {
public function _passwordHash ($value) {
$this->data['passwordHash'] = ctype_graph($value) && (strlen($value) >= 60) ? $value : '';
}
+ public function _apiPasswordHash ($value) {
+ $this->data['apiPasswordHash'] = ctype_graph($value) && (strlen($value) >= 60) ? $value : '';
+ }
public function _mail_login ($value) {
$value = filter_var($value, FILTER_VALIDATE_EMAIL);
if ($value) {
@@ -185,29 +190,47 @@ class FreshRSS_Configuration {
}
}
public function _sharing ($values) {
- $are_url = array ('shaarli', 'wallabag', 'diaspora');
- foreach ($values as $key => $value) {
- if (in_array($key, $are_url)) {
+ $this->data['sharing'] = array();
+ foreach ($values as $value) {
+ if (!is_array($value)) {
+ continue;
+ }
+
+ // Verify URL and add default value when needed
+ if (isset($value['url'])) {
$is_url = (
- filter_var ($value, FILTER_VALIDATE_URL) ||
+ filter_var ($value['url'], 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 = '';
+ continue;
}
- } elseif (!is_bool($value)) {
- $value = true;
+ } else {
+ $value['url'] = null;
}
- $this->data['sharing'][$key] = $value;
+ // Add a default name
+ if (empty($value['name'])) {
+ $value['name'] = $value['type'];
+ }
+
+ $this->data['sharing'][] = $value;
}
}
public function _theme($value) {
$this->data['theme'] = $value;
}
+ public function _content_width($value) {
+ if ($value === 'medium' ||
+ $value === 'large' ||
+ $value === 'no_limit') {
+ $this->data['content_width'] = $value;
+ } else {
+ $this->data['content_width'] = 'thin';
+ }
+ }
public function _token($value) {
$this->data['token'] = $value;
}
diff --git a/app/Models/Entry.php b/app/Models/Entry.php
index a6c67221b..fa9066d5b 100644
--- a/app/Models/Entry.php
+++ b/app/Models/Entry.php
@@ -1,6 +1,11 @@
<?php
class FreshRSS_Entry extends Minz_Model {
+ const STATE_ALL = 0;
+ const STATE_READ = 1;
+ const STATE_NOT_READ = 2;
+ const STATE_FAVORITE = 4;
+ const STATE_NOT_FAVORITE = 8;
private $id = 0;
private $guid;
diff --git a/app/Models/EntryDAO.php b/app/Models/EntryDAO.php
index aaf4dcf6a..4e24541dc 100644
--- a/app/Models/EntryDAO.php
+++ b/app/Models/EntryDAO.php
@@ -35,11 +35,45 @@ class FreshRSS_EntryDAO extends Minz_ModelPdo {
}
}
- public function markFavorite ($id, $is_favorite = true) {
+ 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);
+ }
$sql = 'UPDATE `' . $this->prefix . 'entry` e '
. 'SET e.is_favorite = ? '
- . 'WHERE e.id=?';
- $values = array ($is_favorite ? 1 : 0, $id);
+ . 'WHERE e.id IN (' . str_repeat('?,', count($ids) - 1). '?)';
+ $values = array ($is_favorite ? 1 : 0);
+ $values = array_merge($values, $ids);
$stm = $this->bd->prepare ($sql);
if ($stm && $stm->execute ($values)) {
return $stm->rowCount();
@@ -49,30 +83,79 @@ class FreshRSS_EntryDAO extends Minz_ModelPdo {
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();
+
+ public function markRead($ids, $is_read = true) {
+ if (is_array($ids)) {
+ if (count($ids) < 6) { //Speed heuristics
+ $affected = 0;
+ foreach ($ids as $id) {
+ $affected += $this->markRead($id, $is_read);
+ }
+ return $affected;
+ }
+
+ $this->bd->beginTransaction();
+ $sql = 'UPDATE `' . $this->prefix . 'entry` e '
+ . 'SET e.is_read = ? '
+ . 'WHERE e.id IN (' . str_repeat('?,', count($ids) - 1). '?)';
+ $values = array($is_read ? 1 : 0);
+ $values = array_merge($values, $ids);
+ $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 '
+ . '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);
+ 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;
} else {
- $info = $stm->errorInfo();
- Minz_Log::record ('SQL error : ' . $info[2], Minz_Log::ERROR);
- return false;
+ $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, $ids);
+ $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) {
+
+ public function markReadEntries ($idMax = 0, $onlyFavorites = false, $priorityMin = 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 e.is_read = 0 AND ';
- if ($favorites) {
- $sql .= 'e.is_favorite = 1';
- } else {
- $sql .= 'f.priority > 0';
+ . 'WHERE e.is_read = 0';
+ if ($onlyFavorites) {
+ $sql .= ' AND e.is_favorite = 1';
+ } elseif ($priorityMin >= 0) {
+ $sql .= ' AND f.priority > ' . intval($priorityMin);
}
$stm = $this->bd->prepare ($sql);
if ($stm && $stm->execute ()) {
@@ -87,11 +170,11 @@ class FreshRSS_EntryDAO extends Minz_ModelPdo {
$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';
+ . 'WHERE e.is_read = 0 AND e.id <= ?';
+ if ($onlyFavorites) {
+ $sql .= ' AND e.is_favorite = 1';
+ } elseif ($priorityMin >= 0) {
+ $sql .= ' AND f.priority > ' . intval($priorityMin);
}
$values = array ($idMax);
$stm = $this->bd->prepare ($sql);
@@ -126,8 +209,9 @@ class FreshRSS_EntryDAO extends Minz_ModelPdo {
return $affected;
}
}
+
public function markReadCat ($id, $idMax = 0) {
- if ($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';
@@ -181,8 +265,70 @@ class FreshRSS_EntryDAO extends Minz_ModelPdo {
return $affected;
}
}
+
+ public function markReadCatName($name, $idMax = 0) {
+ if ($idMax == 0) {
+ $sql = 'UPDATE `' . $this->prefix . 'entry` e '
+ . 'INNER JOIN `' . $this->prefix . 'feed` f ON e.id_feed = f.id '
+ . 'INNER JOIN `' . $this->prefix . 'category` c ON c.id = f.category '
+ . 'SET e.is_read = 1, f.cache_nbUnreads=0 '
+ . 'WHERE c.name = ?';
+ $values = array($name);
+ $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 '
+ . 'INNER JOIN `' . $this->prefix . 'category` c ON c.id = f.category '
+ . 'SET e.is_read = 1 '
+ . 'WHERE c.name = ? AND e.id <= ?';
+ $values = array($name, $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 '
+ . 'INNER JOIN `' . $this->prefix . 'category` c ON c.id = f.category '
+ . 'SET f.cache_nbUnreads=COALESCE(x.nbUnreads, 0) '
+ . 'WHERE c.name = ?';
+ $values = array($name);
+ $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) {
+ 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';
@@ -244,7 +390,7 @@ class FreshRSS_EntryDAO extends Minz_ModelPdo {
$stm->execute ($values);
$res = $stm->fetchAll (PDO::FETCH_ASSOC);
$entries = self::daoToEntry ($res);
- return isset ($entries[0]) ? $entries[0] : false;
+ return isset ($entries[0]) ? $entries[0] : null;
}
public function searchById ($id) {
@@ -257,10 +403,13 @@ class FreshRSS_EntryDAO extends Minz_ModelPdo {
$stm->execute ($values);
$res = $stm->fetchAll (PDO::FETCH_ASSOC);
$entries = self::daoToEntry ($res);
- return isset ($entries[0]) ? $entries[0] : false;
+ return isset ($entries[0]) ? $entries[0] : null;
}
- public function listWhere($type = 'a', $id = '', $state = 'all', $order = 'DESC', $limit = 1, $firstId = '', $filter = '', $date_min = 0, $keepHistoryDefault = 0) {
+ private function sqlListWhere($type = 'a', $id = '', $state = FreshRSS_Entry::STATE_ALL, $order = 'DESC', $limit = 1, $firstId = '', $filter = '', $date_min = 0, $showOlderUnreadsorFavorites = false, $keepHistoryDefault = 0) {
+ if (!$state) {
+ $state = FreshRSS_Entry::STATE_ALL;
+ }
$where = '';
$joinFeed = false;
$values = array();
@@ -269,7 +418,7 @@ class FreshRSS_EntryDAO extends Minz_ModelPdo {
$where .= 'f.priority > 0 ';
$joinFeed = true;
break;
- case 's':
+ case 's': //Deprecated: use $state instead
$where .= 'e1.is_favorite = 1 ';
break;
case 'c':
@@ -281,24 +430,30 @@ class FreshRSS_EntryDAO extends Minz_ModelPdo {
$where .= 'e1.id_feed = ? ';
$values[] = intval($id);
break;
+ case 'A':
+ $where .= '1 ';
+ break;
default:
throw new FreshRSS_EntriesGetter_Exception ('Bad type in Entry->listByType: [' . $type . ']!');
}
- switch ($state) {
- case 'all':
- break;
- case 'not_read':
+
+ if ($state & FreshRSS_Entry::STATE_NOT_READ) {
+ if (!($state & FreshRSS_Entry::STATE_READ)) {
$where .= 'AND e1.is_read = 0 ';
- break;
- case 'read':
- $where .= 'AND e1.is_read = 1 ';
- break;
- case 'favorite':
+ }
+ }
+ elseif ($state & FreshRSS_Entry::STATE_READ) {
+ $where .= 'AND e1.is_read = 1 ';
+ }
+ if ($state & FreshRSS_Entry::STATE_FAVORITE) {
+ if (!($state & FreshRSS_Entry::STATE_NOT_FAVORITE)) {
$where .= 'AND e1.is_favorite = 1 ';
- break;
- default:
- throw new FreshRSS_EntriesGetter_Exception ('Bad state in Entry->listByType: [' . $state . ']!');
+ }
+ }
+ elseif ($state & FreshRSS_Entry::STATE_NOT_FAVORITE) {
+ $where .= 'AND e1.is_favorite = 0 ';
}
+
switch ($order) {
case 'DESC':
case 'ASC':
@@ -310,70 +465,84 @@ class FreshRSS_EntryDAO extends Minz_ModelPdo {
$where .= 'AND e1.id ' . ($order === 'DESC' ? '<=' : '>=') . $firstId . ' ';
}
if (($date_min > 0) && ($type !== 's')) {
- $where .= 'AND (e1.id >= ' . $date_min . '000000 OR e1.is_read = 0 OR e1.is_favorite = 1 OR (f.keep_history <> 0';
- if (intval($keepHistoryDefault) === 0) {
- $where .= ' AND f.keep_history <> -2'; //default
+ $where .= 'AND (e1.id >= ' . $date_min . '000000';
+ if ($showOlderUnreadsorFavorites) { //Lax date constraint
+ $where .= ' OR e1.is_read = 0 OR e1.is_favorite = 1 OR (f.keep_history <> 0';
+ if (intval($keepHistoryDefault) === 0) {
+ $where .= ' AND f.keep_history <> -2'; //default
+ }
+ $where .= ')';
}
- $where .= ')) ';
+ $where .= ') ';
$joinFeed = true;
}
$search = '';
if ($filter !== '') {
+ require_once(LIB_PATH . '/lib_date.php');
$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
+ //sort($terms); //Put #tags first //TODO: Put the cheapest filters 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 ? ';
+ if (stripos($word, 'intitle:') === 0) {
+ $word = substr($word, strlen('intitle:'));
+ $search .= 'AND e1.title LIKE ? ';
+ $values[] = '%' . $word .'%';
+ } elseif (stripos($word, 'inurl:') === 0) {
+ $word = substr($word, strlen('inurl:'));
+ $search .= 'AND CONCAT(e1.link, e1.guid) LIKE ? ';
+ $values[] = '%' . $word .'%';
+ } elseif (stripos($word, 'author:') === 0) {
+ $word = substr($word, strlen('author:'));
+ $search .= 'AND e1.author LIKE ? ';
+ $values[] = '%' . $word .'%';
+ } elseif (stripos($word, 'date:') === 0) {
+ $word = substr($word, strlen('date:'));
+ list($minDate, $maxDate) = parseDateInterval($word);
+ if ($minDate) {
+ $search .= 'AND e1.id >= ' . $minDate . '000000 ';
+ }
+ if ($maxDate) {
+ $search .= 'AND e1.id <= ' . $maxDate . '000000 ';
+ }
+ } elseif (stripos($word, 'pubdate:') === 0) {
+ $word = substr($word, strlen('pubdate:'));
+ list($minDate, $maxDate) = parseDateInterval($word);
+ if ($minDate) {
+ $search .= 'AND e1.date >= ' . $minDate . ' ';
+ }
+ if ($maxDate) {
+ $search .= 'AND e1.date <= ' . $maxDate . ' ';
+ }
+ } else {
+ if ($word[0] === '#' && isset($word[1])) {
+ $search .= 'AND e1.tags LIKE ? ';
$values[] = '%' . $word .'%';
} else {
- 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 .'%';
- }
+ $search .= 'AND CONCAT(e1.title, UNCOMPRESS(e1.content_bin)) LIKE ? ';
+ $values[] = '%' . $word .'%';
}
}
}
}
+ return array($values,
+ '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/
+ }
+
+ public function listWhere($type = 'a', $id = '', $state = FreshRSS_Entry::STATE_ALL, $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 '
. '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/
+ . 'INNER JOIN ('
+ . $sql
. ') e2 ON e2.id = e.id '
. 'ORDER BY e.id ' . $order;
@@ -383,6 +552,15 @@ class FreshRSS_EntryDAO extends Minz_ModelPdo {
return self::daoToEntry ($stm->fetchAll (PDO::FETCH_ASSOC));
}
+ public function listIdsWhere($type = 'a', $id = '', $state = FreshRSS_Entry::STATE_ALL, $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);
+ $stm->execute($values);
+
+ return $stm->fetchAll(PDO::FETCH_COLUMN, 0);
+ }
+
public function listLastGuidsByFeed($id, $n) {
$sql = 'SELECT guid FROM `' . $this->prefix . 'entry` WHERE id_feed=? ORDER BY id DESC LIMIT ' . intval($n);
$stm = $this->bd->prepare ($sql);
diff --git a/app/Models/Feed.php b/app/Models/Feed.php
index 22c019080..757eacd59 100644
--- a/app/Models/Feed.php
+++ b/app/Models/Feed.php
@@ -193,10 +193,10 @@ class FreshRSS_Feed extends Minz_Model {
}
$feed = customSimplePie();
$feed->set_feed_url ($url);
- $feed->init ();
+ $mtime = $feed->init();
- if ($feed->error ()) {
- throw new FreshRSS_Feed_Exception ($feed->error . ' [' . $url . ']');
+ if ((!$mtime) || $feed->error()) {
+ throw new FreshRSS_Feed_Exception ($feed->error() . ' [' . $url . ']');
}
// si on a utilisé l'auto-discover, notre url va avoir changé
@@ -210,18 +210,27 @@ class FreshRSS_Feed extends Minz_Model {
}
if ($loadDetails) {
- $title = htmlspecialchars(html_only_entity_decode($feed->get_title()), ENT_COMPAT, 'UTF-8');
- $this->_name ($title === null ? $this->url : $title);
+ $title = strtr(html_only_entity_decode($feed->get_title()), array('<' => '&lt;', '>' => '&gt;', '"' => '&quot;')); //HTML to HTML-PRE //ENT_COMPAT except &
+ $this->_name ($title == '' ? $this->url : $title);
$this->_website(html_only_entity_decode($feed->get_link()));
$this->_description(html_only_entity_decode($feed->get_description()));
}
- // et on charge les articles du flux
- $this->loadEntries ($feed);
+ if (($mtime === true) || ($mtime > $this->lastUpdate)) {
+ syslog(LOG_DEBUG, 'FreshRSS no cache ' . $mtime . ' > ' . $this->lastUpdate . ' for ' . $subscribe_url);
+ $this->loadEntries($feed); // et on charge les articles du flux
+ } else {
+ syslog(LOG_DEBUG, 'FreshRSS use cache for ' . $subscribe_url);
+ $this->entries = array();
+ }
+
+ $feed->__destruct(); //http://simplepie.org/wiki/faq/i_m_getting_memory_leaks
+ unset($feed);
}
}
}
+
private function loadEntries ($feed) {
$entries = array ();
@@ -245,11 +254,16 @@ class FreshRSS_Feed extends Minz_Model {
$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="" />';
+ if (empty($elinks[$elink])) {
+ $elinks[$elink] = '1';
+ $mime = strtolower($enclosure->get_type());
+ if (strpos($mime, 'image/') === 0) {
+ $content .= '<br /><img src="' . $elink . '" alt="" />';
+ } elseif (strpos($mime, 'audio/') === 0) {
+ $content .= '<br /><audio src="' . $elink . '" controls="controls" />';
+ } elseif (strpos($mime, 'video/') === 0) {
+ $content .= '<br /><video src="' . $elink . '" controls="controls" />';
+ }
}
}
@@ -267,8 +281,27 @@ class FreshRSS_Feed extends Minz_Model {
$entry->loadCompleteContent($this->pathEntries());
$entries[] = $entry;
+ unset($item);
}
$this->entries = $entries;
}
+
+ function lock() {
+ $lock = TMP_PATH . '/' . md5(Minz_Configuration::salt() . $this->url) . '.freshrss.lock';
+ if (file_exists($lock) && ((time() - @filemtime($lock)) > 3600)) {
+ @unlink($lock);
+ }
+ if (($handle = @fopen($lock, 'x')) === false) {
+ return false;
+ }
+ //register_shutdown_function('unlink', $lock);
+ @fclose($handle);
+ return true;
+ }
+
+ function unlock() {
+ $lock = TMP_PATH . '/' . md5(Minz_Configuration::salt() . $this->url) . '.freshrss.lock';
+ @unlink($lock);
+ }
}
diff --git a/app/Models/FeedDAO.php b/app/Models/FeedDAO.php
index e102da4ec..b65ff4af0 100644
--- a/app/Models/FeedDAO.php
+++ b/app/Models/FeedDAO.php
@@ -24,6 +24,36 @@ 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
+ $feed_search = $this->searchByUrl($feed->url());
+ if (!$feed_search) {
+ $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 $feed_search->id();
+ }
+
public function updateFeed ($id, $valuesTmp) {
$set = '';
foreach ($valuesTmp as $key => $v) {
@@ -52,21 +82,27 @@ class FreshRSS_FeedDAO extends Minz_ModelPdo {
}
}
- 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);
+ public function updateLastUpdate ($id, $inError = 0, $updateCache = true) {
+ if ($updateCache) {
+ $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=?';
+ } else {
+ $sql = 'UPDATE `' . $this->prefix . 'feed` f '
+ . 'SET lastUpdate=?, error=? '
+ . 'WHERE f.id=?';
+ }
$values = array (
- time (),
+ time(),
$inError,
$id,
);
+ $stm = $this->bd->prepare ($sql);
+
if ($stm && $stm->execute ($values)) {
return $stm->rowCount();
} else {
@@ -164,7 +200,7 @@ class FreshRSS_FeedDAO extends Minz_ModelPdo {
if (isset ($feed[$id])) {
return $feed[$id];
} else {
- return false;
+ return null;
}
}
public function searchByUrl ($url) {
@@ -180,7 +216,7 @@ class FreshRSS_FeedDAO extends Minz_ModelPdo {
if (isset ($feed)) {
return $feed;
} else {
- return false;
+ return null;
}
}
@@ -192,8 +228,27 @@ class FreshRSS_FeedDAO extends Minz_ModelPdo {
return self::daoToFeed ($stm->fetchAll (PDO::FETCH_ASSOC));
}
- public function listFeedsOrderUpdate () {
- $sql = 'SELECT id, name, url, pathEntries, httpAuth, keep_history FROM `' . $this->prefix . 'feed` ORDER BY lastUpdate';
+ public function arrayFeedCategoryNames() { //For API
+ $sql = 'SELECT f.id, f.name, c.name as c_name FROM `' . $this->prefix . 'feed` f '
+ . 'INNER JOIN `' . $this->prefix . 'category` c ON c.id = f.category';
+ $stm = $this->bd->prepare ($sql);
+ $stm->execute ();
+ $res = $stm->fetchAll(PDO::FETCH_ASSOC);
+ $feedCategoryNames = array();
+ foreach ($res as $line) {
+ $feedCategoryNames[$line['id']] = array(
+ 'name' => $line['name'],
+ 'c_name' => $line['c_name'],
+ );
+ }
+ return $feedCategoryNames;
+ }
+
+ public function listFeedsOrderUpdate ($cacheDuration = 1500) {
+ $sql = 'SELECT id, name, url, lastUpdate, pathEntries, httpAuth, keep_history '
+ . 'FROM `' . $this->prefix . 'feed` '
+ . 'WHERE lastUpdate < ' . (time() - intval($cacheDuration))
+ . ' ORDER BY lastUpdate';
$stm = $this->bd->prepare ($sql);
$stm->execute ();
@@ -220,6 +275,7 @@ class FreshRSS_FeedDAO extends Minz_ModelPdo {
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);
@@ -229,6 +285,7 @@ class FreshRSS_FeedDAO extends Minz_ModelPdo {
return $res[0]['count'];
}
+
public function updateCachedValues () { //For one single feed, call updateLastUpdate($id)
$sql = 'UPDATE `' . $this->prefix . 'feed` f '
. 'INNER JOIN ('
@@ -241,9 +298,7 @@ class FreshRSS_FeedDAO extends Minz_ModelPdo {
. '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)) {
+ if ($stm && $stm->execute()) {
return $stm->rowCount();
} else {
$info = $stm->errorInfo();
diff --git a/app/Models/Share.php b/app/Models/Share.php
new file mode 100644
index 000000000..b146db722
--- /dev/null
+++ b/app/Models/Share.php
@@ -0,0 +1,44 @@
+<?php
+
+class FreshRSS_Share {
+
+ static public function generateUrl($options, $selected, $link, $title) {
+ $share = $options[$selected['type']];
+ $matches = array(
+ '~URL~',
+ '~TITLE~',
+ '~LINK~',
+ );
+ $replaces = array(
+ $selected['url'],
+ self::transformData($title, self::getTransform($share, 'title')),
+ self::transformData($link, self::getTransform($share, 'link')),
+ );
+ $url = str_replace($matches, $replaces, $share['url']);
+ return $url;
+ }
+
+ static private function transformData($data, $transform) {
+ if (!is_array($transform)) {
+ return $data;
+ }
+ if (count($transform) === 0) {
+ return $data;
+ }
+ foreach ($transform as $action) {
+ $data = call_user_func($action, $data);
+ }
+ return $data;
+ }
+
+ static private function getTransform($options, $type) {
+ $transform = $options['transform'];
+
+ if (array_key_exists($type, $transform)) {
+ return $transform[$type];
+ }
+
+ return $transform;
+ }
+
+}
diff --git a/app/Models/Themes.php b/app/Models/Themes.php
index c7099a1df..620149934 100644
--- a/app/Models/Themes.php
+++ b/app/Models/Themes.php
@@ -77,6 +77,7 @@ class FreshRSS_Themes extends Minz_Model {
'down' => '▽',
'favorite' => '★',
'help' => 'ⓘ',
+ 'key' => '⚿',
'link' => '↗',
'login' => '🔒',
'logout' => '🔓',
@@ -84,6 +85,7 @@ class FreshRSS_Themes extends Minz_Model {
'non-starred' => '☆',
'prev' => '⏪',
'read' => '☑',
+ 'rss' => '☄',
'unread' => '☐',
'refresh' => '🔃', //↻
'search' => '🔍',
@@ -91,6 +93,9 @@ class FreshRSS_Themes extends Minz_Model {
'starred' => '★',
'tag' => '⚐',
'up' => '△',
+ 'view-normal' => '☰',
+ 'view-global' => '☷',
+ 'view-reader' => '☕',
);
if (!isset($alts[$name])) {
return '';