Newer
Older

Yassine Doghri
committed
<?php

Yassine Doghri
committed

Yassine Doghri
committed
/**
* @copyright 2020 Podlibre
* @license https://www.gnu.org/licenses/agpl-3.0.en.html AGPL3
* @link https://castopod.org/
*/
namespace App\Controllers\Admin;
use CodeIgniter\Exceptions\PageNotFoundException;
use CodeIgniter\HTTP\RedirectResponse;
use Config\Database;
use App\Entities\Episode;
use App\Entities\Note;
use App\Entities\Podcast;

Yassine Doghri
committed
use App\Models\EpisodeModel;
use App\Models\NoteModel;

Yassine Doghri
committed
use App\Models\PodcastModel;
use App\Models\SoundbiteModel;

Yassine Doghri
committed
use CodeIgniter\I18n\Time;

Yassine Doghri
committed
class EpisodeController extends BaseController

Yassine Doghri
committed
{

Yassine Doghri
committed
* @var Podcast
*/
protected $podcast;
/**

Yassine Doghri
committed
* @var Episode|null
*/
protected $episode;

Yassine Doghri
committed
public function _remap(string $method, string ...$params): mixed

Yassine Doghri
committed
{
if (
($this->podcast = (new PodcastModel())->getPodcastById(
(int) $params[0],
)) === null
) {
throw PageNotFoundException::forPageNotFound();
}

Yassine Doghri
committed
if (count($params) > 1) {
if (

Yassine Doghri
committed
!($this->episode = (new EpisodeModel())

Yassine Doghri
committed
->where([
'id' => $params[1],
'podcast_id' => $params[0],

Yassine Doghri
committed
])
->first())
) {
throw PageNotFoundException::forPageNotFound();

Yassine Doghri
committed
}
unset($params[1]);
unset($params[0]);

Yassine Doghri
committed
}
return $this->$method(...$params);

Yassine Doghri
committed
}
public function list(): string

Yassine Doghri
committed
{
$episodes = (new EpisodeModel())
->where('podcast_id', $this->podcast->id)
->orderBy('created_at', 'desc');

Yassine Doghri
committed
$data = [
'podcast' => $this->podcast,
'episodes' => $episodes->paginate(10),
'pager' => $episodes->pager,

Yassine Doghri
committed
];
replace_breadcrumb_params([
0 => $this->podcast->title,
]);

Yassine Doghri
committed
return view('admin/episode/list', $data);
}
public function view(): string
$data = [
'podcast' => $this->podcast,
'episode' => $this->episode,
];
replace_breadcrumb_params([
0 => $this->podcast->title,
1 => $this->episode->title,
]);
return view('admin/episode/view', $data);
}
public function create(): string

