Alexandre Alapetite
2024-01-03 11:23:06 +01:00
committed by GitHub
parent 1e5f5078ed
commit 70e71b8364
61 changed files with 355 additions and 121 deletions

View File

@@ -679,29 +679,81 @@ class FreshRSS_feed_Controller extends FreshRSS_ActionController {
return [$updated_feeds, reset($feeds) ?: null, $nb_new_articles];
}
public static function commitNewEntries(): bool {
$entryDAO = FreshRSS_Factory::createEntryDao();
if (!$entryDAO->inTransaction()) {
$entryDAO->beginTransaction();
}
$newUnreadEntriesPerFeed = $entryDAO->newUnreadEntriesPerFeed();
if ($entryDAO->commitNewEntries()) {
$feedDAO = FreshRSS_Factory::createFeedDao();
$feeds = $feedDAO->listFeedsOrderUpdate(-1);
foreach ($feeds as $feed) {
if (!empty($newUnreadEntriesPerFeed[$feed->id()]) && $feed->keepMaxUnread() !== null &&
($feed->nbNotRead() + $newUnreadEntriesPerFeed[$feed->id()] > $feed->keepMaxUnread())) {
Minz_Log::debug('New unread entries (' . ($feed->nbNotRead() + $newUnreadEntriesPerFeed[$feed->id()]) . ') exceeding max number of ' .
$feed->keepMaxUnread() . ' for [' . $feed->url(false) . ']');
$feed->markAsReadMaxUnread();
/**
* @param array<int,int> $newUnreadEntriesPerFeed
* @return int|false The number of articles marked as read, of false if error
*/
private static function keepMaxUnreads(array $newUnreadEntriesPerFeed) {
$affected = 0;
$feedDAO = FreshRSS_Factory::createFeedDao();
$feeds = $feedDAO->listFeedsOrderUpdate(-1);
foreach ($feeds as $feed) {
if (!empty($newUnreadEntriesPerFeed[$feed->id()]) && $feed->keepMaxUnread() !== null &&
($feed->nbNotRead() + $newUnreadEntriesPerFeed[$feed->id()] > $feed->keepMaxUnread())) {
Minz_Log::debug('New unread entries (' . ($feed->nbNotRead() + $newUnreadEntriesPerFeed[$feed->id()]) . ') exceeding max number of ' .
$feed->keepMaxUnread() . ' for [' . $feed->url(false) . ']');
$n = $feed->markAsReadMaxUnread();
if ($n === false) {
$affected = false;
break;
} else {
$affected += $n;
}
}
$feedDAO->updateCachedValues();
}
if ($feedDAO->updateCachedValues() === false) {
$affected = false;
}
return $affected;
}
/**
* Auto-add labels to new articles.
* @param int $nbNewEntries The number of top recent entries to process.
* @return int|false The number of new labels added, or false in case of error.
*/
private static function applyLabelActions(int $nbNewEntries) {
$tagDAO = FreshRSS_Factory::createTagDao();
$labels = $tagDAO->listTags() ?: [];
$labels = array_filter($labels, static function (FreshRSS_Tag $label) {
return !empty($label->filtersAction('label'));
});
if (count($labels) <= 0) {
return 0;
}
if ($entryDAO->inTransaction()) {
$entryDAO->commit();
$entryDAO = FreshRSS_Factory::createEntryDao();
/** @var array<array{id_tag:int,id_entry:string}> $applyLabels */
$applyLabels = [];
foreach (FreshRSS_Entry::fromTraversable($entryDAO->selectAll($nbNewEntries)) as $entry) {
foreach ($labels as $label) {
$label->applyFilterActions($entry, $applyLabel);
if ($applyLabel) {
$applyLabels[] = [
'id_tag' => $label->id(),
'id_entry' => $entry->id(),
];
}
}
}
return $tagDAO->tagEntries($applyLabels);
}
public static function commitNewEntries(): bool {
$entryDAO = FreshRSS_Factory::createEntryDao();
$newUnreadEntriesPerFeed = $entryDAO->newUnreadEntriesPerFeed();
$nbNewEntries = array_sum($newUnreadEntriesPerFeed);
if ($nbNewEntries > 0) {
if (!$entryDAO->inTransaction()) {
$entryDAO->beginTransaction();
}
if ($entryDAO->commitNewEntries()) {
self::keepMaxUnreads($newUnreadEntriesPerFeed);
self::applyLabelActions($nbNewEntries);
}
if ($entryDAO->inTransaction()) {
$entryDAO->commit();
}
}
$databaseDAO = FreshRSS_Factory::createDatabaseDAO();

View File

@@ -21,7 +21,6 @@ class FreshRSS_tag_Controller extends FreshRSS_ActionController {
$this->ajax = Minz_Request::paramBoolean('ajax');
if ($this->ajax) {
$this->view->_layout(null);
Minz_Request::_param('ajax');
}
}
@@ -84,6 +83,51 @@ class FreshRSS_tag_Controller extends FreshRSS_ActionController {
}
}
/**
* This action updates the given tag.
*/
public function updateAction(): void {
if (Minz_Request::paramBoolean('ajax')) {
$this->view->_layout(null);
}
$tagDAO = FreshRSS_Factory::createTagDao();
$id = Minz_Request::paramInt('id');
$tag = $tagDAO->searchById($id);
if ($id === 0 || $tag === null) {
Minz_Error::error(404);
return;
}
$this->view->tag = $tag;
FreshRSS_View::prependTitle($tag->name() . ' · ' . _t('sub.title') . ' · ');
if (Minz_Request::isPost()) {
invalidateHttpCache();
$ok = true;
if ($tag->name() !== Minz_Request::paramString('name')) {
$ok = $tagDAO->updateTagName($tag->id(), Minz_Request::paramString('name')) !== false;
}
if ($ok) {
$tag->_filtersAction('label', Minz_Request::paramTextToArray('filteractions_label'));
$ok = $tagDAO->updateTagAttributes($tag->id(), $tag->attributes()) !== false;
}
invalidateHttpCache();
$url_redirect = ['c' => 'tag', 'a' => 'update', 'params' => ['id' => $id]];
if ($ok) {
Minz_Request::good(_t('feedback.tag.updated'), $url_redirect);
} else {
Minz_Request::bad(_t('feedback.tag.error'), $url_redirect);
}
}
}
public function getTagsForEntryAction(): void {
if (!FreshRSS_Auth::hasAccess() && !FreshRSS_Context::systemConf()->allow_anonymous) {
Minz_Error::error(403);

View File

@@ -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;
}

View File

@@ -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);
}
}

View File

@@ -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;
}
}

View File

@@ -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;

View File

@@ -300,6 +300,35 @@ SQL;
return false;
}
/**
* @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
*/

