feat(media): save audio, images, transcripts and chapters to media for episode and persons

This commit is contained in:
Yassine Doghri 2021-12-20 17:12:12 +00:00
parent fafaa7e689
commit 58e2a00a87
22 changed files with 144 additions and 129 deletions

View File

@ -126,6 +126,10 @@ class Images extends BaseConfig
], ],
]; ];
public string $avatarDefaultPath = 'castopod-avatar-default.jpg';
public string $avatarDefaultMimeType = 'image/jpg';
public string $podcastBannerDefaultPath = 'castopod-banner-default.jpg'; public string $podcastBannerDefaultPath = 'castopod-banner-default.jpg';
public string $podcastBannerDefaultMimeType = 'image/jpeg'; public string $podcastBannerDefaultMimeType = 'image/jpeg';

View File

@ -37,7 +37,7 @@ class AddMedia extends Migration
], ],
'file_metadata' => [ 'file_metadata' => [
'type' => 'JSON', 'type' => 'JSON',
'nullable' => true, 'null' => true,
], ],
'type' => [ 'type' => [
'type' => 'ENUM', 'type' => 'ENUM',
@ -46,6 +46,7 @@ class AddMedia extends Migration
], ],
'description' => [ 'description' => [
'type' => 'TEXT', 'type' => 'TEXT',
'null' => true,
], ],
'language_code' => [ 'language_code' => [
'type' => 'VARCHAR', 'type' => 'VARCHAR',

View File

@ -54,7 +54,6 @@ class AddPodcasts extends Migration
'type' => 'INT', 'type' => 'INT',
'unsigned' => true, 'unsigned' => true,
'null' => true, 'null' => true,
'default' => null,
], ],
'language_code' => [ 'language_code' => [
'type' => 'VARCHAR', 'type' => 'VARCHAR',
@ -69,7 +68,6 @@ class AddPodcasts extends Migration
'type' => 'ENUM', 'type' => 'ENUM',
'constraint' => ['clean', 'explicit'], 'constraint' => ['clean', 'explicit'],
'null' => true, 'null' => true,
'default' => null,
], ],
'owner_name' => [ 'owner_name' => [
'type' => 'VARCHAR', 'type' => 'VARCHAR',

View File

@ -79,7 +79,6 @@ class AddEpisodes extends Migration
'type' => 'ENUM', 'type' => 'ENUM',
'constraint' => ['clean', 'explicit'], 'constraint' => ['clean', 'explicit'],
'null' => true, 'null' => true,
'default' => null,
], ],
'number' => [ 'number' => [
'type' => 'INT', 'type' => 'INT',

View File

@ -39,7 +39,6 @@ class AddPlatforms extends Migration
'type' => 'VARCHAR', 'type' => 'VARCHAR',
'constraint' => 512, 'constraint' => 512,
'null' => true, 'null' => true,
'default' => null,
], ],
]); ]);
$this->forge->addField('`created_at` timestamp NOT NULL DEFAULT NOW()'); $this->forge->addField('`created_at` timestamp NOT NULL DEFAULT NOW()');

View File

