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

feat(episodes): schedule episode with future publication_date by using cache expiration time

- merge publication date fields into one field instanciated with flatpickr datetime picker
- get user timezone to convert user publication_date input to UTC
- remove setPublishedAt() method from episode entity
- add publication pill component to display the episode publication date info
- clear cache after episode insert
- use CI is_really_writable() helper in install instead of is_writable()
- fix latest episodes layout
- update tsconfig to only include ts folders
- update DEPENDENCIES.md to include flatpickr
- add format_duration helper to format episode enclosure duration instead of translating it (causes
translation bug)
- add Time.ts module to convert UTC time to user localized time for episode publication dates
- fix some layout issues
- update php and js dependencies to latest versions

closes #47
parent 0ab17d10
No related branches found
No related tags found
No related merge requests found
Showing
with 244 additions and 69 deletions
...@@ -37,6 +37,8 @@ Javascript dependencies: ...@@ -37,6 +37,8 @@ Javascript dependencies:
([Free amCharts license](https://github.com/amcharts/amcharts4/blob/master/dist/script/LICENSE)) ([Free amCharts license](https://github.com/amcharts/amcharts4/blob/master/dist/script/LICENSE))
- [Choices.js](https://joshuajohnson.co.uk/Choices/) - [Choices.js](https://joshuajohnson.co.uk/Choices/)
([MIT License](https://github.com/jshjohnson/Choices/blob/master/LICENSE)) ([MIT License](https://github.com/jshjohnson/Choices/blob/master/LICENSE))
- [flatpickr](https://flatpickr.js.org/)
([MIT License](https://github.com/flatpickr/flatpickr/blob/master/LICENSE.md))
Other: Other:
......
...@@ -26,7 +26,7 @@ class BaseController extends Controller ...@@ -26,7 +26,7 @@ class BaseController extends Controller
* *
* @var array * @var array
*/ */
protected $helpers = ['auth', 'breadcrumb', 'svg', 'components']; protected $helpers = ['auth', 'breadcrumb', 'svg', 'components', 'misc'];
/** /**
* Constructor. * Constructor.
......
...@@ -10,6 +10,7 @@ namespace App\Controllers\Admin; ...@@ -10,6 +10,7 @@ namespace App\Controllers\Admin;
use App\Models\EpisodeModel; use App\Models\EpisodeModel;
use App\Models\PodcastModel; use App\Models\PodcastModel;
use CodeIgniter\I18n\Time;
class Episode extends BaseController class Episode extends BaseController
{ {
...@@ -95,9 +96,7 @@ class Episode extends BaseController ...@@ -95,9 +96,7 @@ class Episode extends BaseController
'enclosure' => 'uploaded[enclosure]|ext_in[enclosure,mp3,m4a]', 'enclosure' => 'uploaded[enclosure]|ext_in[enclosure,mp3,m4a]',
'image' => 'image' =>
'is_image[image]|ext_in[image,jpg,png]|min_dims[image,1400,1400]|is_image_squared[image]', 'is_image[image]|ext_in[image,jpg,png]|min_dims[image,1400,1400]|is_image_squared[image]',
'publication_date' => 'valid_date[Y-m-d]|permit_empty', 'publication_date' => 'valid_date[Y-m-d H:i]|permit_empty',
'publication_time' =>
'regex_match[/^(0[0-9]|1[0-9]|2[0-3]):[0-5][0-9]$/]|permit_empty',
]; ];
if (!$this->validate($rules)) { if (!$this->validate($rules)) {
...@@ -125,11 +124,12 @@ class Episode extends BaseController ...@@ -125,11 +124,12 @@ class Episode extends BaseController
'block' => $this->request->getPost('block') == 'yes', 'block' => $this->request->getPost('block') == 'yes',
'created_by' => user(), 'created_by' => user(),
'updated_by' => user(), 'updated_by' => user(),
'published_at' => Time::createFromFormat(
'Y-m-d H:i',
$this->request->getPost('publication_date'),
$this->request->getPost('client_timezone')
)->setTimezone('UTC'),
]); ]);
$newEpisode->setPublishedAt(
$this->request->getPost('publication_date'),
$this->request->getPost('publication_time')
);
$episodeModel = new EpisodeModel(); $episodeModel = new EpisodeModel();
...@@ -185,9 +185,7 @@ class Episode extends BaseController ...@@ -185,9 +185,7 @@ class Episode extends BaseController
'uploaded[enclosure]|ext_in[enclosure,mp3,m4a]|permit_empty', 'uploaded[enclosure]|ext_in[enclosure,mp3,m4a]|permit_empty',
'image' => 'image' =>
'is_image[image]|ext_in[image,jpg,png]|min_dims[image,1400,1400]|is_image_squared[image]', 'is_image[image]|ext_in[image,jpg,png]|min_dims[image,1400,1400]|is_image_squared[image]',
'publication_date' => 'valid_date[Y-m-d]|permit_empty', 'publication_date' => 'valid_date[Y-m-d H:i]|permit_empty',
'publication_time' =>
'regex_match[/^(0[0-9]|1[0-9]|2[0-3]):[0-5][0-9]$/]|permit_empty',
]; ];
if (!$this->validate($rules)) { if (!$this->validate($rules)) {
...@@ -210,10 +208,11 @@ class Episode extends BaseController ...@@ -210,10 +208,11 @@ class Episode extends BaseController
: null; : null;
$this->episode->type = $this->request->getPost('type'); $this->episode->type = $this->request->getPost('type');
$this->episode->block = $this->request->getPost('block') == 'yes'; $this->episode->block = $this->request->getPost('block') == 'yes';
$this->episode->setPublishedAt( $this->episode->published_at = Time::createFromFormat(
'Y-m-d H:i',
$this->request->getPost('publication_date'), $this->request->getPost('publication_date'),
$this->request->getPost('publication_time') $this->request->getPost('client_timezone')
); )->setTimezone('UTC');
$this->episode->updated_by = user(); $this->episode->updated_by = user();
$enclosure = $this->request->getFile('enclosure'); $enclosure = $this->request->getFile('enclosure');
......
...@@ -388,11 +388,8 @@ class Podcast extends BaseController ...@@ -388,11 +388,8 @@ class Podcast extends BaseController
: $nsItunes->block === 'yes', : $nsItunes->block === 'yes',
'created_by' => user(), 'created_by' => user(),
'updated_by' => user(), 'updated_by' => user(),
'published_at' => strtotime($item->pubDate),
]); ]);
$newEpisode->setPublishedAt(
date('Y-m-d', strtotime($item->pubDate)),
date('H:i:s', strtotime($item->pubDate))
);
$episodeModel = new EpisodeModel(); $episodeModel = new EpisodeModel();
......
...@@ -26,7 +26,7 @@ class BaseController extends Controller ...@@ -26,7 +26,7 @@ class BaseController extends Controller
* *
* @var array * @var array
*/ */
protected $helpers = ['analytics', 'svg', 'components']; protected $helpers = ['analytics', 'svg', 'components', 'misc'];
/** /**
* Constructor. * Constructor.
......
...@@ -48,7 +48,8 @@ class Episode extends BaseController ...@@ -48,7 +48,8 @@ class Episode extends BaseController
$cacheName = "page_podcast{$this->episode->podcast_id}_episode{$this->episode->id}_{$locale}"; $cacheName = "page_podcast{$this->episode->podcast_id}_episode{$this->episode->id}_{$locale}";
if (!($cachedView = cache($cacheName))) { if (!($cachedView = cache($cacheName))) {
$previousNextEpisodes = (new EpisodeModel())->getPreviousNextEpisodes( $episodeModel = new EpisodeModel();
$previousNextEpisodes = $episodeModel->getPreviousNextEpisodes(
$this->episode, $this->episode,
$this->podcast->type $this->podcast->type
); );
...@@ -60,9 +61,15 @@ class Episode extends BaseController ...@@ -60,9 +61,15 @@ class Episode extends BaseController
'episode' => $this->episode, 'episode' => $this->episode,
]; ];
$secondsToNextUnpublishedEpisode = $episodeModel->getSecondsToNextUnpublishedEpisode(
$this->podcast->id
);
// The page cache is set to a decade so it is deleted manually upon podcast update // The page cache is set to a decade so it is deleted manually upon podcast update
return view('episode', $data, [ return view('episode', $data, [
'cache' => DECADE, 'cache' => $secondsToNextUnpublishedEpisode
? $secondsToNextUnpublishedEpisode
: DECADE,
'cache_name' => $cacheName, 'cache_name' => $cacheName,
]); ]);
} }
......
...@@ -8,6 +8,7 @@ ...@@ -8,6 +8,7 @@
namespace App\Controllers; namespace App\Controllers;
use App\Models\EpisodeModel;
use App\Models\PodcastModel; use App\Models\PodcastModel;
use CodeIgniter\Controller; use CodeIgniter\Controller;
...@@ -31,15 +32,29 @@ class Feed extends Controller ...@@ -31,15 +32,29 @@ class Feed extends Controller
// If things go wrong the show must go on and the user must be able to download the file // If things go wrong the show must go on and the user must be able to download the file
log_message('critical', $e); log_message('critical', $e);
} }
$cacheName = $cacheName =
"podcast{$podcast->id}_feed" . "podcast{$podcast->id}_feed" .
($service ? "_{$service['slug']}" : ''); ($service ? "_{$service['slug']}" : '');
if (!($found = cache($cacheName))) { if (!($found = cache($cacheName))) {
$found = get_rss_feed( $found = get_rss_feed(
$podcast, $podcast,
$service ? '?s=' . urlencode($service['name']) : '' $service ? '?s=' . urlencode($service['name']) : ''
); );
cache()->save($cacheName, $found, DECADE);
// The page cache is set to expire after next episode publication or a decade by default so it is deleted manually upon podcast update
$secondsToNextUnpublishedEpisode = (new EpisodeModel())->getSecondsToNextUnpublishedEpisode(
$podcast->id
);
cache()->save(
$cacheName,
$found,
$secondsToNextUnpublishedEpisode
? $secondsToNextUnpublishedEpisode
: DECADE
);
} }
return $this->response->setXML($found); return $this->response->setXML($found);
} }
......
...@@ -48,7 +48,7 @@ class Install extends Controller ...@@ -48,7 +48,7 @@ class Install extends Controller
} }
// Check if the created .env file is writable to continue install process // Check if the created .env file is writable to continue install process
if (is_writable(ROOTPATH . '.env')) { if (is_really_writable(ROOTPATH . '.env')) {
try { try {
$dotenv->required([ $dotenv->required([
'app.baseURL', 'app.baseURL',
......
...@@ -113,7 +113,7 @@ class Podcast extends BaseController ...@@ -113,7 +113,7 @@ class Podcast extends BaseController
'podcast' => $this->podcast, 'podcast' => $this->podcast,
'episodesNav' => $episodesNavigation, 'episodesNav' => $episodesNavigation,
'activeQuery' => $activeQuery, 'activeQuery' => $activeQuery,
'episodes' => (new EpisodeModel())->getPodcastEpisodes( 'episodes' => $episodeModel->getPodcastEpisodes(
$this->podcast->id, $this->podcast->id,
$this->podcast->type, $this->podcast->type,
$yearQuery, $yearQuery,
...@@ -121,8 +121,14 @@ class Podcast extends BaseController ...@@ -121,8 +121,14 @@ class Podcast extends BaseController
), ),
]; ];
$secondsToNextUnpublishedEpisode = $episodeModel->getSecondsToNextUnpublishedEpisode(
$this->podcast->id
);
return view('podcast', $data, [ return view('podcast', $data, [
'cache' => DECADE, 'cache' => $secondsToNextUnpublishedEpisode
? $secondsToNextUnpublishedEpisode
: DECADE,
'cache_name' => $cacheName, 'cache_name' => $cacheName,
]); ]);
} }
......
...@@ -10,6 +10,7 @@ namespace App\Entities; ...@@ -10,6 +10,7 @@ namespace App\Entities;
use App\Models\PodcastModel; use App\Models\PodcastModel;
use CodeIgniter\Entity; use CodeIgniter\Entity;
use CodeIgniter\I18n\Time;
use League\CommonMark\CommonMarkConverter; use League\CommonMark\CommonMarkConverter;
class Episode extends Entity class Episode extends Entity
...@@ -49,6 +50,11 @@ class Episode extends Entity ...@@ -49,6 +50,11 @@ class Episode extends Entity
*/ */
protected $description_html; protected $description_html;
/**
* @var boolean
*/
protected $is_published;
protected $dates = [ protected $dates = [
'published_at', 'published_at',
'created_at', 'created_at',
...@@ -232,17 +238,6 @@ class Episode extends Entity ...@@ -232,17 +238,6 @@ class Episode extends Entity
return $converter->convertToHtml($this->attributes['description']); return $converter->convertToHtml($this->attributes['description']);
} }
public function setPublishedAt($date, $time)
{
if (empty($date)) {
$this->attributes['published_at'] = null;
} else {
$this->attributes['published_at'] = $date . ' ' . $time;
}
return $this;
}
public function setCreatedBy(\App\Entities\User $user) public function setCreatedBy(\App\Entities\User $user)
{ {
$this->attributes['created_by'] = $user->id; $this->attributes['created_by'] = $user->id;
...@@ -256,4 +251,17 @@ class Episode extends Entity ...@@ -256,4 +251,17 @@ class Episode extends Entity
return $this; return $this;
} }
public function getIsPublished()
{
if ($this->is_published) {
return $this->is_published;
}
helper('date');
$this->is_published = $this->published_at->isBefore(Time::now());
return $this->is_published;
}
} }
...@@ -256,3 +256,51 @@ if (!function_exists('data_table')) { ...@@ -256,3 +256,51 @@ if (!function_exists('data_table')) {
} }
// ------------------------------------------------------------------------ // ------------------------------------------------------------------------
if (!function_exists('publication_pill')) {
/**
* Data table component
*
* Creates a stylized table.
*
* @param \CodeIgniter\I18n\Time $publicationDate publication datetime of the episode
* @param boolean $isPublished whether or not the episode has been published
* @param string $customClass css class to add to the component
*
* @return string
*/
function publication_pill(
$publicationDate,
$isPublished,
$customClass = ''
): string {
$class = $isPublished
? 'text-green-500 border-green-500'
: 'text-orange-600 border-orange-600';
$label = lang(
$isPublished ? 'Episode.published' : 'Episode.scheduled',
[
'<time
pubdate
datetime="' .
$publicationDate->format(DateTime::ATOM) .
'"
title="' .
$publicationDate .
'">' .
lang('Common.mediumDate', [$publicationDate]) .
'</time>',
]
);
return '<span class="px-1 border ' .
$class .
' ' .
$customClass .
'">' .
$label .
'</span>';
}
}
// ------------------------------------------------------------------------
...@@ -143,3 +143,27 @@ function slugify($text) ...@@ -143,3 +143,27 @@ function slugify($text)
return $text; return $text;
} }
//--------------------------------------------------------------------
if (!function_exists('format_duration')) {
/**
* Formats duration in seconds to an hh:mm:ss string
*
* @param int $seconds seconds to format
* @param string $separator
*
* @return string
*/
function format_duration($seconds, $separator = ':')
{
return sprintf(
'%02d%s%02d%s%02d',
floor($seconds / 3600),
$separator,
($seconds / 60) % 60,
$separator,
$seconds % 60
);
}
}
...@@ -14,7 +14,6 @@ return [ ...@@ -14,7 +14,6 @@ return [
'home' => 'Home', 'home' => 'Home',
'explicit' => 'Explicit', 'explicit' => 'Explicit',
'mediumDate' => '{0,date,medium}', 'mediumDate' => '{0,date,medium}',
'duration' => '{0,duration}',
'powered_by' => 'Powered by {castopod}.', 'powered_by' => 'Powered by {castopod}.',
'actions' => 'Actions', 'actions' => 'Actions',
'pageInfo' => 'Page {currentPage} out of {pageCount}', 'pageInfo' => 'Page {currentPage} out of {pageCount}',
......
...@@ -22,6 +22,8 @@ return [ ...@@ -22,6 +22,8 @@ return [
'delete' => 'Delete', 'delete' => 'Delete',
'go_to_page' => 'Go to page', 'go_to_page' => 'Go to page',
'create' => 'Add an episode', 'create' => 'Add an episode',
'published' => 'Published on {0}',
'scheduled' => 'Scheduled for {0}',
'form' => [ 'form' => [
'enclosure' => 'Audio file', 'enclosure' => 'Audio file',
'enclosure_hint' => 'Choose an .mp3 or .m4a audio file.', 'enclosure_hint' => 'Choose an .mp3 or .m4a audio file.',
...@@ -54,11 +56,9 @@ return [ ...@@ -54,11 +56,9 @@ return [
'This text is added at the end of each episode description, it is a good place to input your social links for example.', 'This text is added at the end of each episode description, it is a good place to input your social links for example.',
'publication_section_title' => 'Publication info', 'publication_section_title' => 'Publication info',
'publication_section_subtitle' => '', 'publication_section_subtitle' => '',
'published_at' => [ 'publication_date' => 'Publication date',
'label' => 'Publication date', 'publication_date_hint' =>
'date' => 'Date', 'You can schedule the episode release by setting a future publication date. This field must be formatted as YYYY-MM-DD HH:mm',
'time' => 'Time',
],
'parental_advisory' => [ 'parental_advisory' => [
'label' => 'Parental advisory', 'label' => 'Parental advisory',
'hint' => 'Does the episode contain explicit content?', 'hint' => 'Does the episode contain explicit content?',
......
...@@ -14,7 +14,6 @@ return [ ...@@ -14,7 +14,6 @@ return [
'home' => 'Accueil', 'home' => 'Accueil',
'explicit' => 'Explicite', 'explicit' => 'Explicite',
'mediumDate' => '{0,date,medium}', 'mediumDate' => '{0,date,medium}',
'duration' => '{0,duration}',
'powered_by' => 'Propulsé par {castopod}.', 'powered_by' => 'Propulsé par {castopod}.',
'actions' => 'Actions', 'actions' => 'Actions',
'pageInfo' => 'Page {currentPage} sur {pageCount}', 'pageInfo' => 'Page {currentPage} sur {pageCount}',
......
...@@ -22,6 +22,8 @@ return [ ...@@ -22,6 +22,8 @@ return [
'delete' => 'Supprimer', 'delete' => 'Supprimer',
'go_to_page' => 'Voir', 'go_to_page' => 'Voir',
'create' => 'Ajouter un épisode', 'create' => 'Ajouter un épisode',
'published' => 'Publié le {0}',
'scheduled' => 'Planifié pour le {0}',
'form' => [ 'form' => [
'enclosure' => 'Fichier audio', 'enclosure' => 'Fichier audio',
'enclosure_hint' => 'Sélectionnez un fichier audio .mp3 ou .m4a.', 'enclosure_hint' => 'Sélectionnez un fichier audio .mp3 ou .m4a.',
...@@ -54,11 +56,9 @@ return [ ...@@ -54,11 +56,9 @@ return [
'Ce texte est ajouté à la fin de chaque description d’épisode, c’est un bon endroit pour placer vos liens sociaux par exemple.', 'Ce texte est ajouté à la fin de chaque description d’épisode, c’est un bon endroit pour placer vos liens sociaux par exemple.',
'publication_section_title' => 'Information de publication', 'publication_section_title' => 'Information de publication',
'publication_section_subtitle' => '', 'publication_section_subtitle' => '',
'published_at' => [ 'publication_date' => 'Date de publication',
'label' => 'Date de publication', 'publication_date_hint' =>
'date' => 'Date', 'Vous pouvez planifier la sortie de l’épisode en saisissant une date de publication future. Ce champ doit être au format YYYY-MM-DD HH:mm',
'time' => 'Heure',
],
'parental_advisory' => [ 'parental_advisory' => [
'label' => 'Avertissement parental', 'label' => 'Avertissement parental',
'hint' => 'L’épisode contient-il un contenu explicite ?', 'hint' => 'L’épisode contient-il un contenu explicite ?',
......
...@@ -12,6 +12,7 @@ return [ ...@@ -12,6 +12,7 @@ return [
'messages' => [ 'messages' => [
'wrongPasswordError' => 'wrongPasswordError' =>
'Le mot de passe que vous avez saisi est invalide.', 'Le mot de passe que vous avez saisi est invalide.',
'passwordChangeSuccess' => 'Le mot de passe a été modifié avec succès !', 'passwordChangeSuccess' =>
'Le mot de passe a été modifié avec succès !',
], ],
]; ];
...@@ -57,32 +57,21 @@ class EpisodeModel extends Model ...@@ -57,32 +57,21 @@ class EpisodeModel extends Model
]; ];
protected $validationMessages = []; protected $validationMessages = [];
protected $afterInsert = ['writeEnclosureMetadata']; protected $afterInsert = ['writeEnclosureMetadata', 'clearCache'];
// clear cache beforeUpdate because if slug changes, so will the episode link // clear cache beforeUpdate because if slug changes, so will the episode link
protected $beforeUpdate = ['clearCache']; protected $beforeUpdate = ['clearCache'];
protected $afterUpdate = ['writeEnclosureMetadata']; protected $afterUpdate = ['writeEnclosureMetadata'];
protected $beforeDelete = ['clearCache']; protected $beforeDelete = ['clearCache'];
protected function writeEnclosureMetadata(array $data)
{
helper('id3');
$episode = (new EpisodeModel())->find(
is_array($data['id']) ? $data['id'][0] : $data['id']
);
write_enclosure_tags($episode);
return $data;
}
public function getEpisodeBySlug($podcastId, $episodeSlug) public function getEpisodeBySlug($podcastId, $episodeSlug)
{ {
if (!($found = cache("podcast{$podcastId}_episode@{$episodeSlug}"))) { if (!($found = cache("podcast{$podcastId}_episode@{$episodeSlug}"))) {
$found = $this->where([ $found = $this->where([
'podcast_id' => $podcastId, 'podcast_id' => $podcastId,
'slug' => $episodeSlug, 'slug' => $episodeSlug,
])->first(); ])
->where('`published_at` <= NOW()', null, false)
->first();
cache()->save( cache()->save(
"podcast{$podcastId}_episode@{$episodeSlug}", "podcast{$podcastId}_episode@{$episodeSlug}",
...@@ -120,6 +109,7 @@ class EpisodeModel extends Model ...@@ -120,6 +109,7 @@ class EpisodeModel extends Model
'podcast_id' => $episode->podcast_id, 'podcast_id' => $episode->podcast_id,
$sortNumberField . ' <' => $sortNumberValue, $sortNumberField . ' <' => $sortNumberValue,
]) ])
->where('`published_at` <= NOW()', null, false)
->first(); ->first();
$nextData = $this->orderBy('(' . $sortNumberField . ') ASC') $nextData = $this->orderBy('(' . $sortNumberField . ') ASC')
...@@ -127,6 +117,7 @@ class EpisodeModel extends Model ...@@ -127,6 +117,7 @@ class EpisodeModel extends Model
'podcast_id' => $episode->podcast_id, 'podcast_id' => $episode->podcast_id,
$sortNumberField . ' >' => $sortNumberValue, $sortNumberField . ' >' => $sortNumberValue,
]) ])
->where('`published_at` <= NOW()', null, false)
->first(); ->first();
return [ return [
...@@ -160,7 +151,9 @@ class EpisodeModel extends Model ...@@ -160,7 +151,9 @@ class EpisodeModel extends Model
); );
if (!($found = cache($cacheName))) { if (!($found = cache($cacheName))) {
$where = ['podcast_id' => $podcastId]; $where = [
'podcast_id' => $podcastId,
];
if ($year) { if ($year) {
$where['YEAR(published_at)'] = $year; $where['YEAR(published_at)'] = $year;
$where['season_number'] = null; $where['season_number'] = null;
...@@ -172,15 +165,27 @@ class EpisodeModel extends Model ...@@ -172,15 +165,27 @@ class EpisodeModel extends Model
if ($podcastType == 'serial') { if ($podcastType == 'serial') {
// podcast is serial // podcast is serial
$found = $this->where($where) $found = $this->where($where)
->where('`published_at` <= NOW()', null, false)
->orderBy('season_number DESC, number ASC') ->orderBy('season_number DESC, number ASC')
->findAll(); ->findAll();
} else { } else {
$found = $this->where($where) $found = $this->where($where)
->where('`published_at` <= NOW()', null, false)
->orderBy('published_at', 'DESC') ->orderBy('published_at', 'DESC')
->findAll(); ->findAll();
} }
cache()->save($cacheName, $found, DECADE); $secondsToNextUnpublishedEpisode = $this->getSecondsToNextUnpublishedEpisode(
$podcastId
);
cache()->save(
$cacheName,
$found,
$secondsToNextUnpublishedEpisode
? $secondsToNextUnpublishedEpisode
: DECADE
);
} }
return $found; return $found;
...@@ -197,12 +202,23 @@ class EpisodeModel extends Model ...@@ -197,12 +202,23 @@ class EpisodeModel extends Model
'season_number' => null, 'season_number' => null,
$this->deletedField => null, $this->deletedField => null,
]) ])
->where('`published_at` <= NOW()', null, false)
->groupBy('year') ->groupBy('year')
->orderBy('year', 'DESC') ->orderBy('year', 'DESC')
->get() ->get()
->getResultArray(); ->getResultArray();
cache()->save("podcast{$podcastId}_years", $found, DECADE); $secondsToNextUnpublishedEpisode = $this->getSecondsToNextUnpublishedEpisode(
$podcastId
);
cache()->save(
"podcast{$podcastId}_years",
$found,
$secondsToNextUnpublishedEpisode
? $secondsToNextUnpublishedEpisode
: DECADE
);
} }
return $found; return $found;
...@@ -219,12 +235,23 @@ class EpisodeModel extends Model ...@@ -219,12 +235,23 @@ class EpisodeModel extends Model
'season_number is not' => null, 'season_number is not' => null,
$this->deletedField => null, $this->deletedField => null,
]) ])
->where('`published_at` <= NOW()', null, false)
->groupBy('season_number') ->groupBy('season_number')
->orderBy('season_number', 'ASC') ->orderBy('season_number', 'ASC')
->get() ->get()
->getResultArray(); ->getResultArray();
cache()->save("podcast{$podcastId}_seasons", $found, DECADE); $secondsToNextUnpublishedEpisode = $this->getSecondsToNextUnpublishedEpisode(
$podcastId
);
cache()->save(
"podcast{$podcastId}_seasons",
$found,
$secondsToNextUnpublishedEpisode
? $secondsToNextUnpublishedEpisode
: DECADE
);
} }
return $found; return $found;
...@@ -264,6 +291,43 @@ class EpisodeModel extends Model ...@@ -264,6 +291,43 @@ class EpisodeModel extends Model
return $defaultQuery; return $defaultQuery;
} }
/**
* Returns the timestamp difference in seconds between the next episode to publish and the current timestamp
* Returns false if there's no episode to publish
*
* @param int $podcastId
*
* @return int|false seconds
*/
public function getSecondsToNextUnpublishedEpisode(int $podcastId)
{
$result = $this->select(
'TIMESTAMPDIFF(SECOND, NOW(), `published_at`) as timestamp_diff'
)
->where([
'podcast_id' => $podcastId,
])
->where('`published_at` > NOW()', null, false)
->orderBy('published_at', 'asc')
->get()
->getResultArray();
return (int) $result ? $result[0]['timestamp_diff'] : false;
}
protected function writeEnclosureMetadata(array $data)
{
helper('id3');
$episode = (new EpisodeModel())->find(
is_array($data['id']) ? $data['id'][0] : $data['id']
);
write_enclosure_tags($episode);
return $data;
}
protected function clearCache(array $data) protected function clearCache(array $data)
{ {
$episodeModel = new EpisodeModel(); $episodeModel = new EpisodeModel();
......
...@@ -59,7 +59,7 @@ class PodcastModel extends Model ...@@ -59,7 +59,7 @@ class PodcastModel extends Model
]; ];
protected $validationMessages = []; protected $validationMessages = [];
// clear cache before update if by any chance, the podcast name changes, and so will the podcast link // clear cache before update if by any chance, the podcast name changes, so will the podcast link
protected $beforeUpdate = ['clearCache']; protected $beforeUpdate = ['clearCache'];
protected $beforeDelete = ['clearCache']; protected $beforeDelete = ['clearCache'];
......
import ClientTimezone from "./modules/ClientTimezone";
import DateTimePicker from "./modules/DateTimePicker";
import Dropdown from "./modules/Dropdown"; import Dropdown from "./modules/Dropdown";
import MarkdownEditor from "./modules/MarkdownEditor"; import MarkdownEditor from "./modules/MarkdownEditor";
import MultiSelect from "./modules/MultiSelect"; import MultiSelect from "./modules/MultiSelect";
import SidebarToggler from "./modules/SidebarToggler"; import SidebarToggler from "./modules/SidebarToggler";
import Slugify from "./modules/Slugify"; import Slugify from "./modules/Slugify";
import Time from "./modules/Time";
import Tooltip from "./modules/Tooltip"; import Tooltip from "./modules/Tooltip";
Dropdown(); Dropdown();
...@@ -11,3 +14,6 @@ MarkdownEditor(); ...@@ -11,3 +14,6 @@ MarkdownEditor();
MultiSelect(); MultiSelect();
Slugify(); Slugify();
SidebarToggler(); SidebarToggler();
ClientTimezone();
DateTimePicker();
Time();
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