aboutsummaryrefslogtreecommitdiff
path: root/app/Models
diff options
context:
space:
mode:
authorGravatar Alexandre Alapetite <alexandre@alapetite.fr> 2024-01-03 11:23:06 +0100
committerGravatar GitHub <noreply@github.com> 2024-01-03 11:23:06 +0100
commit70e71b8364c1317af04f92fd86df4541fa269e0c (patch)
tree193f4db62a85a218e227ee79ce7554a32eade390 /app/Models
parent1e5f5078ed029640f69bdcc5ba51dd4dc69574ca (diff)
Auto-label (#5954)
Add labels automatically to incoming articles fix https://github.com/FreshRSS/FreshRSS/issues/2380 fix https://github.com/FreshRSS/FreshRSS/issues/2420 fix https://github.com/FreshRSS/FreshRSS/issues/3279 fix https://github.com/FreshRSS/FreshRSS/discussions/4947 fix https://github.com/FreshRSS/FreshRSS/issues/5728 fix https://github.com/FreshRSS/FreshRSS/issues/5599
Diffstat (limited to 'app/Models')
-rw-r--r--app/Models/Entry.php13
-rw-r--r--app/Models/EntryDAO.php19
-rw-r--r--app/Models/FilterActionsTrait.php11
-rw-r--r--app/Models/Tag.php2
-rw-r--r--app/Models/TagDAO.php29
-rw-r--r--app/Models/View.php1
6 files changed, 61 insertions, 14 deletions
diff --git a/app/Models/Entry.php b/app/Models/Entry.php
index 62ba91db3..16f01483a 100644
--- a/app/Models/Entry.php
+++ b/app/Models/Entry.php
@@ -50,7 +50,7 @@ class FreshRSS_Entry extends Minz_Model {
}
/** @param array{'id'?:string,'id_feed'?:int,'guid'?:string,'title'?:string,'author'?:string,'content'?:string,'link'?:string,'date'?:int|string,'lastSeen'?:int,
- * 'hash'?:string,'is_read'?:bool|int,'is_favorite'?:bool|int,'tags'?:string|array<string>,'attributes'?:string,'thumbnail'?:string,'timestamp'?:string} $dao */
+ * 'hash'?:string,'is_read'?:bool|int,'is_favorite'?:bool|int,'tags'?:string|array<string>,'attributes'?:?string,'thumbnail'?:string,'timestamp'?:string} $dao */
public static function fromArray(array $dao): FreshRSS_Entry {
FreshRSS_DatabaseDAO::pdoInt($dao, ['id_feed', 'date', 'lastSeen', 'is_read', 'is_favorite']);
@@ -98,6 +98,17 @@ class FreshRSS_Entry extends Minz_Model {
return $entry;
}
+ /**
+ * @param Traversable<array{'id'?:string,'id_feed'?:int,'guid'?:string,'title'?:string,'author'?:string,'content'?:string,'link'?:string,'date'?:int|string,'lastSeen'?:int,
+ * 'hash'?:string,'is_read'?:bool|int,'is_favorite'?:bool|int,'tags'?:string|array<string>,'attributes'?:?string,'thumbnail'?:string,'timestamp'?:string}> $daos
+ * @return Traversable<FreshRSS_Entry>
+ */
+ public static function fromTraversable(Traversable $daos): Traversable {
+ foreach ($daos as $dao) {
+ yield FreshRSS_Entry::fromArray($dao);
+ }
+ }
+
public function id(): string {
return $this->id;
}
diff --git a/app/Models/EntryDAO.php b/app/Models/EntryDAO.php
index 232db8521..b809c7475 100644
--- a/app/Models/EntryDAO.php
+++ b/app/Models/EntryDAO.php
@@ -95,7 +95,7 @@ SQL;
private $addEntryPrepared = false;
/** @param array{'id':string,'guid':string,'title':string,'author':string,'content':string,'link':string,'date':int,'lastSeen':int,'hash':string,
- * 'is_read':bool|int|null,'is_favorite':bool|int|null,'id_feed':int,'tags':string,'attributes':array<string,mixed>} $valuesTmp */
+ * 'is_read':bool|int|null,'is_favorite':bool|int|null,'id_feed':int,'tags':string,'attributes'?:null|string|array<string,mixed>} $valuesTmp */
public function addEntry(array $valuesTmp, bool $useTmpTable = true): bool {
if ($this->addEntryPrepared == null) {
$sql = static::sqlIgnoreConflict(
@@ -693,19 +693,22 @@ SQL;
}
/** @return Traversable<array{'id':string,'guid':string,'title':string,'author':string,'content':string,'link':string,'date':int,'lastSeen':int,
- * 'hash':string,'is_read':?bool,'is_favorite':?bool,'id_feed':int,'tags':string,'attributes':array<string,mixed>}> */
- public function selectAll(): Traversable {
+ * 'hash':string,'is_read':bool,'is_favorite':bool,'id_feed':int,'tags':string,'attributes':?string}> */
+ public function selectAll(?int $limit = null): Traversable {
$content = static::isCompressed() ? 'UNCOMPRESS(content_bin) AS content' : 'content';
$hash = static::sqlHexEncode('hash');
$sql = <<<SQL
SELECT id, guid, title, author, {$content}, link, date, `lastSeen`, {$hash} AS hash, is_read, is_favorite, id_feed, tags, attributes
FROM `_entry`
SQL;
+ if (is_int($limit) && $limit >= 0) {
+ $sql .= ' ORDER BY id DESC LIMIT ' . $limit;
+ }
$stm = $this->pdo->query($sql);
if ($stm != false) {
while ($row = $stm->fetch(PDO::FETCH_ASSOC)) {
/** @var array{'id':string,'guid':string,'title':string,'author':string,'content':string,'link':string,'date':int,'lastSeen':int,
- * 'hash':string,'is_read':?bool,'is_favorite':?bool,'id_feed':int,'tags':string,'attributes':array<string,mixed>} $row */
+ * 'hash':string,'is_read':bool,'is_favorite':bool,'id_feed':int,'tags':string,'attributes':?string} $row */
yield $row;
}
} else {
@@ -727,7 +730,7 @@ FROM `_entry` WHERE id_feed=:id_feed AND guid=:guid
SQL;
$res = $this->fetchAssoc($sql, [':id_feed' => $id_feed, ':guid' => $guid]);
/** @var array<array{'id':string,'id_feed':int,'guid':string,'title':string,'author':string,'content':string,'link':string,'date':int,
- * 'is_read':int,'is_favorite':int,'tags':string,'attributes'?:string}> $res */
+ * 'is_read':int,'is_favorite':int,'tags':string,'attributes':?string}> $res */
return isset($res[0]) ? FreshRSS_Entry::fromArray($res[0]) : null;
}
@@ -740,7 +743,7 @@ FROM `_entry` WHERE id=:id
SQL;
$res = $this->fetchAssoc($sql, [':id' => $id]);
/** @var array<array{'id':string,'id_feed':int,'guid':string,'title':string,'author':string,'content':string,'link':string,'date':int,
- * 'is_read':int,'is_favorite':int,'tags':string,'attributes'?:string}> $res */
+ * 'is_read':int,'is_favorite':int,'tags':string,'attributes':?string}> $res */
return isset($res[0]) ? FreshRSS_Entry::fromArray($res[0]) : null;
}
@@ -1167,7 +1170,7 @@ SQL;
while ($row = $stm->fetch(PDO::FETCH_ASSOC)) {
if (is_array($row)) {
/** @var array{'id':string,'id_feed':int,'guid':string,'title':string,'author':string,'content':string,'link':string,'date':int,
- * 'hash':string,'is_read':int,'is_favorite':int,'tags':string,'attributes'?:string} $row */
+ * 'hash':string,'is_read':int,'is_favorite':int,'tags':string,'attributes'?:?string} $row */
yield FreshRSS_Entry::fromArray($row);
}
}
@@ -1212,7 +1215,7 @@ SQL;
while ($row = $stm->fetch(PDO::FETCH_ASSOC)) {
if (is_array($row)) {
/** @var array{'id':string,'id_feed':int,'guid':string,'title':string,'author':string,'content':string,'link':string,'date':int,
- * 'hash':string,'is_read':int,'is_favorite':int,'tags':string,'attributes'?:string} $row */
+ * 'hash':string,'is_read':int,'is_favorite':int,'tags':string,'attributes':?string} $row */
yield FreshRSS_Entry::fromArray($row);
}
}
diff --git a/app/Models/FilterActionsTrait.php b/app/Models/FilterActionsTrait.php
index aefb549af..a19c0a361 100644
--- a/app/Models/FilterActionsTrait.php
+++ b/app/Models/FilterActionsTrait.php
@@ -50,8 +50,7 @@ trait FreshRSS_FilterActionsTrait {
$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)) {
+ if (in_array($action, $filterAction->actions(), true)) {
$filters[] = $filterAction->booleanSearch();
}
}
@@ -120,7 +119,11 @@ trait FreshRSS_FilterActionsTrait {
$this->_filterActions($filterActions);
}
- public function applyFilterActions(FreshRSS_Entry $entry): void {
+ /**
+ * @param bool $applyLabel Parameter by reference, which will be set to true if the callers needs to apply a label to the article entry.
+ */
+ public function applyFilterActions(FreshRSS_Entry $entry, ?bool &$applyLabel = null): void {
+ $applyLabel = false;
foreach ($this->filterActions() as $filterAction) {
if ($entry->matches($filterAction->booleanSearch())) {
foreach ($filterAction->actions() as $action) {
@@ -135,7 +138,7 @@ trait FreshRSS_FilterActionsTrait {
$entry->_isFavorite(true);
break;
case 'label':
- //TODO: Implement more actions
+ $applyLabel = true;
break;
}
}
diff --git a/app/Models/Tag.php b/app/Models/Tag.php
index cedc4647d..0ed7c462c 100644
--- a/app/Models/Tag.php
+++ b/app/Models/Tag.php
@@ -2,7 +2,7 @@
declare(strict_types=1);
class FreshRSS_Tag extends Minz_Model {
- use FreshRSS_AttributesTrait;
+ use FreshRSS_AttributesTrait, FreshRSS_FilterActionsTrait;
private int $id = 0;
private string $name;
diff --git a/app/Models/TagDAO.php b/app/Models/TagDAO.php
index fba27dc7b..391bde36d 100644
--- a/app/Models/TagDAO.php
+++ b/app/Models/TagDAO.php
@@ -301,6 +301,35 @@ SQL;
}
/**
+ * @param array<array{id_tag:int,id_entry:string}> $addLabels Labels to insert as batch
+ * @return int|false Number of new entries or false in case of error
+ */
+ public function tagEntries(array $addLabels) {
+ $hasValues = false;
+ $sql = 'INSERT ' . $this->sqlIgnore() . ' INTO `_entrytag`(id_tag, id_entry) VALUES ';
+ foreach ($addLabels as $addLabel) {
+ $id_tag = (int)($addLabel['id_tag'] ?? 0);
+ $id_entry = $addLabel['id_entry'] ?? '';
+ if ($id_tag > 0 && ctype_digit($id_entry)) {
+ $sql .= "({$id_tag},{$id_entry}),";
+ $hasValues = true;
+ }
+ }
+ $sql = rtrim($sql, ',');
+ if (!$hasValues) {
+ return false;
+ }
+
+ $affected = $this->pdo->exec($sql);
+ if ($affected !== false) {
+ return $affected;
+ }
+ $info = $this->pdo->errorInfo();
+ Minz_Log::error('SQL error ' . __METHOD__ . json_encode($info));
+ return false;
+ }
+
+ /**
* @return array<int,array{'id':int,'name':string,'id_entry':string,'checked':bool}>|false
*/
public function getTagsForEntry(string $id_entry) {
diff --git a/app/Models/View.php b/app/Models/View.php
index 3c188dd99..4dd0be36a 100644
--- a/app/Models/View.php
+++ b/app/Models/View.php
@@ -13,6 +13,7 @@ class FreshRSS_View extends Minz_View {
/** @var array<FreshRSS_Category> */
public array $categories;
public ?FreshRSS_Category $category;
+ public ?FreshRSS_Tag $tag;
public string $current_user;
/** @var iterable<FreshRSS_Entry> */
public $entries;