diff --git a/app/Database/Migrations/2020-05-30-101500_add_podcasts.php b/app/Database/Migrations/2020-05-30-101500_add_podcasts.php index 82b9552abb4651c085b05506b49112dab1caf121..bc992195d9df008147eacdd4e4b7c529c91121e7 100644 --- a/app/Database/Migrations/2020-05-30-101500_add_podcasts.php +++ b/app/Database/Migrations/2020-05-30-101500_add_podcasts.php @@ -183,10 +183,6 @@ class AddPodcasts extends Migration 'updated_at' => [ 'type' => 'DATETIME', ], - 'deleted_at' => [ - 'type' => 'DATETIME', - 'null' => true, - ], ]); $this->forge->addPrimaryKey('id'); diff --git a/app/Database/Seeds/AuthSeeder.php b/app/Database/Seeds/AuthSeeder.php index 889ab902e8843b8908b4c31224a0865d32e3581e..8f09f54ad44d9c0dcd544fc72a8415271ce0b958 100644 --- a/app/Database/Seeds/AuthSeeder.php +++ b/app/Database/Seeds/AuthSeeder.php @@ -127,12 +127,6 @@ class AuthSeeder extends Seeder ], [ 'name' => 'delete', - 'description' => - 'Delete a podcast without removing it from database', - 'has_permission' => ['superadmin'], - ], - [ - 'name' => 'delete_permanently', 'description' => 'Delete any podcast from the database', 'has_permission' => ['superadmin'], ], diff --git a/app/Entities/Podcast.php b/app/Entities/Podcast.php index b943ce8b4a2dd7e5ae1add7ba71b03764a328853..84e99cda07931d730af26f59347bf9ba4cd9ff1c 100644 --- a/app/Entities/Podcast.php +++ b/app/Entities/Podcast.php @@ -81,7 +81,6 @@ use RuntimeException; * @property int $updated_by * @property Time $created_at; * @property Time $updated_at; - * @property Time|null $deleted_at; * * @property Episode[] $episodes * @property Person[] $persons diff --git a/app/Models/PodcastModel.php b/app/Models/PodcastModel.php index febf44e14013ef6df4f38b8fe6024b0279d1d934..b28c9e2d158e9718b36c9804e89f5da93f10b492 100644 --- a/app/Models/PodcastModel.php +++ b/app/Models/PodcastModel.php @@ -73,11 +73,6 @@ class PodcastModel extends Model */ protected $returnType = Podcast::class; - /** - * @var bool - */ - protected $useSoftDeletes = true; - /** * @var bool */ diff --git a/modules/Admin/Config/Routes.php b/modules/Admin/Config/Routes.php index b263686ee7aaac2ddde083f7427ffa2ed5e2403f..64c394f0bed07b34ad0f7bf0e76cf8362b35d934 100644 --- a/modules/Admin/Config/Routes.php +++ b/modules/Admin/Config/Routes.php @@ -127,6 +127,9 @@ $routes->group( 'as' => 'podcast-delete', 'filter' => 'permission:podcasts-delete', ]); + $routes->post('delete', 'PodcastController::attemptDelete/$1', [ + 'filter' => 'permission:podcasts-delete', + ]); $routes->group('persons', function ($routes): void { $routes->get('/', 'PodcastPersonController/$1', [ diff --git a/modules/Admin/Controllers/EpisodeController.php b/modules/Admin/Controllers/EpisodeController.php index 9521c8381d1da42cd41b61ff79ae5e2123a88b82..90a17a2d934ffc7765118d04c7a2602a63a27b3f 100644 --- a/modules/Admin/Controllers/EpisodeController.php +++ b/modules/Admin/Controllers/EpisodeController.php @@ -739,8 +739,6 @@ class EpisodeController extends BaseController ->with('error', lang('Episode.messages.deletePublishedEpisodeError')); } - $audio = $this->episode->audio; - $db = db_connect(); $db->transStart(); @@ -755,7 +753,7 @@ class EpisodeController extends BaseController ->with('errors', $episodeModel->errors()); } - $episodeMediaList = [$this->episode->transcript, $this->episode->chapters, $audio]; + $episodeMediaList = [$this->episode->transcript, $this->episode->chapters, $this->episode->audio]; //only delete episode cover if different from podcast's if ($this->episode->cover_id !== null) { @@ -775,6 +773,8 @@ class EpisodeController extends BaseController } } + $db->transComplete(); + $warnings = []; //remove episode media files from disk @@ -787,8 +787,6 @@ class EpisodeController extends BaseController } } - $db->transComplete(); - if ($warnings !== []) { return redirect() ->route('episode-list', [$this->podcast->id]) diff --git a/modules/Admin/Controllers/PodcastController.php b/modules/Admin/Controllers/PodcastController.php index 8fbbee4d9422081235b3828709877ec60430075b..aef52ed50bc730cf264f05343d62d553fc7175b9 100644 --- a/modules/Admin/Controllers/PodcastController.php +++ b/modules/Admin/Controllers/PodcastController.php @@ -21,6 +21,15 @@ use App\Models\PodcastModel; use CodeIgniter\Exceptions\PageNotFoundException; use CodeIgniter\HTTP\RedirectResponse; use Config\Services; +use Modules\Analytics\Models\AnalyticsPodcastByCountryModel; +use Modules\Analytics\Models\AnalyticsPodcastByEpisodeModel; +use Modules\Analytics\Models\AnalyticsPodcastByHourModel; +use Modules\Analytics\Models\AnalyticsPodcastByPlayerModel; +use Modules\Analytics\Models\AnalyticsPodcastByRegionModel; +use Modules\Analytics\Models\AnalyticsPodcastModel; +use Modules\Analytics\Models\AnalyticsWebsiteByBrowserModel; +use Modules\Analytics\Models\AnalyticsWebsiteByEntryPageModel; +use Modules\Analytics\Models\AnalyticsWebsiteByRefererModel; class PodcastController extends BaseController { @@ -420,10 +429,166 @@ class PodcastController extends BaseController ]); } - public function delete(): RedirectResponse + public function delete(): string { - (new PodcastModel())->delete($this->podcast->id); + helper(['form']); - return redirect()->route('podcast-list'); + $data = [ + 'podcast' => $this->podcast, + ]; + + replace_breadcrumb_params([ + 0 => $this->podcast->title, + ]); + return view('podcast/delete', $data); + } + + public function attemptDelete(): RedirectResponse + { + $rules = [ + 'understand' => 'required', + ]; + + if (! $this->validate($rules)) { + return redirect() + ->back() + ->withInput() + ->with('errors', $this->validator->getErrors()); + } + + $db = db_connect(); + + $db->transStart(); + + //delete podcast episodes + $podcastEpisodes = (new EpisodeModel())->where('podcast_id', $this->podcast->id) + ->findAll(); + + foreach ($podcastEpisodes as $podcastEpisode) { + $episodeModel = new EpisodeModel(); + + if (! $episodeModel->delete($podcastEpisode->id)) { + $db->transRollback(); + return redirect() + ->back() + ->withInput() + ->with('errors', $episodeModel->errors()); + } + + $episodeMediaList = [$podcastEpisode->transcript, $podcastEpisode->chapters, $podcastEpisode->audio]; + + //only delete episode cover if different from podcast's + if ($podcastEpisode->cover_id !== null) { + $episodeMediaList[] = $podcastEpisode->cover; + } + + foreach ($episodeMediaList as $episodeMedia) { + if ($episodeMedia !== null && ! $episodeMedia->delete()) { + $db->transRollback(); + return redirect() + ->back() + ->withInput() + ->with('error', lang('Podcast.messages.deleteEpisodeMediaError', [ + 'episode_slug' => $podcastEpisode->slug, + 'type' => $episodeMedia->type, + ])); + } + } + } + + //delete podcast + $podcastModel = new PodcastModel(); + + if (! $podcastModel->delete($this->podcast->id)) { + $db->transRollback(); + return redirect() + ->back() + ->withInput() + ->with('errors', $podcastModel->errors()); + } + + //delete podcast media + $podcastMediaList = [ + [ + 'type' => 'cover', + 'file' => $this->podcast->cover, + ], + [ + 'type' => 'banner', + 'file' => $this->podcast->banner, + ], + ]; + + foreach ($podcastMediaList as $podcastMedia) { + if ($podcastMedia['file'] !== null && ! $podcastMedia['file']->delete()) { + $db->transRollback(); + return redirect() + ->back() + ->withInput() + ->with('error', lang('Podcast.messages.deletePodcastMediaError', [ + 'type' => $podcastMedia['type'], + ])); + } + } + + //delete podcast actor + $actorModel = new ActorModel(); + + if (! $actorModel->delete($this->podcast->actor_id)) { + $db->transRollback(); + return redirect() + ->back() + ->withInput() + ->with('errors', $actorModel->errors()); + } + + //delete podcast analytics + $analyticsModels = [ + new AnalyticsPodcastModel(), + new AnalyticsPodcastByCountryModel(), + new AnalyticsPodcastByEpisodeModel(), + new AnalyticsPodcastByHourModel(), + new AnalyticsPodcastByPlayerModel(), + new AnalyticsPodcastByRegionModel(), + new AnalyticsWebsiteByBrowserModel(), + new AnalyticsWebsiteByEntryPageModel(), + new AnalyticsWebsiteByRefererModel(), + ]; + foreach ($analyticsModels as $analyticsModel) { + if (! $analyticsModel->where([ + 'podcast_id' => $this->podcast->id, + ])->delete()) { + $db->transRollback(); + return redirect() + ->back() + ->withInput() + ->with('errors', $analyticsModel->errors()); + } + } + + $db->transComplete(); + + //delete podcast media files and folder + $folder = 'podcasts/' . $this->podcast->handle; + + $mediaRoot = config('App') + ->mediaRoot . '/' . $folder; + + helper('filesystem'); + + if (! delete_files($mediaRoot) || ! rmdir($mediaRoot)) { + return redirect()->route('podcast-list') + ->with('message', lang('Podcast.messages.deleteSuccess', [ + 'podcast_handle' => $this->podcast->handle, + ])) + ->with('warning', lang('Podcast.messages.deletePodcastMediaFolderError', [ + 'folder_path' => $folder, + ])); + } + + return redirect()->route('podcast-list') + ->with('message', lang('Podcast.messages.deleteSuccess', [ + 'podcast_handle' => $this->podcast->handle, + ])); } } diff --git a/modules/Admin/Language/en/Episode.php b/modules/Admin/Language/en/Episode.php index 718bca1f933b651f4e5c467cc5e7952f639be5a1..bd92e883df1567cb3a7a2c8c9632375be2683875 100644 --- a/modules/Admin/Language/en/Episode.php +++ b/modules/Admin/Language/en/Episode.php @@ -63,7 +63,7 @@ return [ image {cover} audio {audio} other {media} - } file {file_path}. You must manually remove it from your disk.', + } file {file_path}. You may manually remove it from your disk.', 'sameSlugError' => 'An episode with the chosen slug already exists.', ], 'form' => [ diff --git a/modules/Admin/Language/en/Podcast.php b/modules/Admin/Language/en/Podcast.php index 67335c104face3c7339f7a274606553ad744e8ba..eb0f30f19c0d401af447d403a5890926a36afa29 100644 --- a/modules/Admin/Language/en/Podcast.php +++ b/modules/Admin/Language/en/Podcast.php @@ -26,6 +26,20 @@ return [ 'createSuccess' => 'Podcast has been successfully created!', 'editSuccess' => 'Podcast has been successfully updated!', 'importSuccess' => 'Podcast has been successfully imported!', + 'deleteSuccess' => 'Podcast @{podcast_handle} successfully deleted!', + 'deletePodcastMediaError' => 'Failed to delete podcast {type, select, + cover {cover} + banner {banner} + other {media} + }.', + 'deleteEpisodeMediaError' => 'Failed to delete podcast episode {episode_slug} {type, select, + transcript {transcript} + chapters {chapters} + image {cover} + audio {audio} + other {media} + }.', + 'deletePodcastMediaFolderError' => 'Failed to delete podcast media folder {folder_path}. You may manually remove it from your disk.', ], 'form' => [ 'identity_section_title' => 'Podcast identity', @@ -219,6 +233,12 @@ return [ 'film_reviews' => 'Film Reviews', 'tv_reviews' => 'TV Reviews', ], + 'delete_form' => [ + 'disclaimer' => + "Deleting the podcast will delete all episodes, media files, posts and analytics associated with it. This action is irreversible, you will not be able to retrieve them afterwards.", + 'understand' => 'I understand, I want the podcast to be permanently deleted', + 'submit' => 'Delete', + ], 'by' => 'By {publisher}', 'season' => 'Season {seasonNumber}', 'list_of_episodes_year' => '{year} episodes ({episodeCount})', diff --git a/modules/Auth/Database/Seeds/AuthSeeder.php b/modules/Auth/Database/Seeds/AuthSeeder.php index cb4c819aa15b19c56b9cd66e9357d43f64af0ccf..976c904b72433976d0452139658479f7f989768d 100644 --- a/modules/Auth/Database/Seeds/AuthSeeder.php +++ b/modules/Auth/Database/Seeds/AuthSeeder.php @@ -115,12 +115,6 @@ class AuthSeeder extends Seeder ], [ 'name' => 'delete', - 'description' => - 'Delete a podcast without removing it from database', - 'has_permission' => ['superadmin'], - ], - [ - 'name' => 'delete_permanently', 'description' => 'Delete any podcast from the database', 'has_permission' => ['superadmin'], ], diff --git a/themes/cp_admin/_message_block.php b/themes/cp_admin/_message_block.php index 8e3ee6df5c3c6316b3e7ae86044c620b94f60bf7..5f1ba623b56c2838bbfe4359aa2dea7e324cce2b 100644 --- a/themes/cp_admin/_message_block.php +++ b/themes/cp_admin/_message_block.php @@ -18,6 +18,10 @@ if (session()->has('message')): ?> </Alert> <?php endif; ?> +<?php if (session()->has('warning')): ?> + <Alert variant="warning" class="mb-4"><?= esc(session('warning')) ?></Alert> +<?php endif; ?> + <?php if (session()->has('warnings')): ?> <Alert variant="warning" class="mb-4"> <ul> diff --git a/themes/cp_admin/podcast/delete.php b/themes/cp_admin/podcast/delete.php new file mode 100644 index 0000000000000000000000000000000000000000..28251e149add6bf8347bb56bf6c310bf819026ee --- /dev/null +++ b/themes/cp_admin/podcast/delete.php @@ -0,0 +1,27 @@ +<?= $this->extend('_layout') ?> + +<?= $this->section('title') ?> +<?= lang('Podcast.delete') ?> +<?= $this->endSection() ?> + +<?= $this->section('pageTitle') ?> +<?= lang('Podcast.delete') ?> +<?= $this->endSection() ?> + +<?= $this->section('content') ?> + +<form action="<?= route_to('podcast-delete', $podcast->id) ?>" method="POST" class="flex flex-col w-full max-w-xl mx-auto"> +<?= csrf_field() ?> + +<Alert variant="danger" glyph="alert" class="font-semibold"><?= lang('Podcast.delete_form.disclaimer') ?></Alert> + +<Forms.Checkbox class="mt-2" name="understand" required="true" isChecked="false"><?= lang('Podcast.delete_form.understand') ?></Forms.Checkbox> + +<div class="self-end mt-4"> + <Button uri="<?= route_to('podcast-view', $podcast->id) ?>"><?= lang('Common.cancel') ?></Button> + <Button type="submit" variant="danger"><?= lang('Podcast.delete_form.submit') ?></Button> +</div> + +</form> + +<?= $this->endSection() ?> diff --git a/themes/cp_admin/podcast/edit.php b/themes/cp_admin/podcast/edit.php index 527e3544df2251782bba2e586da7e8ee2017ad7d..ef31830e9e6fbd401bec5d48717daff94d724983 100644 --- a/themes/cp_admin/podcast/edit.php +++ b/themes/cp_admin/podcast/edit.php @@ -244,9 +244,10 @@ </Forms.Toggler> </Forms.Section> -<Button variant="primary" type="submit" class="self-end"><?= lang('Podcast.form.submit_edit') ?></Button> </div> </form> +<Button class="mt-8" variant="danger" uri="<?= route_to('podcast-delete', $podcast->id) ?>" iconLeft="delete-bin"><?= lang('Podcast.delete') ?></Button> + <?= $this->endSection() ?>