@ -15,14 +15,13 @@ use App\Entities\Media\Chapters;
use App\Entities\Media\Image; use App\Entities\Media\Image;
use App\Entities\Media\Transcript; use App\Entities\Media\Transcript;
use App\Libraries\SimpleRSSElement; use App\Libraries\SimpleRSSElement;
use App\Models\ClipsModel; use App\Models\ClipModel;
use App\Models\EpisodeCommentModel; use App\Models\EpisodeCommentModel;
use App\Models\MediaModel; use App\Models\MediaModel;
use App\Models\PersonModel; use App\Models\PersonModel;
use App\Models\PodcastModel; use App\Models\PodcastModel;
use App\Models\PostModel; use App\Models\PostModel;
use CodeIgniter\Entity\Entity; use CodeIgniter\Entity\Entity;
use CodeIgniter\Files\File;
use CodeIgniter\HTTP\Files\UploadedFile; use CodeIgniter\HTTP\Files\UploadedFile;
use CodeIgniter\I18n\Time; use CodeIgniter\I18n\Time;
use League\CommonMark\CommonMarkConverter; use League\CommonMark\CommonMarkConverter;
@ -181,7 +180,7 @@ class Episode extends Entity
} else { } else {
$cover = new Image([ $cover = new Image([
'file_name' => $this->attributes['slug'], 'file_name' => $this->attributes['slug'],
'file_directory' => 'podcasts/' . $this->attributes['handle'], 'file_directory' => 'podcasts/' . $this->getPodcast()->handle,
'sizes' => config('Images') 'sizes' => config('Images')
->podcastCoverSizes, ->podcastCoverSizes,
'uploaded_by' => user_id(), 'uploaded_by' => user_id(),
@ -197,10 +196,19 @@ class Episode extends Entity
public function getCover(): Image public function getCover(): Image
{ {
if (! $this->cover instanceof Image) { if ($this->cover instanceof Image) {
$this->cover = (new MediaModel('image'))->getMediaById($this->cover_id); return $this->cover;
} }
if ($this->cover_id === null) {
$this->cover = $this->getPodcast()
->getCover();
return $this->cover;
}
$this->cover = (new MediaModel('image'))->getMediaById($this->cover_id);
return $this->cover; return $this->cover;
} }
@ -210,22 +218,22 @@ class Episode extends Entity
return $this; return $this;
} }
if ($this->audio_id !== null) { if ($this->audio_id !== 0) {
$this->getAudio() $this->getAudio()
->setFile($file); ->setFile($file);
$this->getAudio() $this->getAudio()
->updated_by = (int) user_id(); ->updated_by = (int) user_id();
(new MediaModel('audio'))->updateMedia($this->getAudio()); (new MediaModel('audio'))->updateMedia($this->getAudio());
} else { } else {
$transcript = new Audio([ $audio = new Audio([
'file_name' => $this->attributes['slug'], 'file_name' => $this->attributes['slug'],
'file_directory' => 'podcasts/' . $this->attributes['handle'], 'file_directory' => 'podcasts/' . $this->getPodcast()->handle,
'uploaded_by' => user_id(), 'uploaded_by' => user_id(),
'updated_by' => user_id(), 'updated_by' => user_id(),
]); ]);
$transcript->setFile($file); $audio->setFile($file);
$this->attributes['transcript_id'] = (new MediaModel())->saveMedia($transcript); $this->attributes['audio_id'] = (new MediaModel())->saveMedia($audio);
} }
return $this; return $this;
@ -255,7 +263,7 @@ class Episode extends Entity
} else { } else {
$transcript = new Transcript([ $transcript = new Transcript([
'file_name' => $this->attributes['slug'] . '-transcript', 'file_name' => $this->attributes['slug'] . '-transcript',
'file_directory' => 'podcasts/' . $this->attributes['handle'], 'file_directory' => 'podcasts/' . $this->getPodcast()->handle,
'uploaded_by' => user_id(), 'uploaded_by' => user_id(),
'updated_by' => user_id(), 'updated_by' => user_id(),
]); ]);
@ -291,7 +299,7 @@ class Episode extends Entity
} else { } else {
$chapters = new Chapters([ $chapters = new Chapters([
'file_name' => $this->attributes['slug'] . '-chapters', 'file_name' => $this->attributes['slug'] . '-chapters',
'file_directory' => 'podcasts/' . $this->attributes['handle'], 'file_directory' => 'podcasts/' . $this->getPodcast()->handle,
'uploaded_by' => user_id(), 'uploaded_by' => user_id(),
'updated_by' => user_id(), 'updated_by' => user_id(),
]); ]);
@ -306,7 +314,7 @@ class Episode extends Entity
public function getChapters(): ?Chapters public function getChapters(): ?Chapters
{ {
if ($this->chapters_id !== null && $this->chapters === null) { if ($this->chapters_id !== null && $this->chapters === null) {
$this->chapters = (new MediaModel('document'))->getMediaById($this->chapters_id); $this->chapters = (new MediaModel('chapters'))->getMediaById($this->chapters_id);
} }
return $this->chapters; return $this->chapters;
@ -324,7 +332,7 @@ class Episode extends Entity
helper('analytics'); helper('analytics');
// remove 'podcasts/' from audio file path // remove 'podcasts/' from audio file path
$strippedAudioFilePath = substr($this->audio->file_path, 9); $strippedAudioFilePath = substr($this->getAudio()->file_path, 9);
return generate_episode_analytics_url( return generate_episode_analytics_url(
$this->podcast_id, $this->podcast_id,
@ -400,7 +408,7 @@ class Episode extends Entity
} }
if ($this->clips === null) { if ($this->clips === null) {
$this->clips = (new ClipsModel())->getEpisodeClips($this->getPodcast() ->id, $this->id); $this->clips = (new ClipModel())->getEpisodeClips($this->getPodcast() ->id, $this->id);
} }
return $this->clips; return $this->clips;

View File

@ -39,12 +39,13 @@ class Audio extends BaseMedia
parent::setFile($file); parent::setFile($file);
$getID3 = new GetID3(); $getID3 = new GetID3();
$audioMetadata = $getID3->analyze((string) $file); $audioMetadata = $getID3->analyze(media_path($this->file_path));
$this->attributes['file_mimetype'] = $audioMetadata['mimetype']; $this->attributes['file_mimetype'] = $audioMetadata['mime_type'];
$this->attributes['file_size'] = $audioMetadata['filesize']; $this->attributes['file_size'] = $audioMetadata['filesize'];
$this->attributes['description'] = $audioMetadata['comments']['comment']; // @phpstan-ignore-next-line
$this->attributes['file_metadata'] = $audioMetadata; $this->attributes['description'] = @$audioMetadata['id3v2']['comments']['comment'];
$this->attributes['file_metadata'] = json_encode($audioMetadata);
return $this; return $this;
} }

View File

@ -20,11 +20,12 @@ use CodeIgniter\Files\File;
* @property string $file_directory * @property string $file_directory
* @property string $file_extension * @property string $file_extension
* @property string $file_name * @property string $file_name
* @property string $file_name_with_extension
* @property int $file_size * @property int $file_size
* @property string $file_mimetype * @property string $file_mimetype
* @property array $file_metadata * @property array|null $file_metadata
* @property 'image'|'audio'|'video'|'document' $type * @property 'image'|'audio'|'video'|'document' $type
* @property string $description * @property string|null $description
* @property string|null $language_code * @property string|null $language_code
* @property int $uploaded_by * @property int $uploaded_by
* @property int $updated_by * @property int $updated_by
@ -33,8 +34,6 @@ class BaseMedia extends Entity
{ {
protected File $file; protected File $file;
protected string $type = 'document';
/** /**
* @var string[] * @var string[]
*/ */
@ -49,9 +48,9 @@ class BaseMedia extends Entity
'file_path' => 'string', 'file_path' => 'string',
'file_size' => 'int', 'file_size' => 'int',
'file_mimetype' => 'string', 'file_mimetype' => 'string',
'file_metadata' => 'json-array', 'file_metadata' => '?json-array',
'type' => 'string', 'type' => 'string',
'description' => 'string', 'description' => '?string',
'language_code' => '?string', 'language_code' => '?string',
'uploaded_by' => 'integer', 'uploaded_by' => 'integer',
'updated_by' => 'integer', 'updated_by' => 'integer',
@ -81,6 +80,7 @@ class BaseMedia extends Entity
$this->attributes['file_name'] = $filename; $this->attributes['file_name'] = $filename;
$this->attributes['file_directory'] = $dirname; $this->attributes['file_directory'] = $dirname;
$this->attributes['file_extension'] = $extension; $this->attributes['file_extension'] = $extension;
$this->attributes['file_name_with_extension'] = "{$filename}.{$extension}";
} }
} }
@ -101,4 +101,10 @@ class BaseMedia extends Entity
return $this; return $this;
} }
public function deleteFile(): void
{
helper('media');
unlink(media_path($this->file_path));
}
} }

