mirror of
https://github.com/FreshRSS/FreshRSS.git
synced 2025-12-23 21:47:44 -05:00
Last user modified (#7886)
* feat: Add user modified functionality Closes https://github.com/FreshRSS/FreshRSS/issues/7862 Changes proposed in this pull request: This is an implementation of the proposed feature. It allows entries to have a new field that will be updated whenever an item is marked as read/unread or bookmark/removed from bookmarks. And a new sort criteria to sort by it. How to test the feature manually: 1. Mark items from a feed as read/unread 2. Mark items from a feed as bookmark / remove bookmark 3. Sort by the new criteria * feat: Add sort functionality * feat: Add sort nav button * fix: Use correct migrations * fix: Add internationalization * fix: Linter errors * chore: PR comments * Update app/i18n/fr/index.php Co-authored-by: Alexandre Alapetite <alexandre@alapetite.fr> * Update app/i18n/pl/index.php Co-authored-by: Inverle <inverle@proton.me> * Update app/i18n/nl/index.php Co-authored-by: Frans de Jonge <fransdejonge@gmail.com> * make fix-all * Fixes * More fixes sort * Fix wrong index * Fix unneeded column * Fix auto-create indexes * Some copilot suggestions * One more fix Co-authored-by: Alexandre Alapetite <alexandre@alapetite.fr> --------- Co-authored-by: Alexandre Alapetite <alexandre@alapetite.fr> Co-authored-by: Inverle <inverle@proton.me> Co-authored-by: Frans de Jonge <fransdejonge@gmail.com>
This commit is contained in:
committed by
GitHub
parent
ec1f5ee61b
commit
673067a52d
@@ -234,11 +234,11 @@ Voir le [dépôt dédié à ces extensions](https://github.com/FreshRSS/Extensio
|
||||
| English (United States) (en-US) | ■■■■■■■■■■ 100% | [contribuer](https://github.com/search?q=repo%3AFreshRSS%2FFreshRSS+path%3Aapp%2Fi18n%2Fen-US+%2F%28TODO%7CDIRTY%29%24%2F) |
|
||||
| Español (es) | ■■■■■■■■■・ 91% | [contribuer](https://github.com/search?q=repo%3AFreshRSS%2FFreshRSS+path%3Aapp%2Fi18n%2Fes+%2F%28TODO%7CDIRTY%29%24%2F) |
|
||||
| فارسی (fa) | ■■■■■■■■■・ 97% | [contribuer](https://github.com/search?q=repo%3AFreshRSS%2FFreshRSS+path%3Aapp%2Fi18n%2Ffa+%2F%28TODO%7CDIRTY%29%24%2F) |
|
||||
| Suomi (fi) | ■■■■■■■■■■ 100% | [contribuer](https://github.com/search?q=repo%3AFreshRSS%2FFreshRSS+path%3Aapp%2Fi18n%2Ffi+%2F%28TODO%7CDIRTY%29%24%2F) |
|
||||
| Suomi (fi) | ■■■■■■■■■・ 99% | [contribuer](https://github.com/search?q=repo%3AFreshRSS%2FFreshRSS+path%3Aapp%2Fi18n%2Ffi+%2F%28TODO%7CDIRTY%29%24%2F) |
|
||||
| Français (fr) | ■■■■■■■■■■ 100% | [contribuer](https://github.com/search?q=repo%3AFreshRSS%2FFreshRSS+path%3Aapp%2Fi18n%2Ffr+%2F%28TODO%7CDIRTY%29%24%2F) |
|
||||
| עברית (he) | ■■■■・・・・・・ 45% | [contribuer](https://github.com/search?q=repo%3AFreshRSS%2FFreshRSS+path%3Aapp%2Fi18n%2Fhe+%2F%28TODO%7CDIRTY%29%24%2F) |
|
||||
| Magyar (hu) | ■■■■■■■■■・ 99% | [contribuer](https://github.com/search?q=repo%3AFreshRSS%2FFreshRSS+path%3Aapp%2Fi18n%2Fhu+%2F%28TODO%7CDIRTY%29%24%2F) |
|
||||
| Bahasa Indonesia (id) | ■■■■■■■■■・ 97% | [contribuer](https://github.com/search?q=repo%3AFreshRSS%2FFreshRSS+path%3Aapp%2Fi18n%2Fid+%2F%28TODO%7CDIRTY%29%24%2F) |
|
||||
| Bahasa Indonesia (id) | ■■■■■■■■■・ 96% | [contribuer](https://github.com/search?q=repo%3AFreshRSS%2FFreshRSS+path%3Aapp%2Fi18n%2Fid+%2F%28TODO%7CDIRTY%29%24%2F) |
|
||||
| Italiano (it) | ■■■■■■■■■・ 96% | [contribuer](https://github.com/search?q=repo%3AFreshRSS%2FFreshRSS+path%3Aapp%2Fi18n%2Fit+%2F%28TODO%7CDIRTY%29%24%2F) |
|
||||
| 日本語 (ja) | ■■■■■■■■■・ 95% | [contribuer](https://github.com/search?q=repo%3AFreshRSS%2FFreshRSS+path%3Aapp%2Fi18n%2Fja+%2F%28TODO%7CDIRTY%29%24%2F) |
|
||||
| 한국어 (ko) | ■■■■■■■■・・ 88% | [contribuer](https://github.com/search?q=repo%3AFreshRSS%2FFreshRSS+path%3Aapp%2Fi18n%2Fko+%2F%28TODO%7CDIRTY%29%24%2F) |
|
||||
@@ -247,7 +247,7 @@ Voir le [dépôt dédié à ces extensions](https://github.com/FreshRSS/Extensio
|
||||
| Occitan (oc) | ■■■■■■■■・・ 81% | [contribuer](https://github.com/search?q=repo%3AFreshRSS%2FFreshRSS+path%3Aapp%2Fi18n%2Foc+%2F%28TODO%7CDIRTY%29%24%2F) |
|
||||
| Polski (pl) | ■■■■■■■■■■ 100% | [contribuer](https://github.com/search?q=repo%3AFreshRSS%2FFreshRSS+path%3Aapp%2Fi18n%2Fpl+%2F%28TODO%7CDIRTY%29%24%2F) |
|
||||
| Português (Brasil) (pt-BR) | ■■■■■■■■・・ 88% | [contribuer](https://github.com/search?q=repo%3AFreshRSS%2FFreshRSS+path%3Aapp%2Fi18n%2Fpt-BR+%2F%28TODO%7CDIRTY%29%24%2F) |
|
||||
| Português (Portugal) (pt-PT) | ■■■■■■■■・・ 88% | [contribuer](https://github.com/search?q=repo%3AFreshRSS%2FFreshRSS+path%3Aapp%2Fi18n%2Fpt-PT+%2F%28TODO%7CDIRTY%29%24%2F) |
|
||||
| Português (Portugal) (pt-PT) | ■■■■■■■■・・ 87% | [contribuer](https://github.com/search?q=repo%3AFreshRSS%2FFreshRSS+path%3Aapp%2Fi18n%2Fpt-PT+%2F%28TODO%7CDIRTY%29%24%2F) |
|
||||
| Русский (ru) | ■■■■■■■■・・ 88% | [contribuer](https://github.com/search?q=repo%3AFreshRSS%2FFreshRSS+path%3Aapp%2Fi18n%2Fru+%2F%28TODO%7CDIRTY%29%24%2F) |
|
||||
| Slovenčina (sk) | ■■■■■■■■・・ 88% | [contribuer](https://github.com/search?q=repo%3AFreshRSS%2FFreshRSS+path%3Aapp%2Fi18n%2Fsk+%2F%28TODO%7CDIRTY%29%24%2F) |
|
||||
| Türkçe (tr) | ■■■■■■■■■・ 96% | [contribuer](https://github.com/search?q=repo%3AFreshRSS%2FFreshRSS+path%3Aapp%2Fi18n%2Ftr+%2F%28TODO%7CDIRTY%29%24%2F) |
|
||||
|
||||
@@ -132,11 +132,11 @@ See the [repository dedicated to those extensions](https://github.com/FreshRSS/E
|
||||
| English (United States) (en-US) | ■■■■■■■■■■ 100% | [contribute](https://github.com/search?q=repo%3AFreshRSS%2FFreshRSS+path%3Aapp%2Fi18n%2Fen-US+%2F%28TODO%7CDIRTY%29%24%2F) |
|
||||
| Español (es) | ■■■■■■■■■・ 91% | [contribute](https://github.com/search?q=repo%3AFreshRSS%2FFreshRSS+path%3Aapp%2Fi18n%2Fes+%2F%28TODO%7CDIRTY%29%24%2F) |
|
||||
| فارسی (fa) | ■■■■■■■■■・ 97% | [contribute](https://github.com/search?q=repo%3AFreshRSS%2FFreshRSS+path%3Aapp%2Fi18n%2Ffa+%2F%28TODO%7CDIRTY%29%24%2F) |
|
||||
| Suomi (fi) | ■■■■■■■■■■ 100% | [contribute](https://github.com/search?q=repo%3AFreshRSS%2FFreshRSS+path%3Aapp%2Fi18n%2Ffi+%2F%28TODO%7CDIRTY%29%24%2F) |
|
||||
| Suomi (fi) | ■■■■■■■■■・ 99% | [contribute](https://github.com/search?q=repo%3AFreshRSS%2FFreshRSS+path%3Aapp%2Fi18n%2Ffi+%2F%28TODO%7CDIRTY%29%24%2F) |
|
||||
| Français (fr) | ■■■■■■■■■■ 100% | [contribute](https://github.com/search?q=repo%3AFreshRSS%2FFreshRSS+path%3Aapp%2Fi18n%2Ffr+%2F%28TODO%7CDIRTY%29%24%2F) |
|
||||
| עברית (he) | ■■■■・・・・・・ 45% | [contribute](https://github.com/search?q=repo%3AFreshRSS%2FFreshRSS+path%3Aapp%2Fi18n%2Fhe+%2F%28TODO%7CDIRTY%29%24%2F) |
|
||||
| Magyar (hu) | ■■■■■■■■■・ 99% | [contribute](https://github.com/search?q=repo%3AFreshRSS%2FFreshRSS+path%3Aapp%2Fi18n%2Fhu+%2F%28TODO%7CDIRTY%29%24%2F) |
|
||||
| Bahasa Indonesia (id) | ■■■■■■■■■・ 97% | [contribute](https://github.com/search?q=repo%3AFreshRSS%2FFreshRSS+path%3Aapp%2Fi18n%2Fid+%2F%28TODO%7CDIRTY%29%24%2F) |
|
||||
| Bahasa Indonesia (id) | ■■■■■■■■■・ 96% | [contribute](https://github.com/search?q=repo%3AFreshRSS%2FFreshRSS+path%3Aapp%2Fi18n%2Fid+%2F%28TODO%7CDIRTY%29%24%2F) |
|
||||
| Italiano (it) | ■■■■■■■■■・ 96% | [contribute](https://github.com/search?q=repo%3AFreshRSS%2FFreshRSS+path%3Aapp%2Fi18n%2Fit+%2F%28TODO%7CDIRTY%29%24%2F) |
|
||||
| 日本語 (ja) | ■■■■■■■■■・ 95% | [contribute](https://github.com/search?q=repo%3AFreshRSS%2FFreshRSS+path%3Aapp%2Fi18n%2Fja+%2F%28TODO%7CDIRTY%29%24%2F) |
|
||||
| 한국어 (ko) | ■■■■■■■■・・ 88% | [contribute](https://github.com/search?q=repo%3AFreshRSS%2FFreshRSS+path%3Aapp%2Fi18n%2Fko+%2F%28TODO%7CDIRTY%29%24%2F) |
|
||||
@@ -145,7 +145,7 @@ See the [repository dedicated to those extensions](https://github.com/FreshRSS/E
|
||||
| Occitan (oc) | ■■■■■■■■・・ 81% | [contribute](https://github.com/search?q=repo%3AFreshRSS%2FFreshRSS+path%3Aapp%2Fi18n%2Foc+%2F%28TODO%7CDIRTY%29%24%2F) |
|
||||
| Polski (pl) | ■■■■■■■■■■ 100% | [contribute](https://github.com/search?q=repo%3AFreshRSS%2FFreshRSS+path%3Aapp%2Fi18n%2Fpl+%2F%28TODO%7CDIRTY%29%24%2F) |
|
||||
| Português (Brasil) (pt-BR) | ■■■■■■■■・・ 88% | [contribute](https://github.com/search?q=repo%3AFreshRSS%2FFreshRSS+path%3Aapp%2Fi18n%2Fpt-BR+%2F%28TODO%7CDIRTY%29%24%2F) |
|
||||
| Português (Portugal) (pt-PT) | ■■■■■■■■・・ 88% | [contribute](https://github.com/search?q=repo%3AFreshRSS%2FFreshRSS+path%3Aapp%2Fi18n%2Fpt-PT+%2F%28TODO%7CDIRTY%29%24%2F) |
|
||||
| Português (Portugal) (pt-PT) | ■■■■■■■■・・ 87% | [contribute](https://github.com/search?q=repo%3AFreshRSS%2FFreshRSS+path%3Aapp%2Fi18n%2Fpt-PT+%2F%28TODO%7CDIRTY%29%24%2F) |
|
||||
| Русский (ru) | ■■■■■■■■・・ 88% | [contribute](https://github.com/search?q=repo%3AFreshRSS%2FFreshRSS+path%3Aapp%2Fi18n%2Fru+%2F%28TODO%7CDIRTY%29%24%2F) |
|
||||
| Slovenčina (sk) | ■■■■■■■■・・ 88% | [contribute](https://github.com/search?q=repo%3AFreshRSS%2FFreshRSS+path%3Aapp%2Fi18n%2Fsk+%2F%28TODO%7CDIRTY%29%24%2F) |
|
||||
| Türkçe (tr) | ■■■■■■■■■・ 96% | [contribute](https://github.com/search?q=repo%3AFreshRSS%2FFreshRSS+path%3Aapp%2Fi18n%2Ftr+%2F%28TODO%7CDIRTY%29%24%2F) |
|
||||
|
||||
@@ -285,7 +285,7 @@ class FreshRSS_index_Controller extends FreshRSS_ActionController {
|
||||
|
||||
$continuation_values = [];
|
||||
if (FreshRSS_Context::$continuation_id !== '0') {
|
||||
if (in_array(FreshRSS_Context::$sort, ['c.name', 'date', 'f.name', 'link', 'title'], true)) {
|
||||
if (in_array(FreshRSS_Context::$sort, ['c.name', 'date', 'f.name', 'link', 'title', 'lastUserModified'], true)) {
|
||||
$pagingEntry = $entryDAO->searchById(FreshRSS_Context::$continuation_id);
|
||||
|
||||
if ($pagingEntry !== null && in_array(FreshRSS_Context::$sort, ['c.name', 'f.name'], true)) {
|
||||
@@ -302,6 +302,7 @@ class FreshRSS_index_Controller extends FreshRSS_ActionController {
|
||||
'f.name' => $pagingEntry->feed()?->name() ?? '',
|
||||
'link' => $pagingEntry->link(true),
|
||||
'title' => $pagingEntry->title(),
|
||||
'lastUserModified' => $pagingEntry->lastUserModified(),
|
||||
};
|
||||
if ($pagingEntry !== null && FreshRSS_Context::$sort === 'c.name') {
|
||||
// Secondary sort criterion
|
||||
|
||||
@@ -42,7 +42,7 @@ final class FreshRSS_Context {
|
||||
public static int $state = 0;
|
||||
/** @var 'ASC'|'DESC' */
|
||||
public static string $order = 'DESC';
|
||||
/** @var 'id'|'c.name'|'date'|'f.name'|'link'|'title'|'rand' */
|
||||
/** @var 'id'|'c.name'|'date'|'f.name'|'link'|'title'|'rand'|'lastUserModified' */
|
||||
public static string $sort = 'id';
|
||||
public static int $number = 0;
|
||||
public static int $offset = 0;
|
||||
@@ -252,7 +252,7 @@ final class FreshRSS_Context {
|
||||
$order = Minz_Request::paramString('order', true) ?: FreshRSS_Context::userConf()->sort_order;
|
||||
self::$order = in_array($order, ['ASC', 'DESC'], true) ? $order : 'DESC';
|
||||
$sort = Minz_Request::paramString('sort', true) ?: FreshRSS_Context::userConf()->sort;
|
||||
self::$sort = in_array($sort, ['id', 'c.name', 'date', 'f.name', 'link', 'title', 'rand'], true) ? $sort : 'id';
|
||||
self::$sort = in_array($sort, ['id', 'c.name', 'date', 'f.name', 'link', 'title', 'rand', 'lastUserModified'], true) ? $sort : 'id';
|
||||
self::$number = Minz_Request::paramInt('nb') ?: FreshRSS_Context::userConf()->posts_per_page;
|
||||
if (self::$number > FreshRSS_Context::userConf()->max_posts_per_rss) {
|
||||
self::$number = max(
|
||||
|
||||
@@ -24,6 +24,7 @@ class FreshRSS_Entry extends Minz_Model {
|
||||
private string $link;
|
||||
private int $date;
|
||||
private int $lastSeen = 0;
|
||||
private int $lastUserModified = 0;
|
||||
/** In microseconds */
|
||||
private string $date_added = '0';
|
||||
private string $hash = '';
|
||||
@@ -53,7 +54,8 @@ class FreshRSS_Entry extends Minz_Model {
|
||||
$this->_guid($guid);
|
||||
}
|
||||
|
||||
/** @param array{id?:string,id_feed?:int,guid?:string,title?:string,author?:string,content?:string,link?:string,date?:int|string,lastSeen?:int,
|
||||
/** @param array{id?:string,id_feed?:int,guid?:string,title?:string,author?:string,content?:string,link?:string,
|
||||
* date?:int|string,lastSeen?:int,lastUserModified?:int,
|
||||
* 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 {
|
||||
if (empty($dao['content']) || !is_string($dao['content'])) {
|
||||
@@ -97,6 +99,9 @@ class FreshRSS_Entry extends Minz_Model {
|
||||
if (isset($dao['lastSeen'])) {
|
||||
$entry->_lastSeen($dao['lastSeen']);
|
||||
}
|
||||
if (isset($dao['lastUserModified'])) {
|
||||
$entry->_lastUserModified($dao['lastUserModified']);
|
||||
}
|
||||
if (!empty($dao['attributes'])) {
|
||||
$entry->_attributes($dao['attributes']);
|
||||
}
|
||||
@@ -107,8 +112,11 @@ class FreshRSS_Entry extends Minz_Model {
|
||||
}
|
||||
|
||||
/**
|
||||
* @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
|
||||
* @param Traversable<array{id?:string,id_feed?:int,guid?:string,
|
||||
* title?:string,author?:string,content?:string,link?:string,
|
||||
* date?:int|string,lastSeen?:int,lastUserModified?: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 {
|
||||
@@ -421,6 +429,10 @@ HTML;
|
||||
return $this->lastSeen;
|
||||
}
|
||||
|
||||
public function lastUserModified(): int {
|
||||
return $this->lastUserModified;
|
||||
}
|
||||
|
||||
/**
|
||||
* @phpstan-return ($raw is false ? string : ($microsecond is true ? string : int))
|
||||
*/
|
||||
@@ -556,6 +568,11 @@ HTML;
|
||||
$this->lastSeen = $value > 0 ? $value : 0;
|
||||
}
|
||||
|
||||
public function _lastUserModified(int|string $value): void {
|
||||
$value = (int)$value;
|
||||
$this->lastUserModified = $value > 0 ? $value : 0;
|
||||
}
|
||||
|
||||
/** @param int|numeric-string $value */
|
||||
public function _dateAdded(int|string $value, bool $microsecond = false): void {
|
||||
if ($microsecond) {
|
||||
@@ -1046,8 +1063,9 @@ HTML;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return 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>}
|
||||
* @return array{id:string,guid:string,title:string,author:string,content:string,link:string,date:int,
|
||||
* lastSeen:int,lastUserModified:int,
|
||||
* hash:string,is_read:?bool,is_favorite:?bool,id_feed:int,tags:string,attributes:array<string,mixed>}
|
||||
*/
|
||||
public function toArray(): array {
|
||||
return [
|
||||
@@ -1059,6 +1077,7 @@ HTML;
|
||||
'link' => $this->link(raw: true),
|
||||
'date' => $this->date(true),
|
||||
'lastSeen' => $this->lastSeen(),
|
||||
'lastUserModified' => $this->lastUserModified(),
|
||||
'hash' => $this->hash(),
|
||||
'is_read' => $this->isRead(),
|
||||
'is_favorite' => $this->isFavorite(),
|
||||
|
||||
@@ -119,6 +119,7 @@ SQL;
|
||||
$this->pdo->commit();
|
||||
}
|
||||
Minz_Log::warning(__METHOD__ . ': ' . $name);
|
||||
require APP_PATH . '/SQL/install.sql.' . $this->pdo->dbType() . '.php';
|
||||
try {
|
||||
if ($name === 'attributes') { //v1.20.0
|
||||
$sql = <<<'SQL'
|
||||
@@ -127,6 +128,13 @@ ALTER TABLE `_entrytmp` ADD COLUMN attributes TEXT;
|
||||
SQL;
|
||||
return $this->pdo->exec($sql) !== false;
|
||||
}
|
||||
if ($name === 'lastUserModified') { //v1.28.0
|
||||
$sql = $GLOBALS['ALTER_TABLE_ENTRY_LAST_USER_MODIFIED'];
|
||||
if (!is_string($sql)) {
|
||||
throw new Exception('ALTER_TABLE_ENTRY_LAST_USER_MODIFIED is not a string!');
|
||||
}
|
||||
return $this->pdo->exec($sql) !== false;
|
||||
}
|
||||
} catch (Exception $e) {
|
||||
Minz_Log::error(__METHOD__ . ' error: ' . $e->getMessage());
|
||||
}
|
||||
@@ -261,8 +269,11 @@ SQL;
|
||||
|
||||
private PDOStatement|null|false $updateEntryPrepared = null;
|
||||
|
||||
/** @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 */
|
||||
/**
|
||||
* @param array{id:string,guid:string,title:string,author:string,content:string,link:string,
|
||||
* date:int,lastSeen:int,lastUserModified?:int,hash:string,
|
||||
* is_read:bool|int|null,is_favorite:bool|int|null,id_feed:int,tags:string,attributes:array<string,mixed>} $valuesTmp
|
||||
*/
|
||||
public function updateEntry(array $valuesTmp): bool {
|
||||
if (!isset($valuesTmp['is_read'])) {
|
||||
$valuesTmp['is_read'] = null;
|
||||
@@ -270,12 +281,16 @@ SQL;
|
||||
if (!isset($valuesTmp['is_favorite'])) {
|
||||
$valuesTmp['is_favorite'] = null;
|
||||
}
|
||||
if (empty($valuesTmp['lastUserModified'])) {
|
||||
$valuesTmp['lastUserModified'] = 0;
|
||||
}
|
||||
|
||||
if ($this->updateEntryPrepared == null) {
|
||||
$sql = 'UPDATE `_entry` '
|
||||
. 'SET title=:title, author=:author, '
|
||||
. (static::isCompressed() ? 'content_bin=COMPRESS(:content)' : 'content=:content')
|
||||
. ', link=:link, date=:date, `lastSeen`=:last_seen'
|
||||
. ', `lastUserModified`=MAX(:last_user_modified, `lastUserModified`)'
|
||||
. ', hash=' . static::sqlHexDecode(':hash')
|
||||
. ', is_read=COALESCE(:is_read, is_read)'
|
||||
. ', is_favorite=COALESCE(:is_favorite, is_favorite)'
|
||||
@@ -300,6 +315,7 @@ SQL;
|
||||
$this->updateEntryPrepared->bindParam(':link', $valuesTmp['link']);
|
||||
$this->updateEntryPrepared->bindParam(':date', $valuesTmp['date'], PDO::PARAM_INT);
|
||||
$this->updateEntryPrepared->bindParam(':last_seen', $valuesTmp['lastSeen'], PDO::PARAM_INT);
|
||||
$this->updateEntryPrepared->bindParam(':last_user_modified', $valuesTmp['lastUserModified'], PDO::PARAM_INT);
|
||||
if ($valuesTmp['is_read'] === null) {
|
||||
$this->updateEntryPrepared->bindValue(':is_read', null, PDO::PARAM_NULL);
|
||||
} else {
|
||||
@@ -332,7 +348,8 @@ SQL;
|
||||
return true;
|
||||
} else {
|
||||
$info = $this->updateEntryPrepared == false ? $this->pdo->errorInfo() : $this->updateEntryPrepared->errorInfo();
|
||||
/** @var array{id:string,guid:string,title:string,author:string,content:string,link:string,date:int,lastSeen:int,hash:string,
|
||||
/** @var array{id:string,guid:string,title:string,author:string,content:string,link:string,
|
||||
* date:int,lastSeen:int,lastUserModified:int,hash:string,
|
||||
* is_read:bool|int|null,is_favorite:bool|int|null,id_feed:int,tags:string,attributes:array<string,mixed>} $valuesTmp */
|
||||
/** @var array{0:string,1:int,2:string} $info */
|
||||
if ($this->autoUpdateDb($info)) {
|
||||
@@ -381,9 +398,10 @@ SQL;
|
||||
return $affected;
|
||||
}
|
||||
$sql = 'UPDATE `_entry` '
|
||||
. 'SET is_favorite=? '
|
||||
. 'SET is_favorite=?, `lastUserModified`=? '
|
||||
. 'WHERE id IN (' . str_repeat('?,', count($ids) - 1) . '?)';
|
||||
$values = [$is_favorite ? 1 : 0];
|
||||
$values[] = time();
|
||||
$values = array_merge($values, $ids);
|
||||
$stm = $this->pdo->prepare($sql);
|
||||
if ($stm !== false && $stm->execute($values)) {
|
||||
@@ -462,9 +480,9 @@ SQL;
|
||||
|
||||
FreshRSS_UserDAO::touch();
|
||||
$sql = 'UPDATE `_entry` '
|
||||
. 'SET is_read=? '
|
||||
. 'WHERE id IN (' . str_repeat('?,', count($ids) - 1) . '?)';
|
||||
$values = [$is_read ? 1 : 0];
|
||||
. 'SET is_read=?, `lastUserModified`=? '
|
||||
. 'WHERE is_read<>? AND id IN (' . str_repeat('?,', count($ids) - 1) . '?)';
|
||||
$values = [$is_read ? 1 : 0, time(), $is_read ? 1 : 0];
|
||||
$values = array_merge($values, $ids);
|
||||
$stm = $this->pdo->prepare($sql);
|
||||
if ($stm === false || !$stm->execute($values)) {
|
||||
@@ -480,10 +498,10 @@ SQL;
|
||||
} else {
|
||||
FreshRSS_UserDAO::touch();
|
||||
$sql = 'UPDATE `_entry` e INNER JOIN `_feed` f ON e.id_feed=f.id '
|
||||
. 'SET e.is_read=?,'
|
||||
. 'SET e.is_read=?,`lastUserModified`=?,'
|
||||
. 'f.`cache_nbUnreads`=f.`cache_nbUnreads`' . ($is_read ? '-' : '+') . '1 '
|
||||
. 'WHERE e.id=? AND e.is_read=?';
|
||||
$values = [$is_read ? 1 : 0, $ids, $is_read ? 0 : 1];
|
||||
$values = [$is_read ? 1 : 0, time(), $ids, $is_read ? 0 : 1];
|
||||
$stm = $this->pdo->prepare($sql);
|
||||
if ($stm !== false && $stm->execute($values)) {
|
||||
return $stm->rowCount();
|
||||
@@ -516,8 +534,8 @@ SQL;
|
||||
Minz_Log::debug('Calling markReadEntries(0) is deprecated!');
|
||||
}
|
||||
|
||||
$sql = 'UPDATE `_entry` SET is_read = ? WHERE is_read <> ? AND id <= ?';
|
||||
$values = [$is_read ? 1 : 0, $is_read ? 1 : 0, $idMax];
|
||||
$sql = 'UPDATE `_entry` SET is_read = ?, `lastUserModified`=? WHERE is_read <> ? AND id <= ?';
|
||||
$values = [$is_read ? 1 : 0, time(), $is_read ? 1 : 0, $idMax];
|
||||
if ($onlyFavorites) {
|
||||
$sql .= ' AND is_favorite=1';
|
||||
}
|
||||
@@ -569,11 +587,11 @@ SQL;
|
||||
|
||||
$sql = <<<'SQL'
|
||||
UPDATE `_entry`
|
||||
SET is_read = ?
|
||||
SET is_read = ?, `lastUserModified` = ?
|
||||
WHERE is_read <> ? AND id <= ?
|
||||
AND id_feed IN (SELECT f.id FROM `_feed` f WHERE f.category=? AND f.priority >= ? AND f.priority < ?)
|
||||
SQL;
|
||||
$values = [$is_read ? 1 : 0, $is_read ? 1 : 0, $idMax, $id, FreshRSS_Feed::PRIORITY_CATEGORY, FreshRSS_Feed::PRIORITY_IMPORTANT];
|
||||
$values = [$is_read ? 1 : 0, time(), $is_read ? 1 : 0, $idMax, $id, FreshRSS_Feed::PRIORITY_CATEGORY, FreshRSS_Feed::PRIORITY_IMPORTANT];
|
||||
|
||||
[$searchValues, $search] = $this->sqlListEntriesWhere(alias: '', state: $state, filters: $filters);
|
||||
|
||||
@@ -613,9 +631,9 @@ SQL;
|
||||
}
|
||||
|
||||
$sql = 'UPDATE `_entry` '
|
||||
. 'SET is_read=? '
|
||||
. 'SET is_read=?, `lastUserModified`=? '
|
||||
. 'WHERE id_feed=? AND is_read <> ? AND id <= ?';
|
||||
$values = [$is_read ? 1 : 0, $id_feed, $is_read ? 1 : 0, $idMax];
|
||||
$values = [$is_read ? 1 : 0, time(), $id_feed, $is_read ? 1 : 0, $idMax];
|
||||
|
||||
[$searchValues, $search] = $this->sqlListEntriesWhere(alias: '', state: $state, filters: $filters);
|
||||
|
||||
@@ -664,11 +682,11 @@ SQL;
|
||||
}
|
||||
|
||||
$sql = 'UPDATE `_entry` e INNER JOIN `_entrytag` et ON et.id_entry = e.id '
|
||||
. 'SET e.is_read = ? '
|
||||
. 'SET e.is_read = ?, `lastUserModified` = ? '
|
||||
. 'WHERE '
|
||||
. ($id == 0 ? '' : 'et.id_tag = ? AND ')
|
||||
. 'e.is_read <> ? AND e.id <= ?';
|
||||
$values = [$is_read ? 1 : 0];
|
||||
$values = [$is_read ? 1 : 0, time()];
|
||||
if ($id != 0) {
|
||||
$values[] = $id;
|
||||
}
|
||||
@@ -756,8 +774,9 @@ SQL;
|
||||
|
||||
/**
|
||||
* @param 'ASC'|'DESC' $order
|
||||
* @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:?string}>
|
||||
* @return Traversable<array{id:string,guid:string,title:string,author:string,content:string,link:string,
|
||||
* date:int,lastSeen:int,lastUserModified:int,
|
||||
* hash:string,is_read:bool,is_favorite:bool,id_feed:int,tags:string,attributes:?string}>
|
||||
*/
|
||||
public function selectAll(string $order = 'ASC', int $limit = -1, int $offset = 0): Traversable {
|
||||
$content = static::isCompressed() ? 'UNCOMPRESS(content_bin) AS content' : 'content';
|
||||
@@ -765,14 +784,14 @@ SQL;
|
||||
$order = in_array($order, ['ASC', 'DESC'], true) ? $order : 'ASC';
|
||||
$sqlLimit = static::sqlLimit($limit, $offset);
|
||||
$sql = <<<SQL
|
||||
SELECT id, guid, title, author, {$content}, link, date, `lastSeen`, {$hash} AS hash, is_read, is_favorite, id_feed, tags, attributes
|
||||
SELECT id, guid, title, author, {$content}, link, date, `lastSeen`, `lastUserModified`, {$hash} AS hash, is_read, is_favorite, id_feed, tags, attributes
|
||||
FROM `_entry`
|
||||
ORDER BY id {$order} {$sqlLimit}
|
||||
SQL;
|
||||
$stm = $this->pdo->query($sql);
|
||||
if ($stm !== false) {
|
||||
while (is_array($row = $stm->fetch(PDO::FETCH_ASSOC))) {
|
||||
/** @var array{id:string,guid:string,title:string,author:string,content:string,link:string,date:int,lastSeen:int,
|
||||
/** @var array{id:string,guid:string,title:string,author:string,content:string,link:string,date:int,lastSeen:int,lastUserModified:int,
|
||||
* hash:string,is_read:bool,is_favorite:bool,id_feed:int,tags:string,attributes:?string} $row */
|
||||
yield $row;
|
||||
}
|
||||
@@ -791,7 +810,7 @@ SQL;
|
||||
$content = static::isCompressed() ? 'UNCOMPRESS(content_bin) AS content' : 'content';
|
||||
$hash = static::sqlHexEncode('hash');
|
||||
$sql = <<<SQL
|
||||
SELECT id, guid, title, author, link, date, is_read, is_favorite, {$hash} AS hash, id_feed, tags, attributes, {$content}
|
||||
SELECT id, guid, title, author, {$content}, link, date, `lastSeen`, `lastUserModified`, {$hash} AS hash, is_read, is_favorite, id_feed, tags, attributes
|
||||
FROM `_entry` WHERE id_feed=:id_feed AND guid=:guid
|
||||
SQL;
|
||||
$res = $this->fetchAssoc($sql, [':id_feed' => $id_feed, ':guid' => $guid]);
|
||||
@@ -804,7 +823,7 @@ SQL;
|
||||
$content = static::isCompressed() ? 'UNCOMPRESS(content_bin) AS content' : 'content';
|
||||
$hash = static::sqlHexEncode('hash');
|
||||
$sql = <<<SQL
|
||||
SELECT id, guid, title, author, link, date, is_read, is_favorite, {$hash} AS hash, id_feed, tags, attributes, {$content}
|
||||
SELECT id, guid, title, author, {$content}, link, date, `lastSeen`, `lastUserModified`, {$hash} AS hash, is_read, is_favorite, id_feed, tags, attributes
|
||||
FROM `_entry` WHERE id=:id
|
||||
SQL;
|
||||
$res = $this->fetchAssoc($sql, [':id' => $id]);
|
||||
@@ -1210,7 +1229,7 @@ SQL;
|
||||
/**
|
||||
* @param numeric-string $id_min
|
||||
* @param numeric-string $id_max
|
||||
* @param 'id'|'c.name'|'date'|'f.name'|'link'|'title'|'rand' $sort
|
||||
* @param 'id'|'c.name'|'date'|'f.name'|'link'|'title'|'rand'|'lastUserModified' $sort
|
||||
* @param 'ASC'|'DESC' $order
|
||||
* @param numeric-string $continuation_id
|
||||
* @param list<string|int> $continuation_values
|
||||
@@ -1278,11 +1297,12 @@ SQL;
|
||||
$values[] = $id_min;
|
||||
}
|
||||
|
||||
if ($continuation_id !== '0' && in_array($sort, ['c.name', 'date', 'f.name', 'link', 'title'], true)) {
|
||||
if ($continuation_id !== '0' && in_array($sort, ['c.name', 'date', 'f.name', 'link', 'title', 'lastUserModified'], true)) {
|
||||
$sign = $order === 'ASC' ? '>' : '<';
|
||||
$orderBy = match ($sort) {
|
||||
'c.name' => 'c.name',
|
||||
'f.name' => 'f.name',
|
||||
'lastUserModified' => $alias . '`lastUserModified`',
|
||||
default => $alias . $sort,
|
||||
};
|
||||
// Keyset pagination (Compatibility syntax due to poor performance of tuple syntax in MySQL https://bugs.mysql.com/bug.php?id=104128)
|
||||
@@ -1320,7 +1340,7 @@ SQL;
|
||||
* @param int $id category/feed/tag ID
|
||||
* @param numeric-string $id_min
|
||||
* @param numeric-string $id_max
|
||||
* @param 'id'|'c.name'|'date'|'f.name'|'link'|'title'|'rand' $sort
|
||||
* @param 'id'|'c.name'|'date'|'f.name'|'link'|'title'|'rand'|'lastUserModified' $sort
|
||||
* @param 'ASC'|'DESC' $order
|
||||
* @param numeric-string $continuation_id
|
||||
* @param list<string|int> $continuation_values
|
||||
@@ -1379,10 +1399,11 @@ SQL;
|
||||
}
|
||||
|
||||
$order = in_array($order, ['ASC', 'DESC'], true) ? $order : 'DESC';
|
||||
$sort = in_array($sort, ['id', 'c.name', 'date', 'f.name', 'link', 'title', 'rand'], true) ? $sort : 'id';
|
||||
$sort = in_array($sort, ['id', 'c.name', 'date', 'f.name', 'link', 'title', 'rand', 'lastUserModified'], true) ? $sort : 'id';
|
||||
$orderBy = match ($sort) {
|
||||
'c.name' => 'c.name',
|
||||
'f.name' => 'f.name',
|
||||
'lastUserModified' => 'e.`lastUserModified`',
|
||||
'rand' => static::sqlRandom(),
|
||||
default => 'e.' . $sort,
|
||||
};
|
||||
@@ -1412,7 +1433,7 @@ SQL;
|
||||
* @param int $id category/feed/tag ID
|
||||
* @param numeric-string $id_min
|
||||
* @param numeric-string $id_max
|
||||
* @param 'id'|'c.name'|'date'|'f.name'|'link'|'title'|'rand' $sort
|
||||
* @param 'id'|'c.name'|'date'|'f.name'|'link'|'title'|'rand'|'lastUserModified' $sort
|
||||
* @param 'ASC'|'DESC' $order
|
||||
* @param numeric-string $continuation_id
|
||||
* @param list<string|int> $continuation_values
|
||||
@@ -1422,7 +1443,7 @@ SQL;
|
||||
string $id_min = '0', string $id_max = '0', string $sort = 'id', string $order = 'DESC',
|
||||
string $continuation_id = '0', array $continuation_values = [], int $limit = 1, int $offset = 0): PDOStatement|false {
|
||||
$order = in_array($order, ['ASC', 'DESC'], true) ? $order : 'DESC';
|
||||
$sort = in_array($sort, ['id', 'c.name', 'date', 'f.name', 'link', 'title', 'rand'], true) ? $sort : 'id';
|
||||
$sort = in_array($sort, ['id', 'c.name', 'date', 'f.name', 'link', 'title', 'rand', 'lastUserModified'], true) ? $sort : 'id';
|
||||
|
||||
[$values, $sql] = $this->sqlListWhere($type, $id, $state, $filters, id_min: $id_min, id_max: $id_max, sort: $sort, order: $order,
|
||||
continuation_id: $continuation_id, continuation_values: $continuation_values, limit: $limit, offset: $offset);
|
||||
@@ -1430,15 +1451,16 @@ SQL;
|
||||
$orderBy = match ($sort) {
|
||||
'c.name' => 'c0.name',
|
||||
'f.name' => 'f0.name',
|
||||
'lastUserModified' => 'e0.`lastUserModified`',
|
||||
'rand' => static::sqlRandom(),
|
||||
default => 'e0.' . $sort,
|
||||
};
|
||||
$content = static::isCompressed() ? 'UNCOMPRESS(e0.content_bin) AS content' : 'e0.content';
|
||||
$hash = static::sqlHexEncode('e0.hash');
|
||||
$sql = <<<SQL
|
||||
SELECT e0.id, e0.guid, e0.title, e0.author, {$content}, e0.link, e0.date, {$hash} AS hash, e0.is_read, e0.is_favorite, e0.id_feed, e0.tags, e0.attributes
|
||||
FROM `_entry` e0
|
||||
INNER JOIN ({$sql}) e2 ON e2.id=e0.id
|
||||
SELECT e0.id, e0.guid, e0.title, e0.author, {$content}, e0.link,
|
||||
e0.date, e0.`lastSeen`, e0.`lastUserModified`, {$hash} AS hash, e0.is_read, e0.is_favorite, e0.id_feed, e0.tags, e0.attributes
|
||||
FROM `_entry` e0 INNER JOIN ({$sql}) e2 ON e2.id=e0.id
|
||||
SQL;
|
||||
if ($sort === 'f.name' || $sort === 'c.name') {
|
||||
$sql .= ' INNER JOIN `_feed` f0 ON f0.id = e0.id_feed ';
|
||||
@@ -1474,7 +1496,7 @@ SQL;
|
||||
* @param int $id category/feed/tag ID
|
||||
* @param numeric-string $id_min
|
||||
* @param numeric-string $id_max
|
||||
* @param 'id'|'c.name'|'date'|'f.name'|'link'|'title'|'rand' $sort
|
||||
* @param 'id'|'c.name'|'date'|'f.name'|'link'|'title'|'rand'|'lastUserModified' $sort
|
||||
* @param 'ASC'|'DESC' $order
|
||||
* @param numeric-string $continuation_id
|
||||
* @param list<string|int> $continuation_values
|
||||
@@ -1520,7 +1542,7 @@ SQL;
|
||||
$hash = static::sqlHexEncode('hash');
|
||||
$repeats = str_repeat('?,', count($ids) - 1) . '?';
|
||||
$sql = <<<SQL
|
||||
SELECT id, guid, title, author, link, date, {$hash} AS hash, is_read, is_favorite, id_feed, tags, attributes, {$content}
|
||||
SELECT id, guid, title, author, link, date, {$hash} AS hash, is_read, is_favorite, id_feed, tags, attributes, {$content}, lastUserModified
|
||||
FROM `_entry`
|
||||
WHERE id IN ({$repeats})
|
||||
ORDER BY id {$order}
|
||||
|
||||
@@ -66,7 +66,7 @@ class FreshRSS_EntryDAOPGSQL extends FreshRSS_EntryDAOSQLite {
|
||||
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) {
|
||||
foreach (['attributes', 'lastUserModified'] as $column) {
|
||||
if (str_contains($errorLines[0], $column)) {
|
||||
return $this->addColumn($column);
|
||||
}
|
||||
|
||||
@@ -64,7 +64,7 @@ class FreshRSS_EntryDAOSQLite extends FreshRSS_EntryDAO {
|
||||
#[\Override]
|
||||
protected function autoUpdateDb(array $errorInfo): bool {
|
||||
if (($tableInfo = $this->pdo->query("PRAGMA table_info('entry')")) !== false && ($columns = $tableInfo->fetchAll(PDO::FETCH_COLUMN, 1)) !== false) {
|
||||
foreach (['attributes'] as $column) {
|
||||
foreach (['attributes', 'lastUserModified'] as $column) {
|
||||
if (!in_array($column, $columns, true)) {
|
||||
return $this->addColumn($column);
|
||||
}
|
||||
@@ -124,8 +124,8 @@ SQL;
|
||||
} else {
|
||||
FreshRSS_UserDAO::touch();
|
||||
$this->pdo->beginTransaction();
|
||||
$sql = 'UPDATE `_entry` SET is_read=? WHERE id=? AND is_read=?';
|
||||
$values = [$is_read ? 1 : 0, $ids, $is_read ? 0 : 1];
|
||||
$sql = 'UPDATE `_entry` SET is_read=?, `lastUserModified` = ? WHERE id=? AND is_read=?';
|
||||
$values = [$is_read ? 1 : 0, time(), $ids, $is_read ? 0 : 1];
|
||||
$stm = $this->pdo->prepare($sql);
|
||||
if ($stm === false || !$stm->execute($values)) {
|
||||
$info = $stm === false ? $this->pdo->errorInfo() : $stm->errorInfo();
|
||||
@@ -165,11 +165,11 @@ SQL;
|
||||
Minz_Log::debug('Calling markReadTag(0) is deprecated!');
|
||||
}
|
||||
|
||||
$sql = 'UPDATE `_entry` SET is_read = ? WHERE is_read <> ? AND id <= ? AND '
|
||||
$sql = 'UPDATE `_entry` SET is_read = ?, `lastUserModified` = ? WHERE is_read <> ? AND id <= ? AND '
|
||||
. 'id IN (SELECT et.id_entry FROM `_entrytag` et '
|
||||
. ($id == 0 ? '' : 'WHERE et.id_tag = ?')
|
||||
. ')';
|
||||
$values = [$is_read ? 1 : 0, $is_read ? 1 : 0, $idMax];
|
||||
$values = [$is_read ? 1 : 0, time(), $is_read ? 1 : 0, $idMax];
|
||||
if ($id != 0) {
|
||||
$values[] = $id;
|
||||
}
|
||||
|
||||
@@ -49,6 +49,7 @@ CREATE TABLE IF NOT EXISTS `_entry` (
|
||||
`link` VARCHAR(16383) CHARACTER SET latin1 COLLATE latin1_bin NOT NULL,
|
||||
`date` BIGINT,
|
||||
`lastSeen` BIGINT DEFAULT 0,
|
||||
`lastUserModified` BIGINT DEFAULT 0, -- v1.28.0
|
||||
`hash` BINARY(16), -- v1.1.1
|
||||
`is_read` BOOLEAN NOT NULL DEFAULT 0,
|
||||
`is_favorite` BOOLEAN NOT NULL DEFAULT 0,
|
||||
@@ -61,6 +62,7 @@ CREATE TABLE IF NOT EXISTS `_entry` (
|
||||
INDEX (`is_favorite`), -- v0.7
|
||||
INDEX (`is_read`), -- v0.7
|
||||
INDEX `entry_lastSeen_index` (`lastSeen`), -- v1.1.1
|
||||
INDEX `entry_last_user_modified_index` (`lastUserModified`), -- v1.28.0
|
||||
INDEX `entry_feed_read_index` (`id_feed`,`is_read`) -- v1.7
|
||||
) DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci
|
||||
ENGINE = INNODB;
|
||||
@@ -109,6 +111,11 @@ CREATE TABLE IF NOT EXISTS `_entrytag` ( -- v1.12
|
||||
ENGINE = INNODB;
|
||||
SQL;
|
||||
|
||||
$GLOBALS['ALTER_TABLE_ENTRY_LAST_USER_MODIFIED'] = <<<'SQL'
|
||||
ALTER TABLE `_entry` ADD `lastUserModified` BIGINT DEFAULT 0; -- 1.28.0
|
||||
CREATE INDEX IF NOT EXISTS `entry_last_user_modified_index` ON `_entry` (`lastUserModified`); -- //v1.28.0
|
||||
SQL;
|
||||
|
||||
$GLOBALS['SQL_DROP_TABLES'] = <<<'SQL'
|
||||
DROP TABLE IF EXISTS `_entrytag`, `_tag`, `_entrytmp`, `_entry`, `_feed`, `_category`;
|
||||
SQL;
|
||||
|
||||
@@ -44,6 +44,7 @@ CREATE TABLE IF NOT EXISTS `_entry` (
|
||||
"link" VARCHAR(16383) NOT NULL,
|
||||
"date" BIGINT,
|
||||
"lastSeen" BIGINT DEFAULT 0,
|
||||
"lastUserModified" BIGINT DEFAULT 0,
|
||||
"hash" BYTEA,
|
||||
"is_read" SMALLINT NOT NULL DEFAULT 0,
|
||||
"is_favorite" SMALLINT NOT NULL DEFAULT 0,
|
||||
@@ -57,6 +58,7 @@ CREATE INDEX IF NOT EXISTS `_is_favorite_index` ON `_entry` ("is_favorite");
|
||||
CREATE INDEX IF NOT EXISTS `_is_read_index` ON `_entry` ("is_read");
|
||||
CREATE INDEX IF NOT EXISTS `_entry_lastSeen_index` ON `_entry` ("lastSeen");
|
||||
CREATE INDEX IF NOT EXISTS `_entry_feed_read_index` ON `_entry` ("id_feed","is_read"); -- v1.7
|
||||
CREATE INDEX IF NOT EXISTS `_entry_last_user_modified_index` ON `_entry` ("lastUserModified"); -- v1.28.0
|
||||
|
||||
INSERT INTO `_category` (id, name)
|
||||
SELECT 1, 'Uncategorized'
|
||||
@@ -98,6 +100,11 @@ CREATE TABLE IF NOT EXISTS `_entrytag` (
|
||||
CREATE INDEX IF NOT EXISTS `_entrytag_id_entry_index` ON `_entrytag` ("id_entry");
|
||||
SQL;
|
||||
|
||||
$GLOBALS['ALTER_TABLE_ENTRY_LAST_USER_MODIFIED'] = <<<'SQL'
|
||||
ALTER TABLE `_entry` ADD `lastUserModified` BIGINT DEFAULT 0; -- 1.28.0
|
||||
CREATE INDEX IF NOT EXISTS `_entry_last_user_modified_index` ON `_entry` (`lastUserModified`);
|
||||
SQL;
|
||||
|
||||
$GLOBALS['SQL_DROP_TABLES'] = <<<'SQL'
|
||||
DROP TABLE IF EXISTS `_entrytag`, `_tag`, `_entrytmp`, `_entry`, `_feed`, `_category`;
|
||||
SQL;
|
||||
|
||||
@@ -45,6 +45,7 @@ CREATE TABLE IF NOT EXISTS `entry` (
|
||||
`link` VARCHAR(16383) NOT NULL,
|
||||
`date` BIGINT,
|
||||
`lastSeen` BIGINT DEFAULT 0,
|
||||
`lastUserModified` BIGINT DEFAULT 0, -- v1.28.0
|
||||
`hash` BINARY(16), -- v1.1.1
|
||||
`is_read` BOOLEAN NOT NULL DEFAULT 0,
|
||||
`is_favorite` BOOLEAN NOT NULL DEFAULT 0,
|
||||
@@ -59,6 +60,7 @@ CREATE INDEX IF NOT EXISTS entry_is_favorite_index ON `entry`(`is_favorite`);
|
||||
CREATE INDEX IF NOT EXISTS entry_is_read_index ON `entry`(`is_read`);
|
||||
CREATE INDEX IF NOT EXISTS entry_lastSeen_index ON `entry`(`lastSeen`); -- //v1.1.1
|
||||
CREATE INDEX IF NOT EXISTS entry_feed_read_index ON `entry`(`id_feed`,`is_read`); -- v1.7
|
||||
CREATE INDEX IF NOT EXISTS entry_last_user_modified_index ON `entry` (`lastUserModified`); -- //v1.28.0
|
||||
|
||||
INSERT OR IGNORE INTO `category` (id, name) VALUES(1, 'Uncategorized');
|
||||
|
||||
@@ -99,6 +101,11 @@ CREATE TABLE IF NOT EXISTS `entrytag` (
|
||||
CREATE INDEX IF NOT EXISTS entrytag_id_entry_index ON `entrytag` (`id_entry`);
|
||||
SQL;
|
||||
|
||||
$GLOBALS['ALTER_TABLE_ENTRY_LAST_USER_MODIFIED'] = <<<'SQL'
|
||||
ALTER TABLE `entry` ADD `lastUserModified` BIGINT DEFAULT 0; -- 1.28.0
|
||||
CREATE INDEX IF NOT EXISTS entry_last_user_modified_index ON `entry` (`lastUserModified`);
|
||||
SQL;
|
||||
|
||||
$GLOBALS['SQL_DROP_TABLES'] = <<<'SQL'
|
||||
DROP TABLE IF EXISTS `entrytag`;
|
||||
DROP TABLE IF EXISTS `tag`;
|
||||
|
||||
@@ -94,6 +94,8 @@ return array(
|
||||
'rand' => 'Random order', // TODO
|
||||
'title_asc' => 'Title A→Z', // TODO
|
||||
'title_desc' => 'Title Z→A', // TODO
|
||||
'user_modified_asc' => 'User modified 1→9', // TODO
|
||||
'user_modified_desc' => 'User modified 9→1', // TODO
|
||||
),
|
||||
'starred' => 'Zobrazit oblíbené',
|
||||
'stats' => 'Statistika',
|
||||
|
||||
@@ -94,6 +94,8 @@ return array(
|
||||
'rand' => 'Zufällige Reihenfolge',
|
||||
'title_asc' => 'Titel A→Z',
|
||||
'title_desc' => 'Titel Z→A',
|
||||
'user_modified_asc' => 'User modified 1→9', // TODO
|
||||
'user_modified_desc' => 'User modified 9→1', // TODO
|
||||
),
|
||||
'starred' => 'Nur Favoriten zeigen',
|
||||
'stats' => 'Statistiken',
|
||||
|
||||
@@ -94,6 +94,8 @@ return array(
|
||||
'rand' => 'Random order', // TODO
|
||||
'title_asc' => 'Title A→Z', // TODO
|
||||
'title_desc' => 'Title Z→A', // TODO
|
||||
'user_modified_asc' => 'User modified 1→9', // TODO
|
||||
'user_modified_desc' => 'User modified 9→1', // TODO
|
||||
),
|
||||
'starred' => 'Show favourites', // TODO
|
||||
'stats' => 'Statistics', // TODO
|
||||
|
||||
@@ -94,6 +94,8 @@ return array(
|
||||
'rand' => 'Random order', // IGNORE
|
||||
'title_asc' => 'Title A→Z', // IGNORE
|
||||
'title_desc' => 'Title Z→A', // IGNORE
|
||||
'user_modified_asc' => 'User modified 1→9', // IGNORE
|
||||
'user_modified_desc' => 'User modified 9→1', // IGNORE
|
||||
),
|
||||
'starred' => 'Show favorites',
|
||||
'stats' => 'Statistics', // IGNORE
|
||||
|
||||
@@ -94,6 +94,8 @@ return array(
|
||||
'rand' => 'Random order',
|
||||
'title_asc' => 'Title A→Z',
|
||||
'title_desc' => 'Title Z→A',
|
||||
'user_modified_asc' => 'User modified 1→9',
|
||||
'user_modified_desc' => 'User modified 9→1',
|
||||
),
|
||||
'starred' => 'Show favourites',
|
||||
'stats' => 'Statistics',
|
||||
|
||||
@@ -94,6 +94,8 @@ return array(
|
||||
'rand' => 'Random order', // TODO
|
||||
'title_asc' => 'Title A→Z', // TODO
|
||||
'title_desc' => 'Title Z→A', // TODO
|
||||
'user_modified_asc' => 'User modified 1→9', // TODO
|
||||
'user_modified_desc' => 'User modified 9→1', // TODO
|
||||
),
|
||||
'starred' => 'Mostrar solo los favoritos',
|
||||
'stats' => 'Estadísticas',
|
||||
|
||||
@@ -94,6 +94,8 @@ return array(
|
||||
'rand' => 'ترتیب تصادفی',
|
||||
'title_asc' => 'عنوانA→Z',
|
||||
'title_desc' => 'عنوان Z→A',
|
||||
'user_modified_asc' => 'User modified 1→9', // TODO
|
||||
'user_modified_desc' => 'User modified 9→1', // TODO
|
||||
),
|
||||
'starred' => ' نمایش موارد دلخواه',
|
||||
'stats' => ' آمار',
|
||||
|
||||
@@ -94,6 +94,8 @@ return array(
|
||||
'rand' => 'Satunnainen järjestys',
|
||||
'title_asc' => 'Otsikko A→Ö',
|
||||
'title_desc' => 'Otsikko Ö→A',
|
||||
'user_modified_asc' => 'User modified 1→9', // TODO
|
||||
'user_modified_desc' => 'User modified 9→1', // TODO
|
||||
),
|
||||
'starred' => 'Näytä suosikit',
|
||||
'stats' => 'Tilastot',
|
||||
|
||||
@@ -94,6 +94,8 @@ return array(
|
||||
'rand' => 'Ordre aléatoire',
|
||||
'title_asc' => 'Titre A→Z',
|
||||
'title_desc' => 'Titre Z→A',
|
||||
'user_modified_asc' => 'Modifié par l’utilisateur 1→9',
|
||||
'user_modified_desc' => 'Modifié par l’utilisateur 9→1',
|
||||
),
|
||||
'starred' => 'Afficher les favoris',
|
||||
'stats' => 'Statistiques',
|
||||
|
||||
@@ -94,6 +94,8 @@ return array(
|
||||
'rand' => 'Random order', // TODO
|
||||
'title_asc' => 'Title A→Z', // TODO
|
||||
'title_desc' => 'Title Z→A', // TODO
|
||||
'user_modified_asc' => 'User modified 1→9', // TODO
|
||||
'user_modified_desc' => 'User modified 9→1', // TODO
|
||||
),
|
||||
'starred' => 'הצגת מועדפים בלבד',
|
||||
'stats' => 'סטטיסטיקות',
|
||||
|
||||
@@ -94,6 +94,8 @@ return array(
|
||||
'rand' => 'Véletlen sorrend',
|
||||
'title_asc' => 'Cím A→Z',
|
||||
'title_desc' => 'Cím Z→A',
|
||||
'user_modified_asc' => 'User modified 1→9', // TODO
|
||||
'user_modified_desc' => 'User modified 9→1', // TODO
|
||||
),
|
||||
'starred' => 'Kedvencek megjelenítése',
|
||||
'stats' => 'Statisztika',
|
||||
|
||||
@@ -94,6 +94,8 @@ return array(
|
||||
'rand' => 'Acak',
|
||||
'title_asc' => 'Judul A→Z',
|
||||
'title_desc' => 'Judul Z→A',
|
||||
'user_modified_asc' => 'User modified 1→9', // TODO
|
||||
'user_modified_desc' => 'User modified 9→1', // TODO
|
||||
),
|
||||
'starred' => 'Tampilkan yang difavoritkan',
|
||||
'stats' => 'Statistik',
|
||||
|
||||
@@ -94,6 +94,8 @@ return array(
|
||||
'rand' => 'Ordine casuale',
|
||||
'title_asc' => 'Titolo A→Z',
|
||||
'title_desc' => 'Titolo Z→A',
|
||||
'user_modified_asc' => 'User modified 1→9', // TODO
|
||||
'user_modified_desc' => 'User modified 9→1', // TODO
|
||||
),
|
||||
'starred' => 'Mostra solo preferiti',
|
||||
'stats' => 'Statistiche',
|
||||
|
||||
@@ -94,6 +94,8 @@ return array(
|
||||
'rand' => 'ランダムに並べる',
|
||||
'title_asc' => 'タイトル順 A→Z',
|
||||
'title_desc' => 'タイトル順 Z→A',
|
||||
'user_modified_asc' => 'User modified 1→9', // TODO
|
||||
'user_modified_desc' => 'User modified 9→1', // TODO
|
||||
),
|
||||
'starred' => 'お気に入りを表示する',
|
||||
'stats' => '統計',
|
||||
|
||||
@@ -94,6 +94,8 @@ return array(
|
||||
'rand' => 'Random order', // TODO
|
||||
'title_asc' => 'Title A→Z', // TODO
|
||||
'title_desc' => 'Title Z→A', // TODO
|
||||
'user_modified_asc' => 'User modified 1→9', // TODO
|
||||
'user_modified_desc' => 'User modified 9→1', // TODO
|
||||
),
|
||||
'starred' => '즐겨찾기만 표시',
|
||||
'stats' => '통계',
|
||||
|
||||
@@ -94,6 +94,8 @@ return array(
|
||||
'rand' => 'Random order', // TODO
|
||||
'title_asc' => 'Title A→Z', // TODO
|
||||
'title_desc' => 'Title Z→A', // TODO
|
||||
'user_modified_asc' => 'User modified 1→9', // TODO
|
||||
'user_modified_desc' => 'User modified 9→1', // TODO
|
||||
),
|
||||
'starred' => 'Rādīt mīļākos',
|
||||
'stats' => 'Statistika',
|
||||
|
||||
@@ -94,6 +94,8 @@ return array(
|
||||
'rand' => 'Willekeurige volgorde',
|
||||
'title_asc' => 'Titel A→Z',
|
||||
'title_desc' => 'Titel Z→A',
|
||||
'user_modified_asc' => 'Aangepast door gebruiker 1→9',
|
||||
'user_modified_desc' => 'Aangepast door gebruiker 9→1',
|
||||
),
|
||||
'starred' => 'Laat alleen favorieten zien',
|
||||
'stats' => 'Statistieken',
|
||||
|
||||
@@ -94,6 +94,8 @@ return array(
|
||||
'rand' => 'Random order', // TODO
|
||||
'title_asc' => 'Title A→Z', // TODO
|
||||
'title_desc' => 'Title Z→A', // TODO
|
||||
'user_modified_asc' => 'User modified 1→9', // TODO
|
||||
'user_modified_desc' => 'User modified 9→1', // TODO
|
||||
),
|
||||
'starred' => 'Mostrar los favorits',
|
||||
'stats' => 'Estatisticas',
|
||||
|
||||
@@ -94,6 +94,8 @@ return array(
|
||||
'rand' => 'Losowa kolejność',
|
||||
'title_asc' => 'Tytuł A→Z',
|
||||
'title_desc' => 'Tytuł Z→A',
|
||||
'user_modified_asc' => 'Zmodyfikowane przez użytkownika 1→9',
|
||||
'user_modified_desc' => 'Zmodyfikowane przez użytkownika 9→1',
|
||||
),
|
||||
'starred' => 'Pokaż ulubione',
|
||||
'stats' => 'Statystyki',
|
||||
|
||||
@@ -94,6 +94,8 @@ return array(
|
||||
'rand' => 'Random order', // TODO
|
||||
'title_asc' => 'Title A→Z', // TODO
|
||||
'title_desc' => 'Title Z→A', // TODO
|
||||
'user_modified_asc' => 'User modified 1→9', // TODO
|
||||
'user_modified_desc' => 'User modified 9→1', // TODO
|
||||
),
|
||||
'starred' => 'Mostrar apenas os favoritos',
|
||||
'stats' => 'Estatísticas',
|
||||
|
||||
@@ -94,6 +94,8 @@ return array(
|
||||
'rand' => 'Random order', // TODO
|
||||
'title_asc' => 'Title A→Z', // TODO
|
||||
'title_desc' => 'Title Z→A', // TODO
|
||||
'user_modified_asc' => 'User modified 1→9', // TODO
|
||||
'user_modified_desc' => 'User modified 9→1', // TODO
|
||||
),
|
||||
'starred' => 'Mostrar apenas os favoritos',
|
||||
'stats' => 'Estatísticas',
|
||||
|
||||
@@ -94,6 +94,8 @@ return array(
|
||||
'rand' => 'Random order', // TODO
|
||||
'title_asc' => 'Title A→Z', // TODO
|
||||
'title_desc' => 'Title Z→A', // TODO
|
||||
'user_modified_asc' => 'User modified 1→9', // TODO
|
||||
'user_modified_desc' => 'User modified 9→1', // TODO
|
||||
),
|
||||
'starred' => 'Показать избранное',
|
||||
'stats' => 'Статистика',
|
||||
|
||||
@@ -94,6 +94,8 @@ return array(
|
||||
'rand' => 'Random order', // TODO
|
||||
'title_asc' => 'Title A→Z', // TODO
|
||||
'title_desc' => 'Title Z→A', // TODO
|
||||
'user_modified_asc' => 'User modified 1→9', // TODO
|
||||
'user_modified_desc' => 'User modified 9→1', // TODO
|
||||
),
|
||||
'starred' => 'Zobraziť obľúbené',
|
||||
'stats' => 'Štatistiky',
|
||||
|
||||
@@ -94,6 +94,8 @@ return array(
|
||||
'rand' => 'Rastgele sıralama',
|
||||
'title_asc' => 'Başlık A→Z',
|
||||
'title_desc' => 'Başlık Z→A',
|
||||
'user_modified_asc' => 'User modified 1→9', // TODO
|
||||
'user_modified_desc' => 'User modified 9→1', // TODO
|
||||
),
|
||||
'starred' => 'Favorileri göster',
|
||||
'stats' => 'İstatistikler',
|
||||
|
||||
@@ -94,6 +94,8 @@ return array(
|
||||
'rand' => 'Довільний порядок',
|
||||
'title_asc' => 'Заголовок А→Я',
|
||||
'title_desc' => 'Заголовок Я→А',
|
||||
'user_modified_asc' => 'User modified 1→9', // TODO
|
||||
'user_modified_desc' => 'User modified 9→1', // TODO
|
||||
),
|
||||
'starred' => 'Показати вподобані',
|
||||
'stats' => 'Статистика',
|
||||
|
||||
@@ -94,6 +94,8 @@ return array(
|
||||
'rand' => 'Random order', // TODO
|
||||
'title_asc' => 'Title A→Z', // TODO
|
||||
'title_desc' => 'Title Z→A', // TODO
|
||||
'user_modified_asc' => 'User modified 1→9', // TODO
|
||||
'user_modified_desc' => 'User modified 9→1', // TODO
|
||||
),
|
||||
'starred' => '显示收藏',
|
||||
'stats' => '统计',
|
||||
|
||||
@@ -94,6 +94,8 @@ return array(
|
||||
'rand' => 'Random order', // TODO
|
||||
'title_asc' => 'Title A→Z', // TODO
|
||||
'title_desc' => 'Title Z→A', // TODO
|
||||
'user_modified_asc' => 'User modified 1→9', // TODO
|
||||
'user_modified_desc' => 'User modified 9→1', // TODO
|
||||
),
|
||||
'starred' => '顯示收藏',
|
||||
'stats' => '統計',
|
||||
|
||||
@@ -233,6 +233,8 @@
|
||||
<a href="<?= Minz_Url::display($url_order, amend: ['params' => ['sort' => 'id', 'order' => 'DESC']]) ?>"><?= _t('index.menu.sort.id_desc') ?></a></li>
|
||||
<li class="item" role="radio" aria-checked="<?= FreshRSS_Context::$order === 'DESC' && FreshRSS_Context::$sort === 'date' ? 'true' : 'false' ?>">
|
||||
<a href="<?= Minz_Url::display($url_order, amend: ['params' => ['sort' => 'date', 'order' => 'DESC']]) ?>"><?= _t('index.menu.sort.date_desc') ?></a></li>
|
||||
<li class="item" role="radio" aria-checked="<?= FreshRSS_Context::$order === 'DESC' && FreshRSS_Context::$sort === 'lastUserModified' ? 'true' : 'false' ?>">
|
||||
<a href="<?= Minz_Url::display($url_order, amend: ['params' => ['sort' => 'lastUserModified', 'order' => 'DESC']]) ?>"><?= _t('index.menu.sort.user_modified_desc') ?></a></li>
|
||||
<li class="item" role="radio" aria-checked="<?= FreshRSS_Context::$order === 'DESC' && FreshRSS_Context::$sort === 'link' ? 'true' : 'false' ?>">
|
||||
<a href="<?= Minz_Url::display($url_order, amend: ['params' => ['sort' => 'link', 'order' => 'DESC']]) ?>"><?= _t('index.menu.sort.link_desc') ?></a></li>
|
||||
<li class="item" role="radio" aria-checked="<?= FreshRSS_Context::$order === 'DESC' && FreshRSS_Context::$sort === 'title' ? 'true' : 'false' ?>">
|
||||
@@ -247,6 +249,8 @@
|
||||
<a href="<?= Minz_Url::display($url_order, amend: ['params' => ['sort' => 'id', 'order' => 'ASC']]) ?>"><?= _t('index.menu.sort.id_asc') ?></a></li>
|
||||
<li class="item" role="radio" aria-checked="<?= FreshRSS_Context::$order === 'ASC' && FreshRSS_Context::$sort === 'date' ? 'true' : 'false' ?>">
|
||||
<a href="<?= Minz_Url::display($url_order, amend: ['params' => ['sort' => 'date', 'order' => 'ASC']]) ?>"><?= _t('index.menu.sort.date_asc') ?></a></li>
|
||||
<li class="item" role="radio" aria-checked="<?= FreshRSS_Context::$order === 'ASC' && FreshRSS_Context::$sort === 'lastUserModified' ? 'true' : 'false' ?>">
|
||||
<a href="<?= Minz_Url::display($url_order, amend: ['params' => ['sort' => 'lastUserModified', 'order' => 'ASC']]) ?>"><?= _t('index.menu.sort.user_modified_asc') ?></a></li>
|
||||
<li class="item" role="radio" aria-checked="<?= FreshRSS_Context::$order === 'ASC' && FreshRSS_Context::$sort === 'link' ? 'true' : 'false' ?>">
|
||||
<a href="<?= Minz_Url::display($url_order, amend: ['params' => ['sort' => 'link', 'order' => 'ASC']]) ?>"><?= _t('index.menu.sort.link_asc') ?></a></li>
|
||||
<li class="item" role="radio" aria-checked="<?= FreshRSS_Context::$order === 'ASC' && FreshRSS_Context::$sort === 'title' ? 'true' : 'false' ?>">
|
||||
|
||||
Reference in New Issue
Block a user