From 0a66de3e6c17d4ac94ee8e13bd00ceaf64b1303e Mon Sep 17 00:00:00 2001
From: Yassine Doghri <yassine@doghri.fr>
Date: Tue, 25 May 2021 18:00:09 +0000
Subject: [PATCH] fix: set cache expiration to next note publish to show note
 on publication date

fix episode, podcast and persons forms + episode scheduling
---
 app/Config/Events.php                         | 22 +++++++++++++++++
 app/Controllers/Admin/EpisodeController.php   | 15 ++++++++----
 .../Admin/EpisodePersonController.php         |  6 ++---
 app/Controllers/Admin/PodcastController.php   |  4 ++--
 app/Controllers/PodcastController.php         |  9 ++++++-
 app/Entities/Episode.php                      |  3 ++-
 app/Entities/Person.php                       |  2 +-
 app/Language/en/Person.php                    | 22 ++++++++---------
 .../ActivityPub/Models/NoteModel.php          | 24 ++++++++++++++++++-
 app/Libraries/Analytics/AnalyticsTrait.php    |  2 +-
 app/Models/EpisodeModel.php                   |  6 ++---
 app/Models/PersonModel.php                    | 18 ++++++++++++--
 app/Views/admin/episode/persons.php           | 22 ++++++++---------
 app/Views/admin/podcast/persons.php           |  6 ++---
 14 files changed, 117 insertions(+), 44 deletions(-)

