diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json index 71a8104d..1b36affd 100644 --- a/.devcontainer/devcontainer.json +++ b/.devcontainer/devcontainer.json @@ -12,6 +12,7 @@ "settings": { "terminal.integrated.defaultProfile.linux": "bash", "editor.formatOnSave": true, + "editor.defaultFormatter": "esbenp.prettier-vscode", "[php]": { "editor.defaultFormatter": "bmewburn.vscode-intelephense-client", "editor.formatOnSave": false diff --git a/app/Config/Routes.php b/app/Config/Routes.php index b8ffad9e..f75b2240 100644 --- a/app/Config/Routes.php +++ b/app/Config/Routes.php @@ -31,7 +31,7 @@ $routes->setAutoRoute(false); * -------------------------------------------------------------------- */ -$routes->addPlaceholder('podcastName', '[a-zA-Z0-9\_]{1,32}'); +$routes->addPlaceholder('podcastHandle', '[a-zA-Z0-9\_]{1,32}'); $routes->addPlaceholder('slug', '[a-zA-Z0-9\-]{1,191}'); $routes->addPlaceholder('base64', '[A-Za-z0-9\.\_]+\-{0,2}'); $routes->addPlaceholder('platformType', '\bpodcasting|\bsocial|\bfunding'); @@ -685,7 +685,7 @@ $routes->group(config('App')->authGateway, function ($routes): void { }); // Podcast's Public routes -$routes->group('@(:podcastName)', function ($routes): void { +$routes->group('@(:podcastHandle)', function ($routes): void { $routes->get('/', 'PodcastController::activity/$1', [ 'as' => 'podcast-activity', ]); @@ -802,7 +802,7 @@ $routes->post('interact-as-actor', 'AuthController::attemptInteractAsActor', [ /** * Overwriting ActivityPub routes file */ -$routes->group('@(:podcastName)', function ($routes): void { +$routes->group('@(:podcastHandle)', function ($routes): void { $routes->post('statuses/new', 'StatusController::attemptCreate/$1', [ 'as' => 'status-attempt-create', 'filter' => 'permission:podcast-manage_publications', diff --git a/app/Controllers/Admin/PodcastController.php b/app/Controllers/Admin/PodcastController.php index 5583213b..ccc256e9 100644 --- a/app/Controllers/Admin/PodcastController.php +++ b/app/Controllers/Admin/PodcastController.php @@ -192,9 +192,9 @@ class PodcastController extends BaseController } $podcast = new Podcast([ - 'guid' => podcast_uuid(url_to('podcast_feed', $this->request->getPost('name'))), + 'guid' => podcast_uuid(url_to('podcast_feed', $this->request->getPost('handle'))), 'title' => $this->request->getPost('title'), - 'name' => $this->request->getPost('name'), + 'handle' => $this->request->getPost('handle'), 'description_markdown' => $this->request->getPost('description'), 'image' => new Image($this->request->getFile('image')), 'language_code' => $this->request->getPost('language'), diff --git a/app/Controllers/Admin/PodcastImportController.php b/app/Controllers/Admin/PodcastImportController.php index e8ca842b..5a237c51 100644 --- a/app/Controllers/Admin/PodcastImportController.php +++ b/app/Controllers/Admin/PodcastImportController.php @@ -131,14 +131,14 @@ class PodcastImportController extends BaseController if (property_exists($nsPodcast, 'guid') && $nsPodcast->guid !== null) { $guid = (string) $nsPodcast->guid; } else { - $guid = podcast_uuid(url_to('podcast_feed', $this->request->getPost('name'))); + $guid = podcast_uuid(url_to('podcast_feed', $this->request->getPost('handle'))); } $podcast = new Podcast([ 'guid' => $guid, - 'name' => $this->request->getPost('name'), + 'handle' => $this->request->getPost('handle'), 'imported_feed_url' => $this->request->getPost('imported_feed_url'), - 'new_feed_url' => url_to('podcast_feed', $this->request->getPost('name')), + 'new_feed_url' => url_to('podcast_feed', $this->request->getPost('handle')), 'title' => (string) $feed->channel[0]->title, 'description_markdown' => $converter->convert($channelDescriptionHtml), 'description_html' => $channelDescriptionHtml, diff --git a/app/Controllers/EpisodeController.php b/app/Controllers/EpisodeController.php index f3d3f9c0..c484ca14 100644 --- a/app/Controllers/EpisodeController.php +++ b/app/Controllers/EpisodeController.php @@ -41,7 +41,7 @@ class EpisodeController extends BaseController } if ( - ($podcast = (new PodcastModel())->getPodcastByName($params[0])) === null + ($podcast = (new PodcastModel())->getPodcastByHandle($params[0])) === null ) { throw PageNotFoundException::forPageNotFound(); } @@ -50,7 +50,7 @@ class EpisodeController extends BaseController if ( ($episode = (new EpisodeModel())->getEpisodeBySlug($params[0], $params[1])) === null - ) { + ) { throw PageNotFoundException::forPageNotFound(); } diff --git a/app/Controllers/FeedController.php b/app/Controllers/FeedController.php index ab0d26a4..55210582 100644 --- a/app/Controllers/FeedController.php +++ b/app/Controllers/FeedController.php @@ -20,11 +20,11 @@ use Opawg\UserAgentsPhp\UserAgentsRSS; class FeedController extends Controller { - public function index(string $podcastName): ResponseInterface + public function index(string $podcastHandle): ResponseInterface { helper('rss'); - $podcast = (new PodcastModel())->where('name', $podcastName) + $podcast = (new PodcastModel())->where('handle', $podcastHandle) ->first(); if (! $podcast) { throw PageNotFoundException::forPageNotFound(); diff --git a/app/Controllers/HomeController.php b/app/Controllers/HomeController.php index a2d4f872..df1b2bb5 100644 --- a/app/Controllers/HomeController.php +++ b/app/Controllers/HomeController.php @@ -31,7 +31,7 @@ class HomeController extends BaseController // check if there's only one podcast to redirect user to it if (count($allPodcasts) === 1) { - return redirect()->route('podcast-activity', [$allPodcasts[0]->name]); + return redirect()->route('podcast-activity', [$allPodcasts[0]->handle]); } // default behavior: list all podcasts on home page diff --git a/app/Controllers/PodcastController.php b/app/Controllers/PodcastController.php index 75d50111..4929435c 100644 --- a/app/Controllers/PodcastController.php +++ b/app/Controllers/PodcastController.php @@ -20,7 +20,6 @@ use App\Models\EpisodeModel; use App\Models\PodcastModel; use App\Models\StatusModel; use CodeIgniter\Exceptions\PageNotFoundException; -use CodeIgniter\HTTP\RedirectResponse; use CodeIgniter\HTTP\Response; class PodcastController extends BaseController @@ -36,7 +35,7 @@ class PodcastController extends BaseController } if ( - ($podcast = (new PodcastModel())->getPodcastByName($params[0])) === null + ($podcast = (new PodcastModel())->getPodcastByHandle($params[0])) === null ) { throw PageNotFoundException::forPageNotFound(); } @@ -48,7 +47,10 @@ class PodcastController extends BaseController return $this->{$method}(...$params); } - public function podcastActor(): RedirectResponse + /** + * @noRector ReturnTypeDeclarationRector + */ + public function podcastActor(): Response { $podcastActor = new PodcastActor($this->podcast); @@ -161,7 +163,7 @@ class PodcastController extends BaseController 'label' => $year['year'], 'number_of_episodes' => $year['number_of_episodes'], 'route' => - route_to('podcast-episodes', $this->podcast->name) . + route_to('podcast-episodes', $this->podcast->handle) . '?year=' . $year['year'], 'is_active' => $isActive, @@ -187,7 +189,7 @@ class PodcastController extends BaseController ]), 'number_of_episodes' => $season['number_of_episodes'], 'route' => - route_to('podcast-episodes', $this->podcast->name) . + route_to('podcast-episodes', $this->podcast->handle) . '?season=' . $season['season_number'], 'is_active' => $isActive, diff --git a/app/Controllers/StatusController.php b/app/Controllers/StatusController.php index 9d19f136..c6f5c2f0 100644 --- a/app/Controllers/StatusController.php +++ b/app/Controllers/StatusController.php @@ -40,7 +40,7 @@ class StatusController extends ActivityPubStatusController public function _remap(string $method, string ...$params): mixed { if ( - ($podcast = (new PodcastModel())->getPodcastByName($params[0],)) === null + ($podcast = (new PodcastModel())->getPodcastByHandle($params[0],)) === null ) { throw PageNotFoundException::forPageNotFound(); } @@ -127,7 +127,7 @@ class StatusController extends ActivityPubStatusController if ( $episodeUri && ($params = extract_params_from_episode_uri(new URI($episodeUri))) && - ($episode = (new EpisodeModel())->getEpisodeBySlug($params['podcastName'], $params['episodeSlug'])) + ($episode = (new EpisodeModel())->getEpisodeBySlug($params['podcastHandle'], $params['episodeSlug'])) ) { $newStatus->episode_id = $episode->id; } diff --git a/app/Database/Migrations/2020-05-30-101500_add_podcasts.php b/app/Database/Migrations/2020-05-30-101500_add_podcasts.php index f330d084..c40b9c09 100644 --- a/app/Database/Migrations/2020-05-30-101500_add_podcasts.php +++ b/app/Database/Migrations/2020-05-30-101500_add_podcasts.php @@ -32,7 +32,7 @@ class AddPodcasts extends Migration 'type' => 'INT', 'unsigned' => true, ], - 'name' => [ + 'handle' => [ 'type' => 'VARCHAR', 'constraint' => 32, ], @@ -193,7 +193,7 @@ class AddPodcasts extends Migration $this->forge->addPrimaryKey('id'); // TODO: remove name in favor of username from actor - $this->forge->addUniqueKey('name'); + $this->forge->addUniqueKey('handle'); $this->forge->addUniqueKey('guid'); $this->forge->addUniqueKey('actor_id'); $this->forge->addForeignKey('actor_id', 'activitypub_actors', 'id', '', 'CASCADE'); diff --git a/app/Entities/Episode.php b/app/Entities/Episode.php index f5b54d6c..cf9ba6ad 100644 --- a/app/Entities/Episode.php +++ b/app/Entities/Episode.php @@ -185,7 +185,7 @@ class Episode extends Entity } // Save image - $image->saveImage('podcasts/' . $this->getPodcast()->name, $this->attributes['slug']); + $image->saveImage('podcasts/' . $this->getPodcast()->handle, $this->attributes['slug']); $this->attributes['image_mimetype'] = $image->mimetype; $this->attributes['image_path'] = $image->path; @@ -214,7 +214,7 @@ class Episode extends Entity $this->attributes['audio_file_path'] = save_media( $audioFile, - 'podcasts/' . $this->getPodcast()->name, + 'podcasts/' . $this->getPodcast()->handle, $this->attributes['slug'], ); $this->attributes['audio_file_duration'] = @@ -237,7 +237,7 @@ class Episode extends Entity $this->attributes['transcript_file_path'] = save_media( $transcriptFile, 'podcasts/' . $this->getPodcast() - ->name, + ->handle, $this->attributes['slug'] . '-transcript', ); @@ -254,7 +254,7 @@ class Episode extends Entity $this->attributes['chapters_file_path'] = save_media( $chaptersFile, 'podcasts/' . $this->getPodcast() - ->name, + ->handle, $this->attributes['slug'] . '-chapters', ); @@ -430,11 +430,11 @@ class Episode extends Entity ? route_to( 'embeddable-player-theme', $this->getPodcast() - ->name, + ->handle, $this->attributes['slug'], $theme, ) - : route_to('embeddable-player', $this->getPodcast() ->name, $this->attributes['slug']), + : route_to('embeddable-player', $this->getPodcast()->handle, $this->attributes['slug']), ); } diff --git a/app/Entities/Podcast.php b/app/Entities/Podcast.php index 6ed3b702..da72d6f4 100644 --- a/app/Entities/Podcast.php +++ b/app/Entities/Podcast.php @@ -26,7 +26,7 @@ use RuntimeException; * @property string $guid * @property int $actor_id * @property Actor|null $actor - * @property string $name + * @property string $handle * @property string $link * @property string $feed_url * @property string $title @@ -140,7 +140,7 @@ class Podcast extends Entity 'id' => 'integer', 'guid' => 'string', 'actor_id' => 'integer', - 'name' => 'string', + 'handle' => 'string', 'title' => 'string', 'description_markdown' => 'string', 'description_html' => 'string', @@ -193,7 +193,7 @@ class Podcast extends Entity public function setImage(Image $image): static { // Save image - $image->saveImage('podcasts/' . $this->attributes['name'], 'cover'); + $image->saveImage('podcasts/' . $this->attributes['handle'], 'cover'); $this->attributes['image_mimetype'] = $image->mimetype; $this->attributes['image_path'] = $image->path; @@ -208,12 +208,12 @@ class Podcast extends Entity public function getLink(): string { - return url_to('podcast-activity', $this->attributes['name']); + return url_to('podcast-activity', $this->attributes['handle']); } public function getFeedUrl(): string { - return url_to('podcast_feed', $this->attributes['name']); + return url_to('podcast_feed', $this->attributes['handle']); } /** diff --git a/app/Helpers/rss_helper.php b/app/Helpers/rss_helper.php index 10eccc26..8ef68192 100644 --- a/app/Helpers/rss_helper.php +++ b/app/Helpers/rss_helper.php @@ -251,7 +251,7 @@ if (! function_exists('get_rss_feed')) { // add link to episode comments as podcast-activity format $comments = $item->addChild('comments', null, $podcastNamespace); - $comments->addAttribute('uri', url_to('episode-comments', $podcast->name, $episode->slug)); + $comments->addAttribute('uri', url_to('episode-comments', $podcast->handle, $episode->slug)); $comments->addAttribute('contentType', 'application/podcast-activity+json'); if ($episode->transcript_file_url) { diff --git a/app/Helpers/url_helper.php b/app/Helpers/url_helper.php index b655ad70..461ba0c1 100644 --- a/app/Helpers/url_helper.php +++ b/app/Helpers/url_helper.php @@ -40,7 +40,7 @@ if (! function_exists('extract_params_from_episode_uri')) { function extract_params_from_episode_uri(URI $episodeUri): ?array { preg_match( - '~@(?P[a-zA-Z0-9\_]{1,32})\/episodes\/(?P[a-zA-Z0-9\-]{1,191})~', + '~@(?P[a-zA-Z0-9\_]{1,32})\/episodes\/(?P[a-zA-Z0-9\-]{1,191})~', $episodeUri->getPath(), $matches, ); @@ -50,14 +50,14 @@ if (! function_exists('extract_params_from_episode_uri')) { } if ( - ! array_key_exists('podcastName', $matches) || + ! array_key_exists('podcastHandle', $matches) || ! array_key_exists('episodeSlug', $matches) ) { return null; } return [ - 'podcastName' => $matches['podcastName'], + 'podcastHandle' => $matches['podcastHandle'], 'episodeSlug' => $matches['episodeSlug'], ]; } diff --git a/app/Language/en/ActivityPub.php b/app/Language/en/ActivityPub.php index 1c64c618..4c651894 100644 --- a/app/Language/en/ActivityPub.php +++ b/app/Language/en/ActivityPub.php @@ -19,17 +19,17 @@ return [ 'submit' => 'Proceed to follow', ], 'favourite' => [ - 'title' => "Favourite {actorDisplayName}'s note", + 'title' => "Favourite {actorDisplayName}'s post", 'subtitle' => 'You are going to favourite:', 'submit' => 'Proceed to favourite', ], 'reblog' => [ - 'title' => "Share {actorDisplayName}'s note", + 'title' => "Share {actorDisplayName}'s post", 'subtitle' => 'You are going to share:', 'submit' => 'Proceed to share', ], 'reply' => [ - 'title' => "Reply to {actorDisplayName}'s note", + 'title' => "Reply to {actorDisplayName}'s post", 'subtitle' => 'You are going to reply to:', 'submit' => 'Proceed to reply', ], diff --git a/app/Language/en/Contributor.php b/app/Language/en/Contributor.php index e2e50026..087e4f6d 100644 --- a/app/Language/en/Contributor.php +++ b/app/Language/en/Contributor.php @@ -10,7 +10,7 @@ declare(strict_types=1); return [ 'podcast_contributors' => 'Podcast contributors', - 'view' => "{username}'s contribution to {podcastName}", + 'view' => "{username}'s contribution to {podcastTitle}", 'add' => 'Add contributor', 'add_contributor' => 'Add a contributor for {0}', 'edit_role' => 'Update role for {0}', diff --git a/app/Language/en/Podcast.php b/app/Language/en/Podcast.php index 828ce4f2..f6118e02 100644 --- a/app/Language/en/Podcast.php +++ b/app/Language/en/Podcast.php @@ -28,9 +28,9 @@ return [ 'identity_section_subtitle' => 'These fields allow you to get noticed.', 'image' => 'Cover image', 'title' => 'Title', - 'name' => 'Name', - 'name_hint' => - 'Used for generating the podcast URL. Uppercase, lowercase, numbers and underscores are accepted.', + 'handle' => 'Handle', + 'handle_hint' => + 'Used to identify the podcast. Uppercase, lowercase, numbers and underscores are accepted.', 'type' => [ 'label' => 'Type', 'hint' => diff --git a/app/Language/en/PodcastImport.php b/app/Language/en/PodcastImport.php index a19eb435..b6e8774c 100644 --- a/app/Language/en/PodcastImport.php +++ b/app/Language/en/PodcastImport.php @@ -17,8 +17,6 @@ return [ 'imported_feed_url' => 'Feed URL', 'imported_feed_url_hint' => 'The feed must be in xml or rss format.', 'new_podcast_section_title' => 'The new podcast', - 'name' => 'Name', - 'name_hint' => 'Used for generating the podcast URL.', 'advanced_params_section_title' => 'Advanced parameters', 'advanced_params_section_subtitle' => 'Keep the default values if you have no idea of what the fields are for.', diff --git a/app/Language/en/Status.php b/app/Language/en/Status.php index 8fa9bcdc..8f5f3a12 100644 --- a/app/Language/en/Status.php +++ b/app/Language/en/Status.php @@ -9,8 +9,8 @@ declare(strict_types=1); */ return [ - 'title' => "{actorDisplayName}'s Note", - 'back_to_actor_statuses' => 'Back to {actor} notes', + 'title' => "{actorDisplayName}'s post", + 'back_to_actor_statuses' => 'Back to {actor} posts', 'actor_shared' => '{actor} shared', 'reply_to' => 'Reply to @{actorUsername}', 'form' => [ @@ -33,8 +33,8 @@ return [ one {# reply} other {# replies} }', - 'expand' => 'Expand note', + 'expand' => 'Expand post', 'block_actor' => 'Block user @{actorUsername}', 'block_domain' => 'Block domain @{actorDomain}', - 'delete' => 'Delete note', + 'delete' => 'Delete post', ]; diff --git a/app/Language/fr/ActivityPub.php b/app/Language/fr/ActivityPub.php index 54c2d186..8951919f 100644 --- a/app/Language/fr/ActivityPub.php +++ b/app/Language/fr/ActivityPub.php @@ -20,17 +20,17 @@ return [ 'submit' => 'Poursuivre', ], 'favourite' => [ - 'title' => 'Mettez la note de {actorDisplayName} en favori', + 'title' => 'Mettez la publication de {actorDisplayName} en favori', 'subtitle' => 'Vous allez mettre en favori :', 'submit' => 'Poursuivre', ], 'reblog' => [ - 'title' => 'Partagez la note de {actorDisplayName}', + 'title' => 'Partagez la publication de {actorDisplayName}', 'subtitle' => 'Vous allez partager :', 'submit' => 'Poursuivre', ], 'reply' => [ - 'title' => 'Répondre à la note de {actorDisplayName}', + 'title' => 'Répondre à la publication de {actorDisplayName}', 'subtitle' => 'Vous allez répondre à :', 'submit' => 'Poursuivre', ], diff --git a/app/Language/fr/Contributor.php b/app/Language/fr/Contributor.php index 4ed4799a..7e62309e 100644 --- a/app/Language/fr/Contributor.php +++ b/app/Language/fr/Contributor.php @@ -10,7 +10,7 @@ declare(strict_types=1); return [ 'podcast_contributors' => 'Contributeurs du podcast', - 'view' => 'Contribution de {username} à {podcastName}', + 'view' => 'Contribution de {username} à {podcastTitle}', 'add' => 'Ajouter un contributeur', 'add_contributor' => 'Ajouter un contributeur pour {0}', 'edit_role' => 'Modifier le rôle de {0}', diff --git a/app/Language/fr/Podcast.php b/app/Language/fr/Podcast.php index b5ccb5b0..4b571ac4 100644 --- a/app/Language/fr/Podcast.php +++ b/app/Language/fr/Podcast.php @@ -29,9 +29,9 @@ return [ 'Ces champs vous permettent de vous faire remarquer.', 'image' => 'Image de couverture', 'title' => 'Titre', - 'name' => 'Nom', - 'name_hint' => - 'Utilisé pour l’adresse du podcast. Les majuscules, les minuscules, les chiffres et le caractère souligné « _ » sont acceptés.', + 'handle' => 'Identifiant', + 'handle_hint' => + 'Utilisé pour identifier le podcast. Les majuscules, les minuscules, les chiffres et le caractère souligné « _ » sont acceptés.', 'type' => [ 'label' => 'Type', 'hint' => @@ -225,9 +225,9 @@ return [ one {# abonné·e} other {# abonné·e·s} }', - 'notes' => '{numberOfStatuses, plural, - one {# message} - other {# messages} + 'statuses' => '{numberOfStatuses, plural, + one {# publication} + other {# publications} }', 'activity' => 'Activité', 'episodes' => 'Épisodes', diff --git a/app/Language/fr/PodcastImport.php b/app/Language/fr/PodcastImport.php index b04e2648..94b3167d 100644 --- a/app/Language/fr/PodcastImport.php +++ b/app/Language/fr/PodcastImport.php @@ -17,8 +17,6 @@ return [ 'imported_feed_url' => 'Adresse du flux', 'imported_feed_url_hint' => 'Le flux doit être au format xml ou rss.', 'new_podcast_section_title' => 'Le nouveau podcast', - 'name' => 'Nom', - 'name_hint' => 'Utilisé pour l’adresse du podcast.', 'advanced_params_section_title' => 'Paramètres avancés', 'advanced_params_section_subtitle' => 'Si vous ne savez pas à quoi servent ces champs, conservez les valeurs par défaut.', diff --git a/app/Language/fr/Status.php b/app/Language/fr/Status.php index 034308d6..c19d0e29 100644 --- a/app/Language/fr/Status.php +++ b/app/Language/fr/Status.php @@ -9,8 +9,8 @@ declare(strict_types=1); */ return [ - 'title' => 'Note de {actorDisplayName}', - 'back_to_actor_statuses' => 'Retour aux notes de {actor}', + 'title' => 'Publication de {actorDisplayName}', + 'back_to_actor_statuses' => 'Retour aux publications de {actor}', 'actor_shared' => '{actor} a partagé', 'reply_to' => 'Répondre à @{actorUsername}', 'form' => [ @@ -34,8 +34,8 @@ return [ one {# réponse} other {# réponses} }', - 'expand' => 'Ouvrir la note', + 'expand' => 'Ouvrir la publication', 'block_actor' => 'Bloquer l’utilisateur @{actorUsername}', 'block_domain' => 'Bloquer le domaine @{actorDomain}', - 'delete' => 'Supprimer la note', + 'delete' => 'Supprimer la publication', ]; diff --git a/app/Libraries/Analytics/Controllers/EpisodeController.php b/app/Libraries/Analytics/Controllers/EpisodeController.php deleted file mode 100644 index 2fb5ede1..00000000 --- a/app/Libraries/Analytics/Controllers/EpisodeController.php +++ /dev/null @@ -1,189 +0,0 @@ -podcast = (new PodcastModel())->getPodcastByName($params[0])) === null - ) { - throw PageNotFoundException::forPageNotFound(); - } - - if ( - ($this->episode = (new EpisodeModel())->getEpisodeBySlug($params[0], $params[1])) !== null - ) { - unset($params[1]); - unset($params[0]); - return $this->{$method}(...$params); - } - - throw PageNotFoundException::forPageNotFound(); - } - - public function index(): string - { - // Prevent analytics hit when authenticated - if (! can_user_interact()) { - $this->registerPodcastWebpageHit($this->episode->podcast_id); - } - - $locale = service('request') - ->getLocale(); - $cacheName = - "page_podcast#{$this->podcast->id}_episode#{$this->episode->id}_{$locale}" . - (can_user_interact() ? '_authenticated' : ''); - - if (! ($cachedView = cache($cacheName))) { - $data = [ - 'podcast' => $this->podcast, - 'episode' => $this->episode, - ]; - - $secondsToNextUnpublishedEpisode = (new EpisodeModel())->getSecondsToNextUnpublishedEpisode( - $this->podcast->id, - ); - - if (can_user_interact()) { - helper('form'); - return view('podcast/episode_authenticated', $data); - } - // The page cache is set to a decade so it is deleted manually upon podcast update - return view('podcast/episode', $data, [ - 'cache' => $secondsToNextUnpublishedEpisode - ? $secondsToNextUnpublishedEpisode - : DECADE, - 'cache_name' => $cacheName, - ]); - } - - return $cachedView; - } - - public function embeddablePlayer(string $theme = 'light-transparent'): string - { - header('Content-Security-Policy: frame-ancestors https://* http://*'); - - // Prevent analytics hit when authenticated - if (! can_user_interact()) { - $this->registerPodcastWebpageHit($this->episode->podcast_id); - } - - $session = 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->podcast->id}_episode#{$this->episode->id}_embeddable_player_{$theme}_{$locale}"; - - if (! ($cachedView = cache($cacheName))) { - $theme = EpisodeModel::$themes[$theme]; - - $data = [ - 'podcast' => $this->podcast, - 'episode' => $this->episode, - 'theme' => $theme, - ]; - - $secondsToNextUnpublishedEpisode = (new 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; - } - - public function oembedJSON(): ResponseInterface - { - return $this->response->setJSON([ - 'type' => 'rich', - 'version' => '1.0', - 'title' => $this->episode->title, - 'provider_name' => $this->podcast->title, - 'provider_url' => $this->podcast->link, - 'author_name' => $this->podcast->title, - 'author_url' => $this->podcast->link, - 'html' => - '', - 'width' => 600, - 'height' => 200, - 'thumbnail_url' => $this->episode->image->large_url, - 'thumbnail_width' => config('Images') - ->largeSize, - 'thumbnail_height' => config('Images') - ->largeSize, - ]); - } - - public function oembedXML(): ResponseInterface - { - $oembed = new SimpleXMLElement(""); - - $oembed->addChild('type', 'rich'); - $oembed->addChild('version', '1.0'); - $oembed->addChild('title', $this->episode->title); - $oembed->addChild('provider_name', $this->podcast->title); - $oembed->addChild('provider_url', $this->podcast->link); - $oembed->addChild('author_name', $this->podcast->title); - $oembed->addChild('author_url', $this->podcast->link); - $oembed->addChild('thumbnail', $this->episode->image->large_url); - $oembed->addChild('thumbnail_width', config('Images')->largeSize); - $oembed->addChild('thumbnail_height', config('Images')->largeSize); - $oembed->addChild( - 'html', - htmlentities( - '', - ), - ); - $oembed->addChild('width', '600'); - $oembed->addChild('height', '200'); - - return $this->response->setXML((string) $oembed); - } -} diff --git a/app/Libraries/PodcastActor.php b/app/Libraries/PodcastActor.php index 3eef392d..48b72fed 100644 --- a/app/Libraries/PodcastActor.php +++ b/app/Libraries/PodcastActor.php @@ -43,6 +43,6 @@ class PodcastActor extends ActorObject $this->category = $category; - $this->episodes = url_to('podcast-episodes', $podcast->name); + $this->episodes = url_to('podcast-episodes', $podcast->handle); } } diff --git a/app/Libraries/PodcastEpisode.php b/app/Libraries/PodcastEpisode.php index 9a8a2002..c12248e3 100644 --- a/app/Libraries/PodcastEpisode.php +++ b/app/Libraries/PodcastEpisode.php @@ -72,7 +72,7 @@ class PodcastEpisode extends ObjectType 'chapters' => $episode->chapters_file_url, ]; - $this->comments = url_to('episode-comments', $episode->podcast->name, $episode->slug); + $this->comments = url_to('episode-comments', $episode->podcast->handle, $episode->slug); if ($episode->published_at !== null) { $this->published = $episode->published_at->format(DATE_W3C); diff --git a/app/Models/EpisodeModel.php b/app/Models/EpisodeModel.php index 11b885b3..78bebf53 100644 --- a/app/Models/EpisodeModel.php +++ b/app/Models/EpisodeModel.php @@ -147,14 +147,14 @@ class EpisodeModel extends Model */ protected $beforeDelete = ['clearCache']; - public function getEpisodeBySlug(string $podcastName, string $episodeSlug): ?Episode + public function getEpisodeBySlug(string $podcastHandle, string $episodeSlug): ?Episode { - $cacheName = "podcast-{$podcastName}_episode-{$episodeSlug}"; + $cacheName = "podcast-{$podcastHandle}_episode-{$episodeSlug}"; if (! ($found = cache($cacheName))) { $found = $this->select('episodes.*') ->join('podcasts', 'podcasts.id = episodes.podcast_id') ->where('slug', $episodeSlug) - ->where('podcasts.name', $podcastName) + ->where('podcasts.handle', $podcastHandle) ->where('`published_at` <= NOW()', null, false) ->first(); @@ -292,7 +292,7 @@ class EpisodeModel extends Model cache() ->deleteMatching("podcast#{$episode->podcast_id}*"); cache() - ->deleteMatching("podcast-{$episode->podcast->name}*"); + ->deleteMatching("podcast-{$episode->podcast->handle}*"); cache() ->delete("podcast_episode#{$episode->id}"); cache() diff --git a/app/Models/PodcastModel.php b/app/Models/PodcastModel.php index 739593e7..d67c925d 100644 --- a/app/Models/PodcastModel.php +++ b/app/Models/PodcastModel.php @@ -35,7 +35,7 @@ class PodcastModel extends Model 'id', 'guid', 'title', - 'name', + 'handle', 'description_markdown', 'description_html', 'episode_description_footer_markdown', @@ -87,8 +87,8 @@ class PodcastModel extends Model */ protected $validationRules = [ 'title' => 'required', - 'name' => - 'required|regex_match[/^[a-zA-Z0-9\_]{1,191}$/]|is_unique[podcasts.name,id,{id}]', + 'handle' => + 'required|regex_match[/^[a-zA-Z0-9\_]{1,191}$/]|is_unique[podcasts.handle,id,{id}]', 'description_markdown' => 'required', 'image_path' => 'required', 'language_code' => 'required', @@ -126,14 +126,14 @@ class PodcastModel extends Model */ protected $beforeDelete = ['clearCache']; - public function getPodcastByName(string $podcastName): ?Podcast + public function getPodcastByHandle(string $podcastHandle): ?Podcast { - $cacheName = "podcast-{$podcastName}"; + $cacheName = "podcast-{$podcastHandle}"; if (! ($found = cache($cacheName))) { - $found = $this->where('name', $podcastName) + $found = $this->where('handle', $podcastHandle) ->first(); cache() - ->save("podcast-{$podcastName}", $found, DECADE); + ->save("podcast-{$podcastHandle}", $found, DECADE); } return $found; @@ -239,7 +239,7 @@ class PodcastModel extends Model ->join('podcasts', 'podcasts.id = podcasts_users.podcast_id') ->where([ 'user_id' => $userId, - 'name' => $podcastId, + 'handle' => $podcastId, ]) ->get() ->getResultObject(); @@ -385,7 +385,7 @@ class PodcastModel extends Model cache() ->deleteMatching("podcast#{$podcast->id}*"); cache() - ->delete("podcast-{$podcast->name}"); + ->delete("podcast-{$podcast->handle}"); } // clear cache for every credit page @@ -413,7 +413,7 @@ class PodcastModel extends Model $publickey = $rsaKey['publickey']; $url = new URI(base_url()); - $username = $data['data']['name']; + $username = $data['data']['handle']; $domain = $url->getHost() . ($url->getPort() ? ':' . $url->getPort() : ''); diff --git a/app/Resources/icons/at.svg b/app/Resources/icons/at.svg new file mode 100644 index 00000000..80191b5f --- /dev/null +++ b/app/Resources/icons/at.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/app/Resources/js/modules/Clipboard.ts b/app/Resources/js/modules/Clipboard.ts index 7b4e4ebf..09718bcf 100644 --- a/app/Resources/js/modules/Clipboard.ts +++ b/app/Resources/js/modules/Clipboard.ts @@ -1,7 +1,6 @@ const Clipboard = (): void => { - const buttons: NodeListOf | null = document.querySelectorAll( - "button[data-type='clipboard-copy']" - ); + const buttons: NodeListOf | null = + document.querySelectorAll("button[data-type='clipboard-copy']"); if (buttons) { for (let i = 0; i < buttons.length; i++) { diff --git a/app/Resources/js/modules/DateTimePicker.ts b/app/Resources/js/modules/DateTimePicker.ts index a3ab215a..7d4a952e 100644 --- a/app/Resources/js/modules/DateTimePicker.ts +++ b/app/Resources/js/modules/DateTimePicker.ts @@ -11,15 +11,13 @@ const isBrowserLocale24h = () => .match(/AM/); const DateTimePicker = (): void => { - const dateTimeContainers: NodeListOf = document.querySelectorAll( - "div[data-picker='datetime']" - ); + const dateTimeContainers: NodeListOf = + document.querySelectorAll("div[data-picker='datetime']"); for (let i = 0; i < dateTimeContainers.length; i++) { const dateTimeContainer = dateTimeContainers[i]; - const dateTimeInput: HTMLInputElement | null = dateTimeContainer.querySelector( - "input[data-input]" - ); + const dateTimeInput: HTMLInputElement | null = + dateTimeContainer.querySelector("input[data-input]"); if (dateTimeInput) { const flatpickrInstance = flatpickr(dateTimeContainer, { diff --git a/app/Resources/js/modules/Dropdown.ts b/app/Resources/js/modules/Dropdown.ts index 231e028f..e94bd176 100644 --- a/app/Resources/js/modules/Dropdown.ts +++ b/app/Resources/js/modules/Dropdown.ts @@ -1,9 +1,8 @@ import { createPopper, Instance, Placement } from "@popperjs/core"; const Dropdown = (): void => { - const dropdownButtons: NodeListOf = document.querySelectorAll( - "[data-dropdown='button']" - ); + const dropdownButtons: NodeListOf = + document.querySelectorAll("[data-dropdown='button']"); for (let i = 0; i < dropdownButtons.length; i++) { const button = dropdownButtons[i]; diff --git a/app/Resources/js/modules/Modal.ts b/app/Resources/js/modules/Modal.ts index 060540a0..5d404c10 100644 --- a/app/Resources/js/modules/Modal.ts +++ b/app/Resources/js/modules/Modal.ts @@ -1,7 +1,6 @@ const Modal = (): void => { - const modalTriggerElements: NodeListOf = document.querySelectorAll( - "[data-modal-target]" - ); + const modalTriggerElements: NodeListOf = + document.querySelectorAll("[data-modal-target]"); for (let i = 0; i < modalTriggerElements.length; i++) { const modalTrigger = modalTriggerElements[i]; @@ -16,9 +15,8 @@ const Modal = (): void => { modal.classList.toggle("hidden"); }); - const closeButtonsElements: NodeListOf = modal.querySelectorAll( - "[data-modal-button]" - ); + const closeButtonsElements: NodeListOf = + modal.querySelectorAll("[data-modal-button]"); for (let j = 0; j < closeButtonsElements.length; j++) { const closeButton = closeButtonsElements[j]; diff --git a/app/Resources/js/modules/MultiSelect.ts b/app/Resources/js/modules/MultiSelect.ts index 9268b76f..ad07852a 100644 --- a/app/Resources/js/modules/MultiSelect.ts +++ b/app/Resources/js/modules/MultiSelect.ts @@ -2,9 +2,8 @@ import Choices from "choices.js"; const MultiSelect = (): void => { // Pass single element - const multiSelects: NodeListOf = document.querySelectorAll( - "select[multiple]" - ); + const multiSelects: NodeListOf = + document.querySelectorAll("select[multiple]"); for (let i = 0; i < multiSelects.length; i++) { const multiSelect = multiSelects[i]; diff --git a/app/Resources/js/modules/PublishMessageWarning.ts b/app/Resources/js/modules/PublishMessageWarning.ts index fbde59f4..161beaf9 100644 --- a/app/Resources/js/modules/PublishMessageWarning.ts +++ b/app/Resources/js/modules/PublishMessageWarning.ts @@ -4,15 +4,13 @@ const PublishMessageWarning = (): void => { ); if (publishForm) { - const messageTextArea: HTMLTextAreaElement | null = publishForm.querySelector( - "[name='message']" - ); + const messageTextArea: HTMLTextAreaElement | null = + publishForm.querySelector("[name='message']"); const submitButton: HTMLButtonElement | null = publishForm.querySelector( "button[type='submit']" ); - const publishMessageWarning: HTMLDivElement | null = publishForm.querySelector( - "[id='publish-warning']" - ); + const publishMessageWarning: HTMLDivElement | null = + publishForm.querySelector("[id='publish-warning']"); if ( messageTextArea && diff --git a/app/Resources/js/modules/Soundbites.ts b/app/Resources/js/modules/Soundbites.ts index 7c2009fa..64833bc1 100644 --- a/app/Resources/js/modules/Soundbites.ts +++ b/app/Resources/js/modules/Soundbites.ts @@ -54,9 +54,8 @@ const Soundbites = (): void => { } } - const soundbitePlayButtons: NodeListOf | null = document.querySelectorAll( - "button[data-type='play-soundbite']" - ); + const soundbitePlayButtons: NodeListOf | null = + document.querySelectorAll("button[data-type='play-soundbite']"); if (soundbitePlayButtons) { for (let i = 0; i < soundbitePlayButtons.length; i++) { const soundbitePlayButton: HTMLButtonElement = soundbitePlayButtons[i]; @@ -70,15 +69,15 @@ const Soundbites = (): void => { } } - const inputFields: NodeListOf | null = document.querySelectorAll( - "input[data-type='soundbite-field']" - ); + const inputFields: NodeListOf | null = + document.querySelectorAll("input[data-type='soundbite-field']"); if (inputFields) { for (let i = 0; i < inputFields.length; i++) { const inputField: HTMLInputElement = inputFields[i]; - const soundbitePlayButton: HTMLButtonElement | null = document.querySelector( - `button[data-type="play-soundbite"][data-soundbite-id="${inputField.dataset.soundbiteId}"]` - ); + const soundbitePlayButton: HTMLButtonElement | null = + document.querySelector( + `button[data-type="play-soundbite"][data-soundbite-id="${inputField.dataset.soundbiteId}"]` + ); if (soundbitePlayButton) { if (inputField.dataset.fieldType == "start-time") { inputField.addEventListener("input", () => { diff --git a/app/Resources/js/modules/ThemePicker.ts b/app/Resources/js/modules/ThemePicker.ts index d495170c..e7a2b13a 100644 --- a/app/Resources/js/modules/ThemePicker.ts +++ b/app/Resources/js/modules/ThemePicker.ts @@ -1,16 +1,14 @@ const ThemePicker = (): void => { - const buttons: NodeListOf | null = document.querySelectorAll( - "button[data-type='theme-picker']" - ); + const buttons: NodeListOf | 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"]` - ); + const urlTextArea: HTMLTextAreaElement | null = + document.querySelector(`textarea[id="url"]`); if (buttons && iframe && iframeTextArea && urlTextArea) { for (let i = 0; i < buttons.length; i++) { diff --git a/app/Resources/js/modules/Time.ts b/app/Resources/js/modules/Time.ts index 17dd8cf2..f620081b 100644 --- a/app/Resources/js/modules/Time.ts +++ b/app/Resources/js/modules/Time.ts @@ -1,7 +1,6 @@ const Time = (): void => { - const timeElements: NodeListOf = document.querySelectorAll( - "time" - ); + const timeElements: NodeListOf = + document.querySelectorAll("time"); for (let i = 0; i < timeElements.length; i++) { const timeElement = timeElements[i]; diff --git a/app/Resources/js/modules/Toggler.ts b/app/Resources/js/modules/Toggler.ts index c726ac1e..4b33a0ba 100644 --- a/app/Resources/js/modules/Toggler.ts +++ b/app/Resources/js/modules/Toggler.ts @@ -1,7 +1,6 @@ const Toggler = (): void => { - const togglerElements: NodeListOf = document.querySelectorAll( - "[data-toggle]" - ); + const togglerElements: NodeListOf = + document.querySelectorAll("[data-toggle]"); for (let i = 0; i < togglerElements.length; i++) { const toggler = togglerElements[i]; diff --git a/app/Views/admin/contributor/view.php b/app/Views/admin/contributor/view.php index 2aca7825..e7f6545e 100644 --- a/app/Views/admin/contributor/view.php +++ b/app/Views/admin/contributor/view.php @@ -3,7 +3,7 @@ section('title') ?> $contributor->username, - 'podcastName' => $contributor->podcast->name, + 'podcastTitle' => $contributor->podcast->title, ]) ?> endSection() ?> diff --git a/app/Views/admin/episode/list.php b/app/Views/admin/episode/list.php index d0f455d3..5780b47c 100644 --- a/app/Views/admin/episode/list.php +++ b/app/Views/admin/episode/list.php @@ -91,7 +91,7 @@ ) ?>"> title ?> @name ?> + ) ?>">@handle ?> diff --git a/app/Views/admin/podcast/create.php b/app/Views/admin/podcast/create.php index b4509d94..93c0d117 100644 --- a/app/Views/admin/podcast/create.php +++ b/app/Views/admin/podcast/create.php @@ -45,18 +45,21 @@ ]) ?> - 'name', - 'name' => 'name', - 'class' => 'form-input mb-4', - 'value' => old('name'), - 'required' => 'required', -]) ?> +
+ + 'handle', + 'name' => 'handle', + 'class' => 'form-input w-full pl-8', + 'value' => old('handle'), + 'required' => 'required', + ]) ?> +
'mb-4']) ?> diff --git a/app/Views/admin/podcast/import.php b/app/Views/admin/podcast/import.php index bec1dd32..e5ed9af8 100644 --- a/app/Views/admin/podcast/import.php +++ b/app/Views/admin/podcast/import.php @@ -51,18 +51,21 @@ - 'name', - 'name' => 'name', - 'class' => 'form-input mb-4', - 'value' => old('name'), - 'required' => 'required', -]) ?> +
+ + 'handle', + 'name' => 'handle', + 'class' => 'form-input w-full pl-8', + 'value' => old('handle'), + 'required' => 'required', + ]) ?> +
">
diff --git a/app/Views/admin/podcast/list.php b/app/Views/admin/podcast/list.php index d6f7af28..28f80217 100644 --- a/app/Views/admin/podcast/list.php +++ b/app/Views/admin/podcast/list.php @@ -37,7 +37,7 @@ $podcast->id, ) ?>" class="flex flex-col p-2 hover:underline">

title ?>

-

@name ?>

+

@handle ?>