diff --git a/app/Config/Routes.php b/app/Config/Routes.php index 91de34a6..bd50617e 100644 --- a/app/Config/Routes.php +++ b/app/Config/Routes.php @@ -326,6 +326,14 @@ $routes->group( 'filter' => 'permission:podcast_episodes-edit', ] ); + $routes->get( + 'embeddable-player', + 'Episode::embeddablePlayer/$1/$2', + [ + 'as' => 'embeddable-player-add', + 'filter' => 'permission:podcast_episodes-edit', + ] + ); $routes->group('persons', function ($routes) { $routes->get('/', 'EpisodePerson/$1/$2', [ @@ -565,9 +573,19 @@ $routes->group(config('App')->authGateway, function ($routes) { // Public routes $routes->group('@(:podcastName)', function ($routes) { $routes->get('/', 'Podcast/$1', ['as' => 'podcast']); - $routes->get('(:slug)', 'Episode/$1/$2', [ - 'as' => 'episode', - ]); + $routes->group('(:slug)', function ($routes) { + $routes->get('/', 'Episode/$1/$2', [ + 'as' => 'episode', + ]); + $routes->group('embeddable-player', function ($routes) { + $routes->get('/', 'Episode::embeddablePlayer/$1/$2', [ + 'as' => 'embeddable-player', + ]); + $routes->get('(:slug)', 'Episode::embeddablePlayer/$1/$2/$3', [ + 'as' => 'embeddable-player-theme', + ]); + }); + }); $routes->head('feed.xml', 'Feed/$1', ['as' => 'podcast_feed']); $routes->get('feed.xml', 'Feed/$1', ['as' => 'podcast_feed']); }); diff --git a/app/Controllers/Admin/Episode.php b/app/Controllers/Admin/Episode.php index 8ad5ce6f..92f9a8ea 100644 --- a/app/Controllers/Admin/Episode.php +++ b/app/Controllers/Admin/Episode.php @@ -420,4 +420,21 @@ class Episode extends BaseController $this->episode->id, ]); } + + public function embeddablePlayer() + { + 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); + } } diff --git a/app/Controllers/Admin/PodcastPlatform.php b/app/Controllers/Admin/PodcastPlatform.php index 9229cbd0..6f5fd99f 100644 --- a/app/Controllers/Admin/PodcastPlatform.php +++ b/app/Controllers/Admin/PodcastPlatform.php @@ -81,6 +81,12 @@ class PodcastPlatform extends BaseController ) ? $podcastPlatform['visible'] == 'yes' : false, + 'is_on_embeddable_player' => array_key_exists( + 'on_embeddable_player', + $podcastPlatform + ) + ? $podcastPlatform['on_embeddable_player'] == 'yes' + : false, ]); } } diff --git a/app/Controllers/Analytics.php b/app/Controllers/Analytics.php index ccaa92f6..c687cb65 100644 --- a/app/Controllers/Analytics.php +++ b/app/Controllers/Analytics.php @@ -49,8 +49,16 @@ class Analytics extends Controller public function hit($base64EpisodeData, ...$filename) { helper('media', 'analytics'); - - $serviceName = isset($_GET['_from']) ? $_GET['_from'] : ''; + $session = \Config\Services::session(); + $session->start(); + $serviceName = ''; + if (isset($_GET['_from'])) { + $serviceName = $_GET['_from']; + } elseif (!empty($session->get('embeddable_player_domain'))) { + $serviceName = $session->get('embeddable_player_domain'); + } elseif ($session->get('referer') !== '- Direct -') { + $serviceName = parse_url($session->get('referer'), PHP_URL_HOST); + } $episodeData = unpack( 'IpodcastId/IepisodeId/IbytesThreshold/IfileSize/Iduration/IpublicationDate', diff --git a/app/Controllers/Episode.php b/app/Controllers/Episode.php index 3df89dda..477a5cad 100644 --- a/app/Controllers/Episode.php +++ b/app/Controllers/Episode.php @@ -36,8 +36,9 @@ class Episode extends BaseController ) { throw \CodeIgniter\Exceptions\PageNotFoundException::forPageNotFound(); } - - return $this->$method(); + unset($params[1]); + unset($params[0]); + return $this->$method(...$params); } public function index() @@ -54,48 +55,12 @@ class Episode extends BaseController $this->podcast->type ); + helper(['persons']); $persons = []; - foreach ($this->episode->episode_persons as $episodePerson) { - if (array_key_exists($episodePerson->person->id, $persons)) { - $persons[$episodePerson->person->id]['roles'] .= - empty($episodePerson->person_group) || - empty($episodePerson->person_role) - ? '' - : (empty( - $persons[$episodePerson->person->id][ - 'roles' - ] - ) - ? '' - : ', ') . - lang( - 'PersonsTaxonomy.persons.' . - $episodePerson->person_group . - '.roles.' . - $episodePerson->person_role . - '.label' - ); - } else { - $persons[$episodePerson->person->id] = [ - 'full_name' => $episodePerson->person->full_name, - 'information_url' => - $episodePerson->person->information_url, - 'thumbnail_url' => - $episodePerson->person->image->thumbnail_url, - 'roles' => - empty($episodePerson->person_group) || - empty($episodePerson->person_role) - ? '' - : lang( - 'PersonsTaxonomy.persons.' . - $episodePerson->person_group . - '.roles.' . - $episodePerson->person_role . - '.label' - ), - ]; - } - } + construct_episode_person_array( + $this->episode->episode_persons, + $persons + ); $data = [ 'previousEpisode' => $previousNextEpisodes['previous'], @@ -120,4 +85,58 @@ class Episode extends BaseController return $cachedView; } + + public function embeddablePlayer($theme = 'light-transparent') + { + self::triggerWebpageHit($this->episode->podcast_id); + + $session = \Config\Services::session(); + $session->start(); + if (isset($_SERVER['HTTP_REFERER'])) { + $session->set( + 'embeddable_player_domain', + parse_url($_SERVER['HTTP_REFERER'], PHP_URL_HOST) + ); + } + + $locale = service('request')->getLocale(); + + $cacheName = "page_podcast{$this->episode->podcast_id}_episode{$this->episode->id}_embeddable_player_{$theme}_{$locale}"; + + if (!($cachedView = cache($cacheName))) { + $episodeModel = new EpisodeModel(); + $theme = EpisodeModel::$themes[$theme]; + helper(['persons']); + $persons = []; + construct_episode_person_array( + $this->episode->episode_persons, + $persons + ); + constructs_podcast_person_array( + $this->podcast->podcast_persons, + $persons + ); + + $data = [ + 'podcast' => $this->podcast, + 'episode' => $this->episode, + 'persons' => $persons, + 'theme' => $theme, + ]; + + $secondsToNextUnpublishedEpisode = $episodeModel->getSecondsToNextUnpublishedEpisode( + $this->podcast->id + ); + + // The page cache is set to a decade so it is deleted manually upon podcast update + return view('embeddable_player', $data, [ + 'cache' => $secondsToNextUnpublishedEpisode + ? $secondsToNextUnpublishedEpisode + : DECADE, + 'cache_name' => $cacheName, + ]); + } + + return $cachedView; + } } diff --git a/app/Controllers/Podcast.php b/app/Controllers/Podcast.php index 9c6fc675..d7a80a6e 100644 --- a/app/Controllers/Podcast.php +++ b/app/Controllers/Podcast.php @@ -109,48 +109,12 @@ class Podcast extends BaseController ]); } + helper(['persons']); $persons = []; - foreach ($this->podcast->podcast_persons as $podcastPerson) { - if (array_key_exists($podcastPerson->person->id, $persons)) { - $persons[$podcastPerson->person->id]['roles'] .= - empty($podcastPerson->person_group) || - empty($podcastPerson->person_role) - ? '' - : (empty( - $persons[$podcastPerson->person->id][ - 'roles' - ] - ) - ? '' - : ', ') . - lang( - 'PersonsTaxonomy.persons.' . - $podcastPerson->person_group . - '.roles.' . - $podcastPerson->person_role . - '.label' - ); - } else { - $persons[$podcastPerson->person->id] = [ - 'full_name' => $podcastPerson->person->full_name, - 'information_url' => - $podcastPerson->person->information_url, - 'thumbnail_url' => - $podcastPerson->person->image->thumbnail_url, - 'roles' => - empty($podcastPerson->person_group) || - empty($podcastPerson->person_role) - ? '' - : lang( - 'PersonsTaxonomy.persons.' . - $podcastPerson->person_group . - '.roles.' . - $podcastPerson->person_role . - '.label' - ), - ]; - } - } + constructs_podcast_person_array( + $this->podcast->podcast_persons, + $persons + ); $data = [ 'podcast' => $this->podcast, diff --git a/app/Database/Migrations/2020-06-08-160000_add_podcasts_platforms.php b/app/Database/Migrations/2020-06-08-160000_add_podcasts_platforms.php index 045add85..4b2a9f9e 100644 --- a/app/Database/Migrations/2020-06-08-160000_add_podcasts_platforms.php +++ b/app/Database/Migrations/2020-06-08-160000_add_podcasts_platforms.php @@ -40,6 +40,11 @@ class AddPodcastsPlatforms extends Migration 'constraint' => 1, 'default' => 0, ], + 'is_on_embeddable_player' => [ + 'type' => 'TINYINT', + 'constraint' => 1, + 'default' => 0, + ], ]); $this->forge->addPrimaryKey(['podcast_id', 'platform_slug']); diff --git a/app/Entities/Episode.php b/app/Entities/Episode.php index 964d51b2..99bc4804 100644 --- a/app/Entities/Episode.php +++ b/app/Entities/Episode.php @@ -94,6 +94,13 @@ class Episode extends Entity */ protected $description; + /** + * The embeddable player URL + * + * @var string + */ + protected $embeddable_player; + /** * @var string */ @@ -421,6 +428,24 @@ class Episode extends Entity ); } + public function getEmbeddablePlayer($theme = null) + { + return base_url( + $theme + ? route_to( + 'embeddable-player-theme', + $this->getPodcast()->name, + $this->attributes['slug'], + $theme + ) + : route_to( + 'embeddable-player', + $this->getPodcast()->name, + $this->attributes['slug'] + ) + ); + } + public function setGuid(string $guid) { return $this->attributes['guid'] = empty($guid) diff --git a/app/Entities/Platform.php b/app/Entities/Platform.php index 90460326..a1080da4 100644 --- a/app/Entities/Platform.php +++ b/app/Entities/Platform.php @@ -21,5 +21,6 @@ class Platform extends Entity 'link_url' => '?string', 'link_content' => '?string', 'is_visible' => '?boolean', + 'is_on_embeddable_player' => '?boolean', ]; } diff --git a/app/Entities/Podcast.php b/app/Entities/Podcast.php index f35cd759..aa4a4afe 100644 --- a/app/Entities/Podcast.php +++ b/app/Entities/Podcast.php @@ -324,6 +324,24 @@ class Podcast extends Entity return $this->podcastingPlatforms; } + /** + * Returns true if the podcast has podcasting platform links + */ + public function getHasPodcastingPlatforms() + { + if (empty($this->id)) { + throw new \RuntimeException( + 'Podcast must be created before getting podcasting platform.' + ); + } + foreach ($this->getPodcastingPlatforms() as $podcastingPlatform) { + if ($podcastingPlatform->is_on_embeddable_player) { + return true; + } + } + return false; + } + /** * Returns the podcast's social platform links * @@ -347,6 +365,24 @@ class Podcast extends Entity return $this->socialPlatforms; } + /** + * Returns true if the podcast has social platform links + */ + public function getHasSocialPlatforms() + { + if (empty($this->id)) { + throw new \RuntimeException( + 'Podcast must be created before getting social platform.' + ); + } + foreach ($this->getSocialPlatforms() as $socialPlatform) { + if ($socialPlatform->is_on_embeddable_player) { + return true; + } + } + return false; + } + /** * Returns the podcast's funding platform links * @@ -370,6 +406,24 @@ class Podcast extends Entity return $this->fundingPlatforms; } + /** + * Returns true if the podcast has social platform links + */ + public function getHasFundingPlatforms() + { + if (empty($this->id)) { + throw new \RuntimeException( + 'Podcast must be created before getting Funding platform.' + ); + } + foreach ($this->getFundingPlatforms() as $fundingPlatform) { + if ($fundingPlatform->is_on_embeddable_player) { + return true; + } + } + return false; + } + public function getOtherCategories() { if (empty($this->id)) { diff --git a/app/Helpers/analytics_helper.php b/app/Helpers/analytics_helper.php index 7439c1e4..3360de83 100644 --- a/app/Helpers/analytics_helper.php +++ b/app/Helpers/analytics_helper.php @@ -111,9 +111,6 @@ function set_user_session_player() $session->start(); if (!$session->has('player')) { - $session = \Config\Services::session(); - $session->start(); - $playerFound = null; $userAgent = $_SERVER['HTTP_USER_AGENT']; diff --git a/app/Helpers/components_helper.php b/app/Helpers/components_helper.php index 3d69bab2..78b1516e 100644 --- a/app/Helpers/components_helper.php +++ b/app/Helpers/components_helper.php @@ -384,29 +384,12 @@ if (!function_exists('location_link')) { $locationOsmid, $class = '' ) { - $link = null; + $link = ''; + if (!empty($locationName)) { - $uri = ''; - if (!empty($locationOsmid)) { - $uri = - 'https://www.openstreetmap.org/' . - ['N' => 'node', 'W' => 'way', 'R' => 'relation'][ - substr($locationOsmid, 0, 1) - ] . - '/' . - substr($locationOsmid, 1); - } elseif (!empty($locationGeo)) { - $uri = - 'https://www.openstreetmap.org/#map=17/' . - str_replace(',', '/', substr($locationGeo, 4)); - } else { - $uri = - 'https://www.openstreetmap.org/search?query=' . - urlencode($locationName); - } $link = button( $locationName, - $uri, + location_url($locationName, $locationGeo, $locationOsmid), [ 'variant' => 'default', 'size' => 'small', @@ -421,6 +404,7 @@ if (!function_exists('location_link')) { ] ); } + return $link; } } diff --git a/app/Helpers/persons_helper.php b/app/Helpers/persons_helper.php new file mode 100644 index 00000000..5fe79d76 --- /dev/null +++ b/app/Helpers/persons_helper.php @@ -0,0 +1,97 @@ +person->id, $persons)) { + $persons[$podcastPerson->person->id]['roles'] .= + empty($podcastPerson->person_group) || + empty($podcastPerson->person_role) + ? '' + : (empty($persons[$podcastPerson->person->id]['roles']) + ? '' + : ', ') . + lang( + 'PersonsTaxonomy.persons.' . + $podcastPerson->person_group . + '.roles.' . + $podcastPerson->person_role . + '.label' + ); + } else { + $persons[$podcastPerson->person->id] = [ + 'full_name' => $podcastPerson->person->full_name, + 'information_url' => $podcastPerson->person->information_url, + 'thumbnail_url' => $podcastPerson->person->image->thumbnail_url, + 'roles' => + empty($podcastPerson->person_group) || + empty($podcastPerson->person_role) + ? '' + : lang( + 'PersonsTaxonomy.persons.' . + $podcastPerson->person_group . + '.roles.' . + $podcastPerson->person_role . + '.label' + ), + ]; + } + } +} + +/** + * Fetches persons from an episode + * + * @param array $episode_persons + * @param array &$persons + */ +function construct_episode_person_array($episode_persons, &$persons) +{ + foreach ($episode_persons as $episodePerson) { + if (array_key_exists($episodePerson->person->id, $persons)) { + $persons[$episodePerson->person->id]['roles'] .= + empty($episodePerson->person_group) || + empty($episodePerson->person_role) + ? '' + : (empty($persons[$episodePerson->person->id]['roles']) + ? '' + : ', ') . + lang( + 'PersonsTaxonomy.persons.' . + $episodePerson->person_group . + '.roles.' . + $episodePerson->person_role . + '.label' + ); + } else { + $persons[$episodePerson->person->id] = [ + 'full_name' => $episodePerson->person->full_name, + 'information_url' => $episodePerson->person->information_url, + 'thumbnail_url' => $episodePerson->person->image->thumbnail_url, + 'roles' => + empty($episodePerson->person_group) || + empty($episodePerson->person_role) + ? '' + : lang( + 'PersonsTaxonomy.persons.' . + $episodePerson->person_group . + '.roles.' . + $episodePerson->person_role . + '.label' + ), + ]; + } + } +} diff --git a/app/Helpers/url_helper.php b/app/Helpers/url_helper.php index ec462ca8..6fa434ae 100644 --- a/app/Helpers/url_helper.php +++ b/app/Helpers/url_helper.php @@ -38,3 +38,39 @@ if (!function_exists('current_season_url')) { return current_url() . $season_query_string; } } + +if (!function_exists('location_url')) { + /** + * Returns URL to display from location info + * + * @param string $locationName + * @param string $locationGeo + * @param string $locationOsmid + * + * @return string + */ + function location_url($locationName, $locationGeo, $locationOsmid) + { + $uri = ''; + + if (!empty($locationOsmid)) { + $uri = + 'https://www.openstreetmap.org/' . + ['N' => 'node', 'W' => 'way', 'R' => 'relation'][ + substr($locationOsmid, 0, 1) + ] . + '/' . + substr($locationOsmid, 1); + } elseif (!empty($locationGeo)) { + $uri = + 'https://www.openstreetmap.org/#map=17/' . + str_replace(',', '/', substr($locationGeo, 4)); + } elseif (!empty($locationName)) { + $uri = + 'https://www.openstreetmap.org/search?query=' . + urlencode($locationName); + } + + return $uri; + } +} diff --git a/app/Language/en/Breadcrumb.php b/app/Language/en/Breadcrumb.php index 03db0bce..ab1e511c 100644 --- a/app/Language/en/Breadcrumb.php +++ b/app/Language/en/Breadcrumb.php @@ -32,4 +32,5 @@ return [ 'listening-time' => 'listening time', 'time-periods' => 'time periods', 'soundbites' => 'soundbites', + 'embeddable-player' => 'embeddable player', ]; diff --git a/app/Language/en/Episode.php b/app/Language/en/Episode.php index 00a48721..18df1b40 100644 --- a/app/Language/en/Episode.php +++ b/app/Language/en/Episode.php @@ -109,4 +109,16 @@ return [ 'Click while playing to get current position, click again to get duration.', 'submit_edit' => 'Save all soundbites', ], + 'embeddable_player' => [ + 'add' => 'Add embeddable player', + 'title' => 'Embeddable player', + 'label' => + 'Pick a theme color, copy the embeddable player to clipboard, then paste it on your website.', + 'clipboard_iframe' => 'Copy embeddable player to clipboard', + 'clipboard_url' => 'Copy address to clipboard', + 'dark' => 'Dark', + 'dark-transparent' => 'Dark transparent', + 'light' => 'Light', + 'light-transparent' => 'Light transparent', + ], ]; diff --git a/app/Language/en/Platforms.php b/app/Language/en/Platforms.php index da822c97..c1890482 100644 --- a/app/Language/en/Platforms.php +++ b/app/Language/en/Platforms.php @@ -11,6 +11,7 @@ return [ 'home_url' => 'Go to {platformName} website', 'submit_url' => 'Submit your podcast on {platformName}', 'visible' => 'Display in podcast homepage?', + 'on_embeddable_player' => 'Display on embeddable player?', 'remove' => 'Remove {platformName}', 'submit' => 'Save', 'messages' => [ diff --git a/app/Language/fr/Breadcrumb.php b/app/Language/fr/Breadcrumb.php index 961d403c..179238e9 100644 --- a/app/Language/fr/Breadcrumb.php +++ b/app/Language/fr/Breadcrumb.php @@ -32,4 +32,5 @@ return [ 'listening-time' => 'drée d’écoute', 'time-periods' => 'périodes', 'soundbites' => 'extraits sonores', + 'embeddable-player' => 'lecteur intégré', ]; diff --git a/app/Language/fr/Episode.php b/app/Language/fr/Episode.php index e691f9ee..78f94404 100644 --- a/app/Language/fr/Episode.php +++ b/app/Language/fr/Episode.php @@ -111,4 +111,16 @@ return [ 'Cliquez pour récupérer la position actuelle, cliquez à nouveau pour récupérer la durée.', 'submit_edit' => 'Enregistrer tous les extraits sonores', ], + 'embeddable_player' => [ + 'add' => 'Ajouter un lecteur intégré', + 'title' => 'Lecteur intégré', + 'label' => + 'Sélectionnez une couleur de thème, copiez le code dans le presse-papier, puis collez-le sur votre site internet.', + 'clipboard_iframe' => 'Copier le lecteur dans le presse papier', + 'clipboard_url' => 'Copier l’adresse dans le presse papier', + 'dark' => 'Sombre', + 'dark-transparent' => 'Sombre transparent', + 'light' => 'Clair', + 'light-transparent' => 'Clair transparent', + ], ]; diff --git a/app/Language/fr/Platforms.php b/app/Language/fr/Platforms.php index d7b66d63..4a4fdc5d 100644 --- a/app/Language/fr/Platforms.php +++ b/app/Language/fr/Platforms.php @@ -11,6 +11,7 @@ return [ 'home_url' => 'Aller au site {platformName}', 'submit_url' => 'Soumettez votre podcast sur {platformName}', 'visible' => 'Afficher sur la page d’accueil du podcast ?', + 'on_embeddable_player' => 'Afficher sur le lecteur intégré ?', 'remove' => 'Supprimer {platformName}', 'submit' => 'Enregistrer', 'messages' => [ diff --git a/app/Models/EpisodeModel.php b/app/Models/EpisodeModel.php index eacbac9b..3237daaf 100644 --- a/app/Models/EpisodeModel.php +++ b/app/Models/EpisodeModel.php @@ -69,6 +69,35 @@ class EpisodeModel extends Model protected $afterUpdate = ['writeEnclosureMetadata']; protected $beforeDelete = ['clearCache']; + public static $themes = [ + 'light-transparent' => [ + 'style' => + 'background-color: #fff; background-image: linear-gradient(45deg, #ccc 12.5%, transparent 12.5%, transparent 50%, #ccc 50%, #ccc 62.5%, transparent 62.5%, transparent 100%); background-size: 5.66px 5.66px;', + 'background' => 'transparent', + 'text' => '#000', + 'inverted' => '#fff', + ], + 'light' => [ + 'style' => 'background-color: #fff;', + 'background' => '#fff', + 'text' => '#000', + 'inverted' => '#fff', + ], + 'dark-transparent' => [ + 'style' => + 'background-color: #001f1a; background-image: linear-gradient(45deg, #888 12.5%, transparent 12.5%, transparent 50%, #888 50%, #888 62.5%, transparent 62.5%, transparent 100%); background-size: 5.66px 5.66px;', + 'background' => 'transparent', + 'text' => '#fff', + 'inverted' => '#000', + ], + 'dark' => [ + 'style' => 'background-color: #001f1a;', + 'background' => '#001f1a', + 'text' => '#fff', + 'inverted' => '#000', + ], + ]; + public function getEpisodeBySlug($podcastId, $episodeSlug) { if (!($found = cache("podcast{$podcastId}_episode@{$episodeSlug}"))) { @@ -411,6 +440,14 @@ class EpisodeModel extends Model } } + foreach (array_keys(self::$themes) as $themeKey) { + foreach ($supportedLocales as $locale) { + cache()->delete( + "page_podcast{$episode->podcast_id}_episode{$episode->id}_embeddable_player_{$themeKey}_{$locale}" + ); + } + } + // delete query cache cache()->delete("podcast{$episode->podcast_id}_defaultQuery"); cache()->delete("podcast{$episode->podcast_id}_years"); diff --git a/app/Models/PlatformModel.php b/app/Models/PlatformModel.php index 827c4de1..1307d1a4 100644 --- a/app/Models/PlatformModel.php +++ b/app/Models/PlatformModel.php @@ -75,7 +75,7 @@ class PlatformModel extends Model !($found = cache("podcast{$podcastId}_platforms_{$platformType}")) ) { $found = $this->select( - 'platforms.*, podcasts_platforms.link_url, podcasts_platforms.link_content, podcasts_platforms.is_visible' + 'platforms.*, podcasts_platforms.link_url, podcasts_platforms.link_content, podcasts_platforms.is_visible, podcasts_platforms.is_on_embeddable_player' ) ->join( 'podcasts_platforms', @@ -103,7 +103,7 @@ class PlatformModel extends Model )) ) { $found = $this->select( - 'platforms.*, podcasts_platforms.link_url, podcasts_platforms.link_content, podcasts_platforms.is_visible' + 'platforms.*, podcasts_platforms.link_url, podcasts_platforms.link_content, podcasts_platforms.is_visible, podcasts_platforms.is_on_embeddable_player' ) ->join( 'podcasts_platforms', @@ -168,6 +168,8 @@ EOD; public function clearCache($podcastId) { + $podcast = (new PodcastModel())->getPodcastById($podcastId); + foreach (['podcasting', 'social', 'funding'] as $platformType) { cache()->delete("podcast{$podcastId}_platforms_{$platformType}"); cache()->delete( @@ -195,5 +197,22 @@ EOD; ); } } + + // clear cache for every localized podcast episode page + foreach ($podcast->episodes as $episode) { + foreach ($supportedLocales as $locale) { + cache()->delete( + "page_podcast{$podcast->id}_episode{$episode->id}_{$locale}" + ); + foreach ( + array_keys(\App\Models\EpisodeModel::$themes) + as $themeKey + ) { + cache()->delete( + "page_podcast{$podcast->id}_episode{$episode->id}_embeddable_player_{$themeKey}_{$locale}" + ); + } + } + } } } diff --git a/app/Models/PodcastModel.php b/app/Models/PodcastModel.php index 4bfed81d..fc23d5dd 100644 --- a/app/Models/PodcastModel.php +++ b/app/Models/PodcastModel.php @@ -193,6 +193,14 @@ class PodcastModel extends Model cache()->delete( "page_podcast{$podcast->id}_episode{$episode->id}_{$locale}" ); + foreach ( + array_keys(\App\Models\EpisodeModel::$themes) + as $themeKey + ) { + cache()->delete( + "page_podcast{$podcast->id}_episode{$episode->id}_embeddable_player_{$themeKey}_{$locale}" + ); + } } } // clear cache for every credit page diff --git a/app/Views/_assets/admin.ts b/app/Views/_assets/admin.ts index 1c54fec2..8cb179e0 100644 --- a/app/Views/_assets/admin.ts +++ b/app/Views/_assets/admin.ts @@ -1,4 +1,6 @@ import ClientTimezone from "./modules/ClientTimezone"; +import Clipboard from "./modules/Clipboard"; +import ThemePicker from "./modules/ThemePicker"; import DateTimePicker from "./modules/DateTimePicker"; import Dropdown from "./modules/Dropdown"; import MarkdownEditor from "./modules/MarkdownEditor"; @@ -19,3 +21,5 @@ ClientTimezone(); DateTimePicker(); Time(); Soundbites(); +Clipboard(); +ThemePicker(); diff --git a/app/Views/_assets/icons/file-copy.svg b/app/Views/_assets/icons/file-copy.svg new file mode 100644 index 00000000..491df11d --- /dev/null +++ b/app/Views/_assets/icons/file-copy.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/app/Views/_assets/icons/movie.svg b/app/Views/_assets/icons/movie.svg new file mode 100644 index 00000000..a3eaa1b7 --- /dev/null +++ b/app/Views/_assets/icons/movie.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/app/Views/_assets/modules/Clipboard.ts b/app/Views/_assets/modules/Clipboard.ts new file mode 100644 index 00000000..2b5b0a59 --- /dev/null +++ b/app/Views/_assets/modules/Clipboard.ts @@ -0,0 +1,23 @@ +const Clipboard = (): void => { + const buttons: NodeListOf< + HTMLButtonElement + > | null = document.querySelectorAll("button[data-type='clipboard-copy']"); + + if (buttons) { + for (let i = 0; i < buttons.length; i++) { + const button: HTMLButtonElement = buttons[i]; + const textArea: HTMLTextAreaElement | null = document.querySelector( + `textarea[id="${button.dataset.clipboardTarget}"]` + ); + if (textArea) { + button.addEventListener("click", () => { + textArea.select(); + textArea.setSelectionRange(0, textArea.value.length); + document.execCommand("copy"); + }); + } + } + } +}; + +export default Clipboard; diff --git a/app/Views/_assets/modules/ThemePicker.ts b/app/Views/_assets/modules/ThemePicker.ts new file mode 100644 index 00000000..167f2600 --- /dev/null +++ b/app/Views/_assets/modules/ThemePicker.ts @@ -0,0 +1,30 @@ +const ThemePicker = (): void => { + const buttons: NodeListOf< + HTMLButtonElement + > | null = document.querySelectorAll("button[data-type='theme-picker']"); + const iframe: HTMLIFrameElement | null = document.querySelector( + `iframe[id="embeddable_player"]` + ); + const iframeTextArea: HTMLTextAreaElement | null = document.querySelector( + `textarea[id="iframe"]` + ); + const urlTextArea: HTMLTextAreaElement | null = document.querySelector( + `textarea[id="url"]` + ); + + if (buttons && iframe && iframeTextArea && urlTextArea) { + for (let i = 0; i < buttons.length; i++) { + const button: HTMLButtonElement = buttons[i]; + const url: string | undefined = button.dataset.url; + if (url) { + button.addEventListener("click", () => { + iframeTextArea.value = ``; + urlTextArea.value = url; + iframe.src = url; + }); + } + } + } +}; + +export default ThemePicker; diff --git a/app/Views/admin/episode/embeddable_player.php b/app/Views/admin/episode/embeddable_player.php new file mode 100644 index 00000000..2144580e --- /dev/null +++ b/app/Views/admin/episode/embeddable_player.php @@ -0,0 +1,66 @@ +extend('admin/_layout') ?> + +section('title') ?> + +endSection() ?> + +section('pageTitle') ?> + +endSection() ?> + +section('content') ?> + + + +
+ $theme): ?> + + +
+ + + +
+ 'iframe', + 'name' => 'iframe', + 'class' => 'form-textarea w-full h-20 mr-2', + ], + "" + ) ?> + 'default'], + [ + 'data-type' => 'clipboard-copy', + 'data-clipboard-target' => 'iframe', + ] + ) ?> +
+ +
+ 'url', + 'name' => 'url', + 'class' => 'form-textarea w-full h-10 mr-2', + ], + $episode->embeddable_player + ) ?> + 'default'], + ['data-type' => 'clipboard-copy', 'data-clipboard-target' => 'url'] + ) ?> +
+ +endSection() ?> diff --git a/app/Views/admin/episode/list.php b/app/Views/admin/episode/list.php index 6bed3c35..fc0f61e3 100644 --- a/app/Views/admin/episode/list.php +++ b/app/Views/admin/episode/list.php @@ -61,6 +61,13 @@ $podcast->id, $episode->id ) ?>"> + + id, $episode->id), + ['variant' => 'info', 'iconLeft' => 'movie'], + ['class' => 'mb-4'] + ) ?> id, $episode->id), diff --git a/app/Views/admin/podcast/latest_episodes.php b/app/Views/admin/podcast/latest_episodes.php index 7c6f17db..71ec4389 100644 --- a/app/Views/admin/podcast/latest_episodes.php +++ b/app/Views/admin/podcast/latest_episodes.php @@ -58,6 +58,13 @@ $podcast->id, $episode->id ) ?>"> + getLocale() ?>"> + + + <?= $episode->title ?> + + + + + + +
+ <?= $episode->title ?> +
+
+ + title ?> + +
+ $podcast->publisher, + ]) ?>
+
+ +
+ has_social_platforms): ?> +
+ social_platforms + as $socialPlatform + ): ?> + is_on_embeddable_player + ): ?> + link_url, + platform_icon( + $socialPlatform->type, + $socialPlatform->slug, + 'h-4 md:h-6' + ), + [ + 'target' => '_blank', + 'rel' => 'noopener noreferrer', + 'title' => $socialPlatform->label, + 'class' => + 'opacity-50 hover:opacity-100', + ] + ) ?> + + +
+ + has_funding_platforms): ?> +
+ funding_platforms + as $fundingPlatform + ): ?> + is_on_embeddable_player + ): ?> + link_url, + platform_icon( + $fundingPlatform->type, + $fundingPlatform->slug, + 'h-4 md:h-6' + ), + [ + 'target' => '_blank', + 'rel' => 'noopener noreferrer', + 'title' => $fundingPlatform->label, + 'class' => + 'opacity-50 hover:opacity-100', + ] + ) ?> + + +
+ +
+ podcasting_platforms + as $podcastingPlatform + ): ?> + is_on_embeddable_player): ?> + link_url, + platform_icon( + $podcastingPlatform->type, + $podcastingPlatform->slug, + 'h-4 md:h-6' + ), + [ + 'target' => '_blank', + 'rel' => 'noopener noreferrer', + 'title' => $podcastingPlatform->label, + 'class' => 'opacity-50 hover:opacity-100', + ] + ) ?> + + + name), + icon('rss', 'mr-2') . lang('Podcast.feed'), + [ + 'target' => '_blank', + 'class' => + 'text-white h-4 md:h-6 md:text-sm text-xs bg-gradient-to-r from-orange-400 to-red-500 hover:to-orange-500 hover:bg-orange-500 inline-flex items-center px-2 py-1 font-semibold rounded-md md:rounded-lg shadow-md hover:bg-orange-600', + ] + ) ?> +
+
+

+ + title ?> + +

+
+
+ number, + $episode->season_number + ) ?> +
+ + + +
+
+ location_name): ?> + + location_name ?> + + +
+ + +
+ + + + + <?= $person['full_name'] ?> + + + + +
+ + +
+ + + + + +
+ + \ No newline at end of file diff --git a/app/Views/episode.php b/app/Views/episode.php index 0b116d7b..5570d4e7 100644 --- a/app/Views/episode.php +++ b/app/Views/episode.php @@ -5,15 +5,16 @@ <?= $episode->title ?> - - + + payment_pointer) -): ?> +): ?> - + diff --git a/app/Views/podcast.php b/app/Views/podcast.php index 29fed4b4..2b429586 100644 --- a/app/Views/podcast.php +++ b/app/Views/podcast.php @@ -6,11 +6,12 @@ <?= $podcast->title ?> - - + + payment_pointer) -): ?> +): ?>