diff --git a/app/Config/Events.php b/app/Config/Events.php
index 59cc1e6c3b..dc9fe3a341 100644
--- a/app/Config/Events.php
+++ b/app/Config/Events.php
@@ -89,6 +89,8 @@ Events::on('on_note_add', function (Note $note): void {
     // same for other events below
     cache()
         ->deleteMatching("page_podcast#{$note->actor->podcast->id}*");
+    cache()
+        ->deleteMatching("podcast#{$note->actor->podcast->id}*");
 });
 
 Events::on('on_note_remove', function (Note $note): void {
@@ -108,6 +110,8 @@ Events::on('on_note_remove', function (Note $note): void {
 
     cache()
         ->deleteMatching("page_podcast#{$note->actor->podcast->id}*");
+    cache()
+        ->deleteMatching("podcast#{$note->actor->podcast->id}*");
     cache()
         ->deleteMatching("page_note#{$note->id}*");
 });
@@ -125,6 +129,8 @@ Events::on('on_note_reblog', function (Actor $actor, Note $note): void {
 
     cache()
         ->deleteMatching("page_podcast#{$note->actor->podcast->id}*");
+    cache()
+        ->deleteMatching("podcast#{$note->actor->podcast->id}*");
     cache()
         ->deleteMatching("page_note#{$note->id}*");
 
@@ -147,6 +153,8 @@ Events::on('on_note_undo_reblog', function (Note $reblogNote): void {
 
     cache()
         ->deleteMatching("page_podcast#{$note->actor->podcast->id}*");
+    cache()
+        ->deleteMatching("podcast#{$note->actor->podcast->id}*");
     cache()
         ->deleteMatching("page_note#{$note->id}*");
 
@@ -169,6 +177,8 @@ Events::on('on_reply_remove', function (Note $reply): void {
 
     cache()
         ->deleteMatching("page_podcast#{$note->actor->podcast->id}*");
+    cache()
+        ->deleteMatching("podcast#{$note->actor->podcast->id}*");
     cache()
         ->deleteMatching("page_note#{$note->id}*");
 });
@@ -182,6 +192,8 @@ Events::on('on_note_favourite', function (Actor $actor, Note $note): void {
 
     cache()
         ->deleteMatching("page_podcast#{$actor->podcast->id}*");
+    cache()
+        ->deleteMatching("podcast#{$actor->podcast->id}*");
     cache()
         ->deleteMatching("page_note#{$note->id}*");
 
@@ -199,6 +211,8 @@ Events::on('on_note_undo_favourite', function (Actor $actor, Note $note): void {
 
     cache()
         ->deleteMatching("page_podcast#{$actor->podcast->id}*");
+    cache()
+        ->deleteMatching("podcast#{$actor->podcast->id}*");
     cache()
         ->deleteMatching("page_note#{$note->id}*");
 
@@ -209,24 +223,32 @@ Events::on('on_note_undo_favourite', function (Actor $actor, Note $note): void {
 
 Events::on('on_block_actor', function (int $actorId): void {
     cache()->deleteMatching('page_podcast*');
+    cache()
+        ->deleteMatching('podcast*');
     cache()
         ->deleteMatching('page_note*');
 });
 
 Events::on('on_unblock_actor', function (int $actorId): void {
     cache()->deleteMatching('page_podcast*');
+    cache()
+        ->deleteMatching('podcast*');
     cache()
         ->deleteMatching('page_note*');
 });
 
 Events::on('on_block_domain', function (string $domainName): void {
     cache()->deleteMatching('page_podcast*');
+    cache()
+        ->deleteMatching('podcast*');
     cache()
         ->deleteMatching('page_note*');
 });
 
 Events::on('on_unblock_domain', function (string $domainName): void {
     cache()->deleteMatching('page_podcast*');
+    cache()
+        ->deleteMatching('podcast*');
     cache()
         ->deleteMatching('page_note*');
 });
diff --git a/app/Controllers/Admin/EpisodeController.php b/app/Controllers/Admin/EpisodeController.php
index 6d1b99fb85..b7c500420c 100644
--- a/app/Controllers/Admin/EpisodeController.php
+++ b/app/Controllers/Admin/EpisodeController.php
@@ -9,6 +9,7 @@
 namespace App\Controllers\Admin;
 
 use App\Entities\Episode;
+use App\Entities\Image;
 use App\Entities\Location;
 use App\Entities\Note;
 use App\Entities\Podcast;
@@ -118,6 +119,12 @@ class EpisodeController extends BaseController
                 ->with('errors', $this->validator->getErrors());
         }
 
+        $image = null;
+        $imageFile = $this->request->getFile('image');
+        if ($imageFile !== null && $imageFile->isValid()) {
+            $image = new Image($imageFile);
+        }
+
         $newEpisode = new Episode([
             'podcast_id' => $this->podcast->id,
             'title' => $this->request->getPost('title'),
@@ -125,7 +132,7 @@ class EpisodeController extends BaseController
             'guid' => '',
             'audio_file' => $this->request->getFile('audio_file'),
             'description_markdown' => $this->request->getPost('description'),
-            'image' => $this->request->getFile('image'),
+            'image' => $image,
             'location' => new Location($this->request->getPost('location_name'),),
             'transcript' => $this->request->getFile('transcript'),
             'chapters' => $this->request->getFile('chapters'),
@@ -253,9 +260,9 @@ class EpisodeController extends BaseController
             $this->episode->audio_file = $audioFile;
         }
 
-        $image = $this->request->getFile('image');
-        if ($image !== null && $image->isValid()) {
-            $this->episode->image = $image;
+        $imageFile = $this->request->getFile('image');
+        if ($imageFile !== null && $imageFile->isValid()) {
+            $this->episode->image = new Image($imageFile);
         }
 
         $transcriptChoice = $this->request->getPost('transcript-choice');
diff --git a/app/Controllers/Admin/EpisodePersonController.php b/app/Controllers/Admin/EpisodePersonController.php
index 0028456472..4257bef2d8 100644
--- a/app/Controllers/Admin/EpisodePersonController.php
+++ b/app/Controllers/Admin/EpisodePersonController.php
@@ -66,7 +66,7 @@ class EpisodePersonController extends BaseController
     public function attemptAdd(): RedirectResponse
     {
         $rules = [
-            'person' => 'required',
+            'persons' => 'required',
         ];
 
         if (! $this->validate($rules)) {
@@ -79,8 +79,8 @@ class EpisodePersonController extends BaseController
         (new PersonModel())->addEpisodePersons(
             $this->podcast->id,
             $this->episode->id,
-            $this->request->getPost('person'),
-            $this->request->getPost('person_group_role'),
+            $this->request->getPost('persons'),
+            $this->request->getPost('roles') ?? [],
         );
 
         return redirect()->back();
diff --git a/app/Controllers/Admin/PodcastController.php b/app/Controllers/Admin/PodcastController.php
index f48875fa90..47717682be 100644
--- a/app/Controllers/Admin/PodcastController.php
+++ b/app/Controllers/Admin/PodcastController.php
@@ -230,7 +230,7 @@ class PodcastController extends BaseController
         // set Podcast categories
         (new CategoryModel())->setPodcastCategories(
             (int) $newPodcastId,
-            $this->request->getPost('other_categories'),
+            $this->request->getPost('other_categories') ?? [],
         );
 
         // set interact as the newly created podcast actor
@@ -320,7 +320,7 @@ class PodcastController extends BaseController
         // set Podcast categories
         (new CategoryModel())->setPodcastCategories(
             $this->podcast->id,
-            $this->request->getPost('other_categories'),
+            $this->request->getPost('other_categories') ?? [],
         );
 
         $db->transComplete();
diff --git a/app/Controllers/PodcastController.php b/app/Controllers/PodcastController.php
index 680cb5fc74..b9b1da35aa 100644
--- a/app/Controllers/PodcastController.php
+++ b/app/Controllers/PodcastController.php
@@ -67,8 +67,15 @@ class PodcastController extends BaseController
                 helper('form');
                 return view('podcast/activity_authenticated', $data);
             }
+
+            $secondsToNextUnpublishedEpisode = (new EpisodeModel())->getSecondsToNextUnpublishedEpisode(
+                $this->podcast->id,
+            );
+
             return view('podcast/activity', $data, [
-                'cache' => DECADE,
+                'cache' => $secondsToNextUnpublishedEpisode
+                    ? $secondsToNextUnpublishedEpisode
+                    : DECADE,
                 'cache_name' => $cacheName,
             ]);
         }
diff --git a/app/Entities/Episode.php b/app/Entities/Episode.php
index 22bca73c87..b54c4376e8 100644
--- a/app/Entities/Episode.php
+++ b/app/Entities/Episode.php
@@ -192,7 +192,8 @@ class Episode extends Entity
             return new Image(null, $imagePath, $this->attributes['image_mimetype'],);
         }
 
-        return $this->podcast->image;
+        return $this->getPodcast()
+            ->image;
     }
 
     /**
diff --git a/app/Entities/Person.php b/app/Entities/Person.php
index c9f9f568fb..3e298f4d73 100644
--- a/app/Entities/Person.php
+++ b/app/Entities/Person.php
@@ -87,7 +87,7 @@ class Person extends Entity
             $this->roles = (new PersonModel())->getPersonRoles(
                 $this->id,
                 $this->attributes['podcast_id'],
-                $this->episode_id
+                $this->attributes['episode_id'] ?? null
             );
         }
 
diff --git a/app/Language/en/Person.php b/app/Language/en/Person.php
index 3d504b2ad4..1c7ce78eb2 100644
--- a/app/Language/en/Person.php
+++ b/app/Language/en/Person.php
@@ -36,12 +36,12 @@ return [
         'manage_section_subtitle' => 'Remove persons from this podcast',
         'add_section_title' => 'Add persons to this podcast',
         'add_section_subtitle' => 'You may pick several persons and roles.',
-        'person' => 'Persons',
-        'person_hint' =>
+        'persons' => 'Persons',
+        'persons_hint' =>
             'You may select one or several persons with the same roles. You need to create the persons first.',
-        'group_role' => 'Groups and roles',
-        'group_role_hint' =>
-            'You may select none, one or several groups and roles for a person.',
+        'roles' => 'Roles',
+        'roles_hint' =>
+            'You may select none, one or several roles for a person.',
         'submit_add' => 'Add person(s)',
         'remove' => 'Remove',
     ],
@@ -50,13 +50,13 @@ return [
         'manage_section_title' => 'Management',
         'manage_section_subtitle' => 'Remove persons from this episode',
         'add_section_title' => 'Add persons to this episode',
-        'add_section_subtitle' => 'You may pick several persons and roles',
-        'person' => 'Persons',
-        'person_hint' =>
+        'add_section_subtitle' => 'You may pick several persons and roles.',
+        'persons' => 'Persons',
+        'persons_hint' =>
             'You may select one or several persons with the same roles. You need to create the persons first.',
-        'group_role' => 'Groups and roles',
-        'group_role_hint' =>
-            'You may select none, one or several groups and roles for a person.',
+        'roles' => 'Roles',
+        'roles_hint' =>
+            'You may select none, one or several roles for a person.',
         'submit_add' => 'Add person(s)',
         'remove' => 'Remove',
     ],
diff --git a/app/Libraries/ActivityPub/Models/NoteModel.php b/app/Libraries/ActivityPub/Models/NoteModel.php
index 5d9b6d2a46..8eca3d44f8 100644
--- a/app/Libraries/ActivityPub/Models/NoteModel.php
+++ b/app/Libraries/ActivityPub/Models/NoteModel.php
@@ -138,13 +138,35 @@ class NoteModel extends UuidModel
                 ->orderBy('published_at', 'DESC')
                 ->findAll();
 
+            $secondsToNextUnpublishedNote = $this->getSecondsToNextUnpublishedNote($actorId,);
+
             cache()
-                ->save($cacheName, $found, DECADE);
+                ->save($cacheName, $found, $secondsToNextUnpublishedNote ? $secondsToNextUnpublishedNote : DECADE,);
         }
 
         return $found;
     }
 
+    /**
+     * Returns the timestamp difference in seconds between the next note to publish and the current timestamp. Returns
+     * false if there's no note to publish
+     */
+    public function getSecondsToNextUnpublishedNote(int $actorId): int | false
+    {
+        $result = $this->select('TIMESTAMPDIFF(SECOND, NOW(), `published_at`) as timestamp_diff',)
+            ->where([
+                'actor_id' => $actorId,
+            ])
+            ->where('`published_at` > NOW()', null, false)
+            ->orderBy('published_at', 'asc')
+            ->get()
+            ->getResultArray();
+
+        return count($result) !== 0
+            ? (int) $result[0]['timestamp_diff']
+            : false;
+    }
+
     /**
      * Retrieves all published replies for a given note. By default, it does not get replies from blocked actors.
      *
diff --git a/app/Libraries/Analytics/AnalyticsTrait.php b/app/Libraries/Analytics/AnalyticsTrait.php
index cef2021884..d2561ffa20 100644
--- a/app/Libraries/Analytics/AnalyticsTrait.php
+++ b/app/Libraries/Analytics/AnalyticsTrait.php
@@ -34,7 +34,7 @@ trait AnalyticsTrait
                     ? '- Direct -'
                     : parse_url($referer, PHP_URL_HOST);
             parse_str(parse_url($referer, PHP_URL_QUERY), $queries);
-            $keywords = array_key_exists('q', $queries) ? $queries['q'] : null;
+            $keywords = $queries['q'] ?? null;
 
             $procedureName = $db->prefixTable('analytics_website');
             $db->query("call {$procedureName}(?,?,?,?,?,?)", [
diff --git a/app/Models/EpisodeModel.php b/app/Models/EpisodeModel.php
index c6a231fe7f..8b0a28281f 100644
--- a/app/Models/EpisodeModel.php
+++ b/app/Models/EpisodeModel.php
@@ -316,12 +316,12 @@ class EpisodeModel extends Model
         cache()
             ->delete("podcast_episode#{$episode->id}");
         cache()
-            ->deleteMatching("podcast#{$episode->podcast_id}_episode#{$episode->id}*",);
+            ->deleteMatching("podcast#{$episode->podcast_id}_episode#{$episode->id}*");
         cache()
-            ->delete("podcast#{$episode->podcast_id}_episode-{$episode->slug}",);
+            ->delete("podcast#{$episode->podcast_id}_episode-{$episode->slug}");
 
         cache()
-            ->deleteMatching("page_podcast#{$episode->podcast_id}_activity*",);
+            ->deleteMatching("page_podcast#{$episode->podcast_id}_activity*");
         cache()
             ->deleteMatching("page_podcast#{$episode->podcast_id}_episode#{$episode->id}_*",);
         cache()
diff --git a/app/Models/PersonModel.php b/app/Models/PersonModel.php
index 293c122e1a..3101acda4d 100644
--- a/app/Models/PersonModel.php
+++ b/app/Models/PersonModel.php
@@ -107,7 +107,7 @@ class PersonModel extends Model
      */
     public function getPersonRoles(int $personId, int $podcastId, ?int $episodeId): array
     {
-        if ($episodeId) {
+        if ($episodeId !== null) {
             $cacheName = "podcast#{$podcastId}_episode#{$episodeId}_person#{$personId}_roles";
 
             if (! ($found = cache($cacheName))) {
@@ -212,7 +212,9 @@ class PersonModel extends Model
         $cacheName = "podcast#{$podcastId}_episode#{$episodeId}_persons";
         if (! ($found = cache($cacheName))) {
             $found = $this
-                ->select('persons.*, episodes_persons.podcast_id, episodes_persons.episode_id')
+                ->select(
+                    'persons.*, episodes_persons.podcast_id as podcast_id, episodes_persons.episode_id as episode_id'
+                )
                 ->distinct()
                 ->join('episodes_persons', 'persons.id = episodes_persons.person_id')
                 ->where('episodes_persons.episode_id', $episodeId)
@@ -321,6 +323,7 @@ class PersonModel extends Model
         }
 
         return $this->db->table('podcasts_persons')
+            ->ignore(true)
             ->insertBatch($data);
     }
 
@@ -331,6 +334,10 @@ class PersonModel extends Model
      */
     public function removePersonFromPodcast(int $podcastId, int $personId): string | bool
     {
+        cache()->deleteMatching("podcast#{$podcastId}_person#{$personId}*");
+        cache()
+            ->delete("podcast#{$podcastId}_persons");
+
         return $this->db->table('podcasts_persons')
             ->delete([
                 'podcast_id' => $podcastId,
@@ -353,6 +360,8 @@ class PersonModel extends Model
         array $groupsRoles
     ): bool | int {
         if ($personIds !== []) {
+            cache()
+                ->delete("podcast#{$podcastId}_episode#{$episodeId}_persons");
             (new EpisodeModel())->clearCache([
                 'id' => $episodeId,
             ]);
@@ -379,6 +388,7 @@ class PersonModel extends Model
                 }
             }
             return $this->db->table('episodes_persons')
+                ->ignore(true)
                 ->insertBatch($data);
         }
         return 0;
@@ -386,6 +396,10 @@ class PersonModel extends Model
 
     public function removePersonFromEpisode(int $podcastId, int $episodeId, int $personId): bool | string
     {
+        cache()->deleteMatching("podcast#{$podcastId}_episode#{$episodeId}_person#{$personId}*");
+        cache()
+            ->delete("podcast#{$podcastId}_episode#{$episodeId}_persons");
+
         return $this->db->table('episodes_persons')
             ->delete([
                 'podcast_id' => $podcastId,
diff --git a/app/Views/admin/episode/persons.php b/app/Views/admin/episode/persons.php
index 08d681de13..293129f524 100644
--- a/app/Views/admin/episode/persons.php
+++ b/app/Views/admin/episode/persons.php
@@ -96,29 +96,29 @@
 ) ?>
 
 <?= form_label(
-    lang('Person.episode_form.person'),
-    'person',
+    lang('Person.episode_form.persons'),
+    'persons',
     [],
-    lang('Person.episode_form.person_hint'),
+    lang('Person.episode_form.persons_hint'),
 ) ?>
-<?= form_multiselect('person[]', $personOptions, old('person', []), [
-    'id' => 'person',
+<?= form_multiselect('persons[]', $personOptions, old('persons', []), [
+    'id' => 'persons',
     'class' => 'form-select mb-4',
     'required' => 'required',
 ]) ?>
 
 <?= form_label(
-    lang('Person.episode_form.group_role'),
-    'group_role',
+    lang('Person.episode_form.roles'),
+    'roles',
     [],
-    lang('Person.episode_form.group_role_hint'),
+    lang('Person.episode_form.roles_hint'),
     true,
 ) ?>
 <?= form_multiselect(
-    'person_group_role[]',
+    'roles[]',
     $taxonomyOptions,
-    old('person_group_role', []),
-    ['id' => 'person_group_role', 'class' => 'form-select mb-4'],
+    old('roles', []),
+    ['id' => 'roles', 'class' => 'form-select mb-4'],
 ) ?>
         
     
diff --git a/app/Views/admin/podcast/persons.php b/app/Views/admin/podcast/persons.php
index 8edb6876a2..506cedfe41 100644
--- a/app/Views/admin/podcast/persons.php
+++ b/app/Views/admin/podcast/persons.php
@@ -94,10 +94,10 @@
 ) ?>
 
 <?= form_label(
-    lang('Person.podcast_form.person'),
-    'person',
+    lang('Person.podcast_form.persons'),
+    'persons',
     [],
-    lang('Person.podcast_form.person_hint'),
+    lang('Person.podcast_form.persons_hint'),
 ) ?>
 <?= form_multiselect('persons[]', $personOptions, old('persons', []), [
     'id' => 'persons',
-- 
GitLab