diff --git a/app/Database/Migrations/2020-06-05-170000_add_episodes.php b/app/Database/Migrations/2020-06-05-170000_add_episodes.php
index 522400a8..5804c460 100644
--- a/app/Database/Migrations/2020-06-05-170000_add_episodes.php
+++ b/app/Database/Migrations/2020-06-05-170000_add_episodes.php
@@ -147,10 +147,6 @@ class AddEpisodes extends Migration
'updated_at' => [
'type' => 'DATETIME',
],
- 'deleted_at' => [
- 'type' => 'DATETIME',
- 'null' => true,
- ],
]);
$this->forge->addPrimaryKey('id');
$this->forge->addUniqueKey(['podcast_id', 'slug']);
diff --git a/app/Database/Seeds/AuthSeeder.php b/app/Database/Seeds/AuthSeeder.php
index dff5c8eb..889ab902 100644
--- a/app/Database/Seeds/AuthSeeder.php
+++ b/app/Database/Seeds/AuthSeeder.php
@@ -207,12 +207,6 @@ class AuthSeeder extends Seeder
],
[
'name' => 'delete',
- 'description' =>
- 'Delete an episode of a podcast without removing it from the database',
- 'has_permission' => ['podcast_admin'],
- ],
- [
- 'name' => 'delete_permanently',
'description' =>
'Delete all occurrences of an episode of a podcast from the database',
'has_permission' => ['podcast_admin'],
diff --git a/app/Entities/Episode.php b/app/Entities/Episode.php
index c706585c..32223685 100644
--- a/app/Entities/Episode.php
+++ b/app/Entities/Episode.php
@@ -78,7 +78,6 @@ use RuntimeException;
* @property Time|null $published_at;
* @property Time $created_at;
* @property Time $updated_at;
- * @property Time|null $deleted_at;
*
* @property Person[] $persons;
* @property Soundbite[] $soundbites;
diff --git a/app/Entities/Media/BaseMedia.php b/app/Entities/Media/BaseMedia.php
index a45ede67..2acf221d 100644
--- a/app/Entities/Media/BaseMedia.php
+++ b/app/Entities/Media/BaseMedia.php
@@ -10,6 +10,8 @@ declare(strict_types=1);
namespace App\Entities\Media;
+use App\Models\MediaModel;
+use CodeIgniter\Database\BaseResult;
use CodeIgniter\Entity\Entity;
use CodeIgniter\Files\File;
@@ -101,9 +103,15 @@ class BaseMedia extends Entity
return $this;
}
- public function deleteFile(): void
+ public function deleteFile(): bool
{
helper('media');
- unlink(media_path($this->file_path));
+ return unlink(media_path($this->file_path));
+ }
+
+ public function delete(): bool|BaseResult
+ {
+ $mediaModel = new MediaModel();
+ return $mediaModel->delete($this->id, true);
}
}
diff --git a/app/Entities/Media/Image.php b/app/Entities/Media/Image.php
index e57426c0..c1524806 100644
--- a/app/Entities/Media/Image.php
+++ b/app/Entities/Media/Image.php
@@ -69,11 +69,13 @@ class Image extends BaseMedia
return $this;
}
- public function deleteFile(): void
+ public function deleteFile(): bool
{
- parent::deleteFile();
+ if (parent::deleteFile()) {
+ return $this->deleteSizes();
+ }
- $this->deleteSizes();
+ return false;
}
public function saveSizes(): void
@@ -89,12 +91,16 @@ class Image extends BaseMedia
}
}
- private function deleteSizes(): void
+ private function deleteSizes(): bool
{
// delete all derived sizes
foreach (array_keys($this->sizes) as $name) {
$pathProperty = $name . '_path';
- unlink(media_path($this->{$pathProperty}));
+ if (! unlink(media_path($this->{$pathProperty}))) {
+ return false;
+ }
}
+
+ return true;
}
}
diff --git a/app/Entities/Media/Transcript.php b/app/Entities/Media/Transcript.php
index a0dbebdf..988e2488 100644
--- a/app/Entities/Media/Transcript.php
+++ b/app/Entities/Media/Transcript.php
@@ -63,12 +63,16 @@ class Transcript extends BaseMedia
return $this;
}
- public function deleteFile(): void
+ public function deleteFile(): bool
{
- parent::deleteFile();
+ if (! parent::deleteFile()) {
+ return false;
+ }
if ($this->json_path) {
- unlink($this->json_path);
+ return unlink($this->json_path);
}
+
+ return true;
}
}
diff --git a/app/Entities/Podcast.php b/app/Entities/Podcast.php
index a54b7f62..b943ce8b 100644
--- a/app/Entities/Podcast.php
+++ b/app/Entities/Podcast.php
@@ -327,6 +327,18 @@ class Podcast extends Entity
return $this->episodes;
}
+ /**
+ * Returns the podcast's episodes count
+ */
+ public function getEpisodesCount(): int|string
+ {
+ if ($this->id === null) {
+ throw new RuntimeException('Podcast must be created before getting number of episodes.');
+ }
+
+ return (new EpisodeModel())->getPodcastEpisodesCount($this->id);
+ }
+
/**
* Returns the podcast's persons
*
diff --git a/app/Models/EpisodeModel.php b/app/Models/EpisodeModel.php
index 573b6e76..3b20c07b 100644
--- a/app/Models/EpisodeModel.php
+++ b/app/Models/EpisodeModel.php
@@ -94,11 +94,6 @@ class EpisodeModel extends Model
*/
protected $returnType = Episode::class;
- /**
- * @var bool
- */
- protected $useSoftDeletes = true;
-
/**
* @var bool
*/
@@ -249,6 +244,18 @@ class EpisodeModel extends Model
return $found;
}
+ /**
+ * Returns number of episodes of a podcast
+ */
+ public function getPodcastEpisodesCount(int $podcastId): int|string
+ {
+ return $this
+ ->where([
+ 'podcast_id' => $podcastId,
+ ])
+ ->countAllResults();
+ }
+
/**
* Returns the timestamp difference in seconds between the next episode to publish and the current timestamp Returns
* false if there's no episode to publish
diff --git a/app/Views/Components/Button.php b/app/Views/Components/Button.php
index 79c96ac5..2c2429b3 100644
--- a/app/Views/Components/Button.php
+++ b/app/Views/Components/Button.php
@@ -38,6 +38,7 @@ class Button extends Component
'danger' => 'text-white bg-red-600 hover:bg-red-700',
'warning' => 'text-black bg-yellow-500 hover:bg-yellow-600',
'info' => 'text-white bg-blue-500 hover:bg-blue-600',
+ 'disabled' => 'text-black bg-gray-300 cursor-not-allowed',
];
$sizeClass = [
diff --git a/modules/Admin/Controllers/EpisodeController.php b/modules/Admin/Controllers/EpisodeController.php
index 6b1373cf..9521c838 100644
--- a/modules/Admin/Controllers/EpisodeController.php
+++ b/modules/Admin/Controllers/EpisodeController.php
@@ -131,6 +131,18 @@ class EpisodeController extends BaseController
->with('errors', $this->validator->getErrors());
}
+ if ((new EpisodeModel())
+ ->where([
+ 'slug' => $this->request->getPost('slug'),
+ 'podcast_id' => $this->podcast->id,
+ ])
+ ->first()) {
+ return redirect()
+ ->back()
+ ->withInput()
+ ->with('error', lang('Episode.messages.sameSlugError'));
+ }
+
$db = db_connect();
$db->transStart();
@@ -681,6 +693,11 @@ class EpisodeController extends BaseController
->with('errors', $episodeModel->errors());
}
+ // set podcast is_published_on_hubs to false to trigger websub push
+ (new PodcastModel())->update($this->episode->podcast->id, [
+ 'is_published_on_hubs' => false,
+ ]);
+
$db->transComplete();
return redirect()->route('episode-view', [$this->podcast->id, $this->episode->id]);
@@ -715,43 +732,74 @@ class EpisodeController extends BaseController
->with('errors', $this->validator->getErrors());
}
+ if ($this->episode->published_at !== null) {
+ return redirect()
+ ->back()
+ ->withInput()
+ ->with('error', lang('Episode.messages.deletePublishedEpisodeError'));
+ }
+
+ $audio = $this->episode->audio;
+
$db = db_connect();
$db->transStart();
- $allPostsLinkedToEpisode = (new PostModel())
- ->where([
- 'episode_id' => $this->episode->id,
- ])
- ->findAll();
- foreach ($allPostsLinkedToEpisode as $post) {
- (new PostModel())->removePost($post);
+ $episodeModel = new EpisodeModel();
+
+ if (! $episodeModel->delete($this->episode->id)) {
+ $db->transRollback();
+ return redirect()
+ ->back()
+ ->withInput()
+ ->with('errors', $episodeModel->errors());
}
- // set podcast is_published_on_hubs to false to trigger websub push
- (new PodcastModel())->update($this->episode->podcast->id, [
- 'is_published_on_hubs' => false,
- ]);
+ $episodeMediaList = [$this->episode->transcript, $this->episode->chapters, $audio];
- $episodeModel = new EpisodeModel();
- if ($this->episode->published_at !== null) {
- // if episode is published, set episode published_at to null to unpublish before deletion
- $this->episode->published_at = null;
+ //only delete episode cover if different from podcast's
+ if ($this->episode->cover_id !== null) {
+ $episodeMediaList[] = $this->episode->cover;
+ }
- if (! $episodeModel->update($this->episode->id, $this->episode)) {
+ //delete episode media records from database
+ foreach ($episodeMediaList as $episodeMedia) {
+ if ($episodeMedia !== null && ! $episodeMedia->delete()) {
$db->transRollback();
return redirect()
->back()
->withInput()
- ->with('errors', $episodeModel->errors());
+ ->with('error', lang('Episode.messages.deleteError', [
+ 'type' => $episodeMedia->type,
+ ]));
}
}
- $episodeModel->delete($this->episode->id);
+ $warnings = [];
+
+ //remove episode media files from disk
+ foreach ($episodeMediaList as $episodeMedia) {
+ if ($episodeMedia !== null && ! $episodeMedia->deleteFile()) {
+ $warnings[] = lang('Episode.messages.deleteFileError', [
+ 'type' => $episodeMedia->type,
+ 'file_path' => $episodeMedia->file_path,
+ ]);
+ }
+ }
$db->transComplete();
- return redirect()->route('episode-list', [$this->podcast->id]);
+ if ($warnings !== []) {
+ return redirect()
+ ->route('episode-list', [$this->podcast->id])
+ ->with('message', lang('Episode.messages.deleteSuccess'))
+ ->with('warnings', $warnings);
+ }
+
+ return redirect()->route('episode-list', [$this->podcast->id])->with(
+ 'message',
+ lang('Episode.messages.deleteSuccess')
+ );
}
public function embed(): string
diff --git a/modules/Admin/Language/en/Episode.php b/modules/Admin/Language/en/Episode.php
index 82ed40dc..718bca1f 100644
--- a/modules/Admin/Language/en/Episode.php
+++ b/modules/Admin/Language/en/Episode.php
@@ -47,6 +47,24 @@ return [
'createSuccess' => 'Episode has been successfully created!',
'editSuccess' => 'Episode has been successfully updated!',
'publishCancelSuccess' => 'Episode publication successfully cancelled!',
+ 'unpublishBeforeDeleteTip' => 'You must unpublish the episode before deleting it.',
+ 'deletePublishedEpisodeError' => 'Please unpublish the episode before deleting it.',
+ 'deleteSuccess' => 'Episode successfully deleted!',
+ 'deleteError' => 'Failed to delete episode {type, select,
+ transcript {transcript}
+ chapters {chapters}
+ image {cover}
+ audio {audio}
+ other {media}
+ }.',
+ 'deleteFileError' => 'Failed to delete {type, select,
+ transcript {transcript}
+ chapters {chapters}
+ image {cover}
+ audio {audio}
+ other {media}
+ } file {file_path}. You must manually remove it from your disk.',
+ 'sameSlugError' => 'An episode with the chosen slug already exists.',
],
'form' => [
'file_size_error' =>
@@ -147,7 +165,7 @@ return [
],
'delete_form' => [
'disclaimer' =>
- "Deleting the episode will delete all the posts associated with it and remove it from the podcast's RSS feed.",
+ "Deleting the episode will delete all media files, comments, video clips and soundbites associated with it.",
'understand' => 'I understand, I want to delete the episode',
'submit' => 'Delete',
],
diff --git a/modules/Admin/Language/fr/Episode.php b/modules/Admin/Language/fr/Episode.php
index ee1ba07a..4b8d039f 100644
--- a/modules/Admin/Language/fr/Episode.php
+++ b/modules/Admin/Language/fr/Episode.php
@@ -147,7 +147,7 @@ return [
],
'delete_form' => [
'disclaimer' =>
- "Supprimer l’épisode supprimera toutes les publications qui lui sont associées et le retirera du flux RSS du podcast.",
+ "Supprimer l’épisode supprimera tous les fichiers multimédia, commentaires, extraits vidéos et extraits sonores qui lui sont associés.",
'understand' => 'Je comprends, je veux supprimer l’épisode',
'submit' => 'Supprimer',
],
diff --git a/modules/Auth/Database/Seeds/AuthSeeder.php b/modules/Auth/Database/Seeds/AuthSeeder.php
index 87e2bfcd..cb4c819a 100644
--- a/modules/Auth/Database/Seeds/AuthSeeder.php
+++ b/modules/Auth/Database/Seeds/AuthSeeder.php
@@ -195,12 +195,6 @@ class AuthSeeder extends Seeder
],
[
'name' => 'delete',
- 'description' =>
- 'Delete an episode of a podcast without removing it from the database',
- 'has_permission' => ['podcast_admin'],
- ],
- [
- 'name' => 'delete_permanently',
'description' =>
'Delete all occurrences of an episode of a podcast from the database',
'has_permission' => ['podcast_admin'],
diff --git a/modules/WebSub/Database/Migrations/2022-03-07-180000_add_is_published_on_hubs_to_podcasts.php b/modules/WebSub/Database/Migrations/2022-03-07-180000_add_is_published_on_hubs_to_podcasts.php
index 523ffdfd..fb3d68ca 100644
--- a/modules/WebSub/Database/Migrations/2022-03-07-180000_add_is_published_on_hubs_to_podcasts.php
+++ b/modules/WebSub/Database/Migrations/2022-03-07-180000_add_is_published_on_hubs_to_podcasts.php
@@ -28,8 +28,6 @@ class AddIsPublishedOnHubsToPodcasts extends Migration
public function down(): void
{
- $prefix = $this->db->getPrefix();
-
- $this->forge->dropColumn($prefix . 'podcasts', 'is_published_on_hubs');
+ $this->forge->dropColumn('podcasts', 'is_published_on_hubs');
}
}
diff --git a/modules/WebSub/Database/Migrations/2022-03-07-181500_add_is_published_on_hubs_to_episodes.php b/modules/WebSub/Database/Migrations/2022-03-07-181500_add_is_published_on_hubs_to_episodes.php
index 3bb71966..dfe92a08 100644
--- a/modules/WebSub/Database/Migrations/2022-03-07-181500_add_is_published_on_hubs_to_episodes.php
+++ b/modules/WebSub/Database/Migrations/2022-03-07-181500_add_is_published_on_hubs_to_episodes.php
@@ -28,8 +28,6 @@ class AddIsPublishedOnHubsToEpisodes extends Migration
public function down(): void
{
- $prefix = $this->db->getPrefix();
-
- $this->forge->dropColumn($prefix . 'episodes', 'is_published_on_hubs');
+ $this->forge->dropColumn('episodes', 'is_published_on_hubs');
}
}
diff --git a/themes/cp_admin/_message_block.php b/themes/cp_admin/_message_block.php
index 1504aa37..8e3ee6df 100644
--- a/themes/cp_admin/_message_block.php
+++ b/themes/cp_admin/_message_block.php
@@ -17,3 +17,13 @@ if (session()->has('message')): ?>
+
+has('warnings')): ?>
+
+
+
+ - = esc($warning) ?>
+
+
+
+
diff --git a/themes/cp_admin/episode/_card.php b/themes/cp_admin/episode/_card.php
index 543d45e0..0d675fe9 100644
--- a/themes/cp_admin/episode/_card.php
+++ b/themes/cp_admin/episode/_card.php
@@ -11,7 +11,7 @@
-
+ ];
+ } else {
+ $label = lang('Episode.delete');
+ $icon = icon('forbid', 'mr-2');
+ $title = lang('Episode.messages.unpublishBeforeDeleteTip');
+ $items[] = [
+ 'type' => 'html',
+ 'content' => esc(<<{$icon}{$label}
+ CODE_SAMPLE),
+ ];
+ } ?>
+
\ No newline at end of file
diff --git a/themes/cp_admin/episode/edit.php b/themes/cp_admin/episode/edit.php
index 5611b60b..9191635d 100644
--- a/themes/cp_admin/episode/edit.php
+++ b/themes/cp_admin/episode/edit.php
@@ -278,7 +278,11 @@
-
+published_at === null): ?>
+
+
+
+
= $this->endSection() ?>
diff --git a/themes/cp_admin/episode/list.php b/themes/cp_admin/episode/list.php
index b491ea96..a7d2762b 100644
--- a/themes/cp_admin/episode/list.php
+++ b/themes/cp_admin/episode/list.php
@@ -73,50 +73,63 @@
[
'header' => lang('Episode.list.actions'),
'cell' => function ($episode, $podcast) {
+ $items = [
+ [
+ 'type' => 'link',
+ 'title' => lang('Episode.go_to_page'),
+ 'uri' => route_to('episode', esc($podcast->handle), esc($episode->slug)),
+ ],
+ [
+ 'type' => 'link',
+ 'title' => lang('Episode.edit'),
+ 'uri' => route_to('episode-edit', $podcast->id, $episode->id),
+ ],
+ [
+ 'type' => 'link',
+ 'title' => lang('Episode.embed.title'),
+ 'uri' => route_to('embed-add', $podcast->id, $episode->id),
+ ],
+ [
+ 'type' => 'link',
+ 'title' => lang('Person.persons'),
+ 'uri' => route_to('episode-persons-manage', $podcast->id, $episode->id),
+ ],
+ [
+ 'type' => 'link',
+ 'title' => lang('VideoClip.list.title'),
+ 'uri' => route_to('video-clips-list', $episode->podcast->id, $episode->id),
+ ],
+ [
+ 'type' => 'link',
+ 'title' => lang('Soundbite.list.title'),
+ 'uri' => route_to('soundbites-list', $podcast->id, $episode->id),
+ ],
+ [
+ 'type' => 'separator',
+ ],
+ ];
+ if ($episode->published_at === null) {
+ $items[] = [
+ 'type' => 'link',
+ 'title' => lang('Episode.delete'),
+ 'uri' => route_to('episode-delete', $podcast->id, $episode->id),
+ 'class' => 'font-semibold text-red-600',
+ ];
+ } else {
+ $label = lang('Episode.delete');
+ $icon = icon('forbid');
+ $title = lang('Episode.messages.unpublishBeforeDeleteTip');
+ $items[] = [
+ 'type' => 'html',
+ 'content' => esc(<<{$icon}{$label}
+ CODE_SAMPLE),
+ ];
+ }
return '' .
- '';
+ '';
},
],
],
diff --git a/themes/cp_admin/podcast/_sidebar.php b/themes/cp_admin/podcast/_sidebar.php
index dadafde8..619e1f4c 100644
--- a/themes/cp_admin/podcast/_sidebar.php
+++ b/themes/cp_admin/podcast/_sidebar.php
@@ -33,7 +33,13 @@ $podcastNavigation = [
'platforms-funding',
],
],
-]; ?>
+];
+
+$counts = [
+ 'episode-list' => $podcast->getEpisodesCount(),
+];
+
+?>