View File

@ -70,9 +70,7 @@ class Image extends BaseMedia
public function deleteFile(): void public function deleteFile(): void
{ {
helper('media'); parent::deleteFile();
unlink(media_path($this->file_path));
$this->deleteSizes(); $this->deleteSizes();
} }

View File

@ -14,6 +14,7 @@ use App\Entities\Media\Image;
use App\Models\MediaModel; use App\Models\MediaModel;
use App\Models\PersonModel; use App\Models\PersonModel;
use CodeIgniter\Entity\Entity; use CodeIgniter\Entity\Entity;
use CodeIgniter\Files\File;
use CodeIgniter\HTTP\Files\UploadedFile; use CodeIgniter\HTTP\Files\UploadedFile;
use RuntimeException; use RuntimeException;
@ -55,20 +56,20 @@ class Person extends Entity
/** /**
* Saves the person avatar in `public/media/persons/` * Saves the person avatar in `public/media/persons/`
*/ */
public function setAvatar(?UploadedFile $file = null): static public function setAvatar(UploadedFile | File $file = null): static
{ {
if ($file === null || ! $file->isValid()) { if ($file === null || ($file instanceof UploadedFile && ! $file->isValid())) {
return $this; return $this;
} }
if (array_key_exists('cover_id', $this->attributes) && $this->attributes['cover_id'] !== null) { if (array_key_exists('avatar_id', $this->attributes) && $this->attributes['avatar_id'] !== null) {
$this->getAvatar() $this->getAvatar()
->setFile($file); ->setFile($file);
$this->getAvatar() $this->getAvatar()
->updated_by = (int) user_id(); ->updated_by = (int) user_id();
(new MediaModel('image'))->updateMedia($this->getAvatar()); (new MediaModel('image'))->updateMedia($this->getAvatar());
} else { } else {
$cover = new Image([ $avatar = new Image([
'file_name' => $this->attributes['unique_name'], 'file_name' => $this->attributes['unique_name'],
'file_directory' => 'persons', 'file_directory' => 'persons',
'sizes' => config('Images') 'sizes' => config('Images')
@ -76,9 +77,9 @@ class Person extends Entity
'uploaded_by' => user_id(), 'uploaded_by' => user_id(),
'updated_by' => user_id(), 'updated_by' => user_id(),
]); ]);
$cover->setFile($file); $avatar->setFile($file);
$this->attributes['cover_id'] = (new MediaModel('image'))->saveMedia($cover); $this->attributes['avatar_id'] = (new MediaModel('image'))->saveMedia($avatar);
} }
return $this; return $this;
@ -89,10 +90,15 @@ class Person extends Entity
if ($this->attributes['avatar_id'] === null) { if ($this->attributes['avatar_id'] === null) {
helper('media'); helper('media');
return new Image([ return new Image([
'file_path' => media_path('castopod-avatar-default.jpg'), 'file_path' => config('Images')
'file_mimetype' => 'image/jpeg', ->avatarDefaultPath,
'sizes' => config('Images') 'file_mimetype' => config('Images')
->personAvatarSizes, ->avatarDefaultMimeType,
'file_size' => 0,
'file_metadata' => [
'sizes' => config('Images')
->personAvatarSizes,
],
]); ]);
} }

View File

@ -19,6 +19,7 @@ use App\Models\PersonModel;
use App\Models\PlatformModel; use App\Models\PlatformModel;
use App\Models\UserModel; use App\Models\UserModel;
use CodeIgniter\Entity\Entity; use CodeIgniter\Entity\Entity;
use CodeIgniter\Files\File;
use CodeIgniter\HTTP\Files\UploadedFile; use CodeIgniter\HTTP\Files\UploadedFile;
use CodeIgniter\I18n\Time; use CodeIgniter\I18n\Time;
use League\CommonMark\CommonMarkConverter; use League\CommonMark\CommonMarkConverter;
@ -194,9 +195,9 @@ class Podcast extends Entity
return $this->actor; return $this->actor;
} }
public function setCover(?UploadedFile $file = null): self public function setCover(UploadedFile | File $file = null): self
{ {
if ($file === null || ! $file->isValid()) { if ($file === null || ($file instanceof UploadedFile && ! $file->isValid())) {
return $this; return $this;
} }
@ -232,9 +233,9 @@ class Podcast extends Entity
return $this->cover; return $this->cover;
} }
public function setBanner(?UploadedFile $file): self public function setBanner(UploadedFile | File $file = null): self
{ {
if ($file === null || ! $file->isValid()) { if ($file === null || ($file instanceof UploadedFile && ! $file->isValid())) {
return $this; return $this;
} }

View File

@ -144,7 +144,7 @@ if (! function_exists('publication_button')) {
case 'scheduled': case 'scheduled':
$label = lang('Episode.publish_edit'); $label = lang('Episode.publish_edit');
$route = route_to('episode-publish_edit', $podcastId, $episodeId); $route = route_to('episode-publish_edit', $podcastId, $episodeId);
$variant = 'accent'; $variant = 'warning';
$iconLeft = 'upload-cloud'; $iconLeft = 'upload-cloud';
break; break;
case 'published': case 'published':

View File

@ -267,7 +267,7 @@ if (! function_exists('get_rss_feed')) {
$transcriptElement->addAttribute('language', $podcast->language_code); $transcriptElement->addAttribute('language', $podcast->language_code);
} }
if ($episode->chapters->file_url !== '') { if ($episode->getChapters() !== null) {
$chaptersElement = $item->addChild('chapters', null, $podcastNamespace); $chaptersElement = $item->addChild('chapters', null, $podcastNamespace);
$chaptersElement->addAttribute('url', $episode->chapters->file_url); $chaptersElement->addAttribute('url', $episode->chapters->file_url);
$chaptersElement->addAttribute('type', 'application/json+chapters'); $chaptersElement->addAttribute('type', 'application/json+chapters');

View File

@ -16,7 +16,7 @@ use App\Entities\Clip;
use CodeIgniter\Database\BaseResult; use CodeIgniter\Database\BaseResult;
use CodeIgniter\Model; use CodeIgniter\Model;
class ClipsModel extends Model class ClipModel extends Model
{ {
/** /**
* @var string * @var string

View File

@ -128,8 +128,10 @@ class MediaModel extends Model
return $this->update($media->id, $media); return $this->update($media->id, $media);
} }
public function deleteMedia(int $mediaId): bool public function deleteMedia(object $media): bool
{ {
return $this->delete($mediaId, true); $media->deleteFile();
return $this->delete($media->id, true);
} }
} }

View File

@ -15,9 +15,10 @@ use App\Entities\EpisodeComment;
use App\Entities\Location; use App\Entities\Location;
use App\Entities\Podcast; use App\Entities\Podcast;
use App\Entities\Post; use App\Entities\Post;
use App\Models\ClipsModel; use App\Models\ClipModel;
use App\Models\EpisodeCommentModel; use App\Models\EpisodeCommentModel;
use App\Models\EpisodeModel; use App\Models\EpisodeModel;
use App\Models\MediaModel;
use App\Models\PodcastModel; use App\Models\PodcastModel;
use App\Models\PostModel; use App\Models\PostModel;
use CodeIgniter\Exceptions\PageNotFoundException; use CodeIgniter\Exceptions\PageNotFoundException;
@ -125,6 +126,9 @@ class EpisodeController extends BaseController
->with('errors', $this->validator->getErrors()); ->with('errors', $this->validator->getErrors());
} }
$db = db_connect();
$db->transStart();
$newEpisode = new Episode([ $newEpisode = new Episode([
'podcast_id' => $this->podcast->id, 'podcast_id' => $this->podcast->id,
'title' => $this->request->getPost('title'), 'title' => $this->request->getPost('title'),
@ -143,10 +147,10 @@ class EpisodeController extends BaseController
? $this->request->getPost('parental_advisory') ? $this->request->getPost('parental_advisory')
: null, : null,
'number' => $this->request->getPost('episode_number') 'number' => $this->request->getPost('episode_number')
? $this->request->getPost('episode_number') ? (int) $this->request->getPost('episode_number')
: null, : null,
'season_number' => $this->request->getPost('season_number') 'season_number' => $this->request->getPost('season_number')
? $this->request->getPost('season_number') ? (int) $this->request->getPost('season_number')
: null, : null,
'type' => $this->request->getPost('type'), 'type' => $this->request->getPost('type'),
'is_blocked' => $this->request->getPost('block') === 'yes', 'is_blocked' => $this->request->getPost('block') === 'yes',
@ -156,9 +160,6 @@ class EpisodeController extends BaseController
'published_at' => null, 'published_at' => null,
]); ]);
$db = db_connect();
$db->transStart();
$transcriptChoice = $this->request->getPost('transcript-choice'); $transcriptChoice = $this->request->getPost('transcript-choice');
if ($transcriptChoice === 'upload-file') { if ($transcriptChoice === 'upload-file') {
$newEpisode->setTranscript($this->request->getFile('transcript_file')); $newEpisode->setTranscript($this->request->getFile('transcript_file'));
@ -178,8 +179,8 @@ class EpisodeController extends BaseController
} }
$episodeModel = new EpisodeModel(); $episodeModel = new EpisodeModel();
if (! ($newEpisodeId = $episodeModel->insert($newEpisode, true))) { if (! ($newEpisodeId = $episodeModel->insert($newEpisode, true))) {
$db->transRollback();
return redirect() return redirect()
->back() ->back()
->withInput() ->withInput()
@ -195,6 +196,7 @@ class EpisodeController extends BaseController
$podcastModel = new PodcastModel(); $podcastModel = new PodcastModel();
if (! $podcastModel->update($this->podcast->id, $this->podcast)) { if (! $podcastModel->update($this->podcast->id, $this->podcast)) {
$db->transRollback();
return redirect() return redirect()
->back() ->back()
->withInput() ->withInput()
@ -202,6 +204,8 @@ class EpisodeController extends BaseController
} }
} }
$db->transComplete();
return redirect()->route('episode-view', [$this->podcast->id, $newEpisodeId]); return redirect()->route('episode-view', [$this->podcast->id, $newEpisodeId]);
} }
@ -268,36 +272,34 @@ class EpisodeController extends BaseController
if ($transcriptChoice === 'upload-file') { if ($transcriptChoice === 'upload-file') {
$transcriptFile = $this->request->getFile('transcript_file'); $transcriptFile = $this->request->getFile('transcript_file');
if ($transcriptFile !== null && $transcriptFile->isValid()) { if ($transcriptFile !== null && $transcriptFile->isValid()) {
$this->episode->transcript_file = $transcriptFile; $this->episode->setTranscript($transcriptFile);
$this->episode->transcript_remote_url = null; $this->episode->transcript_remote_url = null;
} }
} elseif ($transcriptChoice === 'remote-url') { } elseif ($transcriptChoice === 'remote-url') {
if ( if (
($transcriptFileRemoteUrl = $this->request->getPost('transcript_remote_url')) && ($transcriptRemoteUrl = $this->request->getPost('transcript_remote_url')) &&
(($transcriptFile = $this->episode->transcript_file) !== null) (($transcriptFile = $this->episode->transcript_id) !== null)
) { ) {
unlink((string) $transcriptFile); (new MediaModel())->deleteMedia($this->episode->transcript);
$this->episode->transcript->file_path = null;
} }
$this->episode->transcript_remote_url = $transcriptFileRemoteUrl === '' ? null : $transcriptFileRemoteUrl; $this->episode->transcript_remote_url = $transcriptRemoteUrl === '' ? null : $transcriptRemoteUrl;
} }
$chaptersChoice = $this->request->getPost('chapters-choice'); $chaptersChoice = $this->request->getPost('chapters-choice');
if ($chaptersChoice === 'upload-file') { if ($chaptersChoice === 'upload-file') {
$chaptersFile = $this->request->getFile('chapters_file'); $chaptersFile = $this->request->getFile('chapters_file');
if ($chaptersFile !== null && $chaptersFile->isValid()) { if ($chaptersFile !== null && $chaptersFile->isValid()) {
$this->episode->chapters = $chaptersFile; $this->episode->setChapters($chaptersFile);
$this->episode->chapters_remote_url = null; $this->episode->chapters_remote_url = null;
} }
} elseif ($chaptersChoice === 'remote-url') { } elseif ($chaptersChoice === 'remote-url') {
if ( if (
($chaptersFileRemoteUrl = $this->request->getPost('chapters_remote_url')) && ($chaptersRemoteUrl = $this->request->getPost('chapters_remote_url')) &&
(($chaptersFile = $this->episode->chapters_file) !== null) (($chaptersFile = $this->episode->chapters) !== null)
) { ) {
unlink((string) $chaptersFile); (new MediaModel())->deleteMedia($this->episode->chapters);
$this->episode->chapters->file_path = null;
} }
$this->episode->chapters_remote_url = $chaptersFileRemoteUrl === '' ? null : $chaptersFileRemoteUrl; $this->episode->chapters_remote_url = $chaptersRemoteUrl === '' ? null : $chaptersRemoteUrl;
} }
$db = db_connect(); $db = db_connect();
@ -338,16 +340,12 @@ class EpisodeController extends BaseController
public function transcriptDelete(): RedirectResponse public function transcriptDelete(): RedirectResponse
{ {
unlink((string) $this->episode->transcript_file); $mediaModel = new MediaModel();
$this->episode->transcript->file_path = null; if (! $mediaModel->deleteMedia($this->episode->transcript)) {
$episodeModel = new EpisodeModel();
if (! $episodeModel->update($this->episode->id, $this->episode)) {
return redirect() return redirect()
->back() ->back()
->withInput() ->withInput()
->with('errors', $episodeModel->errors()); ->with('errors', $mediaModel->errors());
} }
return redirect()->back(); return redirect()->back();
@ -355,16 +353,12 @@ class EpisodeController extends BaseController
public function chaptersDelete(): RedirectResponse public function chaptersDelete(): RedirectResponse
{ {
unlink((string) $this->episode->chapters_file); $mediaModel = new MediaModel();
$this->episode->chapters->file_path = null; if (! $mediaModel->deleteMedia($this->episode->chapters)) {
$episodeModel = new EpisodeModel();
if (! $episodeModel->update($this->episode->id, $this->episode)) {
return redirect() return redirect()
->back() ->back()
->withInput() ->withInput()
->with('errors', $episodeModel->errors()); ->with('errors', $mediaModel->errors());
} }
return redirect()->back(); return redirect()->back();
@ -797,7 +791,7 @@ class EpisodeController extends BaseController
public function soundbiteDelete(string $clipId): RedirectResponse public function soundbiteDelete(string $clipId): RedirectResponse
{ {
(new ClipsModel())->deleteClip($this->podcast->id, $this->episode->id, (int) $clipId); (new ClipModel())->deleteClip($this->podcast->id, $this->episode->id, (int) $clipId);
return redirect()->route('clips-edit', [$this->podcast->id, $this->episode->id]); return redirect()->route('clips-edit', [$this->podcast->id, $this->episode->id]);
} }

View File

@ -76,24 +76,29 @@ class PersonController extends BaseController
->with('errors', $this->validator->getErrors()); ->with('errors', $this->validator->getErrors());
} }
$db = db_connect();
$db->transStart();
$person = new Person([ $person = new Person([
'avatar' => $this->request->getFile('avatar'),
'full_name' => $this->request->getPost('full_name'), 'full_name' => $this->request->getPost('full_name'),
'unique_name' => $this->request->getPost('unique_name'), 'unique_name' => $this->request->getPost('unique_name'),
'information_url' => $this->request->getPost('information_url'), 'information_url' => $this->request->getPost('information_url'),
'avatar' => $this->request->getFile('avatar'),
'created_by' => user_id(), 'created_by' => user_id(),
'updated_by' => user_id(), 'updated_by' => user_id(),
]); ]);
$personModel = new PersonModel(); $personModel = new PersonModel();
if (! $personModel->insert($person)) { if (! $personModel->insert($person)) {
$db->transRollback();
return redirect() return redirect()
->back() ->back()
->withInput() ->withInput()
->with('errors', $personModel->errors()); ->with('errors', $personModel->errors());
} }
$db->transComplete();
return redirect()->route('person-list'); return redirect()->route('person-list');
} }
@ -128,11 +133,7 @@ class PersonController extends BaseController
$this->person->full_name = $this->request->getPost('full_name'); $this->person->full_name = $this->request->getPost('full_name');
$this->person->unique_name = $this->request->getPost('unique_name'); $this->person->unique_name = $this->request->getPost('unique_name');
$this->person->information_url = $this->request->getPost('information_url'); $this->person->information_url = $this->request->getPost('information_url');
$this->person->setAvatar($this->request->getFile('avatar'));
$avatarFile = $this->request->getFile('avatar');
if ($avatarFile !== null && $avatarFile->isValid()) {
$this->person->avatar = new Image($avatarFile);
}
$this->person->updated_by = user_id(); $this->person->updated_by = user_id();

View File

@ -192,6 +192,9 @@ class PodcastController extends BaseController
$partnerImageUrl = null; $partnerImageUrl = null;
} }
$db = db_connect();
$db->transStart();
$newPodcast = new Podcast([ $newPodcast = new Podcast([
'title' => $this->request->getPost('title'), 'title' => $this->request->getPost('title'),
'handle' => $this->request->getPost('handle'), 'handle' => $this->request->getPost('handle'),
@ -226,9 +229,6 @@ class PodcastController extends BaseController
'updated_by' => user_id(), 'updated_by' => user_id(),
]); ]);
$db = db_connect();
$db->transStart();
$podcastModel = new PodcastModel(); $podcastModel = new PodcastModel();
if (! ($newPodcastId = $podcastModel->insert($newPodcast, true))) { if (! ($newPodcastId = $podcastModel->insert($newPodcast, true))) {
$db->transRollback(); $db->transRollback();
@ -363,10 +363,8 @@ class PodcastController extends BaseController
return redirect()->back(); return redirect()->back();
} }
$this->podcast->banner->deleteFile();
$mediaModel = new MediaModel(); $mediaModel = new MediaModel();
if (! $mediaModel->deleteMedia((int) $this->podcast->banner_id)) { if (! $mediaModel->deleteMedia($this->podcast->banner)) {
return redirect() return redirect()
->back() ->back()
->withInput() ->withInput()

View File

@ -131,6 +131,10 @@ class PodcastImportController extends BaseController
if (property_exists($nsPodcast, 'guid') && $nsPodcast->guid !== null) { if (property_exists($nsPodcast, 'guid') && $nsPodcast->guid !== null) {
$guid = (string) $nsPodcast->guid; $guid = (string) $nsPodcast->guid;
} }
$db = db_connect();
$db->transStart();
$podcast = new Podcast([ $podcast = new Podcast([
'guid' => $guid, 'guid' => $guid,
'handle' => $this->request->getPost('handle'), 'handle' => $this->request->getPost('handle'),
@ -139,7 +143,7 @@ class PodcastImportController extends BaseController
'title' => (string) $feed->channel[0]->title, 'title' => (string) $feed->channel[0]->title,
'description_markdown' => $converter->convert($channelDescriptionHtml), 'description_markdown' => $converter->convert($channelDescriptionHtml),
'description_html' => $channelDescriptionHtml, 'description_html' => $channelDescriptionHtml,
'cover' => new Image($coverFile), 'cover' => $coverFile,
'banner' => null, 'banner' => null,
'language_code' => $this->request->getPost('language'), 'language_code' => $this->request->getPost('language'),
'category_id' => $this->request->getPost('category'), 'category_id' => $this->request->getPost('category'),
@ -185,10 +189,6 @@ class PodcastImportController extends BaseController
} }
$podcastModel = new PodcastModel(); $podcastModel = new PodcastModel();
$db = db_connect();
$db->transStart();
if (! ($newPodcastId = $podcastModel->insert($podcast, true))) { if (! ($newPodcastId = $podcastModel->insert($podcast, true))) {
$db->transRollback(); $db->transRollback();
return redirect() return redirect()
@ -249,7 +249,7 @@ class PodcastImportController extends BaseController
'full_name' => $fullName, 'full_name' => $fullName,
'unique_name' => slugify($fullName), 'unique_name' => slugify($fullName),
'information_url' => $podcastPerson->attributes()['href'], 'information_url' => $podcastPerson->attributes()['href'],
'avatar' => new Image(download_file((string) $podcastPerson->attributes()['img'])), 'avatar' => download_file((string) $podcastPerson->attributes()['img']),
'created_by' => user_id(), 'created_by' => user_id(),
'updated_by' => user_id(), 'updated_by' => user_id(),
]); ]);
@ -326,7 +326,7 @@ class PodcastImportController extends BaseController
property_exists($nsItunes, 'image') && $nsItunes->image !== null && property_exists($nsItunes, 'image') && $nsItunes->image !== null &&
$nsItunes->image->attributes()['href'] !== null $nsItunes->image->attributes()['href'] !== null
) { ) {
$episodeCover = new Image(download_file((string) $nsItunes->image->attributes()['href'])); $episodeCover = download_file((string) $nsItunes->image->attributes()['href']);
} else { } else {
$episodeCover = null; $episodeCover = null;
} }
@ -402,7 +402,7 @@ class PodcastImportController extends BaseController
'full_name' => $fullName, 'full_name' => $fullName,
'unique_name' => slugify($fullName), 'unique_name' => slugify($fullName),
'information_url' => $episodePerson->attributes()['href'], 'information_url' => $episodePerson->attributes()['href'],
'avatar' => new Image(download_file((string) $episodePerson->attributes()['img'])), 'avatar' => download_file((string) $episodePerson->attributes()['img']),
'created_by' => user_id(), 'created_by' => user_id(),
'updated_by' => user_id(), 'updated_by' => user_id(),
]); ]);

View File

@ -48,12 +48,10 @@ class AddActivities extends Migration
'type' => 'ENUM', 'type' => 'ENUM',
'constraint' => ['queued', 'delivered'], 'constraint' => ['queued', 'delivered'],
'null' => true, 'null' => true,
'default' => null,
], ],
'scheduled_at' => [ 'scheduled_at' => [
'type' => 'DATETIME', 'type' => 'DATETIME',
'null' => true, 'null' => true,
'default' => null,
], ],
'created_at' => [ 'created_at' => [
'type' => 'DATETIME', 'type' => 'DATETIME',

View File

@ -161,12 +161,12 @@
<div class="py-2 tab-panels"> <div class="py-2 tab-panels">
<section id="transcript-file-upload" class="flex items-center tab-panel"> <section id="transcript-file-upload" class="flex items-center tab-panel">
<?php if ($episode->transcript_file) : ?> <?php if ($episode->transcript) : ?>
<div class="flex mb-1 gap-x-2"> <div class="flex mb-1 gap-x-2">
<?= anchor( <?= anchor(
$episode->transcript->file_url, $episode->transcript->file_url,
icon('file', 'mr-2 text-skin-muted') . icon('file', 'mr-2 text-skin-muted') .
$episode->transcript_file, $episode->transcript->file_name_with_extension,
[ [
'class' => 'inline-flex items-center text-xs', 'class' => 'inline-flex items-center text-xs',
'target' => '_blank', 'target' => '_blank',
@ -218,33 +218,33 @@
<div class="py-2 tab-panels"> <div class="py-2 tab-panels">
<section id="chapters-file-upload" class="flex items-center tab-panel"> <section id="chapters-file-upload" class="flex items-center tab-panel">
<?php if ($episode->chapters_file) : ?> <?php if ($episode->chapters) : ?>
<div class="flex mb-1 gap-x-2"> <div class="flex mb-1 gap-x-2">
<?= anchor( <?= anchor(
$episode->chapters->file_url, $episode->chapters->file_url,
icon('file', 'mr-2') . $episode->chapters_file, icon('file', 'mr-2') . $episode->chapters->file_name_with_extension,
[ [
'class' => 'inline-flex items-center text-xs', 'class' => 'inline-flex items-center text-xs',
'target' => '_blank', 'target' => '_blank',
'rel' => 'noreferrer noopener', 'rel' => 'noreferrer noopener',
], ],
) . ) .
anchor( anchor(
route_to( route_to(
'chapters-delete', 'chapters-delete',
$podcast->id, $podcast->id,
$episode->id, $episode->id,
),
icon('delete-bin', 'mx-auto'),
[
'class' =>
'text-sm p-1 bg-red-100 rounded-full text-red-700 hover:text-red-900 focus:ring-accent',
'data-tooltip' => 'bottom',
'title' => lang(
'Episode.form.chapters_file_delete',
), ),
icon('delete-bin', 'mx-auto'), ],
[ ) ?>
'class' =>
'text-sm p-1 bg-red-100 rounded-full text-red-700 hover:text-red-900 focus:ring-accent',
'data-tooltip' => 'bottom',
'title' => lang(
'Episode.form.chapters_file_delete',
),
],
) ?>
</div> </div>
<?php endif; ?> <?php endif; ?>
<Forms.Label class="sr-only" for="chapters_file" isOptional="true"><?= lang('Episode.form.chapters_file') ?></Forms.Label> <Forms.Label class="sr-only" for="chapters_file" isOptional="true"><?= lang('Episode.form.chapters_file') ?></Forms.Label>

View File

@ -32,7 +32,8 @@
name="unique_name" name="unique_name"
label="<?= lang('Person.form.unique_name') ?>" label="<?= lang('Person.form.unique_name') ?>"
hint="<?= lang('Person.form.unique_name_hint') ?>" hint="<?= lang('Person.form.unique_name_hint') ?>"
required="true" /> required="true"
data-slugify="slug" />
<Forms.Field <Forms.Field
name="information_url" name="information_url"
label="<?= lang('Person.form.information_url') ?>" label="<?= lang('Person.form.information_url') ?>"