aboutsummaryrefslogtreecommitdiff
path: root/app/Models
diff options
context:
space:
mode:
Diffstat (limited to 'app/Models')
-rw-r--r--app/Models/Auth.php10
-rw-r--r--app/Models/Context.php42
-rw-r--r--app/Models/Entry.php113
-rw-r--r--app/Models/EntryDAO.php4
-rw-r--r--app/Models/EntryDAOPGSQL.php4
-rw-r--r--app/Models/Feed.php104
-rw-r--r--app/Models/FilterAction.php45
-rw-r--r--app/Models/TagDAO.php48
8 files changed, 319 insertions, 51 deletions
diff --git a/app/Models/Auth.php b/app/Models/Auth.php
index 513a9cb2f..6d079a01f 100644
--- a/app/Models/Auth.php
+++ b/app/Models/Auth.php
@@ -13,7 +13,7 @@ class FreshRSS_Auth {
* This method initializes authentication system.
*/
public static function init() {
- if (Minz_Session::param('REMOTE_USER', '') !== httpAuthUser()) {
+ if (isset($_SESSION['REMOTE_USER']) && $_SESSION['REMOTE_USER'] !== httpAuthUser()) {
//HTTP REMOTE_USER has changed
self::removeAccess();
}
@@ -24,6 +24,7 @@ class FreshRSS_Auth {
$conf = Minz_Configuration::get('system');
$current_user = $conf->default_user;
Minz_Session::_param('currentUser', $current_user);
+ Minz_Session::_param('csrf');
}
if (self::$login_ok) {
@@ -56,6 +57,7 @@ class FreshRSS_Auth {
$current_user = trim($credentials[0]);
Minz_Session::_param('currentUser', $current_user);
Minz_Session::_param('passwordHash', trim($credentials[1]));
+ Minz_Session::_param('csrf');
}
return $current_user != '';
case 'http_auth':
@@ -63,6 +65,7 @@ class FreshRSS_Auth {
$login_ok = $current_user != '' && FreshRSS_UserDAO::exists($current_user);
if ($login_ok) {
Minz_Session::_param('currentUser', $current_user);
+ Minz_Session::_param('csrf');
}
return $login_ok;
case 'none':
@@ -196,13 +199,10 @@ class FreshRSS_Auth {
}
public static function isCsrfOk($token = null) {
$csrf = Minz_Session::param('csrf');
- if ($csrf == '') {
- return true; //Not logged in yet
- }
if ($token === null) {
$token = Minz_Request::fetchPOST('_csrf');
}
- return $token === $csrf;
+ return $token != '' && $token === $csrf;
}
}
diff --git a/app/Models/Context.php b/app/Models/Context.php
index 60ec6ff77..95dc47c8c 100644
--- a/app/Models/Context.php
+++ b/app/Models/Context.php
@@ -252,37 +252,29 @@ class FreshRSS_Context {
$found_current_get = false;
switch ($get[0]) {
case 'f':
- // We search the next feed with at least one unread article in
- // same category as the currend feed.
+ // We search the next unread feed with the following priorities: next in same category, or previous in same category, or next, or previous.
foreach (self::$categories as $cat) {
- if ($cat->id() != self::$current_get['category']) {
- // We look into the category of the current feed!
- continue;
- }
-
+ $sameCat = false;
foreach ($cat->feeds() as $feed) {
- if ($feed->id() == self::$current_get['feed']) {
- // Here is our current feed! Fine, the next one will
- // be a potential candidate.
+ if ($found_current_get) {
+ if ($feed->nbNotRead() > 0) {
+ $another_unread_id = $feed->id();
+ break 2;
+ }
+ } elseif ($feed->id() == self::$current_get['feed']) {
$found_current_get = true;
- continue;
- }
-
- if ($feed->nbNotRead() > 0) {
+ } elseif ($feed->nbNotRead() > 0) {
$another_unread_id = $feed->id();
- if ($found_current_get) {
- // We have found our current feed and now we
- // have an feed with unread articles. Leave the
- // loop!
- break;
- }
+ $sameCat = true;
}
}
- break;
+ if ($found_current_get && $sameCat) {
+ break;
+ }
}
- // If no feed have been found, next_get is the current category.
- self::$next_get = empty($another_unread_id) ? 'c_' . self::$current_get['category'] : 'f_' . $another_unread_id;
+ // If there is no more unread feed, show main stream
+ self::$next_get = $another_unread_id == '' ? 'a' : 'f_' . $another_unread_id;
break;
case 'c':
// We search the next category with at least one unread article.
@@ -304,8 +296,8 @@ class FreshRSS_Context {
}
}
- // No unread category? The main stream will be our destination!
- self::$next_get = empty($another_unread_id) ? 'a' : 'c_' . $another_unread_id;
+ // If there is no more unread category, show main stream
+ self::$next_get = $another_unread_id == '' ? 'a' : 'c_' . $another_unread_id;
break;
}
}
diff --git a/app/Models/Entry.php b/app/Models/Entry.php
index f2f3d08fe..3bb977283 100644
--- a/app/Models/Entry.php
+++ b/app/Models/Entry.php
@@ -185,6 +185,119 @@ class FreshRSS_Entry extends Minz_Model {
$this->tags = $value;
}
+ public function matches($booleanSearch) {
+ if (!$booleanSearch || count($booleanSearch->searches()) <= 0) {
+ return true;
+ }
+ foreach ($booleanSearch->searches() as $filter) {
+ $ok = true;
+ if ($ok && $filter->getMinPubdate()) {
+ $ok &= $this->date >= $filter->getMinPubdate();
+ }
+ if ($ok && $filter->getMaxPubdate()) {
+ $ok &= $this->date <= $filter->getMaxPubdate();
+ }
+ if ($ok && $filter->getMinDate()) {
+ $ok &= strnatcmp($this->id, $filter->getMinDate() . '000000') >= 0;
+ }
+ if ($ok && $filter->getMaxDate()) {
+ $ok &= strnatcmp($this->id, $filter->getMaxDate() . '000000') <= 0;
+ }
+ if ($ok && $filter->getInurl()) {
+ foreach ($filter->getInurl() as $url) {
+ $ok &= stripos($this->link, $url) !== false;
+ }
+ }
+ if ($ok && $filter->getNotInurl()) {
+ foreach ($filter->getNotInurl() as $url) {
+ $ok &= stripos($this->link, $url) === false;
+ }
+ }
+ if ($ok && $filter->getAuthor()) {
+ foreach ($filter->getAuthor() as $author) {
+ $ok &= stripos($this->authors, $author) !== false;
+ }
+ }
+ if ($ok && $filter->getNotAuthor()) {
+ foreach ($filter->getNotAuthor() as $author) {
+ $ok &= stripos($this->authors, $author) === false;
+ }
+ }
+ if ($ok && $filter->getIntitle()) {
+ foreach ($filter->getIntitle() as $title) {
+ $ok &= stripos($this->title, $title) !== false;
+ }
+ }
+ if ($ok && $filter->getNotIntitle()) {
+ foreach ($filter->getNotIntitle() as $title) {
+ $ok &= stripos($this->title, $title) === false;
+ }
+ }
+ if ($ok && $filter->getTags()) {
+ foreach ($filter->getTags() as $tag2) {
+ $found = false;
+ foreach ($this->tags as $tag1) {
+ if (strcasecmp($tag1, $tag2) === 0) {
+ $found = true;
+ }
+ }
+ $ok &= $found;
+ }
+ }
+ if ($ok && $filter->getNotTags()) {
+ foreach ($filter->getNotTags() as $tag2) {
+ $found = false;
+ foreach ($this->tags as $tag1) {
+ if (strcasecmp($tag1, $tag2) === 0) {
+ $found = true;
+ }
+ }
+ $ok &= !$found;
+ }
+ }
+ if ($ok && $filter->getSearch()) {
+ foreach ($filter->getSearch() as $needle) {
+ $ok &= (stripos($this->title, $needle) !== false || stripos($this->content, $needle) !== false);
+ }
+ }
+ if ($ok && $filter->getNotSearch()) {
+ foreach ($filter->getNotSearch() as $needle) {
+ $ok &= (stripos($this->title, $needle) === false && stripos($this->content, $needle) === false);
+ }
+ }
+ if ($ok) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ public function applyFilterActions() {
+ if ($this->feed != null) {
+ if ($this->feed->attributes('read_upon_reception') ||
+ ($this->feed->attributes('read_upon_reception') === null && FreshRSS_Context::$user_conf->mark_when['reception'])) {
+ $this->_isRead(true);
+ }
+ foreach ($this->feed->filterActions() as $filterAction) {
+ if ($this->matches($filterAction->booleanSearch())) {
+ foreach ($filterAction->actions() as $action => $params) {
+ switch ($action) {
+ case 'read':
+ $this->_isRead(true);
+ break;
+ case 'star':
+ $this->_is_favorite(true);
+ break;
+ case 'label':
+ //TODO: Implement more actions
+ break;
+ }
+ }
+ }
+ }
+ }
+ }
+
public function isDay($day, $today) {
$date = $this->dateAdded(true);
switch ($day) {
diff --git a/app/Models/EntryDAO.php b/app/Models/EntryDAO.php
index 08927196e..93d1183c9 100644
--- a/app/Models/EntryDAO.php
+++ b/app/Models/EntryDAO.php
@@ -383,7 +383,7 @@ class FreshRSS_EntryDAO extends Minz_ModelPdo implements FreshRSS_Searchable {
*/
public function markRead($ids, $is_read = true) {
FreshRSS_UserDAO::touch();
- if (is_array($ids)) { //Many IDs at once (used by API)
+ if (is_array($ids)) { //Many IDs at once
if (count($ids) < 6) { //Speed heuristics
$affected = 0;
foreach ($ids as $id) {
@@ -1065,7 +1065,7 @@ SQL;
$dao['date'],
$dao['is_read'],
$dao['is_favorite'],
- $dao['tags']
+ isset($dao['tags']) ? $dao['tags'] : ''
);
if (isset($dao['id'])) {
$entry->_id($dao['id']);
diff --git a/app/Models/EntryDAOPGSQL.php b/app/Models/EntryDAOPGSQL.php
index aef258b6f..e571e457f 100644
--- a/app/Models/EntryDAOPGSQL.php
+++ b/app/Models/EntryDAOPGSQL.php
@@ -37,7 +37,9 @@ BEGIN
INSERT INTO `' . $this->prefix . 'entry` (id, guid, title, author, content, link, date, `lastSeen`, hash, is_read, is_favorite, id_feed, tags)
(SELECT rank + row_number() OVER(ORDER BY date) AS id, guid, title, author, content, link, date, `lastSeen`, hash, is_read, is_favorite, id_feed, tags
FROM `' . $this->prefix . 'entrytmp` AS etmp
- WHERE NOT EXISTS (SELECT 1 FROM `' . $this->prefix . 'entry` AS ereal WHERE etmp.id_feed = ereal.id_feed AND etmp.guid = ereal.guid)
+ WHERE NOT EXISTS (
+ SELECT 1 FROM `' . $this->prefix . 'entry` AS ereal
+ WHERE (etmp.id = ereal.id) OR (etmp.id_feed = ereal.id_feed AND etmp.guid = ereal.guid))
ORDER BY date);
DELETE FROM `' . $this->prefix . 'entrytmp` WHERE id <= maxrank;
END $$;';
diff --git a/app/Models/Feed.php b/app/Models/Feed.php
index b21a8bbbe..89989236c 100644
--- a/app/Models/Feed.php
+++ b/app/Models/Feed.php
@@ -32,6 +32,7 @@ class FreshRSS_Feed extends Minz_Model {
private $lockPath = '';
private $hubUrl = '';
private $selfUrl = '';
+ private $filterActions = null;
public function __construct($url, $validate = true) {
if ($validate) {
@@ -498,6 +499,109 @@ class FreshRSS_Feed extends Minz_Model {
@unlink($this->lockPath);
}
+ public function filterActions() {
+ if ($this->filterActions == null) {
+ $this->filterActions = array();
+ $filters = $this->attributes('filters');
+ if (is_array($filters)) {
+ foreach ($filters as $filter) {
+ $filterAction = FreshRSS_FilterAction::fromJSON($filter);
+ if ($filterAction != null) {
+ $this->filterActions[] = $filterAction;
+ }
+ }
+ }
+ }
+ return $this->filterActions;
+ }
+
+ private function _filterActions($filterActions) {
+ $this->filterActions = $filterActions;
+ if (is_array($this->filterActions) && !empty($this->filterActions)) {
+ $this->_attributes('filters', array_map(function ($af) {
+ return $af == null ? null : $af->toJSON();
+ }, $this->filterActions));
+ } else {
+ $this->_attributes('filters', null);
+ }
+ }
+
+ public function filtersAction($action) {
+ $action = trim($action);
+ if ($action == '') {
+ return array();
+ }
+ $filters = array();
+ $filterActions = $this->filterActions();
+ for ($i = count($filterActions) - 1; $i >= 0; $i--) {
+ $filterAction = $filterActions[$i];
+ if ($filterAction != null && $filterAction->booleanSearch() != null &&
+ $filterAction->actions() != null && in_array($action, $filterAction->actions(), true)) {
+ $filters[] = $filterAction->booleanSearch();
+ }
+ }
+ return $filters;
+ }
+
+ public function _filtersAction($action, $filters) {
+ $action = trim($action);
+ if ($action == '' || !is_array($filters)) {
+ return false;
+ }
+ $filters = array_unique(array_map('trim', $filters));
+ $filterActions = $this->filterActions();
+
+ //Check existing filters
+ for ($i = count($filterActions) - 1; $i >= 0; $i--) {
+ $filterAction = $filterActions[$i];
+ if ($filterAction == null || !is_array($filterAction->actions()) ||
+ $filterAction->booleanSearch() == null || trim($filterAction->booleanSearch()->getRawInput()) == '') {
+ array_splice($filterAction, $i, 1);
+ continue;
+ }
+ $actions = $filterAction->actions();
+ //Remove existing rules with same action
+ for ($j = count($actions) - 1; $j >= 0; $j--) {
+ if ($actions[$j] === $action) {
+ array_splice($actions, $j, 1);
+ }
+ }
+ //Update existing filter with new action
+ for ($k = count($filters) - 1; $k >= 0; $k --) {
+ $filter = $filters[$k];
+ if ($filter === $filterAction->booleanSearch()->getRawInput()) {
+ $actions[] = $action;
+ array_splice($filters, $k, 1);
+ }
+ }
+ //Save result
+ if (empty($actions)) {
+ array_splice($filterActions, $i, 1);
+ } else {
+ $filterAction->_actions($actions);
+ }
+ }
+
+ //Add new filters
+ for ($k = count($filters) - 1; $k >= 0; $k --) {
+ $filter = $filters[$k];
+ if ($filter != '') {
+ $filterAction = FreshRSS_FilterAction::fromJSON(array(
+ 'search' => $filter,
+ 'actions' => array($action),
+ ));
+ if ($filterAction != null) {
+ $filterActions[] = $filterAction;
+ }
+ }
+ }
+
+ if (empty($filterActions)) {
+ $filterActions = null;
+ }
+ $this->_filterActions($filterActions);
+ }
+
//<WebSub>
public function pubSubHubbubEnabled() {
diff --git a/app/Models/FilterAction.php b/app/Models/FilterAction.php
new file mode 100644
index 000000000..23a45d14e
--- /dev/null
+++ b/app/Models/FilterAction.php
@@ -0,0 +1,45 @@
+<?php
+
+class FreshRSS_FilterAction {
+
+ private $booleanSearch = null;
+ private $actions = null;
+
+ private function __construct($booleanSearch, $actions) {
+ $this->booleanSearch = $booleanSearch;
+ $this->_actions($actions);
+ }
+
+ public function booleanSearch() {
+ return $this->booleanSearch;
+ }
+
+ public function actions() {
+ return $this->actions;
+ }
+
+ public function _actions($actions) {
+ if (is_array($actions)) {
+ $this->actions = array_unique($actions);
+ } else {
+ $this->actions = null;
+ }
+ }
+
+ public function toJSON() {
+ if (is_array($this->actions) && $this->booleanSearch != null) {
+ return array(
+ 'search' => $this->booleanSearch->getRawInput(),
+ 'actions' => $this->actions,
+ );
+ }
+ return '';
+ }
+
+ public static function fromJSON($json) {
+ if (!empty($json['search']) && !empty($json['actions']) && is_array($json['actions'])) {
+ return new FreshRSS_FilterAction(new FreshRSS_BooleanSearch($json['search']), $json['actions']);
+ }
+ return null;
+ }
+}
diff --git a/app/Models/TagDAO.php b/app/Models/TagDAO.php
index dba854aa4..297d24c96 100644
--- a/app/Models/TagDAO.php
+++ b/app/Models/TagDAO.php
@@ -187,9 +187,17 @@ class FreshRSS_TagDAO extends Minz_ModelPdo implements FreshRSS_Searchable {
public function count() {
$sql = 'SELECT COUNT(*) AS count FROM `' . $this->prefix . 'tag`';
$stm = $this->bd->prepare($sql);
- $stm->execute();
- $res = $stm->fetchAll(PDO::FETCH_ASSOC);
- return $res[0]['count'];
+ if ($stm && $stm->execute()) {
+ $res = $stm->fetchAll(PDO::FETCH_ASSOC);
+ return $res[0]['count'];
+ } else {
+ $info = $stm == null ? array(0 => '', 1 => '', 2 => 'syntax error') : $stm->errorInfo();
+ if ($this->autoUpdateDb($info)) {
+ return $this->count();
+ }
+ Minz_Log::error('SQL error TagDAO::count: ' . $info[2]);
+ return false;
+ }
}
public function countEntries($id) {
@@ -256,9 +264,8 @@ class FreshRSS_TagDAO extends Minz_ModelPdo implements FreshRSS_Searchable {
}
}
- //For API
- public function getEntryIdsTagNames($entries) {
- $sql = 'SELECT et.id_entry, t.name '
+ public function getTagsForEntries($entries) {
+ $sql = 'SELECT et.id_entry, et.id_tag, t.name '
. 'FROM `' . $this->prefix . 'tag` t '
. 'INNER JOIN `' . $this->prefix . 'entrytag` et ON et.id_tag = t.id';
@@ -282,26 +289,31 @@ class FreshRSS_TagDAO extends Minz_ModelPdo implements FreshRSS_Searchable {
$stm = $this->bd->prepare($sql);
if ($stm && $stm->execute($values)) {
- $result = array();
- foreach ($stm->fetchAll(PDO::FETCH_ASSOC) as $line) {
- $entryId = 'e_' . $line['id_entry'];
- $tagName = $line['name'];
- if (empty($result[$entryId])) {
- $result[$entryId] = array();
- }
- $result[$entryId][] = $tagName;
- }
- return $result;
+ return $stm->fetchAll(PDO::FETCH_ASSOC);
} else {
$info = $stm == null ? array(0 => '', 1 => '', 2 => 'syntax error') : $stm->errorInfo();
if ($this->autoUpdateDb($info)) {
- return $this->getTagNamesEntryIds($id_entry);
+ return $this->getTagsForEntries($entries);
}
- Minz_Log::error('SQL error getTagNamesEntryIds: ' . $info[2]);
+ Minz_Log::error('SQL error getTagsForEntries: ' . $info[2]);
return false;
}
}
+ //For API
+ public function getEntryIdsTagNames($entries) {
+ $result = array();
+ foreach ($this->getTagsForEntries($entries) as $line) {
+ $entryId = 'e_' . $line['id_entry'];
+ $tagName = $line['name'];
+ if (empty($result[$entryId])) {
+ $result[$entryId] = array();
+ }
+ $result[$entryId][] = $tagName;
+ }
+ return $result;
+ }
+
public static function daoToTag($listDAO) {
$list = array();
if (!is_array($listDAO)) {