View File

@@ -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;

View File

@@ -116,8 +116,10 @@ return array(
),
'tag' => array(
'created' => 'Štítek „%s“ byl vytvořen.',
'error' => 'Label could not be updated!', // TODO
'name_exists' => 'Název štítku již existuje.',
'renamed' => 'Štítek „%s“ byl přejmenován na „%s“.',
'updated' => 'Label has been updated.', // TODO
),
'update' => array(
'can_apply' => 'FreshRSS bude nyní aktualizováno na <strong>verzi %s</strong>.',

View File

@@ -196,6 +196,7 @@ return array(
'subscription_tools' => 'Nástroje odběrů',
),
'tag' => array(
'auto_label' => 'Add this label to new articles', // TODO
'name' => 'Název',
'new_name' => 'Nový název',
'old_name' => 'Starý název',

View File

@@ -116,8 +116,10 @@ return array(
),
'tag' => array(
'created' => 'Label „%s“ wurde erstellt.',
'error' => 'Label could not be updated!', // TODO
'name_exists' => 'Label-Name existiert bereits.',
'renamed' => 'Das Label „%s“ wurde umbenannt in „%s“.',
'updated' => 'Label has been updated.', // TODO
),
'update' => array(
'can_apply' => 'FreshRSS wird nun auf die <strong>Version %s</strong> aktualisiert.',

View File

@@ -196,6 +196,7 @@ return array(
'subscription_tools' => 'Abonnement-Tools',
),
'tag' => array(
'auto_label' => 'Add this label to new articles', // TODO
'name' => 'Name', // IGNORE
'new_name' => 'Neuer Name',
'old_name' => 'Alter Name',

View File

@@ -116,8 +116,10 @@ return array(
),
'tag' => array(
'created' => 'Label “%s” has been created.', // TODO
'error' => 'Label could not be updated!', // TODO
'name_exists' => 'Label name already exists.', // TODO
'renamed' => 'Label “%s” has been renamed to “%s”.', // TODO
'updated' => 'Label has been updated.', // TODO
),
'update' => array(
'can_apply' => 'FreshRSS will now be updated to the <strong>version %s</strong>.', // DIRTY

View File

@@ -196,6 +196,7 @@ return array(
'subscription_tools' => 'Subscription tools', // TODO
),
'tag' => array(
'auto_label' => 'Add this label to new articles', // TODO
'name' => 'Name', // TODO
'new_name' => 'New name', // TODO
'old_name' => 'Old name', // TODO

View File

@@ -116,8 +116,10 @@ return array(
),
'tag' => array(
'created' => 'Label “%s” has been created.', // IGNORE
'error' => 'Label could not be updated!', // IGNORE
'name_exists' => 'Label name already exists.', // IGNORE
'renamed' => 'Label “%s” has been renamed to “%s”.', // IGNORE
'updated' => 'Label has been updated.', // IGNORE
),
'update' => array(
'can_apply' => 'FreshRSS will now be updated to the <strong>version %s</strong>.', // IGNORE

View File

@@ -196,6 +196,7 @@ return array(
'subscription_tools' => 'Subscription tools', // IGNORE
),
'tag' => array(
'auto_label' => 'Add this label to new articles', // IGNORE
'name' => 'Name', // IGNORE
'new_name' => 'New name', // IGNORE
'old_name' => 'Old name', // IGNORE

View File

@@ -116,8 +116,10 @@ return array(
),
'tag' => array(
'created' => 'Label “%s” has been created.',
'error' => 'Label could not be updated!',
'name_exists' => 'Label name already exists.',
'renamed' => 'Label “%s” has been renamed to “%s”.',
'updated' => 'Label has been updated.',
),
'update' => array(
'can_apply' => 'An update of FreshRSS is available: <strong>Version %s</strong>.',

View File

@@ -196,6 +196,7 @@ return array(
'subscription_tools' => 'Subscription tools',
),
'tag' => array(
'auto_label' => 'Add this label to new articles',
'name' => 'Name',
'new_name' => 'New name',
'old_name' => 'Old name',

View File

@@ -116,8 +116,10 @@ return array(
),
'tag' => array(
'created' => 'Se ha creado la etiqueta “%s”.',
'error' => 'Label could not be updated!', // TODO
'name_exists' => 'El nombre de la etiqueta ya existe.',
'renamed' => 'La etiqueta “%s” ha sido renombrada a “%s”.',
'updated' => 'Label has been updated.', // TODO
),
'update' => array(
'can_apply' => 'FreshRSS se va a actualizar a la <strong>versión %s</strong>.',

View File

@@ -196,6 +196,7 @@ return array(
'subscription_tools' => 'Herramientas de suscripción',
),
'tag' => array(
'auto_label' => 'Add this label to new articles', // TODO
'name' => 'Nombre',
'new_name' => 'Nuevo nombre',
'old_name' => 'Nombre antiguo',

View File

@@ -116,8 +116,10 @@ return array(
),
'tag' => array(
'created' => ' برچسب "%s" ایجاد شده است.',
'error' => 'Label could not be updated!', // TODO
'name_exists' => ' نام برچسب از قبل وجود دارد.',
'renamed' => ' برچسب "%s" به "%s" تغییر نام داده است.',
'updated' => 'Label has been updated.', // TODO
),
'update' => array(
'can_apply' => ' به‌روزرسانی FreshRSS موجود است: <strong>نسخه %s</strong>.',

View File

@@ -196,6 +196,7 @@ return array(
'subscription_tools' => 'ابزارهای اشتراک',
),
'tag' => array(
'auto_label' => 'Add this label to new articles', // TODO
'name' => ' نام',
'new_name' => ' نام جدید',
'old_name' => ' نام قدیمی',

View File

@@ -116,8 +116,10 @@ return array(
),
'tag' => array(
'created' => 'Létiquette <em>%s</em> a été créée.',
'name_exists' => 'Létiquette existe déjà.',
'error' => 'Létiquette na pas pu être modifiée',
'name_exists' => 'Létiquette existe déjà!',
'renamed' => 'Létiquette <em>%s</em> a été renommée en <em>%s</em>.',
'updated' => 'Létiquette a été mise à jour.',
),
'update' => array(
'can_apply' => 'FreshRSS va maintenant être mis à jour vers la <strong>version %s</strong>.',

View File

@@ -196,6 +196,7 @@ return array(
'subscription_tools' => 'Outils dabonnement',
),
'tag' => array(
'auto_label' => 'Ajoute létiquette aux nouveaux articles',
'name' => 'Nom',
'new_name' => 'Nouveau nom',
'old_name' => 'Ancien nom',

View File

@@ -116,8 +116,10 @@ return array(
),
'tag' => array(
'created' => 'Label “%s” has been created.', // TODO
'error' => 'Label could not be updated!', // TODO
'name_exists' => 'Label name already exists.', // TODO
'renamed' => 'Label “%s” has been renamed to “%s”.', // TODO
'updated' => 'Label has been updated.', // TODO
),
'update' => array(
'can_apply' => 'FreshRSS will be now updated to the <strong>version %s</strong>.',

View File

@@ -196,6 +196,7 @@ return array(
'subscription_tools' => 'Subscription tools', // TODO
),
'tag' => array(
'auto_label' => 'Add this label to new articles', // TODO
'name' => 'Name', // TODO
'new_name' => 'New name', // TODO
'old_name' => 'Old name', // TODO

View File

@@ -116,8 +116,10 @@ return array(
),
'tag' => array(
'created' => 'Címke “%s” létrehozva.',
'error' => 'Label could not be updated!', // TODO
'name_exists' => 'Címke név már létezik.',
'renamed' => 'Címke “%s” átnevezve “%s”.',
'updated' => 'Label has been updated.', // TODO
),
'update' => array(
'can_apply' => 'Egy FreshRSS frissítés elérhető : <strong>Verzió %s</strong>.',

View File

@@ -196,6 +196,7 @@ return array(
'subscription_tools' => 'Hírforrás eszközök',
),
'tag' => array(
'auto_label' => 'Add this label to new articles', // TODO
'name' => 'Név',
'new_name' => 'Új név',
'old_name' => 'Régi név',

View File

@@ -116,8 +116,10 @@ return array(
),
'tag' => array(
'created' => 'Label “%s”has been created.', // DIRTY
'error' => 'Label could not be updated!', // TODO
'name_exists' => 'Label name already exists.', // TODO
'renamed' => 'Label “%s”has been renamed to “%s”.', // DIRTY
'updated' => 'Label has been updated.', // TODO
),
'update' => array(
'can_apply' => 'An update of FreshRSS is available: <strong>Version %s</strong>.', // TODO

View File

@@ -196,6 +196,7 @@ return array(
'subscription_tools' => 'Subscription tools', // TODO
),
'tag' => array(
'auto_label' => 'Add this label to new articles', // TODO
'name' => 'Name', // TODO
'new_name' => 'New name', // TODO
'old_name' => 'Old name', // TODO

View File

@@ -116,8 +116,10 @@ return array(
),
'tag' => array(
'created' => 'Il Tag “%s” è stato creato.', // DIRTY
'error' => 'Label could not be updated!', // TODO
'name_exists' => 'Il nome del tag è già presente.', // DIRTY
'renamed' => 'Il Tag “%s” è stato rinominato in “%s”.', // DIRTY
'updated' => 'Label has been updated.', // TODO
),
'update' => array(
'can_apply' => 'FreshRSS verrà aggiornato alla <strong>versione %s</strong>.',

View File

@@ -196,6 +196,7 @@ return array(
'subscription_tools' => 'Strumenti di sottoscrizione',
),
'tag' => array(
'auto_label' => 'Add this label to new articles', // TODO
'name' => 'Nome',
'new_name' => 'Nuovo nome',
'old_name' => 'Vecchio nome',

View File

@@ -116,8 +116,10 @@ return array(
),
'tag' => array(
'created' => '“%s” タグが作成されました',
'error' => 'Label could not be updated!', // TODO
'name_exists' => 'このタグ名は既に存在します',
'renamed' => '“%s”タグは“%s”に改名されました',
'updated' => 'Label has been updated.', // TODO
),
'update' => array(
'can_apply' => 'FreshRSSは<strong>バージョン %s</strong>に更新されます。',

View File

@@ -196,6 +196,7 @@ return array(
'subscription_tools' => '購読ツール',
),
'tag' => array(
'auto_label' => 'Add this label to new articles', // TODO
'name' => '名前',
'new_name' => '新しい名前',
'old_name' => '古い名前',

View File

@@ -116,8 +116,10 @@ return array(
),
'tag' => array(
'created' => '“%s” 태그가 생성되었습니다.',
'error' => 'Label could not be updated!', // TODO
'name_exists' => '같은 이름의 태그가 이미 존재합니다.',
'renamed' => '“%s” 태그가 “%s” (으)로 이름이 변경되었습니다.',
'updated' => 'Label has been updated.', // TODO
),
'update' => array(
'can_apply' => 'FreshRSS가 <strong>%s</strong> 버전으로 업데이트됩니다.',

View File

@@ -196,6 +196,7 @@ return array(
'subscription_tools' => '구독 도구',
),
'tag' => array(
'auto_label' => 'Add this label to new articles', // TODO
'name' => '이름',
'new_name' => '새 이름',
'old_name' => '이전 이름',

View File

@@ -116,8 +116,10 @@ return array(
),
'tag' => array(
'created' => 'Birka “%s” tika uztaisīta.',
'error' => 'Label could not be updated!', // TODO
'name_exists' => 'Birkas nosaukums jau pastāv.',
'renamed' => 'Birka “%s” tika pārdēvēts par “%s”.',
'updated' => 'Label has been updated.', // TODO
),
'update' => array(
'can_apply' => 'FreshRSS tagad būs atjaunots uz <strong>%s versiju</strong>.',

View File

@@ -196,6 +196,7 @@ return array(
'subscription_tools' => 'Abonamentu rīki',
),
'tag' => array(
'auto_label' => 'Add this label to new articles', // TODO
'name' => 'Vārds',
'new_name' => 'Jaunais vārds',
'old_name' => 'Vecais vārds',

View File

@@ -116,8 +116,10 @@ return array(
),
'tag' => array(
'created' => 'Label „%s” aangemaakt.',
'error' => 'Label could not be updated!', // TODO
'name_exists' => 'Label bestaat al.',
'renamed' => 'Label „%s” hernoemd naar „%s”.',
'updated' => 'Label has been updated.', // TODO
),
'update' => array(
'can_apply' => 'FreshRSS word nu bijgewerkt naar <strong>versie %s</strong>.',

View File

@@ -196,6 +196,7 @@ return array(
'subscription_tools' => 'Hulpmiddelen voor abonnementen',
),
'tag' => array(
'auto_label' => 'Add this label to new articles', // TODO
'name' => 'Naam',
'new_name' => 'Nieuwe naam',
'old_name' => 'Oude naam',

View File

@@ -116,8 +116,10 @@ return array(
),
'tag' => array(
'created' => 'Letiqueta « %s » es estada creada.',
'error' => 'Label could not be updated!', // TODO
'name_exists' => 'Lo nom de letiqueta existís ja.',
'renamed' => 'Letiqueta « %s » es estada renomenada en « %s ».',
'updated' => 'Label has been updated.', // TODO
),
'update' => array(
'can_apply' => 'FreshRSS es per èsser mes a jorn en <strong>version %s</strong>.',

View File

@@ -196,6 +196,7 @@ return array(
'subscription_tools' => 'Aisinas dabonament',
),
'tag' => array(
'auto_label' => 'Add this label to new articles', // TODO
'name' => 'Nom',
'new_name' => 'Nom novèl',
'old_name' => 'Nom ancian',

View File

@@ -116,8 +116,10 @@ return array(
),
'tag' => array(
'created' => 'Etykieta “%s” została stworzona.',
'error' => 'Label could not be updated!', // TODO
'name_exists' => 'Etykieta o podanej nazwie już istnieje.',
'renamed' => 'Etykieta “%s” została zmieniona na “%s”.',
'updated' => 'Label has been updated.', // TODO
),
'update' => array(
'can_apply' => 'FreshRSS zostanie zaktualizowany do <strong>wersji %s</strong>.',

View File

@@ -196,6 +196,7 @@ return array(
'subscription_tools' => 'Narzędzia subskrypcji',
),
'tag' => array(
'auto_label' => 'Add this label to new articles', // TODO
'name' => 'Nazwa',
'new_name' => 'Nowa nazwa',
'old_name' => 'Poprzednia nazwa',

View File

@@ -116,8 +116,10 @@ return array(
),
'tag' => array(
'created' => 'A Tag “%s” foi criada.', // DIRTY
'error' => 'Label could not be updated!', // TODO
'name_exists' => 'O nome da tag já existe.', // DIRTY
'renamed' => 'A Tag “%s” foi renomeada para “%s”.', // DIRTY
'updated' => 'Label has been updated.', // TODO
),
'update' => array(
'can_apply' => 'O FreshRSS será atualizado para a <strong>versão %s</strong>.',

View File

@@ -196,6 +196,7 @@ return array(
'subscription_tools' => 'Ferramentas de inscrição',
),
'tag' => array(
'auto_label' => 'Add this label to new articles', // TODO
'name' => 'Nome',
'new_name' => 'Nome novo',
'old_name' => 'Nome antigo',

View File

@@ -116,8 +116,10 @@ return array(
),
'tag' => array(
'created' => 'Метка “%s” создана.',
'error' => 'Label could not be updated!', // TODO
'name_exists' => 'Метка с таким названием уже существует.',
'renamed' => 'Метка “%s” переименована в “%s”.',
'updated' => 'Label has been updated.', // TODO
),
'update' => array(
'can_apply' => 'FreshRSS будет обновлён до <strong>версии %s</strong>.',

View File

@@ -196,6 +196,7 @@ return array(
'subscription_tools' => 'Инструменты подписки',
),
'tag' => array(
'auto_label' => 'Add this label to new articles', // TODO
'name' => 'Название',
'new_name' => 'Новое название',
'old_name' => 'Старое название',

View File

@@ -116,8 +116,10 @@ return array(
),
'tag' => array(
'created' => 'Štítok “%s” bol vytvorený.',
'error' => 'Label could not be updated!', // TODO
'name_exists' => 'Názov štítku už existuje.',
'renamed' => 'Štítok “%s” bol premenovaný na “%s”.',
'updated' => 'Label has been updated.', // TODO
),
'update' => array(
'can_apply' => 'FreshRSS sa teraz aktualizuje <strong>na verziu %s</strong>.',

View File

@@ -196,6 +196,7 @@ return array(
'subscription_tools' => 'Nástroje na odoberanie kanálov',
),
'tag' => array(
'auto_label' => 'Add this label to new articles', // TODO
'name' => 'Názov',
'new_name' => 'Nový názov',
'old_name' => 'Starý názov',

View File

@@ -116,8 +116,10 @@ return array(
),
'tag' => array(
'created' => '“%s” etiketi oluşturuldu.',
'error' => 'Label could not be updated!', // TODO
'name_exists' => 'Etiket zaten mevcut.',
'renamed' => '“%s” isimli etiketin ismi “%s” olarak değiştirildi.',
'updated' => 'Label has been updated.', // TODO
),
'update' => array(
'can_apply' => 'FreshRSS <strong>%s sürümüne</strong> güncellenecek.',

View File

@@ -196,6 +196,7 @@ return array(
'subscription_tools' => 'Abonelik araçları',
),
'tag' => array(
'auto_label' => 'Add this label to new articles', // TODO
'name' => 'İsim',
'new_name' => 'Eski isim',
'old_name' => 'Yeni isim',

View File

@@ -116,8 +116,10 @@ return array(
),
'tag' => array(
'created' => '标签 “%s” 已创建。',
'error' => 'Label could not be updated!', // TODO
'name_exists' => '标签名已存在。',
'renamed' => '标签 “%s” 已被重命名为 “%s”。',
'updated' => 'Label has been updated.', // TODO
),
'update' => array(
'can_apply' => 'FreshRSS 将更新到 <strong>版本 %s</strong>。',

View File

@@ -196,6 +196,7 @@ return array(
'subscription_tools' => '订阅工具',
),
'tag' => array(
'auto_label' => 'Add this label to new articles', // TODO
'name' => '名称',
'new_name' => '新名称',
'old_name' => '旧名称',

View File

@@ -116,8 +116,10 @@ return array(
),
'tag' => array(
'created' => '標簽 “%s” 已創建。',
'error' => 'Label could not be updated!', // TODO
'name_exists' => '標簽名已存在。',
'renamed' => '標簽 “%s” 已被重命名為 “%s”。',
'updated' => 'Label has been updated.', // TODO
),
'update' => array(
'can_apply' => 'FreshRSS 將更新到 <strong>版本 %s</strong>。',

View File

@@ -196,6 +196,7 @@ return array(
'subscription_tools' => '訂閱工具',
),
'tag' => array(
'auto_label' => 'Add this label to new articles', // TODO
'name' => '名稱',
'new_name' => '新名稱',
'old_name' => '舊名稱',

View File

@@ -153,9 +153,7 @@
<script id="tag_config_template" type="text/html">
<ul class="dropdown-menu">
<li class="item">
<button class="as-link confirm" disabled="disabled"
form="mark-read-aside" formaction="<?= _url('tag', 'delete', 'id_tag', '------') ?>"
type="submit"><?= _t('gen.action.remove') ?></button>
<a class="configure open-slider" href="<?= _url('tag', 'update', 'id', '------') ?>"><?= _t('gen.action.manage') ?></a>
</li>
</ul>
<a class="dropdown-close" href="#close"></a>

View File

@@ -27,60 +27,12 @@
</div>
</form>
<h2><?= _t('sub.title.rename_label') ?></h2>
<?php
$disabled = '';
if (count($this->tags) < 1) {
$disabled = ' disabled="disabled"';
} ?>
<form id="rename_tag" method="post" action="<?= _url('tag', 'rename') ?>" autocomplete="off">
<input type="hidden" name="_csrf" value="<?= FreshRSS_Auth::csrfToken() ?>" />
<div class="form-group">
<label class="group-name" for="id_tag"><?= _t('sub.tag.old_name') ?></label>
<div class="group-controls">
<select name="id_tag" id="id_tag"<?= $disabled?>>
<?php foreach ($this->tags as $tag): ?>
<option value="<?= $tag->id() ?>"><?= $tag->name() ?></option>
<?php endforeach; ?>
</select>
</div>
</div>
<div class="form-group">
<label class="group-name" for="rename_new_name"><?= _t('sub.tag.new_name') ?></label>
<div class="group-controls">
<input id="rename_new_name" name="name" type="text" autocomplete="off"<?= $disabled?>/>
</div>
</div>
<div class="form-group form-actions">
<div class="group-controls">
<?php if (!$disabled) { ?>
<button type="submit" class="btn btn-attention confirm"><?= _t('gen.action.rename') ?></button>
<?php } ?>
</div>
</div>
</form>
<h2><?= _t('sub.title.delete_label') ?></h2>
<form id="delete_tag" method="post" action="<?= _url('tag', 'delete') ?>" autocomplete="off">
<input type="hidden" name="_csrf" value="<?= FreshRSS_Auth::csrfToken() ?>" />
<div class="form-group">
<label class="group-name" for="id_tag_delete"><?= _t('sub.tag.name') ?></label>
<div class="group-controls">
<select name="id_tag" id="id_tag_delete"<?= $disabled?>>
<?php foreach ($this->tags as $tag): ?>
<option value="<?= $tag->id() ?>"><?= $tag->name() ?></option>
<?php endforeach; ?>
</select>
</div>
</div>
<div class="form-group form-actions">
<div class="group-controls">
<?php if (!$disabled) { ?>
<button type="submit" class="btn btn-attention confirm"><?= _t('gen.action.remove') ?></button>
<?php } ?>
</div>
</div>
</form>
<?php if (count($this->tags) > 0): ?>
<h2><?= _t('gen.action.manage') ?></h2>
<ul>
<?php foreach ($this->tags as $tag): ?>
<li><a href="<?= _url('tag', 'update', 'id', $tag->id()) ?>"><?= $tag->name() ?></a></li>
<?php endforeach; ?>
</ul>
<?php endif; ?>
</main>

View File

@@ -0,0 +1,69 @@
<?php
declare(strict_types=1);
/** @var FreshRSS_View $this */
if (!Minz_Request::paramBoolean('ajax')) {
$this->partial('aside_subscription');
}
if ($this->tag === null) {
throw new FreshRSS_Context_Exception('Tag not initialised!');
}
?>
<div class="post">
<h2>
<?= $this->tag->name() ?>
</h2>
<div>
<a href="<?= _url('index', 'index', 'get', 't_' . $this->tag->id()) ?>"><?= _i('link') ?> <?= _t('gen.action.filter') ?></a>
</div>
<form method="post" action="<?= _url('tag', 'update', 'id', $this->tag->id(), '#', 'slider') ?>" autocomplete="off">
<input type="hidden" name="_csrf" value="<?= FreshRSS_Auth::csrfToken() ?>" />
<legend><?= _t('sub.category.information') ?></legend>
<div class="form-group">
<label class="group-name" for="name"><?= _t('sub.tag.name') ?></label>
<div class="group-controls">
<input type="text" name="name" id="name" value="<?= $this->tag->name() ?>" />
</div>
</div>
<div class="form-group form-actions">
<div class="group-controls">
<button type="submit" class="btn btn-important"><?= _t('gen.action.submit') ?></button>
<button type="reset" class="btn"><?= _t('gen.action.cancel') ?></button>
</div>
</div>
<legend><?= _t('sub.feed.filteractions') ?></legend>
<div class="form-group">
<label class="group-name" for="filteractions_label"><?= _t('sub.tag.auto_label') ?></label>
<div class="group-controls">
<textarea name="filteractions_label" id="filteractions_label" class="w100"><?php
foreach ($this->tag->filtersAction('label') as $filterRead) {
echo $filterRead->getRawInput(), PHP_EOL;
}
?></textarea>
<p class="help"><?= _i('help') ?> <?= _t('sub.feed.filteractions.help') ?></p>
</div>
</div>
<div class="form-group form-actions">
<div class="group-controls">
<button type="submit" class="btn btn-important"><?= _t('gen.action.submit') ?></button>
<button type="reset" class="btn"><?= _t('gen.action.cancel') ?></button>
</div>
</div>
</form>
<h2><?= _t('sub.title.delete_label') ?></h2>
<form id="delete_tag" method="post" action="<?= _url('tag', 'delete', 'id_tag', $this->tag->id()) ?>">
<input type="hidden" name="_csrf" value="<?= FreshRSS_Auth::csrfToken() ?>" />
<div class="form-group form-actions">
<div class="group-controls">
<button type="submit" class="btn btn-attention confirm"><?= _t('gen.action.remove') ?></button>
</div>
</div>
</form>
</div>

View File

@@ -30,7 +30,7 @@ la machine sur laquelle est installée votre instance de FreshRSS.
Le script qui permet de mettre à jour les articles sappelle
*actualize_script.php* et se trouve dans le répertoire *app* de votre
instance de FreshRSS. La syntaxe des tâches planifiées ne sera pas expliqué
instance de FreshRSS. La syntaxe des tâches planifiées ne sera pas expliquée
ici, cependant voici [une introduction rapide à
crontab](http://www.adminschoice.com/crontab-quick-reference/) qui peut vous
aider.
@@ -205,48 +205,48 @@ the search field.
Il est possible dutiliser le champ de recherche pour raffiner les résultats :
* par ID de flux : `f:123` ou plusieurs flux (*ou*) : `f:123,234,345`
* par auteur : `author:nom` or `author:'nom composé'`
* par titre : `intitle:mot` or `intitle:'mot composé'`
* par URL: `inurl:mot` or `inurl:'mot composé'`
* par tag: `#tag`
* par texte libre : `mot` or `'mot composé'`
* by date of discovery, using the [ISO 8601 time interval format](http://en.wikipedia.org/wiki/ISO_8601#Time_intervals): `date:<date-interval>`
* From a specific day, or month, or year:
* par auteur : `author:nom` ou `author:'nom composé'`
* par titre : `intitle:mot` ou `intitle:'mot composé'`
* par URL : `inurl:mot` ou `inurl:'mot composé'`
* par tag : `#tag`
* par texte libre : `mot` ou `'mot composé'`
* par date dajout, en utilisant le [format ISO 8601 dintervalle entre deux dates](https://fr.wikipedia.org/wiki/ISO_8601#Intervalle_entre_deux_dates) : `date:<intervalle-de-dates>`
* Dun jour spécifique, ou mois, ou année :
* `date:2014-03-30`
* `date:2014-03` or `date:201403`
* `date:2014`
* From a specific time of a given day:
* Dune heure spécifiée dun jour donné :
* `date:2014-05-30T13`
* `date:2014-05-30T13:30`
* Between two given dates:
* Entre deux dates :
* `date:2014-02/2014-04`
* `date:2014-02--2014-04`
* `date:2014-02/04`
* `date:2014-02-03/05`
* `date:2014-02-03T22:00/22:15`
* `date:2014-02-03T22:00/15`
* After a given date:
* Après une date donnée :
* `date:2014-03/`
* Before a given date:
* Avant une date donnée :
* `date:/2014-03`
* For a specific duration after a given date:
* Pour une certaine durée après une date donnée :
* `date:2014-03/P1W`
* For a specific duration before a given date:
* Pour une certaine durée avant une date donnée :
* `date:P1W/2014-05-25T23:59:59`
* For the past duration before now (the trailing slash is optional):
* `date:P1Y/` or `date:P1Y` (past year)
* `date:P2M/` (past two months)
* `date:P3W/` (past three weeks)
* `date:P4D/` (past four days)
* `date:PT5H/` (past five hours)
* `date:PT30M/` (past thirty minutes)
* `date:PT90S/` (past ninety seconds)
* `date:P1DT1H/` (past one day and one hour)
* par date de publication, avec la même syntaxe: `pubdate:<date-interval>`
* Pour une certaine durée avant maintenant (la barre oblique finale est facultative) :
* `date:P1Y/` or `date:P1Y` (depuis un an)
* `date:P2M/` (depuis deux mois)
* `date:P3W/` (depuis trois semaines)
* `date:P4D/` (depuis quatre jours)
* `date:PT5H/` (depuis cinq heures)
* `date:PT30M/` (depuis trente minutes)
* `date:PT90S/` (depuis 90 secondes)
* `date:P1DT1H/` (depuis un jour et une heure)
* par date de publication, avec la même syntaxe : `pubdate:<date-interval>`
* par ID détiquette : `L:12` ou de plusieurs étiquettes : `L:12,13,14` ou avec nimporte quelle étiquette : `L:*`
* par nom détiquette : `label:étiquette`, `label:"mon étiquette"` ou dune étiquette parmis une liste (*ou*) : `labels:"mon étiquette,mon autre étiquette"`
* par nom détiquette : `label:étiquette`, `label:"mon étiquette"` ou dune étiquette parmi une liste (*ou*) : `labels:"mon étiquette,mon autre étiquette"`
* par plusieurs noms détiquettes (*et*) : `label:"mon étiquette" label:"mon autre étiquette"`
* par ID darticle (entrée) : `e:1639310674957894` ou de plusieurs articles (*ou*): `e:1639310674957894,1639310674957893`
* par ID darticle (entrée) : `e:1639310674957894` ou de plusieurs articles (*ou*) : `e:1639310674957894,1639310674957893`
* par nom de filtre utilisateur (recherche enregistrée) : `search:maRecherche`, `search:"Ma recherche"` ou par ID de recherche : `S:3`
* en interne, ces références sont remplacées par le filtre utilisateur correspondant dans lexpression de recherche

View File

@@ -8818,7 +8818,7 @@ msgstr ""
#. type: Bullet: '* '
#: en/./users/10_filter.md:93
msgid "by free-text: `keyword` or `'composed keyword'`"
msgstr "par texte libre : `mot` or `'mot composé'`"
msgstr "par texte libre : `mot` ou `'mot composé'`"
#. type: Bullet: '* '
#: en/./users/10_filter.md:93