aboutsummaryrefslogtreecommitdiff
path: root/app/Models
diff options
context:
space:
mode:
authorGravatar Alexandre Alapetite <alexandre@alapetite.fr> 2022-07-19 15:17:08 +0200
committerGravatar GitHub <noreply@github.com> 2022-07-19 15:17:08 +0200
commit0866fdaee85bc8530437436abe7f8536b8b0f4f0 (patch)
treefd73e2e55740423bfab64581c4934b6d50dd3162 /app/Models
parent2d807e06b1abffdbc40a60c3623e22e3c6b818c6 (diff)
Add database field attributes for entries (#4444)
* Add database field attributes for entries Just like we already have for categories, feeds, etc. No core use yet, but allows in particular extensions to save per-entry data * Fix PHPStand * Fix wrong variable
Diffstat (limited to 'app/Models')
-rw-r--r--app/Models/Entry.php28
-rw-r--r--app/Models/EntryDAO.php70
-rw-r--r--app/Models/EntryDAOPGSQL.php16
-rw-r--r--app/Models/EntryDAOSQLite.php14
4 files changed, 107 insertions, 21 deletions
diff --git a/app/Models/Entry.php b/app/Models/Entry.php
index fb17268b3..72e59e38c 100644
--- a/app/Models/Entry.php
+++ b/app/Models/Entry.php
@@ -44,6 +44,7 @@ class FreshRSS_Entry extends Minz_Model {
private $feed;
private $tags;
+ private $attributes = [];
public function __construct(int $feedId = 0, string $guid = '', string $title = '', string $authors = '', string $content = '',
string $link = '', $pubdate = 0, bool $is_read = false, bool $is_favorite = false, string $tags = '') {
@@ -88,6 +89,9 @@ class FreshRSS_Entry extends Minz_Model {
if (!empty($dao['categories'])) {
$entry->_tags($dao['categories']);
}
+ if (!empty($dao['attributes'])) {
+ $entry->_attributes('', $dao['attributes']);
+ }
return $entry;
}
@@ -228,6 +232,29 @@ class FreshRSS_Entry extends Minz_Model {
}
}
+ public function attributes($key = '') {
+ if ($key == '') {
+ return $this->attributes;
+ } else {
+ return isset($this->attributes[$key]) ? $this->attributes[$key] : null;
+ }
+ }
+
+ public function _attributes(string $key, $value) {
+ if ($key == '') {
+ if (is_string($value)) {
+ $value = json_decode($value, true);
+ }
+ if (is_array($value)) {
+ $this->attributes = $value;
+ }
+ } elseif ($value === null) {
+ unset($this->attributes[$key]);
+ } else {
+ $this->attributes[$key] = $value;
+ }
+ }
+
public function hash(): string {
if ($this->hash == '') {
//Do not include $this->date because it may be automatically generated when lacking
@@ -588,6 +615,7 @@ class FreshRSS_Entry extends Minz_Model {
'is_favorite' => $this->isFavorite(),
'id_feed' => $this->feed(),
'tags' => $this->tags(true),
+ 'attributes' => $this->attributes(),
);
}
}
diff --git a/app/Models/EntryDAO.php b/app/Models/EntryDAO.php
index d87f3b7c4..5faaac1fb 100644
--- a/app/Models/EntryDAO.php
+++ b/app/Models/EntryDAO.php
@@ -65,9 +65,33 @@ SQL;
return $ok;
}
+ protected function addColumn(string $name) {
+ Minz_Log::warning(__method__ . ': ' . $name);
+ try {
+ if ($name === 'attributes') { //v1.20.0
+ $sql = <<<'SQL'
+ALTER TABLE `_entry` ADD COLUMN attributes TEXT;
+ALTER TABLE `_entrytmp` ADD COLUMN attributes TEXT;
+SQL;
+ return $this->pdo->exec($sql) !== false;
+ }
+ } catch (Exception $e) {
+ Minz_Log::error(__method__ . ' error: ' . $e->getMessage());
+ }
+ return false;
+ }
+
//TODO: Move the database auto-updates to DatabaseDAO
protected function autoUpdateDb(array $errorInfo) {
if (isset($errorInfo[0])) {
+ if ($errorInfo[0] === FreshRSS_DatabaseDAO::ER_BAD_FIELD_ERROR || $errorInfo[0] === FreshRSS_DatabaseDAOPGSQL::UNDEFINED_COLUMN) {
+ $errorLines = explode("\n", $errorInfo[2], 2); // The relevant column name is on the first line, other lines are noise
+ foreach (['attributes'] as $column) {
+ if (stripos($errorLines[0], $column) !== false) {
+ return $this->addColumn($column);
+ }
+ }
+ }
if ($errorInfo[0] === FreshRSS_DatabaseDAO::ER_BAD_TABLE_ERROR) {
if (stripos($errorInfo[2], 'tag') !== false) {
$tagDAO = FreshRSS_Factory::createTagDao();
@@ -97,12 +121,12 @@ SQL;
$sql = static::sqlIgnoreConflict(
'INSERT INTO `_' . ($useTmpTable ? 'entrytmp' : 'entry') . '` (id, guid, title, author, '
. (static::isCompressed() ? 'content_bin' : 'content')
- . ', link, date, `lastSeen`, hash, is_read, is_favorite, id_feed, tags) '
+ . ', link, date, `lastSeen`, hash, is_read, is_favorite, id_feed, tags, attributes) '
. 'VALUES(:id, :guid, :title, :author, '
. (static::isCompressed() ? 'COMPRESS(:content)' : ':content')
. ', :link, :date, :last_seen, '
. static::sqlHexDecode(':hash')
- . ', :is_read, :is_favorite, :id_feed, :tags)');
+ . ', :is_read, :is_favorite, :id_feed, :tags, :attributes)');
$this->addEntryPrepared = $this->pdo->prepare($sql);
}
if ($this->addEntryPrepared) {
@@ -135,6 +159,11 @@ SQL;
$valuesTmp['tags'] = mb_strcut($valuesTmp['tags'], 0, 1023, 'UTF-8');
$valuesTmp['tags'] = safe_utf8($valuesTmp['tags']);
$this->addEntryPrepared->bindParam(':tags', $valuesTmp['tags']);
+ if (!isset($valuesTmp['attributes'])) {
+ $valuesTmp['attributes'] = [];
+ }
+ $this->addEntryPrepared->bindValue(':attributes', is_string($valuesTmp['attributes']) ? $valuesTmp['attributes'] :
+ json_encode($valuesTmp['attributes'], JSON_UNESCAPED_SLASHES));
if (static::hasNativeHex()) {
$this->addEntryPrepared->bindParam(':hash', $valuesTmp['hash']);
@@ -164,9 +193,9 @@ SET @rank=(SELECT MAX(id) - COUNT(*) FROM `_entrytmp`);
INSERT IGNORE INTO `_entry` (
id, guid, title, author, content_bin, link, date, `lastSeen`,
- hash, is_read, is_favorite, id_feed, tags
+ hash, is_read, is_favorite, id_feed, tags, attributes
)
-SELECT @rank:=@rank+1 AS id, guid, title, author, content_bin, link, date, `lastSeen`, hash, is_read, is_favorite, id_feed, tags
+SELECT @rank:=@rank+1 AS id, guid, title, author, content_bin, link, date, `lastSeen`, hash, is_read, is_favorite, id_feed, tags, attributes
FROM `_entrytmp`
ORDER BY date, id;
@@ -197,7 +226,7 @@ SQL;
. ', link=:link, date=:date, `lastSeen`=:last_seen'
. ', hash=' . static::sqlHexDecode(':hash')
. ', is_read=COALESCE(:is_read, is_read)'
- . ', tags=:tags '
+ . ', tags=:tags, attributes=:attributes '
. 'WHERE id_feed=:id_feed AND guid=:guid';
$this->updateEntryPrepared = $this->pdo->prepare($sql);
}
@@ -229,6 +258,11 @@ SQL;
$valuesTmp['tags'] = mb_strcut($valuesTmp['tags'], 0, 1023, 'UTF-8');
$valuesTmp['tags'] = safe_utf8($valuesTmp['tags']);
$this->updateEntryPrepared->bindParam(':tags', $valuesTmp['tags']);
+ if (!isset($valuesTmp['attributes'])) {
+ $valuesTmp['attributes'] = [];
+ }
+ $this->updateEntryPrepared->bindValue(':attributes', is_string($valuesTmp['attributes']) ? $valuesTmp['attributes'] :
+ json_encode($valuesTmp['attributes'], JSON_UNESCAPED_SLASHES));
if (static::hasNativeHex()) {
$this->updateEntryPrepared->bindParam(':hash', $valuesTmp['hash']);
@@ -654,11 +688,20 @@ SQL;
public function selectAll() {
$sql = 'SELECT id, guid, title, author, '
. (static::isCompressed() ? 'UNCOMPRESS(content_bin) AS content' : 'content')
- . ', link, date, `lastSeen`, ' . static::sqlHexEncode('hash') . ' AS hash, is_read, is_favorite, id_feed, tags '
+ . ', link, date, `lastSeen`, ' . static::sqlHexEncode('hash') . ' AS hash, is_read, is_favorite, id_feed, tags, attributes '
. 'FROM `_entry`';
$stm = $this->pdo->query($sql);
- while ($row = $stm->fetch(PDO::FETCH_ASSOC)) {
- yield $row;
+ if ($stm != false) {
+ while ($row = $stm->fetch(PDO::FETCH_ASSOC)) {
+ yield $row;
+ }
+ } else {
+ $info = $this->pdo->errorInfo();
+ if ($this->autoUpdateDb($info)) {
+ yield from $this->selectAll();
+ }
+ Minz_Log::error(__method__ . ' error: ' . json_encode($info));
+ yield false;
}
}
@@ -667,7 +710,7 @@ SQL;
// un guid est unique pour un flux donné
$sql = 'SELECT id, guid, title, author, '
. (static::isCompressed() ? 'UNCOMPRESS(content_bin) AS content' : 'content')
- . ', link, date, is_read, is_favorite, id_feed, tags '
+ . ', link, date, is_read, is_favorite, id_feed, tags, attributes '
. 'FROM `_entry` WHERE id_feed=:id_feed AND guid=:guid';
$stm = $this->pdo->prepare($sql);
$stm->bindParam(':id_feed', $id_feed, PDO::PARAM_INT);
@@ -681,7 +724,7 @@ SQL;
public function searchById($id) {
$sql = 'SELECT id, guid, title, author, '
. (static::isCompressed() ? 'UNCOMPRESS(content_bin) AS content' : 'content')
- . ', link, date, is_read, is_favorite, id_feed, tags '
+ . ', link, date, is_read, is_favorite, id_feed, tags, attributes '
. 'FROM `_entry` WHERE id=:id';
$stm = $this->pdo->prepare($sql);
$stm->bindParam(':id', $id, PDO::PARAM_INT);
@@ -1065,7 +1108,7 @@ SQL;
$sql = 'SELECT e0.id, e0.guid, e0.title, e0.author, '
. (static::isCompressed() ? 'UNCOMPRESS(content_bin) AS content' : 'content')
- . ', e0.link, e0.date, e0.is_read, e0.is_favorite, e0.id_feed, e0.tags '
+ . ', e0.link, e0.date, e0.is_read, e0.is_favorite, e0.id_feed, e0.tags, e0.attributes '
. 'FROM `_entry` e0 '
. 'INNER JOIN ('
. $sql
@@ -1077,6 +1120,9 @@ SQL;
return $stm;
} else {
$info = $stm == null ? $this->pdo->errorInfo() : $stm->errorInfo();
+ if ($this->autoUpdateDb($info)) {
+ return $this->listWhereRaw($type, $id, $state, $order, $limit, $firstId, $filters, $date_min);
+ }
Minz_Log::error('SQL error listWhereRaw: ' . $info[2]);
return false;
}
@@ -1110,7 +1156,7 @@ SQL;
$sql = 'SELECT id, guid, title, author, '
. (static::isCompressed() ? 'UNCOMPRESS(content_bin) AS content' : 'content')
- . ', link, date, is_read, is_favorite, id_feed, tags '
+ . ', link, date, is_read, is_favorite, id_feed, tags, attributes '
. 'FROM `_entry` '
. 'WHERE id IN (' . str_repeat('?,', count($ids) - 1). '?) '
. 'ORDER BY id ' . $order;
diff --git a/app/Models/EntryDAOPGSQL.php b/app/Models/EntryDAOPGSQL.php
index 13bf8b41f..8702db706 100644
--- a/app/Models/EntryDAOPGSQL.php
+++ b/app/Models/EntryDAOPGSQL.php
@@ -20,6 +20,14 @@ class FreshRSS_EntryDAOPGSQL extends FreshRSS_EntryDAOSQLite {
protected function autoUpdateDb(array $errorInfo) {
if (isset($errorInfo[0])) {
+ if ($errorInfo[0] === FreshRSS_DatabaseDAO::ER_BAD_FIELD_ERROR || $errorInfo[0] === FreshRSS_DatabaseDAOPGSQL::UNDEFINED_COLUMN) {
+ $errorLines = explode("\n", $errorInfo[2], 2); // The relevant column name is on the first line, other lines are noise
+ foreach (['attributes'] as $column) {
+ if (stripos($errorLines[0], $column) !== false) {
+ return $this->addColumn($column);
+ }
+ }
+ }
if ($errorInfo[0] === FreshRSS_DatabaseDAOPGSQL::UNDEFINED_TABLE) {
if (stripos($errorInfo[2], 'tag') !== false) {
$tagDAO = FreshRSS_Factory::createTagDao();
@@ -32,10 +40,6 @@ class FreshRSS_EntryDAOPGSQL extends FreshRSS_EntryDAOSQLite {
return false;
}
- protected function addColumn(string $name) {
- return false;
- }
-
public function commitNewEntries() {
//TODO: Update to PostgreSQL 9.5+ syntax with ON CONFLICT DO NOTHING
$sql = 'DO $$
@@ -44,9 +48,9 @@ maxrank bigint := (SELECT MAX(id) FROM `_entrytmp`);
rank bigint := (SELECT maxrank - COUNT(*) FROM `_entrytmp`);
BEGIN
INSERT INTO `_entry`
- (id, guid, title, author, content, link, date, `lastSeen`, hash, is_read, is_favorite, id_feed, tags)
+ (id, guid, title, author, content, link, date, `lastSeen`, hash, is_read, is_favorite, id_feed, tags, attributes)
(SELECT rank + row_number() OVER(ORDER BY date, id) AS id, guid, title, author, content,
- link, date, `lastSeen`, hash, is_read, is_favorite, id_feed, tags
+ link, date, `lastSeen`, hash, is_read, is_favorite, id_feed, tags, attributes
FROM `_entrytmp` AS etmp
WHERE NOT EXISTS (
SELECT 1 FROM `_entry` AS ereal
diff --git a/app/Models/EntryDAOSQLite.php b/app/Models/EntryDAOSQLite.php
index e4e079959..27a7c3560 100644
--- a/app/Models/EntryDAOSQLite.php
+++ b/app/Models/EntryDAOSQLite.php
@@ -23,6 +23,14 @@ class FreshRSS_EntryDAOSQLite extends FreshRSS_EntryDAO {
}
protected function autoUpdateDb(array $errorInfo) {
+ if ($tableInfo = $this->pdo->query("PRAGMA table_info('entry')")) {
+ $columns = $tableInfo->fetchAll(PDO::FETCH_COLUMN, 1);
+ foreach (['attributes'] as $column) {
+ if (!in_array($column, $columns)) {
+ return $this->addColumn($column);
+ }
+ }
+ }
if ($tableInfo = $this->pdo->query("SELECT sql FROM sqlite_master where name='tag'")) {
$showCreate = $tableInfo->fetchColumn();
if (stripos($showCreate, 'tag') === false) {
@@ -43,13 +51,13 @@ class FreshRSS_EntryDAOSQLite extends FreshRSS_EntryDAO {
$sql = '
DROP TABLE IF EXISTS `tmp`;
CREATE TEMP TABLE `tmp` AS
- SELECT id, guid, title, author, content, link, date, `lastSeen`, hash, is_read, is_favorite, id_feed, tags
+ SELECT id, guid, title, author, content, link, date, `lastSeen`, hash, is_read, is_favorite, id_feed, tags, attributes
FROM `_entrytmp`
ORDER BY date, id;
INSERT OR IGNORE INTO `_entry`
- (id, guid, title, author, content, link, date, `lastSeen`, hash, is_read, is_favorite, id_feed, tags)
+ (id, guid, title, author, content, link, date, `lastSeen`, hash, is_read, is_favorite, id_feed, tags, attributes)
SELECT rowid + (SELECT MAX(id) - COUNT(*) FROM `tmp`) AS id,
- guid, title, author, content, link, date, `lastSeen`, hash, is_read, is_favorite, id_feed, tags
+ guid, title, author, content, link, date, `lastSeen`, hash, is_read, is_favorite, id_feed, tags, attributes
FROM `tmp`
ORDER BY date, id;
DELETE FROM `_entrytmp` WHERE id <= (SELECT MAX(id) FROM `tmp`);