Yassine Doghri
committed
{
helper(['form']);
$data = [
'podcast' => $this->podcast,
];

Yassine Doghri
committed
replace_breadcrumb_params([
0 => $this->podcast->title,
]);
return view('admin/episode/create', $data);

Yassine Doghri
committed
public function attemptCreate(): RedirectResponse
{
$rules = [
'audio_file' => 'uploaded[audio_file]|ext_in[audio_file,mp3,m4a]',
'image' =>
'is_image[image]|ext_in[image,jpg,png]|min_dims[image,1400,1400]|is_image_squared[image]',
'transcript_file' =>
'ext_in[transcript,txt,html,srt,json]|permit_empty',
'chapters_file' => 'ext_in[chapters,json]|permit_empty',
];
if (!$this->validate($rules)) {
return redirect()
->back()
->withInput()
->with('errors', $this->validator->getErrors());

Yassine Doghri
committed
}
$newEpisode = new Episode([
'podcast_id' => $this->podcast->id,
'title' => $this->request->getPost('title'),
'slug' => $this->request->getPost('slug'),
'audio_file' => $this->request->getFile('audio_file'),
'description_markdown' => $this->request->getPost('description'),
'image' => $this->request->getFile('image'),
'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,

Yassine Doghri
committed
'number' => $this->request->getPost('episode_number')
? $this->request->getPost('episode_number')
: null,
'season_number' => $this->request->getPost('season_number')
? $this->request->getPost('season_number')
: null,
'type' => $this->request->getPost('type'),
'is_blocked' => $this->request->getPost('block') == 'yes',

Benjamin Bellamy
committed
'custom_rss_string' => $this->request->getPost('custom_rss'),
'created_by' => user_id(),
'updated_by' => user_id(),
'published_at' => null,
]);
$transcriptChoice = $this->request->getPost('transcript-choice');
if (
$transcriptChoice === 'upload-file' &&
($transcriptFile = $this->request->getFile('transcript_file'))
) {
$newEpisode->transcript_file = $transcriptFile;
} elseif ($transcriptChoice === 'remote-url') {
$newEpisode->transcript_file_remote_url = $this->request->getPost(
'transcript_file_remote_url',
);
}
$chaptersChoice = $this->request->getPost('chapters-choice');
if (
$chaptersChoice === 'upload-file' &&
($chaptersFile = $this->request->getFile('chapters_file'))
) {
$newEpisode->chapters_file = $chaptersFile;
} elseif ($chaptersChoice === 'remote-url') {
$newEpisode->chapters_file_remote_url = $this->request->getPost(
'chapters_file_remote_url',
);
}

Yassine Doghri
committed
$episodeModel = new EpisodeModel();
if (!($newEpisodeId = $episodeModel->insert($newEpisode, true))) {
return redirect()
->back()
->withInput()

Yassine Doghri
committed
->with('errors', $episodeModel->errors());
}
// update podcast's episode_description_footer_markdown if changed
$podcastModel = new PodcastModel();
if ($this->podcast->hasChanged('episode_description_footer_markdown')) {
$this->podcast->episode_description_footer_markdown = $this->request->getPost(
'description_footer',
);
if (!$podcastModel->update($this->podcast->id, $this->podcast)) {
return redirect()
->back()
->withInput()
->with('errors', $podcastModel->errors());
}
}
return redirect()->route('episode-view', [
$this->podcast->id,
$newEpisodeId,
]);

Yassine Doghri
committed
}
public function edit(): string

Yassine Doghri
committed
{
helper(['form']);
$data = [
'podcast' => $this->podcast,
'episode' => $this->episode,
];

Yassine Doghri
committed
replace_breadcrumb_params([
0 => $this->podcast->title,
1 => $this->episode->title,
]);
return view('admin/episode/edit', $data);
}
public function attemptEdit(): RedirectResponse
{
$rules = [
'audio_file' =>
'uploaded[audio_file]|ext_in[audio_file,mp3,m4a]|permit_empty',
'image' =>
'is_image[image]|ext_in[image,jpg,png]|min_dims[image,1400,1400]|is_image_squared[image]',
'transcript_file' =>
'ext_in[transcript_file,txt,html,srt,json]|permit_empty',
'chapters_file' => 'ext_in[chapters_file,json]|permit_empty',
];
if (!$this->validate($rules)) {
return redirect()
->back()
->withInput()
->with('errors', $this->validator->getErrors());
}

Yassine Doghri
committed
$this->episode->title = $this->request->getPost('title');
$this->episode->slug = $this->request->getPost('slug');
$this->episode->description_markdown = $this->request->getPost(
'description',
$this->episode->location = $this->request->getPost('location_name');
$this->episode->parental_advisory =
$this->request->getPost('parental_advisory') !== 'undefined'
? $this->request->getPost('parental_advisory')
: null;

Yassine Doghri
committed
$this->episode->number = $this->request->getPost('episode_number')
? $this->request->getPost('episode_number')
: null;
$this->episode->season_number = $this->request->getPost('season_number')
? $this->request->getPost('season_number')
: null;
$this->episode->type = $this->request->getPost('type');
$this->episode->is_blocked = $this->request->getPost('block') == 'yes';

Benjamin Bellamy
committed
$this->episode->custom_rss_string = $this->request->getPost(
'custom_rss',

Benjamin Bellamy
committed
);

Yassine Doghri
committed
$this->episode->updated_by = user_id();
$audioFile = $this->request->getFile('audio_file');
if ($audioFile !== null && $audioFile->isValid()) {
$this->episode->audio_file = $audioFile;

Yassine Doghri
committed
}
$image = $this->request->getFile('image');
if ($image !== null && $image->isValid()) {
$this->episode->image = $image;
}
$transcriptChoice = $this->request->getPost('transcript-choice');
if ($transcriptChoice === 'upload-file') {
$transcriptFile = $this->request->getFile('transcript_file');
if ($transcriptFile->isValid()) {
$this->episode->transcript_file = $transcriptFile;
$this->episode->transcript_file_remote_url = null;
}
} elseif ($transcriptChoice === 'remote-url') {
if (
($transcriptFileRemoteUrl = $this->request->getPost(
'transcript_file_remote_url',
)) &&
(($transcriptFile = $this->episode->transcript_file) &&
$transcriptFile !== null)
unlink($transcriptFile);
$this->episode->transcript_file_path = null;
}
$this->episode->transcript_file_remote_url = $transcriptFileRemoteUrl;
$chaptersChoice = $this->request->getPost('chapters-choice');
if ($chaptersChoice === 'upload-file') {
$chaptersFile = $this->request->getFile('chapters_file');
if ($chaptersFile->isValid()) {
$this->episode->chapters_file = $chaptersFile;
$this->episode->chapters_file_remote_url = null;
}
} elseif ($chaptersChoice === 'remote-url') {
if (
($chaptersFileRemoteUrl = $this->request->getPost(
'chapters_file_remote_url',
)) &&
(($chaptersFile = $this->episode->chapters_file) &&
$chaptersFile !== null)
unlink($chaptersFile);
$this->episode->chapters_file_path = null;
}
$this->episode->chapters_file_remote_url = $chaptersFileRemoteUrl;

Yassine Doghri
committed
$episodeModel = new EpisodeModel();
if (!$episodeModel->update($this->episode->id, $this->episode)) {
return redirect()
->back()
->withInput()

Yassine Doghri
committed
->with('errors', $episodeModel->errors());
}
// update podcast's episode_description_footer_markdown if changed
$this->podcast->episode_description_footer_markdown = $this->request->getPost(
'description_footer',
);
if ($this->podcast->hasChanged('episode_description_footer_markdown')) {
$podcastModel = new PodcastModel();
if (!$podcastModel->update($this->podcast->id, $this->podcast)) {
return redirect()
->back()
->withInput()
->with('errors', $podcastModel->errors());
}
}
return redirect()->route('episode-view', [
$this->podcast->id,
$this->episode->id,
]);

Yassine Doghri
committed
}
public function transcriptDelete(): RedirectResponse
unlink($this->episode->transcript_file);
$this->episode->transcript_file_path = null;
$episodeModel = new EpisodeModel();
if (!$episodeModel->update($this->episode->id, $this->episode)) {
return redirect()
->back()
->withInput()
->with('errors', $episodeModel->errors());
}
return redirect()->back();
}
public function chaptersDelete(): RedirectResponse
unlink($this->episode->chapters_file);
$this->episode->chapters_file_path = null;
$episodeModel = new EpisodeModel();
if (!$episodeModel->update($this->episode->id, $this->episode)) {
return redirect()
->back()
->withInput()
->with('errors', $episodeModel->errors());
}
return redirect()->back();
}
public function publish(): string
{
if ($this->episode->publication_status === 'not_published') {
helper(['form']);
$data = [
'podcast' => $this->podcast,
'episode' => $this->episode,
];
replace_breadcrumb_params([
0 => $this->podcast->title,
1 => $this->episode->title,
]);
return view('admin/episode/publish', $data);
}
throw PageNotFoundException::forPageNotFound();
}
public function attemptPublish(): RedirectResponse
{
$rules = [
'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());
}
$db = Database::connect();
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
$db->transStart();
$newNote = new Note([
'actor_id' => $this->podcast->actor_id,
'episode_id' => $this->episode->id,
'message' => $this->request->getPost('message'),
'created_by' => user_id(),
]);
$publishMethod = $this->request->getPost('publication_method');
if ($publishMethod === 'schedule') {
$scheduledPublicationDate = $this->request->getPost(
'scheduled_publication_date',
);
if ($scheduledPublicationDate) {
$scheduledDateUTC = Time::createFromFormat(
'Y-m-d H:i',
$scheduledPublicationDate,
$this->request->getPost('client_timezone'),
)->setTimezone('UTC');
$this->episode->published_at = $scheduledDateUTC;
$newNote->published_at = $scheduledDateUTC;
} else {
$db->transRollback();
return redirect()
->back()
->withInput()
->with('error', 'Schedule date must be set!');
}
} else {
$dateNow = Time::now();
$this->episode->published_at = $dateNow;
$newNote->published_at = $dateNow;
}
$noteModel = new NoteModel();
if (!$noteModel->addNote($newNote)) {
$db->transRollback();
return redirect()
->back()
->withInput()
->with('errors', $noteModel->errors());
}
$episodeModel = new EpisodeModel();
if (!$episodeModel->update($this->episode->id, $this->episode)) {
$db->transRollback();
return redirect()
->back()
->withInput()
->with('errors', $episodeModel->errors());
}
$db->transComplete();
return redirect()->route('episode-view', [
$this->podcast->id,
$this->episode->id,
]);
}
public function publishEdit(): string
{
if ($this->episode->publication_status === 'scheduled') {
helper(['form']);
$data = [
'podcast' => $this->podcast,
'episode' => $this->episode,
'note' => (new NoteModel())
->where([
'actor_id' => $this->podcast->actor_id,
'episode_id' => $this->episode->id,
])
->first(),
];
replace_breadcrumb_params([
0 => $this->podcast->title,
1 => $this->episode->title,
]);
return view('admin/episode/publish_edit', $data);
}
throw PageNotFoundException::forPageNotFound();
}
public function attemptPublishEdit(): RedirectResponse
{
$rules = [
'note_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());
}
$db = Database::connect();
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
$db->transStart();
$note = (new NoteModel())->getNoteById(
$this->request->getPost('note_id'),
);
$note->message = $this->request->getPost('message');
$publishMethod = $this->request->getPost('publication_method');
if ($publishMethod === 'schedule') {
$scheduledPublicationDate = $this->request->getPost(
'scheduled_publication_date',
);
if ($scheduledPublicationDate) {
$scheduledDateUTC = Time::createFromFormat(
'Y-m-d H:i',
$scheduledPublicationDate,
$this->request->getPost('client_timezone'),
)->setTimezone('UTC');
$this->episode->published_at = $scheduledDateUTC;
$note->published_at = $scheduledDateUTC;
} else {
$db->transRollback();
return redirect()
->back()
->withInput()
->with('error', 'Schedule date must be set!');
}
} else {
$dateNow = Time::now();
$this->episode->published_at = $dateNow;
$note->published_at = $dateNow;
}
$noteModel = new NoteModel();
if (!$noteModel->editNote($note)) {
$db->transRollback();
return redirect()
->back()
->withInput()
->with('errors', $noteModel->errors());
}
$episodeModel = new EpisodeModel();
if (!$episodeModel->update($this->episode->id, $this->episode)) {
$db->transRollback();
return redirect()
->back()
->withInput()
->with('errors', $episodeModel->errors());
}
$db->transComplete();
return redirect()->route('episode-view', [
$this->podcast->id,
$this->episode->id,
]);
}
public function unpublish(): string
{
if ($this->episode->publication_status === 'published') {
helper(['form']);
$data = [
'podcast' => $this->podcast,
'episode' => $this->episode,
];
replace_breadcrumb_params([
0 => $this->podcast->title,
1 => $this->episode->title,
]);
return view('admin/episode/unpublish', $data);
}
throw PageNotFoundException::forPageNotFound();
}
public function attemptUnpublish(): RedirectResponse
{
$rules = [
'understand' => 'required',
];
if (!$this->validate($rules)) {
return redirect()
->back()
->withInput()
->with('errors', $this->validator->getErrors());
}
$db = Database::connect();
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
$db->transStart();
$allNotesLinkedToEpisode = (new NoteModel())
->where([
'episode_id' => $this->episode->id,
])
->findAll();
foreach ($allNotesLinkedToEpisode as $note) {
(new NoteModel())->removeNote($note);
}
// set episode published_at to null to unpublish
$this->episode->published_at = null;
$episodeModel = new EpisodeModel();
if (!$episodeModel->update($this->episode->id, $this->episode)) {
$db->transRollback();
return redirect()
->back()
->withInput()
->with('errors', $episodeModel->errors());
}
$db->transComplete();
return redirect()->route('episode-view', [
$this->podcast->id,
$this->episode->id,
]);
}
public function delete(): RedirectResponse

Yassine Doghri
committed
{

Yassine Doghri
committed
(new EpisodeModel())->delete($this->episode->id);

Yassine Doghri
committed
return redirect()->route('episode-list', [$this->podcast->id]);

Yassine Doghri
committed
}
public function soundbitesEdit(): string
{
helper(['form']);
$data = [
'podcast' => $this->podcast,
'episode' => $this->episode,
];
replace_breadcrumb_params([
0 => $this->podcast->title,
1 => $this->episode->title,
]);
return view('admin/episode/soundbites', $data);
}
public function soundbitesAttemptEdit(): RedirectResponse
{
$soundbites_array = $this->request->getPost('soundbites_array');
$rules = [
'soundbites_array.0.start_time' =>
'permit_empty|required_with[soundbites_array.0.duration]|decimal|greater_than_equal_to[0]',
'soundbites_array.0.duration' =>
'permit_empty|required_with[soundbites_array.0.start_time]|decimal|greater_than_equal_to[0]',
];
foreach (array_keys($soundbites_array) as $soundbite_id) {
$rules += [
"soundbites_array.{$soundbite_id}.start_time" => 'required|decimal|greater_than_equal_to[0]',
"soundbites_array.{$soundbite_id}.duration" => 'required|decimal|greater_than_equal_to[0]',
];
}
if (!$this->validate($rules)) {
return redirect()
->back()
->withInput()
->with('errors', $this->validator->getErrors());
}
foreach ($soundbites_array as $soundbite_id => $soundbite) {
if (
$soundbite['start_time'] !== null &&
$soundbite['duration'] !== null
) {
$data = [
'podcast_id' => $this->podcast->id,
'episode_id' => $this->episode->id,
'start_time' => $soundbite['start_time'],
'duration' => $soundbite['duration'],
'label' => $soundbite['label'],
'updated_by' => user_id(),
];
if ($soundbite_id == 0) {
$data += ['created_by' => user_id()];
} else {
$data += ['id' => $soundbite_id];
}
$soundbiteModel = new SoundbiteModel();
if (!$soundbiteModel->save($data)) {
return redirect()
->back()
->withInput()
->with('errors', $soundbiteModel->errors());
}
}
}
return redirect()->route('soundbites-edit', [
$this->podcast->id,
$this->episode->id,
]);
}
public function soundbiteDelete(int $soundbiteId): RedirectResponse
{
(new SoundbiteModel())->deleteSoundbite(
$this->podcast->id,
$this->episode->id,
$soundbiteId,
);
return redirect()->route('soundbites-edit', [
$this->podcast->id,
$this->episode->id,
]);
}
public function embeddablePlayer(): string
{
helper(['form']);
$data = [
'podcast' => $this->podcast,
'episode' => $this->episode,
'themes' => EpisodeModel::$themes,
];
replace_breadcrumb_params([
0 => $this->podcast->title,
1 => $this->episode->title,
]);
return view('admin/episode/embeddable_player', $data);
}