Skip to content
Snippets Groups Projects
EpisodeController.php 34.9 KiB
Newer Older
  • Learn to ignore specific revisions
  •  * @copyright  2020 Ad Aures
    
     * @license    https://www.gnu.org/licenses/agpl-3.0.en.html AGPL3
     * @link       https://castopod.org/
     */
    
    
    use App\Entities\EpisodeComment;
    
    use App\Models\EpisodeCommentModel;
    
    use App\Models\EpisodeModel;
    use App\Models\PodcastModel;
    
    use CodeIgniter\Exceptions\PageNotFoundException;
    
    use CodeIgniter\HTTP\Files\UploadedFile;
    
    use CodeIgniter\HTTP\RedirectResponse;
    
    use Modules\Media\Entities\Chapters;
    use Modules\Media\Entities\Transcript;
    
    use Modules\Media\Models\MediaModel;
    
    class EpisodeController extends BaseController
    
        public function _remap(string $method, string ...$params): mixed
    
                throw PageNotFoundException::forPageNotFound();
    
            if (count($params) === 1) {
                if (! ($podcast = (new PodcastModel())->getPodcastById((int) $params[0])) instanceof Podcast) {
    
                    throw PageNotFoundException::forPageNotFound();
    
                ! ($episode = (new EpisodeModel())->getEpisodeById((int) $params[1])) instanceof Episode
    
            ) {
                throw PageNotFoundException::forPageNotFound();
    
            unset($params[0]);
            unset($params[1]);
    
            return $this->{$method}($episode, ...$params);
    
        public function list(Podcast $podcast): string
    
            /** @var ?string $query */
            $query = $this->request->getGet('q');
    
            if ($query !== null && $query !== '') {
    
                // Default value for MySQL Full-Text Search's minimum length of words is 4.
                // Use LIKE operator as a fallback.
                if (strlen($query) < 4) {
    
                        ->select('episodes.*, IFNULL(SUM(ape.hits),0) as downloads')
                        ->join('analytics_podcasts_by_episode ape', 'episodes.id=ape.episode_id', 'left')
    
                        ->where('episodes.podcast_id', $podcast->id)
    
                        ->like('title', $episodeModel->db->escapeLikeString($query))
                        ->orLike('description_markdown', $episodeModel->db->escapeLikeString($query))
                        ->orLike('slug', $episodeModel->db->escapeLikeString($query))
                        ->orLike('location_name', $episodeModel->db->escapeLikeString($query))
    
                        ->groupBy('episodes.id')
    
                        ->orderBy('-`published_at`', '', false)
    
                        ->select('episodes.*, IFNULL(SUM(ape.hits),0) as downloads')
                        ->join('analytics_podcasts_by_episode ape', 'episodes.id=ape.episode_id', 'left')
    
                        ->where('episodes.podcast_id', $podcast->id)
    
                        ->where(
                            "MATCH (title, description_markdown, slug, location_name) AGAINST ('{$episodeModel->db->escapeString(
                                $query
                            )}')"
                        )
    
                        ->groupBy('episodes.id');
    
                    ->select('episodes.*, IFNULL(SUM(ape.hits),0) as downloads')
                    ->join('analytics_podcasts_by_episode ape', 'episodes.id=ape.episode_id', 'left')
    
                    ->where('episodes.podcast_id', $podcast->id)
    
                    ->groupBy('episodes.id')
    
                    ->orderBy('-`published_at`', '', false)
    
                    ->orderBy('created_at', 'desc');
            }
    
            helper('form');
    
                'episodes' => $episodes->paginate(10),
    
                'pager'    => $episodes->pager,
                'query'    => $query,
    
            $this->setHtmlHead(lang('Episode.all_podcast_episodes'));
    
            replace_breadcrumb_params([
    
        public function view(Episode $episode): string
    
                'podcast' => $episode->podcast,
                'episode' => $episode,
    
            replace_breadcrumb_params([
    
                0 => $episode->podcast->at_handle,
                1 => $episode->title,
    
        public function createView(Podcast $podcast): string
    
            $currentSeasonNumber = (new EpisodeModel())->getCurrentSeasonNumber($podcast->id);
    
                'nextEpisodeNumber'   => (new EpisodeModel())->getNextEpisodeNumber($podcast->id, $currentSeasonNumber),
    
            replace_breadcrumb_params([
    
            return view('episode/create', $data);
    
        public function createAction(Podcast $podcast): RedirectResponse
    
                'title'           => 'required',
                'slug'            => 'required|max_length[128]',
    
                'audio_file'      => 'uploaded[audio_file]|ext_in[audio_file,mp3,m4a]',
                'cover'           => 'is_image[cover]|ext_in[cover,jpg,jpeg,png]|min_dims[cover,1400,1400]|is_image_ratio[cover,1,1]',
    
                'transcript_file' => 'ext_in[transcript_file,srt,vtt]',
                'chapters_file'   => 'ext_in[chapters_file,json]|is_json[chapters_file]',
    
            if ($podcast->type === 'serial' && $this->request->getPost('type') === 'full') {
    
                return redirect()
                    ->back()
                    ->withInput()
                    ->with('errors', $this->validator->getErrors());
    
            if ((new EpisodeModel())
                ->where([
    
                ->first() instanceof Episode) {
    
                return redirect()
                    ->back()
                    ->withInput()
                    ->with('error', lang('Episode.messages.sameSlugError'));
            }
    
    
            $newEpisode = new Episode([
    
                'created_by'           => user_id(),
                'updated_by'           => user_id(),
    
                'title'                => $this->request->getPost('title'),
                'slug'                 => $this->request->getPost('slug'),
                'guid'                 => null,
                'audio'                => $this->request->getFile('audio_file'),
                'cover'                => $this->request->getFile('cover'),
    
                'description_markdown' => $this->request->getPost('description'),
    
                'location'             => $this->request->getPost('location_name') === '' ? null : new Location(
                    $this->request->getPost('location_name')
                ),
                'transcript'        => $this->request->getFile('transcript'),
                'chapters'          => $this->request->getFile('chapters'),
                'parental_advisory' => $this->request->getPost('parental_advisory') !== 'undefined'
    
                        ? $this->request->getPost('parental_advisory')
                        : null,
    
                'number' => $this->request->getPost('episode_number')
    
                    ? (int) $this->request->getPost('episode_number')
    
                    : null,
                'season_number' => $this->request->getPost('season_number')
    
                    ? (int) $this->request->getPost('season_number')
    
                'type'         => $this->request->getPost('type'),
                'is_blocked'   => $this->request->getPost('block') === 'yes',
                'is_premium'   => $this->request->getPost('premium') === 'yes',
                'published_at' => null,
    
            $transcriptChoice = $this->request->getPost('transcript-choice');
    
            if ($transcriptChoice === 'upload-file') {
                $newEpisode->setTranscript($this->request->getFile('transcript_file'));
    
            } elseif ($transcriptChoice === 'remote-url') {
    
                $newEpisode->transcript_remote_url = $this->request->getPost(
                    'transcript_remote_url'
    
                ) === '' ? null : $this->request->getPost('transcript_remote_url');
    
            }
    
            $chaptersChoice = $this->request->getPost('chapters-choice');
    
            if ($chaptersChoice === 'upload-file') {
                $newEpisode->setChapters($this->request->getFile('chapters_file'));
    
            } elseif ($chaptersChoice === 'remote-url') {
    
                $newEpisode->chapters_remote_url = $this->request->getPost(
                    'chapters_remote_url'
                ) === '' ? null : $this->request->getPost('chapters_remote_url');
    
            if (! ($newEpisodeId = $episodeModel->insert($newEpisode, true))) {
    
                    ->with('errors', $episodeModel->errors());
    
            return redirect()->route('episode-view', [$podcast->id, $newEpisodeId])->with(
    
                'message',
                lang('Episode.messages.createSuccess')
            );
    
        public function editView(Episode $episode): string
    
                'podcast' => $episode->podcast,
                'episode' => $episode,
    
            replace_breadcrumb_params([
    
                0 => $episode->podcast->at_handle,
                1 => $episode->title,
    
        public function editAction(Episode $episode): RedirectResponse
    
                'title'           => 'required',
                'slug'            => 'required|max_length[128]',
    
                'cover'           => 'is_image[cover]|ext_in[cover,jpg,jpeg,png]|min_dims[cover,1400,1400]|is_image_ratio[cover,1,1]',
    
                'transcript_file' => 'ext_in[transcript_file,srt,vtt]',
                'chapters_file'   => 'ext_in[chapters_file,json]|is_json[chapters_file]',
    
            if ($episode->podcast->type === 'serial' && $this->request->getPost('type') === 'full') {
    
                return redirect()
                    ->back()
                    ->withInput()
                    ->with('errors', $this->validator->getErrors());
            }
    
            $episode->title = $this->request->getPost('title');
            $episode->slug = $validData['slug'];
            $episode->description_markdown = $this->request->getPost('description');
            $episode->location = $this->request->getPost('location_name') === '' ? null : new Location(
    
                $this->request->getPost('location_name')
            );
    
                $this->request->getPost('parental_advisory') !== 'undefined'
                    ? $this->request->getPost('parental_advisory')
                    : null;
    
            $episode->number = $this->request->getPost('episode_number') ?: null;
            $episode->season_number = $this->request->getPost('season_number') ?: null;
            $episode->type = $this->request->getPost('type');
            $episode->is_blocked = $this->request->getPost('block') === 'yes';
            $episode->is_premium = $this->request->getPost('premium') === 'yes';
    
            $episode->updated_by = (int) user_id();
            $episode->setAudio($this->request->getFile('audio_file'));
            $episode->setCover($this->request->getFile('cover'));
    
            // republish on websub hubs upon edit
    
            $transcriptChoice = $this->request->getPost('transcript-choice');
            if ($transcriptChoice === 'upload-file') {
                $transcriptFile = $this->request->getFile('transcript_file');
    
                if ($transcriptFile instanceof UploadedFile && $transcriptFile->isValid()) {
    
                    $episode->setTranscript($transcriptFile);
                    $episode->transcript_remote_url = null;
    
                }
            } elseif ($transcriptChoice === 'remote-url') {
                if (
    
                    ($transcriptRemoteUrl = $this->request->getPost('transcript_remote_url')) &&
    
                    (($transcriptFile = $episode->transcript_id) !== null)
    
                    (new MediaModel())->deleteMedia($episode->transcript);
    
                $episode->transcript_remote_url = $transcriptRemoteUrl === '' ? null : $transcriptRemoteUrl;
    
    
            $chaptersChoice = $this->request->getPost('chapters-choice');
            if ($chaptersChoice === 'upload-file') {
                $chaptersFile = $this->request->getFile('chapters_file');
    
                if ($chaptersFile instanceof UploadedFile && $chaptersFile->isValid()) {
    
                    $episode->setChapters($chaptersFile);
                    $episode->chapters_remote_url = null;
    
                }
            } elseif ($chaptersChoice === 'remote-url') {
                if (
    
                    ($chaptersRemoteUrl = $this->request->getPost('chapters_remote_url')) &&
    
                    (($chaptersFile = $episode->chapters) instanceof Chapters)
    
                    (new MediaModel())->deleteMedia($episode->chapters);
    
                $episode->chapters_remote_url = $chaptersRemoteUrl === '' ? null : $chaptersRemoteUrl;
    
            if (! $episodeModel->update($episode->id, $episode)) {
    
                    ->with('errors', $episodeModel->errors());
    
            return redirect()->route('episode-edit', [$episode->podcast_id, $episode->id])->with(
    
                'message',
                lang('Episode.messages.editSuccess')
            );
    
        public function transcriptDelete(Episode $episode): RedirectResponse
    
            if (! $episode->transcript instanceof Transcript) {
    
            if (! $mediaModel->deleteMedia($episode->transcript)) {
    
                return redirect()
                    ->back()
                    ->withInput()
    
        public function chaptersDelete(Episode $episode): RedirectResponse
    
            if (! $episode->chapters instanceof Chapters) {
    
            if (! $mediaModel->deleteMedia($episode->chapters)) {
    
                return redirect()
                    ->back()
                    ->withInput()
    
        public function publishView(Episode $episode): string | RedirectResponse
    
            if ($episode->publication_status === 'not_published') {
    
                    'podcast' => $episode->podcast,
                    'episode' => $episode,
    
                    0 => $episode->podcast->at_handle,
                    1 => $episode->title,
    
                return view('episode/publish', $data);
    
            return redirect()->route('episode-view', [$episode->podcast_id, $episode->id])->with(
    
        public function publishAction(Episode $episode): RedirectResponse
    
            if ($episode->podcast->publication_status === 'published') {
    
                    'publication_method'         => 'required',
                    'scheduled_publication_date' => 'valid_date[Y-m-d H:i]|permit_empty',
    
                if (! $this->validate($rules)) {
                    return redirect()
                        ->back()
                        ->withInput()
                        ->with('errors', $this->validator->getErrors());
                }
    
                'actor_id'   => $episode->podcast->actor_id,
                'episode_id' => $episode->id,
    
                'message'    => $this->request->getPost('message'),
    
            if ($episode->podcast->publication_status === 'published') {
    
                $publishMethod = $this->request->getPost('publication_method');
                if ($publishMethod === 'schedule') {
                    $scheduledPublicationDate = $this->request->getPost('scheduled_publication_date');
                    if ($scheduledPublicationDate) {
    
                        $episode->published_at = Time::createFromFormat(
    
                            'Y-m-d H:i',
                            $scheduledPublicationDate,
                            $this->request->getPost('client_timezone'),
                        )->setTimezone(app_timezone());
                    } else {
                        $db->transRollback();
                        return redirect()
                            ->back()
                            ->withInput()
                            ->with('error', lang('Episode.messages.scheduleDateError'));
                    }
    
            } elseif ($episode->podcast->publication_status === 'scheduled') {
    
                // podcast publication date has already been set
    
                $episode->published_at = $episode->podcast->published_at->addSeconds(1);
    
            $newPost->published_at = $episode->published_at;
    
            $postModel = new PostModel();
            if (! $postModel->addPost($newPost)) {
    
                $db->transRollback();
                return redirect()
                    ->back()
                    ->withInput()
    
                    ->with('errors', $postModel->errors());
    
            if (! $episodeModel->update($episode->id, $episode)) {
    
                $db->transRollback();
                return redirect()
                    ->back()
                    ->withInput()
                    ->with('errors', $episodeModel->errors());
            }
    
            $db->transComplete();
    
    
            return redirect()->route('episode-view', [$episode->podcast_id, $episode->id])->with(
    
                'message',
                lang('Episode.messages.publishSuccess', [
    
                    'publication_status' => $episode->publication_status,
    
        public function publishEditView(Episode $episode): string | RedirectResponse
    
            if (in_array($episode->publication_status, ['scheduled', 'with_podcast'], true)) {
    
                    'podcast' => $episode->podcast,
                    'episode' => $episode,
    
                            'actor_id'   => $episode->podcast->actor_id,
                            'episode_id' => $episode->id,
    
                $this->setHtmlHead(lang('Episode.publish_edit'));
    
                    0 => $episode->podcast->at_handle,
                    1 => $episode->title,
    
                return view('episode/publish_edit', $data);
    
            return redirect()->route('episode-view', [$episode->podcast_id, $episode->id])->with(
    
        public function publishEditAction(Episode $episode): RedirectResponse
    
            if ($episode->podcast->publication_status === 'published') {
    
                    'post_id'                    => 'required',
                    'publication_method'         => 'required',
                    'scheduled_publication_date' => 'valid_date[Y-m-d H:i]|permit_empty',
    
                if (! $this->validate($rules)) {
                    return redirect()
                        ->back()
                        ->withInput()
                        ->with('errors', $this->validator->getErrors());
                }
    
            if ($episode->podcast->publication_status === 'published') {
    
                $publishMethod = $this->request->getPost('publication_method');
                if ($publishMethod === 'schedule') {
                    $scheduledPublicationDate = $this->request->getPost('scheduled_publication_date');
                    if ($scheduledPublicationDate) {
    
                        $episode->published_at = Time::createFromFormat(
    
                            'Y-m-d H:i',
                            $scheduledPublicationDate,
                            $this->request->getPost('client_timezone'),
                        )->setTimezone(app_timezone());
                    } else {
                        $db->transRollback();
                        return redirect()
                            ->back()
                            ->withInput()
                            ->with('error', lang('Episode.messages.scheduleDateError'));
                    }
    
            } elseif ($episode->podcast->publication_status === 'scheduled') {
    
                // podcast publication date has already been set
    
                $episode->published_at = $episode->podcast->published_at->addSeconds(1);
    
            $post = (new PostModel())->getPostById($this->request->getPost('post_id'));
    
            if ($post instanceof Post) {
    
                $post->message = $this->request->getPost('message');
    
                $post->published_at = $episode->published_at;
    
                $postModel = new PostModel();
                if (! $postModel->editPost($post)) {
    
                    $db->transRollback();
                    return redirect()
                        ->back()
                        ->withInput()
    
                        ->with('errors', $postModel->errors());
    
            if (! $episodeModel->update($episode->id, $episode)) {
    
                $db->transRollback();
                return redirect()
                    ->back()
                    ->withInput()
                    ->with('errors', $episodeModel->errors());
            }
    
            $db->transComplete();
    
    
            return redirect()->route('episode-view', [$episode->podcast_id, $episode->id])->with(
    
                'message',
                lang('Episode.messages.publishSuccess', [
    
                    'publication_status' => $episode->publication_status,
    
        public function publishCancelAction(Episode $episode): RedirectResponse
    
            if (in_array($episode->publication_status, ['scheduled', 'with_podcast'], true)) {
    
                $postModel = new PostModel();
                $post = $postModel
    
                        'actor_id'   => $episode->podcast->actor_id,
                        'episode_id' => $episode->id,
    
                if (! $episodeModel->update($episode->id, $episode)) {
    
                    $db->transRollback();
                    return redirect()
                        ->back()
                        ->withInput()
                        ->with('errors', $episodeModel->errors());
                }
    
                $db->transComplete();
    
    
                return redirect()->route('episode-view', [$episode->podcast_id, $episode->id])->with(
    
                    'message',
                    lang('Episode.messages.publishCancelSuccess')
                );
    
            return redirect()->route('episode-view', [$episode->podcast_id, $episode->id]);
    
        public function publishDateEditView(Episode $episode): string|RedirectResponse
    
            // only accessible if episode is already published
    
            if ($episode->publication_status !== 'published') {
                return redirect()->route('episode-view', [$episode->podcast_id, $episode->id])->with(
    
                'podcast' => $episode->podcast,
                'episode' => $episode,
    
            $this->setHtmlHead(lang('Episode.publish_date_edit'));
    
                0 => $episode->podcast->title,
                1 => $episode->title,
    
            ]);
            return view('episode/publish_date_edit', $data);
        }
    
        /**
         * Allows to set an episode's publication date to a past date
         *
         * Prevents setting a future date as it does not make sense to set a future published date to an already published
         * episode. This also prevents any side-effects from occurring.
         */
    
        public function publishDateEditAction(Episode $episode): RedirectResponse
    
        {
            $rules = [
                'new_publication_date' => 'valid_date[Y-m-d H:i]',
            ];
    
            if (! $this->validate($rules)) {
                return redirect()
                    ->back()
                    ->withInput()
                    ->with('errors', $this->validator->getErrors());
            }
    
    
            $validData = $this->validator->getValidated();
    
            $newPublicationDate = $validData['new_publication_date'];
    
    
            $newPublicationDate = Time::createFromFormat(
                'Y-m-d H:i',
                $newPublicationDate,
                $this->request->getPost('client_timezone'),
            )->setTimezone(app_timezone());
    
            if ($newPublicationDate->isAfter(Time::now())) {
                return redirect()
                    ->back()
                    ->withInput()
                    ->with('error', lang('Episode.publish_date_edit_future_error'));
            }
    
    
            $episode->published_at = $newPublicationDate;
    
            if (! $episodeModel->update($episode->id, $episode)) {
    
                return redirect()
                    ->back()
                    ->withInput()
                    ->with('errors', $episodeModel->errors());
    
            return redirect()->route('episode-view', [$episode->podcast_id, $episode->id])->with(
    
                'message',
                lang('Episode.publish_date_edit_success')
    
        public function unpublishView(Episode $episode): string | RedirectResponse
    
            if ($episode->publication_status !== 'published') {
                return redirect()->route('episode-view', [$episode->podcast_id, $episode->id])->with(
    
                    'error',
                    lang('Episode.unpublish_error')
                );
            }
    
            helper(['form']);
    
            $data = [
    
                'podcast' => $episode->podcast,
                'episode' => $episode,
    
            $this->setHtmlHead(lang('Episode.unpublish'));
    
                0 => $episode->podcast->title,
                1 => $episode->title,
    
        public function unpublishAction(Episode $episode): RedirectResponse
    
                return redirect()
                    ->back()
                    ->withInput()
                    ->with('errors', $this->validator->getErrors());
            }
    
    
            $allPostsLinkedToEpisode = (new PostModel())
    
            foreach ($allPostsLinkedToEpisode as $post) {
                (new PostModel())->removePost($post);
    
            $allCommentsLinkedToEpisode = (new EpisodeCommentModel())
                ->where([
    
                    'in_reply_to_id' => null,
                ])
                ->findAll();
            foreach ($allCommentsLinkedToEpisode as $comment) {
                (new EpisodeCommentModel())->removeComment($comment);
            }
    
    
            // set episode published_at to null to unpublish
    
            if (! $episodeModel->update($episode->id, $episode)) {
    
                $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($episode->podcast_id, [
    
                'is_published_on_hubs' => 0,
    
            return redirect()->route('episode-view', [$episode->podcast_id, $episode->id]);
    
        public function deleteView(Episode $episode): string
    
                'podcast' => $episode->podcast,
                'episode' => $episode,
    
                0 => $episode->podcast->at_handle,
                1 => $episode->title,
    
        public function deleteAction(Episode $episode): RedirectResponse
    
        {
            $rules = [
                'understand' => 'required',
            ];
    
            if (! $this->validate($rules)) {
                return redirect()
                    ->back()
                    ->withInput()
                    ->with('errors', $this->validator->getErrors());
            }
    
    
            if ($episode->published_at instanceof Time) {
    
                return redirect()
                    ->back()
                    ->withInput()
                    ->with('error', lang('Episode.messages.deletePublishedEpisodeError'));
            }
    
    
            $episodeModel = new EpisodeModel();
    
    
            if (! $episodeModel->delete($episode->id)) {
    
                $db->transRollback();
                return redirect()
                    ->back()
                    ->withInput()
                    ->with('errors', $episodeModel->errors());
    
            $episodeMediaList = [$episode->transcript, $episode->chapters, $episode->audio];
    
            //only delete episode cover if different from podcast's
    
            if ($episode->cover_id !== null) {
                $episodeMediaList[] = $episode->cover;
    
            //delete episode media records from database
            foreach ($episodeMediaList as $episodeMedia) {
    
                if ($episodeMedia !== null && ! $mediaModel->delete($episodeMedia->id)) {
    
                    $db->transRollback();
                    return redirect()
                        ->back()
                        ->withInput()
    
                        ->with('error', lang('Episode.messages.deleteError', [
                            'type' => $episodeMedia->type,
                        ]));
    
            $warnings = [];
    
            //remove episode media files from disk
            foreach ($episodeMediaList as $episodeMedia) {
    
                if ($episodeMedia === null) {
                    continue;
                }
    
                if ($episodeMedia->deleteFile()) {
                    continue;
    
    
                $warnings[] = lang('Episode.messages.deleteFileError', [
                    'type'     => $episodeMedia->type,
                    'file_key' => $episodeMedia->file_key,
                ]);
    
            if ($warnings !== []) {
                return redirect()
    
                    ->route('episode-list', [$episode->podcast_id])
    
                    ->with('message', lang('Episode.messages.deleteSuccess'))
                    ->with('warnings', $warnings);
            }
    
    
            return redirect()->route('episode-list', [$episode->podcast_id])->with(
    
                'message',
                lang('Episode.messages.deleteSuccess')
            );
    
        public function embedView(Episode $episode): string
    
                'podcast' => $episode->podcast,
                'episode' => $episode,
    
                'themes'  => EpisodeModel::$themes,
    
            $this->setHtmlHead(lang('Episode.embed.title'));
    
            replace_breadcrumb_params([
    
                0 => $episode->podcast->at_handle,
                1 => $episode->title,
    
            return view('episode/embed', $data);
    
        public function commentCreateAction(Episode $episode): RedirectResponse
    
        {
            $rules = [
                'message' => 'required|max_length[500]',
            ];
    
            if (! $this->validate($rules)) {
                return redirect()
                    ->back()
                    ->withInput()
                    ->with('errors', $this->validator->getErrors());
            }
    
    
            $validData = $this->validator->getValidated();
    
            $newComment = new EpisodeComment([
    
                'actor_id'   => interact_as_actor_id(),
    
                'created_at' => new Time('now'),
                'created_by' => user_id(),
            ]);
    
    
            $commentModel = new EpisodeCommentModel();
    
            if (
                ! $commentModel->addComment($newComment, true)
            ) {
                return redirect()
                    ->back()
                    ->withInput()
                    ->with('errors', $commentModel->errors());
            }
    
            // Comment has been successfully created
            return redirect()->back();
        }
    
        public function commentReplyAction(Episode $episode, string $commentId): RedirectResponse
    
        {
            $rules = [
                'message' => 'required|max_length[500]',
            ];
    
            if (! $this->validate($rules)) {
                return redirect()
                    ->back()
                    ->withInput()
                    ->with('errors', $this->validator->getErrors());
            }
    
    
            $validData = $this->validator->getValidated();
    
    
            $newReply = new EpisodeComment([
    
                'actor_id'       => interact_as_actor_id(),
    
                'in_reply_to_id' => $commentId,
    
                'created_at'     => new Time('now'),
                'created_by'     => user_id(),