From 5de224612d3f61d77b70676d142f0971812b4c37 Mon Sep 17 00:00:00 2001 From: Marien Fressinaud Date: Thu, 25 Jun 2026 08:15:16 +0200 Subject: [PATCH 1/2] fix: Fix grouping links by dates --- src/models/dao/Link.php | 9 +++++++-- src/utils/LinksTimeline.php | 13 +++++++++++-- tests/bootstrap.php | 3 +++ tests/utils/LinksTimelineTest.php | 6 +++--- 4 files changed, 24 insertions(+), 7 deletions(-) diff --git a/src/models/dao/Link.php b/src/models/dao/Link.php index 47bdd716e..9cebc8d2b 100644 --- a/src/models/dao/Link.php +++ b/src/models/dao/Link.php @@ -353,8 +353,13 @@ public static function listComputedByCollectionId( $date_clause = ''; if ($options['published_date'] !== null) { - $date_clause = "AND date_trunc('day', lc.created_at) = :published_date"; - $parameters[':published_date'] = $options['published_date']->format('Y-m-d'); + $date_clause = "AND lc.created_at >= :published_start AND lc.created_at <= :published_end"; + + $start = $options['published_date']->modify('00:00:00'); + $end = $start->modify('23:59:59'); + + $parameters[':published_start'] = $start->format(Database\Column::DATETIME_FORMAT); + $parameters[':published_end'] = $end->format(Database\Column::DATETIME_FORMAT); } $source = $options['source']; diff --git a/src/utils/LinksTimeline.php b/src/utils/LinksTimeline.php index 999c4ec7e..c7fd18b50 100644 --- a/src/utils/LinksTimeline.php +++ b/src/utils/LinksTimeline.php @@ -25,11 +25,20 @@ public function __construct(array $links) continue; } - $date_key = $link->published_at->format('Y-m-d'); + // The key is formatted using translateDate (using IntlDateFormatter + // internally) because the output can differ from the format method + // of DateTimeInterface. This happens because of the timezone. For + // instance, a "2026-06-24 22:30:00" DateTime in UTC+2 would be + // formatted "2026-06-24" with DateTime::format, and "2026-06-25" + // with TwigExtension::translateDate. As we display the dates in + // the interface using the latter, the key MUST be coherent. + $date_key = \Minz\Template\TwigExtension::translateDate($link->published_at, 'Y-MM-dd'); + if (isset($this->dates_groups[$date_key])) { $date_group = $this->dates_groups[$date_key]; } else { - $date_group = new LinksTimeline\DateGroup($link->published_at); + $date = new \DateTimeImmutable($date_key); + $date_group = new LinksTimeline\DateGroup($date); $this->dates_groups[$date_key] = $date_group; } diff --git a/tests/bootstrap.php b/tests/bootstrap.php index d5dbc40ee..0950d8b9c 100644 --- a/tests/bootstrap.php +++ b/tests/bootstrap.php @@ -12,6 +12,9 @@ \Minz\Engine::startSession(); +// Make sure to set the locale. +\App\utils\Locale::setCurrentLocale('en_GB'); + \Minz\Database::reset(); $schema = @file_get_contents(\App\Configuration::$schema_path); diff --git a/tests/utils/LinksTimelineTest.php b/tests/utils/LinksTimelineTest.php index ff1f00590..2d9e1a391 100644 --- a/tests/utils/LinksTimelineTest.php +++ b/tests/utils/LinksTimelineTest.php @@ -15,7 +15,7 @@ public function testConstructGroupsLinksByDates(): void $link2 = LinkFactory::create(); $link2->published_at = new \DateTimeImmutable('2024-03-22 12:00'); $link3 = LinkFactory::create(); - $link3->published_at = new \DateTimeImmutable('2024-03-20 12:00'); + $link3->published_at = new \DateTimeImmutable('2024-03-21 00:00+0200'); $links = [$link1, $link2, $link3]; $timeline = new LinksTimeline($links); @@ -25,11 +25,11 @@ public function testConstructGroupsLinksByDates(): void $this->assertSame(2, count($dates_groups)); $group1 = $dates_groups['2024-03-20']; $group2 = $dates_groups['2024-03-22']; - $this->assertEquals($link1->published_at, $group1->date); + $this->assertEquals($link1->published_at->modify('00:00:00'), $group1->date); $this->assertSame(2, count($group1->links)); $this->assertSame($link1->id, $group1->links[0]->id); $this->assertSame($link3->id, $group1->links[1]->id); - $this->assertEquals($link2->published_at, $group2->date); + $this->assertEquals($link2->published_at->modify('00:00:00'), $group2->date); $this->assertSame(1, count($group2->links)); $this->assertSame($link2->id, $group2->links[0]->id); } From 64c56402773e9551b100f98883d85c79f83a20a9 Mon Sep 17 00:00:00 2001 From: Marien Fressinaud Date: Thu, 25 Jun 2026 11:32:57 +0200 Subject: [PATCH 2/2] fix: Forget URL status when deleting a link --- src/controllers/Links.php | 1 + src/models/UrlStatus.php | 37 ++++++++++++++++++++++++++++++++- src/models/User.php | 14 +++++++++++++ tests/controllers/LinksTest.php | 4 ++++ 4 files changed, 55 insertions(+), 1 deletion(-) diff --git a/src/controllers/Links.php b/src/controllers/Links.php index 911e9a8da..ea52f8ae7 100644 --- a/src/controllers/Links.php +++ b/src/controllers/Links.php @@ -331,6 +331,7 @@ public function delete(Request $request): Response return Response::found($from); } + $user->unmark($link); $link->remove(); return Response::found($from); diff --git a/src/models/UrlStatus.php b/src/models/UrlStatus.php index a9dd92a7d..84f018bea 100644 --- a/src/models/UrlStatus.php +++ b/src/models/UrlStatus.php @@ -171,7 +171,7 @@ public static function markAsReadLater(User $user, Link|array $links): void } /** - * Mark the links as dismissed and remove them from the journal of the user. + * Mark the links as dismissed for the user. * * @param Link|Link[] $links */ @@ -213,6 +213,41 @@ public static function markAsDismissed(User $user, Link|array $links): void $statement->execute($values); } + /** + * Unmark the links for the user (aka remove the corresponding URL statuses). + * + * @param Link|Link[] $links + */ + public static function unmark(User $user, Link|array $links): void + { + if ($links instanceof Link) { + $links = [$links]; + } + + if (!$links) { + return; + } + + $values_as_question_marks = []; + $values = [$user->id]; + + foreach ($links as $link) { + $values_as_question_marks[] = '?'; + $values[] = $link->url_hash; + } + $values_placeholder = implode(", ", $values_as_question_marks); + + $sql = <<prepare($sql); + $statement->execute($values); + } + /** * @see dao\BulkQueries::bulkInsertOnConflict * diff --git a/src/models/User.php b/src/models/User.php index db88382eb..0e3e9f7c5 100644 --- a/src/models/User.php +++ b/src/models/User.php @@ -730,6 +730,20 @@ public function markAsDismissed(Link|array $links): void $news->removeLinks($links, sync_publication_frequency: false); } + /** + * Unmark the links for the user (aka remove the corresponding URL statuses). + * + * @param Link|Link[] $links + */ + public function unmark(Link|array $links): void + { + if ($links instanceof Link) { + $links = [$links]; + } + + UrlStatus::unmark($this, $links); + } + /** * Set the user password. */ diff --git a/tests/controllers/LinksTest.php b/tests/controllers/LinksTest.php index 1df00d1df..ccd0073cc 100644 --- a/tests/controllers/LinksTest.php +++ b/tests/controllers/LinksTest.php @@ -956,6 +956,9 @@ public function testDeleteDeletesLinkAndRedirects(): void $link = LinkFactory::create([ 'user_id' => $user->id, ]); + $user->markAsRead($link); + + $this->assertTrue($user->hasRead($link)); $response = $this->appRun('POST', "/links/{$link->id}/delete", [ 'csrf_token' => $this->csrfToken(forms\links\DeleteLink::class), @@ -963,6 +966,7 @@ public function testDeleteDeletesLinkAndRedirects(): void $this->assertResponseCode($response, 302, '/'); $this->assertFalse(models\Link::exists($link->id)); + $this->assertFalse($user->hasRead($link)); } public function testDeleteRedirectsIfNotConnected(): void