aboutsummaryrefslogtreecommitdiff
path: root/app/Models
diff options
context:
space:
mode:
authorGravatar Alexandre Alapetite <alexandre@alapetite.fr> 2022-08-08 12:04:02 +0200
committerGravatar GitHub <noreply@github.com> 2022-08-08 12:04:02 +0200
commit82ac1d1e676f93b1567eba608c00c6edaf401a9e (patch)
tree1b3609df25f3eb1892aa7d359f52b82d680830a7 /app/Models
parent240afa7d4dcf33de4575a1531e2db3c9f4400c1f (diff)
Refactor entry-to-GReader API format (#4490)
* Refactor entry to GReader API format Some code was copied in two locations and not completely uniform. Cleaning of related variables and functions (e.g. better types for entries and categories as objects vs. as IDs). Usecase: I need to call the same GReader-compatible serialization from an extension * Fixed some edge cases * Keep summary instead of content `summary` and `content` seems to be used interchangeably in the Google Reader API. We have been using `summary` for our client API and `content` in our export/import, so stick to that.
Diffstat (limited to 'app/Models')
-rw-r--r--app/Models/Category.php2
-rw-r--r--app/Models/CategoryDAO.php2
-rw-r--r--app/Models/Context.php2
-rw-r--r--app/Models/Entry.php148
-rw-r--r--app/Models/EntryDAO.php11
-rw-r--r--app/Models/Feed.php35
-rw-r--r--app/Models/FeedDAO.php24
-rw-r--r--app/Models/TagDAO.php2
-rw-r--r--app/Models/View.php2
9 files changed, 173 insertions, 55 deletions
diff --git a/app/Models/Category.php b/app/Models/Category.php
index d75d7e21e..e5da764d3 100644
--- a/app/Models/Category.php
+++ b/app/Models/Category.php
@@ -219,7 +219,7 @@ class FreshRSS_Category extends Minz_Model {
foreach ($dryRunCategory->feeds() as $dryRunFeed) {
if (empty($existingFeeds[$dryRunFeed->url()])) {
// The feed does not exist in the current category, so add that feed
- $dryRunFeed->_category($this->id());
+ $dryRunFeed->_categoryId($this->id());
$ok &= ($feedDAO->addFeedObject($dryRunFeed) !== false);
} else {
$existingFeed = $existingFeeds[$dryRunFeed->url()];
diff --git a/app/Models/CategoryDAO.php b/app/Models/CategoryDAO.php
index cef8e6d63..e9b873d72 100644
--- a/app/Models/CategoryDAO.php
+++ b/app/Models/CategoryDAO.php
@@ -108,7 +108,7 @@ SQL;
$valuesTmp['name'],
);
- if ($stm && $stm->execute($values)) {
+ if ($stm && $stm->execute($values) && $stm->rowCount() > 0) {
return $this->pdo->lastInsertId('`_category_id_seq`');
} else {
$info = $stm == null ? $this->pdo->errorInfo() : $stm->errorInfo();
diff --git a/app/Models/Context.php b/app/Models/Context.php
index ab855966b..db8dd1f09 100644
--- a/app/Models/Context.php
+++ b/app/Models/Context.php
@@ -299,7 +299,7 @@ class FreshRSS_Context {
}
}
self::$current_get['feed'] = $id;
- self::$current_get['category'] = $feed->category();
+ self::$current_get['category'] = $feed->categoryId();
self::$name = $feed->name();
self::$description = $feed->description();
self::$get_unread = $feed->nbNotRead();
diff --git a/app/Models/Entry.php b/app/Models/Entry.php
index 72e59e38c..e383f9060 100644
--- a/app/Models/Entry.php
+++ b/app/Models/Entry.php
@@ -31,6 +31,7 @@ class FreshRSS_Entry extends Minz_Model {
* @var bool|null
*/
private $is_read;
+ /** @var bool|null */
private $is_favorite;
/**
@@ -213,17 +214,22 @@ class FreshRSS_Entry extends Minz_Model {
public function isFavorite() {
return $this->is_favorite;
}
- public function feed($object = false) {
- if ($object) {
- if ($this->feed == null) {
- $feedDAO = FreshRSS_Factory::createFeedDao();
- $this->feed = $feedDAO->searchById($this->feedId);
- }
- return $this->feed;
- } else {
- return $this->feedId;
+
+ /**
+ * @return FreshRSS_Feed|null|false
+ */
+ public function feed() {
+ if ($this->feed === null) {
+ $feedDAO = FreshRSS_Factory::createFeedDao();
+ $this->feed = $feedDAO->searchById($this->feedId);
}
+ return $this->feed;
}
+
+ public function feedId(): int {
+ return $this->feedId;
+ }
+
public function tags($asString = false) {
if ($asString) {
return $this->tags == null ? '' : '#' . implode(' #', $this->tags);
@@ -331,18 +337,21 @@ class FreshRSS_Entry extends Minz_Model {
$this->is_read = $value === null ? null : (bool)$value;
}
public function _isFavorite($value) {
- $this->is_favorite = $value;
+ $this->is_favorite = $value === null ? null : (bool)$value;
}
- public function _feed($value) {
- if ($value != null) {
- $this->feed = $value;
- $this->feedId = $this->feed->id();
- }
+
+ /** @param FreshRSS_Feed|null $feed */
+ public function _feed($feed) {
+ $this->feed = $feed;
+ $this->feedId = $this->feed == null ? 0 : $this->feed->id();
}
- private function _feedId($value) {
+
+ /** @param int|string $id */
+ private function _feedId($id) {
$this->feed = null;
- $this->feedId = intval($value);
+ $this->feedId = intval($id);
}
+
public function _tags($value) {
$this->hash = '';
if (!is_array($value)) {
@@ -558,7 +567,7 @@ class FreshRSS_Entry extends Minz_Model {
public function loadCompleteContent(bool $force = false): bool {
// Gestion du contenu
// Trying to fetch full article content even when feeds do not propose it
- $feed = $this->feed(true);
+ $feed = $this->feed();
if ($feed != null && trim($feed->pathEntries()) != '') {
$entryDAO = FreshRSS_Factory::createEntryDao();
$entry = $force ? null : $entryDAO->searchByGuid($this->feedId, $this->guid);
@@ -613,9 +622,110 @@ class FreshRSS_Entry extends Minz_Model {
'hash' => $this->hash(),
'is_read' => $this->isRead(),
'is_favorite' => $this->isFavorite(),
- 'id_feed' => $this->feed(),
+ 'id_feed' => $this->feedId(),
'tags' => $this->tags(true),
'attributes' => $this->attributes(),
);
}
+
+ /**
+ * Integer format conversion for Google Reader API format
+ * @param string|int $dec Decimal number
+ * @return string 64-bit hexa http://code.google.com/p/google-reader-api/wiki/ItemId
+ */
+ private static function dec2hex($dec): string {
+ return PHP_INT_SIZE < 8 ? // 32-bit ?
+ str_pad(gmp_strval(gmp_init($dec, 10), 16), 16, '0', STR_PAD_LEFT) :
+ str_pad(dechex($dec), 16, '0', STR_PAD_LEFT);
+ }
+
+ /**
+ * N.B.: To avoid expensive lookups, ensure to set `$entry->_feed($feed)` before calling this function.
+ * N.B.: You might have to populate `$entry->_tags()` prior to calling this function.
+ * @param string $mode Set to `'compat'` to use an alternative Unicode representation for problematic HTML special characters not decoded by some clients;
+ * set to `'freshrss'` for using FreshRSS additions for internal use (e.g. export/import).
+ * @return array<string,mixed> A representation of this entry in a format compatible with Google Reader API
+ */
+ public function toGReader(string $mode = ''): array {
+
+ $feed = $this->feed();
+ $category = $feed == null ? null : $feed->category();
+
+ $item = [
+ 'id' => 'tag:google.com,2005:reader/item/' . self::dec2hex($this->id()),
+ 'crawlTimeMsec' => substr($this->dateAdded(true, true), 0, -3),
+ 'timestampUsec' => '' . $this->dateAdded(true, true), //EasyRSS & Reeder
+ 'published' => $this->date(true),
+ // 'updated' => $this->date(true),
+ 'title' => $this->title(),
+ 'summary' => ['content' => $this->content()],
+ 'canonical' => [
+ ['href' => htmlspecialchars_decode($this->link(), ENT_QUOTES)],
+ ],
+ 'alternate' => [
+ [
+ 'href' => htmlspecialchars_decode($this->link(), ENT_QUOTES),
+ 'type' => 'text/html',
+ ],
+ ],
+ 'categories' => [
+ 'user/-/state/com.google/reading-list',
+ ],
+ 'origin' => [
+ 'streamId' => 'feed/' . $this->feedId,
+ ],
+ ];
+ if ($mode === 'compat') {
+ $item['title'] = escapeToUnicodeAlternative($this->title(), false);
+ } elseif ($mode === 'freshrss') {
+ $item['guid'] = $this->guid();
+ unset($item['summary']);
+ $item['content'] = ['content' => $this->content()];
+ }
+ if ($category != null && $mode !== 'freshrss') {
+ $item['categories'][] = 'user/-/label/' . htmlspecialchars_decode($category->name(), ENT_QUOTES);
+ }
+ if ($feed != null) {
+ $item['origin']['htmlUrl'] = htmlspecialchars_decode($feed->website());
+ $item['origin']['title'] = $feed->name(); //EasyRSS
+ if ($mode === 'compat') {
+ $item['origin']['title'] = escapeToUnicodeAlternative($feed->name(), true);
+ } elseif ($mode === 'freshrss') {
+ $item['origin']['feedUrl'] = htmlspecialchars_decode($feed->url());
+ }
+ }
+ foreach ($this->enclosures() as $enclosure) {
+ if (!empty($enclosure['url']) && !empty($enclosure['type'])) {
+ $media = [
+ 'href' => $enclosure['url'],
+ 'type' => $enclosure['type'],
+ ];
+ if (!empty($enclosure['length'])) {
+ $media['length'] = intval($enclosure['length']);
+ }
+ $item['enclosure'][] = $media;
+ }
+ }
+ $author = $this->authors(true);
+ $author = trim($author, '; ');
+ if ($author != '') {
+ if ($mode === 'compat') {
+ $item['author'] = escapeToUnicodeAlternative($author, false);
+ } else {
+ $item['author'] = $author;
+ }
+ }
+ if ($this->isRead()) {
+ $item['categories'][] = 'user/-/state/com.google/read';
+ } elseif ($mode === 'freshrss') {
+ $item['categories'][] = 'user/-/state/com.google/unread';
+ }
+ if ($this->isFavorite()) {
+ $item['categories'][] = 'user/-/state/com.google/starred';
+ }
+ foreach ($this->tags() as $tagName) {
+ $item['categories'][] = 'user/-/label/' . htmlspecialchars_decode($tagName, ENT_QUOTES);
+ }
+ return $item;
+ }
}
diff --git a/app/Models/EntryDAO.php b/app/Models/EntryDAO.php
index 5faaac1fb..d69702c60 100644
--- a/app/Models/EntryDAO.php
+++ b/app/Models/EntryDAO.php
@@ -218,6 +218,9 @@ SQL;
if (!isset($valuesTmp['is_read'])) {
$valuesTmp['is_read'] = null;
}
+ if (!isset($valuesTmp['is_favorite'])) {
+ $valuesTmp['is_favorite'] = null;
+ }
if ($this->updateEntryPrepared === null) {
$sql = 'UPDATE `_entry` '
@@ -226,6 +229,7 @@ SQL;
. ', link=:link, date=:date, `lastSeen`=:last_seen'
. ', hash=' . static::sqlHexDecode(':hash')
. ', is_read=COALESCE(:is_read, is_read)'
+ . ', is_favorite=COALESCE(:is_favorite, is_favorite)'
. ', tags=:tags, attributes=:attributes '
. 'WHERE id_feed=:id_feed AND guid=:guid';
$this->updateEntryPrepared = $this->pdo->prepare($sql);
@@ -254,6 +258,11 @@ SQL;
} else {
$this->updateEntryPrepared->bindValue(':is_read', $valuesTmp['is_read'] ? 1 : 0, PDO::PARAM_INT);
}
+ if ($valuesTmp['is_favorite'] === null) {
+ $this->updateEntryPrepared->bindValue(':is_favorite', null, PDO::PARAM_NULL);
+ } else {
+ $this->updateEntryPrepared->bindValue(':is_favorite', $valuesTmp['is_favorite'] ? 1 : 0, PDO::PARAM_INT);
+ }
$this->updateEntryPrepared->bindParam(':id_feed', $valuesTmp['id_feed'], PDO::PARAM_INT);
$valuesTmp['tags'] = mb_strcut($valuesTmp['tags'], 0, 1023, 'UTF-8');
$valuesTmp['tags'] = safe_utf8($valuesTmp['tags']);
@@ -1102,7 +1111,7 @@ SQL;
. ($limit > 0 ? ' LIMIT ' . intval($limit) : '')); //TODO: See http://explainextended.com/2009/10/23/mysql-order-by-limit-performance-late-row-lookups/
}
- public function listWhereRaw($type = 'a', $id = '', $state = FreshRSS_Entry::STATE_ALL,
+ private function listWhereRaw($type = 'a', $id = '', $state = FreshRSS_Entry::STATE_ALL,
$order = 'DESC', $limit = 1, $firstId = '', $filters = null, $date_min = 0) {
list($values, $sql) = $this->sqlListWhere($type, $id, $state, $order, $limit, $firstId, $filters, $date_min);
diff --git a/app/Models/Feed.php b/app/Models/Feed.php
index e39109b49..4de61167b 100644
--- a/app/Models/Feed.php
+++ b/app/Models/Feed.php
@@ -39,7 +39,9 @@ class FreshRSS_Feed extends Minz_Model {
/** @var int */
private $kind = 0;
/** @var int */
- private $category = 1;
+ private $categoryId = 1;
+ /** @var FreshRSS_Category|null */
+ private $category;
/** @var int */
private $nbEntries = -1;
/** @var int */
@@ -119,9 +121,22 @@ class FreshRSS_Feed extends Minz_Model {
public function hubUrl(): string {
return $this->hubUrl;
}
- public function category(): int {
+
+ /**
+ * @return FreshRSS_Category|null|false
+ */
+ public function category() {
+ if ($this->category === null) {
+ $catDAO = FreshRSS_Factory::createCategoryDao();
+ $this->category = $catDAO->searchById($this->categoryId);
+ }
return $this->category;
}
+
+ public function categoryId(): int {
+ return $this->categoryId;
+ }
+
public function entries() {
Minz_Log::warning(__method__ . ' is deprecated since FreshRSS 1.16.1!');
$simplePie = $this->load(false, true);
@@ -253,10 +268,16 @@ class FreshRSS_Feed extends Minz_Model {
$this->kind = $value;
}
- /** @param int $value */
- public function _category($value) {
- $value = intval($value);
- $this->category = $value >= 0 ? $value : 0;
+ /** @param FreshRSS_Category|null $cat */
+ public function _category($cat) {
+ $this->category = $cat;
+ $this->categoryId = $this->category == null ? 0 : $this->category->id();
+ }
+
+ /** @param int|string $id */
+ public function _categoryId($id) {
+ $this->category = null;
+ $this->categoryId = intval($id);
}
public function _name(string $value) {
@@ -700,7 +721,7 @@ class FreshRSS_Feed extends Minz_Model {
$archiving = $this->attributes('archiving');
if ($archiving == null) {
$catDAO = FreshRSS_Factory::createCategoryDao();
- $category = $catDAO->searchById($this->category());
+ $category = $catDAO->searchById($this->categoryId);
$archiving = $category == null ? null : $category->attributes('archiving');
if ($archiving == null) {
$archiving = FreshRSS_Context::$user_conf->archiving;
diff --git a/app/Models/FeedDAO.php b/app/Models/FeedDAO.php
index 8d54e7be2..233d2a715 100644
--- a/app/Models/FeedDAO.php
+++ b/app/Models/FeedDAO.php
@@ -82,7 +82,7 @@ class FreshRSS_FeedDAO extends Minz_ModelPdo implements FreshRSS_Searchable {
'id' => $feed->id(),
'url' => $feed->url(),
'kind' => $feed->kind(),
- 'category' => $feed->category(),
+ 'category' => $feed->categoryId(),
'name' => $feed->name(),
'website' => $feed->website(),
'description' => $feed->description(),
@@ -341,26 +341,6 @@ SQL;
}
/**
- * For API
- */
- public function arrayFeedCategoryNames(): array {
- $sql = <<<'SQL'
-SELECT f.id, f.name, c.name as c_name FROM `_feed` f
-INNER JOIN `_category` c ON c.id = f.category
-SQL;
- $stm = $this->pdo->query($sql);
- $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;
- }
-
- /**
* Use $defaultCacheDuration == -1 to return all feeds, without filtering them by TTL.
* @return array<FreshRSS_Feed>
*/
@@ -596,7 +576,7 @@ SQL;
$myFeed = new FreshRSS_Feed($dao['url'] ?? '', false);
$myFeed->_kind($dao['kind'] ?? FreshRSS_Feed::KIND_RSS);
- $myFeed->_category($category);
+ $myFeed->_categoryId($category);
$myFeed->_name($dao['name']);
$myFeed->_website($dao['website'] ?? '', false);
$myFeed->_description($dao['description'] ?? '');
diff --git a/app/Models/TagDAO.php b/app/Models/TagDAO.php
index 49abbdefd..f232b2f9f 100644
--- a/app/Models/TagDAO.php
+++ b/app/Models/TagDAO.php
@@ -61,7 +61,7 @@ SQL;
$valuesTmp['name'],
);
- if ($stm && $stm->execute($values)) {
+ if ($stm && $stm->execute($values) && $stm->rowCount() > 0) {
return $this->pdo->lastInsertId('`_tag_id_seq`');
} else {
$info = $stm == null ? $this->pdo->errorInfo() : $stm->errorInfo();
diff --git a/app/Models/View.php b/app/Models/View.php
index 0169f130a..ab1780405 100644
--- a/app/Models/View.php
+++ b/app/Models/View.php
@@ -72,8 +72,6 @@ class FreshRSS_View extends Minz_View {
// Export / Import
public $content;
- public $entriesRaw;
- public $entriesId;
public $entryIdsTagNames;
public $list_title;
public $queryId;