Loading app/Config/Routes.php +9 −0 Original line number Diff line number Diff line Loading @@ -210,6 +210,15 @@ $routes->get('audio/@(:podcastHandle)/(:slug).(:alphanum)', 'EpisodeAudioControl 'as' => 'episode-audio', ], ); // episode preview link $routes->get('/p/(:uuid)', 'EpisodePreviewController::index/$1', [ 'as' => 'episode-preview', ]); $routes->get('/p/(:uuid)/activity', 'EpisodePreviewController::activity/$1', [ 'as' => 'episode-preview-activity', ]); // Other pages $routes->get('/credits', 'CreditsController', [ 'as' => 'credits', Loading app/Controllers/EpisodePreviewController.php 0 → 100644 +66 −0 Original line number Diff line number Diff line <?php declare(strict_types=1); /** * @copyright 2023 Ad Aures * @license https://www.gnu.org/licenses/agpl-3.0.en.html AGPL3 * @link https://castopod.org/ */ namespace App\Controllers; use App\Entities\Episode; use App\Models\EpisodeModel; use CodeIgniter\Exceptions\PageNotFoundException; use CodeIgniter\HTTP\RedirectResponse; class EpisodePreviewController extends BaseController { protected Episode $episode; public function _remap(string $method, string ...$params): mixed { if (count($params) < 1) { throw PageNotFoundException::forPageNotFound(); } // find episode by previewUUID $episode = (new EpisodeModel())->getEpisodeByPreviewId($params[0]); if (! $episode instanceof Episode) { throw PageNotFoundException::forPageNotFound(); } $this->episode = $episode; if ($episode->publication_status === 'published') { // redirect to episode page return redirect()->route('episode', [$episode->podcast->handle, $episode->slug]); } unset($params[0]); return $this->{$method}(...$params); } public function index(): RedirectResponse | string { helper('form'); return view('episode/preview-comments', [ 'podcast' => $this->episode->podcast, 'episode' => $this->episode, ]); } public function activity(): RedirectResponse | string { helper('form'); return view('episode/preview-activity', [ 'podcast' => $this->episode->podcast, 'episode' => $this->episode, ]); } } app/Database/Migrations/2023-08-22-120000_add_episode_preview_id.php 0 → 100644 +36 −0 Original line number Diff line number Diff line <?php declare(strict_types=1); namespace App\Database\Migrations; class AddEpisodePreviewId extends BaseMigration { public function up(): void { $fields = [ 'preview_id' => [ 'type' => 'BINARY', 'constraint' => 16, 'after' => 'podcast_id', ], ]; $this->forge->addColumn('episodes', $fields); // set preview_id as unique key $prefix = $this->db->getPrefix(); $uniquePreviewId = <<<CODE_SAMPLE ALTER TABLE `{$prefix}episodes` ADD CONSTRAINT `preview_id` UNIQUE (`preview_id`); CODE_SAMPLE; $this->db->query($uniquePreviewId); } public function down(): void { $fields = ['preview_id']; $this->forge->dropColumn('episodes', $fields); } } app/Entities/Episode.php +20 −1 Original line number Diff line number Diff line Loading @@ -14,6 +14,7 @@ use App\Entities\Clip\Soundbite; use App\Libraries\SimpleRSSElement; use App\Models\ClipModel; use App\Models\EpisodeCommentModel; use App\Models\EpisodeModel; use App\Models\PersonModel; use App\Models\PodcastModel; use App\Models\PostModel; Loading @@ -21,6 +22,7 @@ use CodeIgniter\Entity\Entity; use CodeIgniter\Files\File; use CodeIgniter\HTTP\Files\UploadedFile; use CodeIgniter\I18n\Time; use Exception; use League\CommonMark\Environment\Environment; use League\CommonMark\Extension\Autolink\AutolinkExtension; use League\CommonMark\Extension\CommonMark\CommonMarkCoreExtension; Loading @@ -39,6 +41,8 @@ use SimpleXMLElement; * @property int $id * @property int $podcast_id * @property Podcast $podcast * @property ?string $preview_id * @property string $preview_link * @property string $link * @property string $guid * @property string $slug Loading Loading @@ -150,6 +154,7 @@ class Episode extends Entity protected $casts = [ 'id' => 'integer', 'podcast_id' => 'integer', 'preview_id' => '?string', 'guid' => 'string', 'slug' => 'string', 'title' => 'string', Loading Loading @@ -667,4 +672,18 @@ class Episode extends Entity urlencode((string) $this->attributes['guid']) . ($serviceSlug !== null ? '&_from=' . $serviceSlug : ''); } public function getPreviewLink(): string { if ($this->preview_id === null) { // generate preview id if (! $previewUUID = (new EpisodeModel())->setEpisodePreviewId($this->id)) { throw new Exception('Could not set episode preview id'); } $this->preview_id = $previewUUID; } return url_to('episode-preview', (string) $this->preview_id); } } app/Helpers/components_helper.php +58 −5 Original line number Diff line number Diff line Loading @@ -9,6 +9,7 @@ declare(strict_types=1); */ use App\Entities\Category; use App\Entities\Episode; use App\Entities\Location; use CodeIgniter\I18n\Time; use CodeIgniter\View\Table; Loading Loading @@ -218,8 +219,8 @@ if (! function_exists('publication_status_banner')) { } return <<<HTML <div class="flex items-center px-12 py-2 border-b bg-stripes-gray border-subtle" role="alert"> <p class="flex items-center text-gray-900"> <div class="flex flex-wrap items-baseline px-4 py-2 border-b md:px-12 bg-stripes-default border-subtle" role="alert"> <p class="flex items-baseline text-gray-900"> <span class="text-xs font-semibold tracking-wide uppercase">{$bannerDisclaimer}</span> <span class="ml-3 text-sm">{$bannerText}</span> </p> Loading @@ -231,6 +232,58 @@ if (! function_exists('publication_status_banner')) { // ------------------------------------------------------------------------ if (! function_exists('episode_publication_status_banner')) { /** * Publication status banner component for podcasts * * Displays the appropriate banner depending on the podcast's publication status. */ function episode_publication_status_banner(Episode $episode, string $class = ''): string { switch ($episode->publication_status) { case 'not_published': $linkRoute = route_to('episode-publish', $episode->podcast_id, $episode->id); $publishLinkLabel = lang('Episode.publish'); break; case 'scheduled': case 'with_podcast': $linkRoute = route_to('episode-publish_edit', $episode->podcast_id, $episode->id); $publishLinkLabel = lang('Episode.publish_edit'); break; default: $bannerDisclaimer = ''; $linkRoute = ''; $publishLinkLabel = ''; break; } $bannerDisclaimer = lang('Episode.publication_status_banner.draft_mode'); $bannerText = lang('Episode.publication_status_banner.text', [ 'publication_status' => $episode->publication_status, 'publication_date' => $episode->published_at instanceof Time ? local_datetime( $episode->published_at ) : null, ], null, false); $previewLinkLabel = lang('Episode.publication_status_banner.preview'); return <<<HTML <div class="flex flex-wrap gap-4 items-baseline px-4 md:px-12 py-2 bg-stripes-default border-subtle {$class}" role="alert"> <p class="flex items-baseline text-gray-900"> <span class="text-xs font-semibold tracking-wide uppercase">{$bannerDisclaimer}</span> <span class="ml-3 text-sm">{$bannerText}</span> </p> <div class="flex items-baseline"> <a href="{$episode->preview_link}" class="ml-1 text-sm font-semibold underline shadow-xs text-accent-base hover:text-accent-hover hover:no-underline">{$previewLinkLabel}</a> <span class="mx-1">•</span> <a href="{$linkRoute}" class="ml-1 text-sm font-semibold underline shadow-xs text-accent-base hover:text-accent-hover hover:no-underline">{$publishLinkLabel}</a> </div> </div> HTML; } } // ------------------------------------------------------------------------ if (! function_exists('episode_numbering')) { /** * Returns relevant translated episode numbering. Loading Loading @@ -360,7 +413,7 @@ if (! function_exists('relative_time')) { $datetime = $time->format(DateTime::ATOM); return <<<HTML <relative-time tense="past" class="{$class}" datetime="{$datetime}"> <relative-time tense="auto" class="{$class}" datetime="{$datetime}"> <time datetime="{$datetime}" title="{$time}">{$translatedDate}</time> Loading @@ -378,7 +431,7 @@ if (! function_exists('local_datetime')) { 'request' )->getLocale(), IntlDateFormatter::MEDIUM, IntlDateFormatter::LONG); $translatedDate = $time->toLocalizedString($formatter->getPattern()); $datetime = $time->format(DateTime::ISO8601); $datetime = $time->format(DateTime::ATOM); return <<<HTML <relative-time datetime="{$datetime}" Loading Loading
app/Config/Routes.php +9 −0 Original line number Diff line number Diff line Loading @@ -210,6 +210,15 @@ $routes->get('audio/@(:podcastHandle)/(:slug).(:alphanum)', 'EpisodeAudioControl 'as' => 'episode-audio', ], ); // episode preview link $routes->get('/p/(:uuid)', 'EpisodePreviewController::index/$1', [ 'as' => 'episode-preview', ]); $routes->get('/p/(:uuid)/activity', 'EpisodePreviewController::activity/$1', [ 'as' => 'episode-preview-activity', ]); // Other pages $routes->get('/credits', 'CreditsController', [ 'as' => 'credits', Loading
app/Controllers/EpisodePreviewController.php 0 → 100644 +66 −0 Original line number Diff line number Diff line <?php declare(strict_types=1); /** * @copyright 2023 Ad Aures * @license https://www.gnu.org/licenses/agpl-3.0.en.html AGPL3 * @link https://castopod.org/ */ namespace App\Controllers; use App\Entities\Episode; use App\Models\EpisodeModel; use CodeIgniter\Exceptions\PageNotFoundException; use CodeIgniter\HTTP\RedirectResponse; class EpisodePreviewController extends BaseController { protected Episode $episode; public function _remap(string $method, string ...$params): mixed { if (count($params) < 1) { throw PageNotFoundException::forPageNotFound(); } // find episode by previewUUID $episode = (new EpisodeModel())->getEpisodeByPreviewId($params[0]); if (! $episode instanceof Episode) { throw PageNotFoundException::forPageNotFound(); } $this->episode = $episode; if ($episode->publication_status === 'published') { // redirect to episode page return redirect()->route('episode', [$episode->podcast->handle, $episode->slug]); } unset($params[0]); return $this->{$method}(...$params); } public function index(): RedirectResponse | string { helper('form'); return view('episode/preview-comments', [ 'podcast' => $this->episode->podcast, 'episode' => $this->episode, ]); } public function activity(): RedirectResponse | string { helper('form'); return view('episode/preview-activity', [ 'podcast' => $this->episode->podcast, 'episode' => $this->episode, ]); } }
app/Database/Migrations/2023-08-22-120000_add_episode_preview_id.php 0 → 100644 +36 −0 Original line number Diff line number Diff line <?php declare(strict_types=1); namespace App\Database\Migrations; class AddEpisodePreviewId extends BaseMigration { public function up(): void { $fields = [ 'preview_id' => [ 'type' => 'BINARY', 'constraint' => 16, 'after' => 'podcast_id', ], ]; $this->forge->addColumn('episodes', $fields); // set preview_id as unique key $prefix = $this->db->getPrefix(); $uniquePreviewId = <<<CODE_SAMPLE ALTER TABLE `{$prefix}episodes` ADD CONSTRAINT `preview_id` UNIQUE (`preview_id`); CODE_SAMPLE; $this->db->query($uniquePreviewId); } public function down(): void { $fields = ['preview_id']; $this->forge->dropColumn('episodes', $fields); } }
app/Entities/Episode.php +20 −1 Original line number Diff line number Diff line Loading @@ -14,6 +14,7 @@ use App\Entities\Clip\Soundbite; use App\Libraries\SimpleRSSElement; use App\Models\ClipModel; use App\Models\EpisodeCommentModel; use App\Models\EpisodeModel; use App\Models\PersonModel; use App\Models\PodcastModel; use App\Models\PostModel; Loading @@ -21,6 +22,7 @@ use CodeIgniter\Entity\Entity; use CodeIgniter\Files\File; use CodeIgniter\HTTP\Files\UploadedFile; use CodeIgniter\I18n\Time; use Exception; use League\CommonMark\Environment\Environment; use League\CommonMark\Extension\Autolink\AutolinkExtension; use League\CommonMark\Extension\CommonMark\CommonMarkCoreExtension; Loading @@ -39,6 +41,8 @@ use SimpleXMLElement; * @property int $id * @property int $podcast_id * @property Podcast $podcast * @property ?string $preview_id * @property string $preview_link * @property string $link * @property string $guid * @property string $slug Loading Loading @@ -150,6 +154,7 @@ class Episode extends Entity protected $casts = [ 'id' => 'integer', 'podcast_id' => 'integer', 'preview_id' => '?string', 'guid' => 'string', 'slug' => 'string', 'title' => 'string', Loading Loading @@ -667,4 +672,18 @@ class Episode extends Entity urlencode((string) $this->attributes['guid']) . ($serviceSlug !== null ? '&_from=' . $serviceSlug : ''); } public function getPreviewLink(): string { if ($this->preview_id === null) { // generate preview id if (! $previewUUID = (new EpisodeModel())->setEpisodePreviewId($this->id)) { throw new Exception('Could not set episode preview id'); } $this->preview_id = $previewUUID; } return url_to('episode-preview', (string) $this->preview_id); } }
app/Helpers/components_helper.php +58 −5 Original line number Diff line number Diff line Loading @@ -9,6 +9,7 @@ declare(strict_types=1); */ use App\Entities\Category; use App\Entities\Episode; use App\Entities\Location; use CodeIgniter\I18n\Time; use CodeIgniter\View\Table; Loading Loading @@ -218,8 +219,8 @@ if (! function_exists('publication_status_banner')) { } return <<<HTML <div class="flex items-center px-12 py-2 border-b bg-stripes-gray border-subtle" role="alert"> <p class="flex items-center text-gray-900"> <div class="flex flex-wrap items-baseline px-4 py-2 border-b md:px-12 bg-stripes-default border-subtle" role="alert"> <p class="flex items-baseline text-gray-900"> <span class="text-xs font-semibold tracking-wide uppercase">{$bannerDisclaimer}</span> <span class="ml-3 text-sm">{$bannerText}</span> </p> Loading @@ -231,6 +232,58 @@ if (! function_exists('publication_status_banner')) { // ------------------------------------------------------------------------ if (! function_exists('episode_publication_status_banner')) { /** * Publication status banner component for podcasts * * Displays the appropriate banner depending on the podcast's publication status. */ function episode_publication_status_banner(Episode $episode, string $class = ''): string { switch ($episode->publication_status) { case 'not_published': $linkRoute = route_to('episode-publish', $episode->podcast_id, $episode->id); $publishLinkLabel = lang('Episode.publish'); break; case 'scheduled': case 'with_podcast': $linkRoute = route_to('episode-publish_edit', $episode->podcast_id, $episode->id); $publishLinkLabel = lang('Episode.publish_edit'); break; default: $bannerDisclaimer = ''; $linkRoute = ''; $publishLinkLabel = ''; break; } $bannerDisclaimer = lang('Episode.publication_status_banner.draft_mode'); $bannerText = lang('Episode.publication_status_banner.text', [ 'publication_status' => $episode->publication_status, 'publication_date' => $episode->published_at instanceof Time ? local_datetime( $episode->published_at ) : null, ], null, false); $previewLinkLabel = lang('Episode.publication_status_banner.preview'); return <<<HTML <div class="flex flex-wrap gap-4 items-baseline px-4 md:px-12 py-2 bg-stripes-default border-subtle {$class}" role="alert"> <p class="flex items-baseline text-gray-900"> <span class="text-xs font-semibold tracking-wide uppercase">{$bannerDisclaimer}</span> <span class="ml-3 text-sm">{$bannerText}</span> </p> <div class="flex items-baseline"> <a href="{$episode->preview_link}" class="ml-1 text-sm font-semibold underline shadow-xs text-accent-base hover:text-accent-hover hover:no-underline">{$previewLinkLabel}</a> <span class="mx-1">•</span> <a href="{$linkRoute}" class="ml-1 text-sm font-semibold underline shadow-xs text-accent-base hover:text-accent-hover hover:no-underline">{$publishLinkLabel}</a> </div> </div> HTML; } } // ------------------------------------------------------------------------ if (! function_exists('episode_numbering')) { /** * Returns relevant translated episode numbering. Loading Loading @@ -360,7 +413,7 @@ if (! function_exists('relative_time')) { $datetime = $time->format(DateTime::ATOM); return <<<HTML <relative-time tense="past" class="{$class}" datetime="{$datetime}"> <relative-time tense="auto" class="{$class}" datetime="{$datetime}"> <time datetime="{$datetime}" title="{$time}">{$translatedDate}</time> Loading @@ -378,7 +431,7 @@ if (! function_exists('local_datetime')) { 'request' )->getLocale(), IntlDateFormatter::MEDIUM, IntlDateFormatter::LONG); $translatedDate = $time->toLocalizedString($formatter->getPattern()); $datetime = $time->format(DateTime::ISO8601); $datetime = $time->format(DateTime::ATOM); return <<<HTML <relative-time datetime="{$datetime}" Loading