Skip to content
Snippets Groups Projects
Commit ac5f0c73 authored by Yassine Doghri's avatar Yassine Doghri
Browse files

feat: edit + delete podcast and episode

- refactor model / entity and controller logic for DRY code
- update episodes and podcasts
migrations
- define callbacks for podcast and episode models for enclosure update and cache
clearing
parent c815ecd6
No related branches found
No related tags found
No related merge requests found
Showing
with 556 additions and 87311 deletions
......@@ -38,6 +38,12 @@ $routes->add('new-podcast', 'Podcast::create', ['as' => 'podcast_create']);
$routes->group('@(:podcastName)', function ($routes) {
$routes->add('/', 'Podcast::view/$1', ['as' => 'podcast_view']);
$routes->add('edit', 'Podcast::edit/$1', [
'as' => 'podcast_edit',
]);
$routes->add('delete', 'Podcast::delete/$1', [
'as' => 'podcast_delete',
]);
$routes->add('feed.xml', 'Feed/$1', ['as' => 'podcast_feed']);
$routes->add('new-episode', 'Episode::create/$1', [
'as' => 'episode_create',
......@@ -45,6 +51,12 @@ $routes->group('@(:podcastName)', function ($routes) {
$routes->add('episodes/(:episodeSlug)', 'Episode::view/$1/$2', [
'as' => 'episode_view',
]);
$routes->add('episodes/(:episodeSlug)/edit', 'Episode::edit/$1/$2', [
'as' => 'episode_edit',
]);
$routes->add('episodes/(:episodeSlug)/delete', 'Episode::delete/$1/$2', [
'as' => 'episode_delete',
]);
});
// Route for podcast audio file analytics (/stats/podcast_id/episode_id/podcast_folder/filename.mp3)
......
......@@ -10,23 +10,44 @@ namespace App\Controllers;
use App\Models\EpisodeModel;
use App\Models\PodcastModel;
helper('podcast');
class Episode extends BaseController
{
public function create($podcast_name)
{
helper(['form', 'database', 'media', 'id3']);
protected \App\Entities\Podcast $podcast;
protected ?\App\Entities\Episode $episode;
$episode_model = new EpisodeModel();
public function _remap($method, ...$params)
{
$podcast_model = new PodcastModel();
$podcast = $podcast_model->where('name', $podcast_name)->first();
$this->podcast = $podcast_model->where('name', $params[0])->first();
if (count($params) > 1) {
$episode_model = new EpisodeModel();
if (
!($episode = $episode_model
->where([
'podcast_id' => $this->podcast->id,
'slug' => $params[1],
])
->first())
) {
throw \CodeIgniter\Exceptions\PageNotFoundException::forPageNotFound();
}
$this->episode = $episode;
}
return $this->$method();
}
public function create()
{
helper(['form']);
if (
!$this->validate([
'episode_file' =>
'uploaded[episode_file]|ext_in[episode_file,mp3,m4a]',
'enclosure' => 'uploaded[enclosure]|ext_in[enclosure,mp3,m4a]',
'image' =>
'uploaded[image]|is_image[image]|ext_in[image,jpg,png]|permit_empty',
'title' => 'required',
'slug' => 'required',
'description' => 'required',
......@@ -34,58 +55,19 @@ class Episode extends BaseController
])
) {
$data = [
'podcast' => $podcast,
'podcast' => $this->podcast,
];
echo view('episode/create', $data);
} else {
$episode_slug = $this->request->getVar('slug');
$episode_file = $this->request->getFile('episode_file');
$episode_file_metadata = get_file_tags($episode_file);
$image = $this->request->getFile('image');
// By default, the episode's image path is set to the podcast's
$image_path = $podcast->image_uri;
// check whether the user has inputted an image and store it
if ($image->isValid()) {
$image_path = save_podcast_media(
$image,
$podcast_name,
$episode_slug
);
} elseif ($APICdata = $episode_file_metadata['attached_picture']) {
// if the user didn't input an image,
// check if the uploaded audio file has an attached cover and store it
$cover_image = new \CodeIgniter\Files\File('episode_cover');
file_put_contents($cover_image, $APICdata);
$image_path = save_podcast_media(
$cover_image,
$podcast_name,
$episode_slug
);
}
$episode_path = save_podcast_media(
$episode_file,
$podcast_name,
$episode_slug
);
$episode = new \App\Entities\Episode([
'podcast_id' => $podcast->id,
$new_episode = new \App\Entities\Episode([
'podcast_id' => $this->podcast->id,
'title' => $this->request->getVar('title'),
'slug' => $episode_slug,
'enclosure_uri' => $episode_path,
'enclosure_length' => $episode_file->getSize(),
'enclosure_type' => $episode_file_metadata['mime_type'],
'slug' => $this->request->getVar('slug'),
'enclosure' => $this->request->getFile('enclosure'),
'pub_date' => $this->request->getVar('pub_date'),
'description' => $this->request->getVar('description'),
'duration' => $episode_file_metadata['playtime_seconds'],
'image_uri' => $image_path,
'image' => $this->request->getFile('image'),
'explicit' => $this->request->getVar('explicit') or false,
'number' => $this->request->getVar('episode_number'),
'season_number' => $this->request->getVar('season_number')
......@@ -97,30 +79,107 @@ class Episode extends BaseController
'block' => $this->request->getVar('block') or false,
]);
$episode_model->save($episode);
$episode_file = write_file_tags($podcast, $episode);
$episode_model = new EpisodeModel();
$episode_model->save($new_episode);
return redirect()->to(
base_url(route_to('episode_view', $podcast_name, $episode_slug))
base_url(
route_to(
'episode_view',
$this->podcast->name,
$new_episode->slug
)
)
);
}
}
public function view($podcast_name, $episode_slug)
public function edit()
{
$podcast_model = new PodcastModel();
$episode_model = new EpisodeModel();
helper(['form']);
if (
!$this->validate([
'enclosure' =>
'uploaded[enclosure]|ext_in[enclosure,mp3,m4a]|permit_empty',
'image' =>
'uploaded[image]|is_image[image]|ext_in[image,jpg,png]|permit_empty',
'title' => 'required',
'slug' => 'required',
'description' => 'required',
'type' => 'required',
])
) {
$data = [
'podcast' => $this->podcast,
'episode' => $this->episode,
];
$podcast = $podcast_model->where('name', $podcast_name)->first();
$episode = $episode_model->where('slug', $episode_slug)->first();
echo view('episode/edit', $data);
} else {
$this->episode->title = $this->request->getVar('title');
$this->episode->slug = $this->request->getVar('slug');
$this->episode->pub_date = $this->request->getVar('pub_date');
$this->episode->description = $this->request->getVar('description');
$this->episode->explicit =
($this->request->getVar('explicit') or false);
$this->episode->number = $this->request->getVar('episode_number');
$this->episode->season_number = $this->request->getVar(
'season_number'
)
? $this->request->getVar('season_number')
: null;
$this->episode->type = $this->request->getVar('type');
$this->episode->author_name = $this->request->getVar('author_name');
$this->episode->author_email = $this->request->getVar(
'author_email'
);
$this->episode->block = ($this->request->getVar('block') or false);
$enclosure = $this->request->getFile('enclosure');
if ($enclosure->isValid()) {
$this->episode->enclosure = $this->request->getFile(
'enclosure'
);
}
$image = $this->request->getFile('image');
if ($image) {
$this->episode->image = $this->request->getFile('image');
}
$episode_model = new EpisodeModel();
$episode_model->save($this->episode);
return redirect()->to(
base_url(
route_to(
'episode_view',
$this->podcast->name,
$this->episode->slug
)
)
);
}
}
public function view()
{
self::triggerWebpageHit($this->podcast->id);
$data = [
'podcast' => $podcast,
'episode' => $episode,
'podcast' => $this->podcast,
'episode' => $this->episode,
];
self::triggerWebpageHit($data['podcast']->id);
return view('episode/view', $data);
}
public function delete()
{
$episode_model = new EpisodeModel();
$episode_model->delete($this->episode->id);
return view('episode/view.php', $data);
return redirect()->to(
base_url(route_to('podcast_view', $this->podcast->name))
);
}
}
......@@ -7,15 +7,26 @@
namespace App\Controllers;
use App\Models\CategoryModel;
use App\Models\EpisodeModel;
use App\Models\LanguageModel;
use App\Models\PodcastModel;
class Podcast extends BaseController
{
protected ?\App\Entities\Podcast $podcast;
public function _remap($method, ...$params)
{
if (count($params) > 0) {
$podcast_model = new PodcastModel();
$this->podcast = $podcast_model->where('name', $params[0])->first();
}
return $this->$method();
}
public function create()
{
helper(['form', 'database', 'media', 'misc']);
helper(['form', 'misc']);
$podcast_model = new PodcastModel();
if (
......@@ -24,8 +35,8 @@ class Podcast extends BaseController
'name' => 'required|regex_match[^[a-z0-9\_]{1,191}$]',
'description' => 'required|max_length[4000]',
'image' =>
'uploaded[image]|is_image[image]|ext_in[image,jpg,png]|max_dims[image,3000,3000]',
'owner_email' => 'required|valid_email|permit_empty',
'uploaded[image]|is_image[image]|ext_in[image,jpg,png]',
'owner_email' => 'required|valid_email',
'type' => 'required',
])
) {
......@@ -41,18 +52,14 @@ class Podcast extends BaseController
echo view('podcast/create', $data);
} else {
$image = $this->request->getFile('image');
$podcast_name = $this->request->getVar('name');
$image_path = save_podcast_media($image, $podcast_name, 'cover');
$podcast = new \App\Entities\Podcast([
'title' => $this->request->getVar('title'),
'name' => $podcast_name,
'name' => $this->request->getVar('name'),
'description' => $this->request->getVar('description'),
'episode_description_footer' => $this->request->getVar(
'episode_description_footer'
),
'image_uri' => $image_path,
'image' => $this->request->getFile('image'),
'language' => $this->request->getVar('language'),
'category' => $this->request->getVar('category'),
'explicit' => $this->request->getVar('explicit') or false,
......@@ -77,20 +84,86 @@ class Podcast extends BaseController
}
}
public function view($podcast_name)
public function edit()
{
$podcast_model = new PodcastModel();
$episode_model = new EpisodeModel();
helper(['form', 'misc']);
if (
!$this->validate([
'title' => 'required',
'name' => 'required|regex_match[^[a-z0-9\_]{1,191}$]',
'description' => 'required|max_length[4000]',
'image' =>
'uploaded[image]|is_image[image]|ext_in[image,jpg,png]|permit_empty',
'owner_email' => 'required|valid_email',
'type' => 'required',
])
) {
$languageModel = new LanguageModel();
$categoryModel = new CategoryModel();
$data = [
'podcast' => $this->podcast,
'languages' => $languageModel->findAll(),
'categories' => $categoryModel->findAll(),
];
echo view('podcast/edit', $data);
} else {
$this->podcast->title = $this->request->getVar('title');
$this->podcast->name = $this->request->getVar('name');
$this->podcast->description = $this->request->getVar('description');
$this->podcast->episode_description_footer = $this->request->getVar(
'episode_description_footer'
);
$image = $this->request->getFile('image');
if ($image->isValid()) {
$this->podcast->image = $this->request->getFile('image');
}
$this->podcast->language = $this->request->getVar('language');
$this->podcast->category = $this->request->getVar('category');
$this->podcast->explicit =
($this->request->getVar('explicit') or false);
$this->podcast->author_name = $this->request->getVar('author_name');
$this->podcast->author_email = $this->request->getVar(
'author_email'
);
$this->podcast->owner_name = $this->request->getVar('owner_name');
$this->podcast->owner_email = $this->request->getVar('owner_email');
$this->podcast->type = $this->request->getVar('type');
$this->podcast->copyright = $this->request->getVar('copyright');
$this->podcast->block = ($this->request->getVar('block') or false);
$this->podcast->complete =
($this->request->getVar('complete') or false);
$this->podcast->custom_html_head = $this->request->getVar(
'custom_html_head'
);
$podcast_model = new PodcastModel();
$podcast_model->save($this->podcast);
return redirect()->to(
base_url(route_to('podcast_view', $this->podcast->name))
);
}
}
public function view()
{
self::triggerWebpageHit($this->podcast->id);
$podcast = $podcast_model->where('name', $podcast_name)->first();
$data = [
'podcast' => $podcast,
'episodes' => $episode_model
->where('podcast_id', $podcast->id)
->findAll(),
'podcast' => $this->podcast,
'episodes' => $this->podcast->episodes,
];
self::triggerWebpageHit($podcast->id);
return view('podcast/view', $data);
}
public function delete()
{
$podcast_model = new PodcastModel();
$podcast_model->delete($this->podcast->id);
return redirect()->to(base_url(route_to('home')));
}
}
......@@ -141,6 +141,10 @@ class AddPodcasts extends Migration
'updated_at' => [
'type' => 'TIMESTAMP',
],
'deleted_at' => [
'type' => 'DATETIME',
'null' => true,
],
]);
$this->forge->addKey('id', true);
$this->forge->createTable('podcasts');
......
......@@ -47,25 +47,6 @@ class AddEpisodes extends Migration
'comment' =>
'The URI attribute points to your podcast media file. The file extension specified within the URI attribute determines whether or not content appears in the podcast directory. Supported file formats include M4A, MP3, MOV, MP4, M4V, and PDF.',
],
'enclosure_length' => [
'type' => 'INT',
'constraint' => 10,
'unsigned' => true,
'comment' =>
'The length attribute is the file size in bytes. You can find this information in the properties of your podcast file (on a Mac, choose File > Get Info and refer to the size field).',
],
'enclosure_type' => [
'type' => 'VARCHAR',
'constraint' => 1024,
'comment' =>
'The type attribute provides the correct category for the type of file you are using. The type values for the supported file formats are: audio/x-m4a, audio/mpeg, video/quicktime, video/mp4, video/x-m4v, and application/pdf.',
],
'guid' => [
'type' => 'VARCHAR',
'constraint' => 1024,
'comment' =>
'The episode’s globally unique identifier (GUID). It is very important that each episode have a unique GUID and that it never changes, even if an episode’s metadata, like title or enclosure URL, do change. Globally unique identifiers (GUID) are case-sensitive strings. If a GUID is not provided an episode’s enclosure URL will be used instead. If a GUID is not provided, make sure that an episode’s enclosure URL is unique and never changes. Failing to comply with these guidelines may result in duplicate episodes being shown to listeners, inaccurate data in Podcast Analytics, and can cause issues with your podcasts’s listing and chart placement in Apple Podcasts.',
],
'pub_date' => [
'type' => 'DATETIME',
'comment' =>
......@@ -87,6 +68,7 @@ class AddEpisodes extends Migration
'image_uri' => [
'type' => 'VARCHAR',
'constraint' => 1024,
'null' => true,
'comment' =>
'The artwork for the show. Specify your show artwork by providing a URL linking to it. Depending on their device, subscribers see your podcast artwork in varying sizes. Therefore, make sure your design is effective at both its original size and at thumbnail size. You should include a show title, brand, or source name as part of your podcast artwork. Artwork must be a minimum size of 1400 x 1400 pixels and a maximum size of 3000 x 3000 pixels, in JPEG or PNG format, 72 dpi, with appropriate file extensions (.jpg, .png), and in the RGB colorspace.',
],
......@@ -146,6 +128,10 @@ class AddEpisodes extends Migration
'updated_at' => [
'type' => 'TIMESTAMP',
],
'deleted_at' => [
'type' => 'DATETIME',
'null' => true,
],
]);
$this->forge->addKey('id', true);
$this->forge->addUniqueKey(['podcast_id', 'slug']);
......
......@@ -12,24 +12,24 @@ use CodeIgniter\Entity;
class Episode extends Entity
{
protected $link;
protected $image_media_path;
protected $image_url;
protected $enclosure_media_path;
protected $enclosure_url;
protected $guid;
protected $podcast;
protected \App\Entities\Podcast $podcast;
protected string $GUID;
protected string $link;
protected \CodeIgniter\Files\File $image;
protected string $image_media_path;
protected string $image_url;
protected \CodeIgniter\Files\File $enclosure;
protected string $enclosure_media_path;
protected string $enclosure_url;
protected array $enclosure_metadata;
protected $casts = [
'slug' => 'string',
'title' => 'string',
'enclosure_uri' => 'string',
'enclosure_length' => 'integer',
'enclosure_type' => 'string',
'pub_date' => 'datetime',
'description' => 'string',
'duration' => 'integer',
'image_uri' => 'string',
'image_uri' => '?string',
'author_name' => '?string',
'author_email' => '?string',
'explicit' => 'boolean',
......@@ -39,6 +39,38 @@ class Episode extends Entity
'block' => 'boolean',
];
public function setImage(?\CodeIgniter\HTTP\Files\UploadedFile $image)
{
if ($image->isValid()) {
// check whether the user has inputted an image and store it
$this->attributes['image_uri'] = save_podcast_media(
$image,
$this->getPodcast()->name,
$this->attributes['slug']
);
} elseif (
$APICdata = $this->getEnclosureMetadata()['attached_picture']
) {
// if the user didn't input an image,
// check if the uploaded audio file has an attached cover and store it
$cover_image = new \CodeIgniter\Files\File('episode_cover');
file_put_contents($cover_image, $APICdata);
$this->attributes['image_uri'] = save_podcast_media(
$cover_image,
$this->getPodcast()->name,
$this->attributes['slug']
);
}
return $this;
}
public function getImage()
{
return new \CodeIgniter\Files\File($this->getImageMediaPath());
}
public function getImageMediaPath()
{
return media_path($this->attributes['image_uri']);
......@@ -46,11 +78,37 @@ class Episode extends Entity
public function getImageUrl()
{
return media_url($this->attributes['image_uri']);
if ($image_uri = $this->attributes['image_uri']) {
return media_url($image_uri);
}
return $this->getPodcast()->image_url;
}
public function setEnclosure(
\CodeIgniter\HTTP\Files\UploadedFile $enclosure = null
) {
if ($enclosure->isValid()) {
helper('media');
$this->attributes['enclosure_uri'] = save_podcast_media(
$enclosure,
$this->getPodcast()->name,
$this->attributes['slug']
);
return $this;
}
}
public function getEnclosure()
{
return new \CodeIgniter\Files\File($this->getEnclosureMediaPath());
}
public function getEnclosureMediaPath()
{
helper('media');
return media_path($this->attributes['enclosure_uri']);
}
......@@ -66,6 +124,13 @@ class Episode extends Entity
);
}
public function getEnclosureMetadata()
{
helper('id3');
return get_file_tags($this->getEnclosure());
}
public function getLink()
{
return base_url(
......@@ -77,7 +142,7 @@ class Episode extends Entity
);
}
public function getGuid()
public function getGUID()
{
return $this->getLink();
}
......
......@@ -13,6 +13,8 @@ use CodeIgniter\Entity;
class Podcast extends Entity
{
protected $link;
protected \CodeIgniter\Files\File $image;
protected $image_media_path;
protected $image_url;
protected $episodes;
......@@ -37,6 +39,31 @@ class Podcast extends Entity
'custom_html_head' => '?string',
];
public function setImage(\CodeIgniter\HTTP\Files\UploadedFile $image = null)
{
if ($image) {
helper('media');
$this->attributes['image_uri'] = save_podcast_media(
$image,
$this->attributes['name'],
'cover'
);
return $this;
}
}
public function getImage()
{
return new \CodeIgniter\Files\File($this->getImageMediaPath());
}
public function getImageMediaPath()
{
return media_path($this->attributes['image_uri']);
}
public function getImageUrl()
{
return media_url($this->attributes['image_uri']);
......
......@@ -142,7 +142,7 @@ function set_user_session_referer()
}
}
function webpage_hit($postcast_id)
function webpage_hit($podcast_id)
{
$session = \Config\Services::session();
$session->start();
......@@ -150,7 +150,7 @@ function webpage_hit($postcast_id)
$procedureName = $db->prefixTable('analytics_website');
$db->query("call $procedureName(?,?,?,?)", [
$postcast_id,
$podcast_id,
$session->get('country'),
$session->get('browser'),
$session->get('referer'),
......
......@@ -33,12 +33,11 @@ function get_file_tags($file)
/**
* Write audio file metadata / ID3 tags
*
* @param App\Entities\Podcast $podcast
* @param App\Entities\Episode $episode
*
* @return UploadedFile
*/
function write_file_tags($podcast, $episode)
function write_enclosure_tags($episode)
{
$TextEncoding = 'UTF-8';
......@@ -55,20 +54,20 @@ function write_file_tags($podcast, $episode)
$APICdata = file_get_contents($cover->getRealPath());
// TODO: variables used for podcast specific tags
// $podcast_url = $podcast->link;
// $podcast_feed_url = $podcast->feed_url;
// $podcast_url = $episode->podcast->link;
// $podcast_feed_url = $episode->podcast->feed_url;
// $episode_media_url = $episode->link;
// populate data array
$TagData = [
'title' => [$episode->title],
'artist' => [$podcast->author],
'album' => [$podcast->title],
'artist' => [$episode->podcast->author],
'album' => [$episode->podcast->title],
'year' => [$episode->pub_date->format('Y')],
'genre' => ['Podcast'],
'comment' => [$episode->description],
'track_number' => [strval($episode->number)],
'copyright_message' => [$podcast->copyright],
'copyright_message' => [$episode->podcast->copyright],
'publisher' => ['Podlibre'],
'encoded_by' => ['Castopod'],
......
......@@ -116,14 +116,20 @@ function get_rss_feed($podcast)
$item = $channel->addChild('item');
$item->addChild('title', $episode->title);
$enclosure = $item->addChild('enclosure');
$enclosure_metadata = $episode->enclosure_metadata;
$enclosure->addAttribute('url', $episode->enclosure_url);
$enclosure->addAttribute('length', $episode->enclosure_length);
$enclosure->addAttribute('type', $episode->enclosure_type);
$enclosure->addAttribute('length', $enclosure_metadata['filesize']);
$enclosure->addAttribute('type', $enclosure_metadata['mime_type']);
$item->addChild('guid', $episode->guid);
$item->addChild('pubDate', $episode->pub_date->format(DATE_RFC1123));
$item->addChildWithCDATA('description', $episode->description);
$item->addChild('duration', $episode->duration, $itunes_namespace);
$item->addChild(
'duration',
$enclosure_metadata['playtime_seconds'],
$itunes_namespace
);
$item->addChild('link', $episode->link);
$episode_itunes_image = $item->addChild(
'image',
......
......@@ -2,6 +2,8 @@
return [
'back_to_podcast' => 'Go back to podcast',
'edit' => 'Edit',
'delete' => 'Delete',
'create' => 'Add an episode',
'form' => [
'file' => 'Audio file',
......@@ -22,6 +24,7 @@ return [
'episode_number' => 'Episode number',
'season_number' => 'Season number',
'block' => 'Block',
'submit' => 'Create episode',
'submit_create' => 'Create episode',
'submit_edit' => 'Save episode',
]
];
\ No newline at end of file
......@@ -2,6 +2,10 @@
return [
'create' => 'Create a Podcast',
'new_episode' => 'New Episode',
'feed' => 'RSS feed',
'edit' => 'Edit',
'delete' => 'Delete',
'form' => [
'title' => 'Title',
'name' => 'Name',
......@@ -24,7 +28,8 @@ return [
'block' => 'Block',
'complete' => 'Complete',
'custom_html_head' => 'Custom HTML code in <head/>',
'submit' => 'Create podcast',
'submit_create' => 'Create podcast',
'submit_edit' => 'Save podcast',
],
'category_options' => [
'uncategorized' => 'uncategorized',
......
......@@ -35,7 +35,40 @@ class EpisodeModel extends Model
];
protected $returnType = 'App\Entities\Episode';
protected $useSoftDeletes = false;
protected $useSoftDeletes = true;
protected $useTimestamps = true;
protected $afterInsert = [
'writeEnclosureMetadata',
'clearPodcastFeedCache',
];
protected $afterUpdate = [
'writeEnclosureMetadata',
'clearPodcastFeedCache',
];
protected function writeEnclosureMetadata(array $data)
{
helper('id3');
$episode = $this->find(
is_array($data['id']) ? $data['id'][0] : $data['id']
);
write_enclosure_tags($episode);
return $data;
}
protected function clearPodcastFeedCache(array $data)
{
$episode = $this->find(
is_array($data['id']) ? $data['id'][0] : $data['id']
);
$cache = \Config\Services::cache();
$cache->delete(md5($episode->podcast->feed_url));
}
}
......@@ -36,7 +36,21 @@ class PodcastModel extends Model
];
protected $returnType = 'App\Entities\Podcast';
protected $useSoftDeletes = false;
protected $useSoftDeletes = true;
protected $useTimestamps = true;
protected $afterInsert = ['clearPodcastFeedCache'];
protected $afterUpdate = ['clearPodcastFeedCache'];
protected function clearPodcastFeedCache(array $data)
{
$podcast = $this->find(
is_array($data['id']) ? $data['id'][0] : $data['id']
);
$cache = \Config\Services::cache();
$cache->delete(md5($podcast->feed_url));
}
}
......@@ -15,8 +15,8 @@
<?= csrf_field() ?>
<div class="flex flex-col mb-4">
<label for="episode_file"><?= lang('Episode.form.file') ?></label>
<input type="file" class="form-input" id="episode_file" name="episode_file" required accept=".mp3,.m4a" />
<label for="enclosure"><?= lang('Episode.form.file') ?></label>
<input type="file" class="form-input" id="enclosure" name="enclosure" required accept=".mp3,.m4a" />
</div>
<div class="flex flex-col mb-4">
......@@ -97,7 +97,7 @@
</div>
<button type="submit" name="submit" class="self-end px-4 py-2 bg-gray-200"><?= lang(
'Episode.form.submit'
'Episode.form.submit_create'
) ?></button>
<?= form_close() ?>
......
<?= $this->extend('layouts/default') ?>
<?= $this->section('content') ?>
<h1 class="mb-6 text-xl"><?= lang('Episode.edit') ?></h1>
<div class="mb-8">
<?= \Config\Services::validation()->listErrors() ?>
</div>
<?= form_open_multipart(
route_to('episode_edit', $podcast->name, $episode->slug),
[
'method' => 'post',
'class' => 'flex flex-col max-w-md',
]
) ?>
<?= csrf_field() ?>
<div class="flex flex-col mb-4">
<label for="enclosure"><?= lang('Episode.form.file') ?></label>
<input type="file" class="form-input" id="enclosure" name="enclosure" accept=".mp3,.m4a" />
</div>
<div class="flex flex-col mb-4">
<label for="title"><?= lang('Episode.form.title') ?></label>
<input type="text" class="form-input" id="title" name="title" value="<?= $episode->title ?>" required />
</div>
<div class="flex flex-col mb-4">
<label for="slug"><?= lang('Episode.form.slug') ?></label>
<input type="text" class="form-input" id="slug" name="slug" value="<?= $episode->slug ?>" required />
</div>
<div class="flex flex-col mb-4">
<label for="description"><?= lang('Episode.form.description') ?></label>
<textarea class="form-textarea" id="description" name="description" required><?= $episode->description ?></textarea>
</div>
<div class="flex flex-col mb-4">
<label for="pub_date"><?= lang('Episode.form.pub_date') ?></label>
<input type="date" class="form-input" id="pub_date" name="pub_date" value="<?= $episode->pub_date->format(
'Y-m-d'
) ?>" />
</div>
<div class="flex flex-col mb-4">
<label for="image"><?= lang('Episode.form.image') ?></label>
<input type="file" class="form-input" id="image" name="image" accept=".jpg,.jpeg,.png" />
</div>
<div class="flex flex-col mb-4">
<label for="episode_number"><?= lang(
'Episode.form.episode_number'
) ?></label>
<input type="number" class="form-input" id="episode_number" name="episode_number" value="<?= $episode->number ?>" required />
</div>
<div class="flex flex-col mb-4">
<label for="season_number"><?= lang('Episode.form.season_number') ?></label>
<input type="number" class="form-input" id="season_number" name="season_number" value="<?= $episode->season_number ?>" />
</div>
<div class="inline-flex items-center mb-4">
<input type="checkbox" id="explicit" name="explicit" class="form-checkbox" <?= $episode->explicit
? 'checked'
: '' ?> />
<label for="explicit" class="pl-2"><?= lang(
'Episode.form.explicit'
) ?></label>
</div>
<div class="flex flex-col mb-4">
<label for="author_name"><?= lang('Podcast.form.author_name') ?></label>
<input type="text" class="form-input" id="author_name" name="author_name" value="<?= $episode->author_name ?>" />
</div>
<div class="flex flex-col mb-4">
<label for="author_email"><?= lang('Podcast.form.author_email') ?></label>
<input type="email" class="form-input" id="author_email" name="author_email" value="<?= $episode->author_email ?>" />
</div>
<fieldset class="flex flex-col mb-4">
<legend><?= lang('Episode.form.type.label') ?></legend>
<label for="full" class="inline-flex items-center">
<input type="radio" class="form-radio" value="full" id="full" name="type" required
<?= $episode->type == 'full' ? 'checked' : '' ?>/>
<span class="ml-2"><?= lang('Episode.form.type.full') ?></span>
</label>
<label for="trailer" class="inline-flex items-center">
<input type="radio" class="form-radio" value="trailer" id="trailer" name="type" required
<?= $episode->type == 'trailer' ? 'checked' : '' ?>/>
<span class="ml-2"><?= lang('Episode.form.type.trailer') ?></span>
</label>
<label for="bonus" class="inline-flex items-center">
<input type="radio" class="form-radio" value="bonus" id="bonus" name="type" required
<?= $episode->type == 'bonus' ? 'checked' : '' ?> />
<span class="ml-2"><?= lang('Episode.form.type.bonus') ?></span>
</label>
</fieldset>
<div class="inline-flex items-center mb-4">
<input type="checkbox" id="block" name="block" class="form-checkbox" <?= $episode->block
? 'checked'
: '' ?> />
<label for="block" class="pl-2"><?= lang('Episode.form.block') ?></label>
</div>
<button type="submit" name="submit" class="self-end px-4 py-2 bg-gray-200"><?= lang(
'Episode.form.submit_edit'
) ?></button>
<?= form_close() ?>
<?= $this->endSection() ?>
......@@ -8,9 +8,23 @@
) ?>">< <?= lang('Episode.back_to_podcast') ?></a>
<h1 class="text-2xl font-semibold"><?= $episode->title ?></h1>
<img src="<?= $episode->image_url ?>" alt="Episode cover" class="object-cover w-40 h-40 mb-6" />
<audio controls preload="none">
<audio controls preload="none" class="mb-12">
<source src="<?= $episode->enclosure_url ?>" type="<?= $episode->enclosure_type ?>">
Your browser does not support the audio tag.
</audio>
<a class="inline-flex px-4 py-2 text-white bg-teal-700 hover:bg-teal-800" href="<?= route_to(
'episode_edit',
$podcast->name,
$episode->slug
) ?>"><?= lang('Episode.edit') ?></a>
<a href="<?= route_to(
'episode_delete',
$podcast->name,
$episode->slug
) ?>" class="inline-flex px-4 py-2 text-white bg-red-700 hover:bg-red-800"><?= lang(
'Episode.delete'
) ?></a>
<?= $this->endSection() ?>
......@@ -9,7 +9,7 @@
<?php if ($podcasts): ?>
<?php foreach ($podcasts as $podcast): ?>
<a href="<?= route_to('podcast_view', $podcast->name) ?>">
<article class="w-48 p-2 mb-4 mr-4 border shadow-sm hover:bg-gray-100 hover:shadow">
<article class="w-48 h-full p-2 mb-4 mr-4 border shadow-sm hover:bg-gray-100 hover:shadow">
<img alt="<?= $podcast->title ?>" src="<?= $podcast->image_url ?>" class="object-cover w-full h-40 mb-2" />
<h2 class="font-semibold leading-tight"><?= $podcast->title ?></h2>
<p class="text-gray-600">@<?= $podcast->name ?></p>
......
This diff is collapsed.
......@@ -130,7 +130,7 @@
</div>
<button type="submit" name="submit" class="self-end px-4 py-2 bg-gray-200"><?= lang(
'Podcast.form.submit'
'Podcast.form.submit_create'
) ?></button>
<?= form_close() ?>
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment