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 82b9552a..bc992195 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 889ab902..8f09f54a 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 b943ce8b..84e99cda 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 febf44e1..b28c9e2d 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 b263686e..64c394f0 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 9521c838..90a17a2d 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 8fbbee4d..aef52ed5 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 718bca1f..bd92e883 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 67335c10..eb0f30f1 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 cb4c819a..976c904b 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 8e3ee6df..5f1ba623 100644 --- a/themes/cp_admin/_message_block.php +++ b/themes/cp_admin/_message_block.php @@ -18,6 +18,10 @@ if (session()->has('message')): ?> +has('warning')): ?> + + + has('warnings')): ?>