diff --git a/.env.example b/.env.example index a1b2a681038cc172a9d68b9cfe873d606149614b..0ac3f1b0d4abf00d257142d23351c01b0bd3db9a 100644 --- a/.env.example +++ b/.env.example @@ -12,6 +12,7 @@ # Instance configuration app.baseURL="https://YOUR_DOMAIN_NAME/" +app.mediaBaseURL="https://YOUR_MEDIA_DOMAIN_NAME/" app.adminGateway="cp-admin" app.authGateway="cp-auth" @@ -19,7 +20,7 @@ app.authGateway="cp-auth" database.default.hostname="localhost" database.default.database="castopod" database.default.username="root" -database.default.password="root" +database.default.password="****" database.default.DBPrefix="cp_" # Cache configuration (advanced) diff --git a/DEPENDENCIES.md b/DEPENDENCIES.md index d4c1c29cc1b68810408f6048748918729c463d12..f987fd2785b74bfdb7ab61ffdd16b740acb5b565 100644 --- a/DEPENDENCIES.md +++ b/DEPENDENCIES.md @@ -20,7 +20,7 @@ PHP Dependencies: ([ BSD-3-Clause License ](https://github.com/vlucas/phpdotenv/blob/master/LICENSE)) - [HTML To Markdown for PHP](https://github.com/thephpleague/html-to-markdown) ([MIT License](https://github.com/thephpleague/html-to-markdown/blob/master/LICENSE)) -- [podlibre/user-agents-php](https://github.com/podlibre/user-agents-php) +- [opawg/user-agents-php](https://github.com/opawg/user-agents-php) ([MIT License](https://github.com/podlibre/user-agents-php/blob/main/LICENSE)) - [podlibre/ipcat](https://github.com/podlibre/ipcat) ([GNU General Public License v3.0](https://github.com/podlibre/ipcat/blob/master/LICENSE)) @@ -47,6 +47,9 @@ Other: - [OPAWG/User agent list](https://github.com/opawg/user-agents) ([by Open Podcast Analytics Working Group](https://github.com/opawg)) ([MIT license](https://github.com/opawg/user-agents/blob/master/LICENSE)) +- [OPAWG/podcast-rss-useragents](https://github.com/opawg/podcast-rss-useragents) + ([by Open Podcast Analytics Working Group](https://github.com/opawg)) + ([MIT license](https://github.com/opawg/podcast-rss-useragents/blob/master/LICENSE)) - [client9/ipcat](https://github.com/client9/ipcat) ([GNU General Public License v3.0](https://github.com/client9/ipcat/blob/master/LICENSE)) - [GeoLite2 City](https://dev.maxmind.com/geoip/geoip2/geolite2/) diff --git a/app/Config/App.php b/app/Config/App.php index c81f2d891a028d9a811ddb443716cbb3f8c72cd4..ffad1a4188ba1afdd926dde049976e014a9bc723 100644 --- a/app/Config/App.php +++ b/app/Config/App.php @@ -22,7 +22,20 @@ class App extends BaseConfig | environments. | */ - public $baseURL = 'http://localhost:8080/'; + public $baseURL = 'http://127.0.0.1:8080/'; + + /* + |-------------------------------------------------------------------------- + | Media Base URL + |-------------------------------------------------------------------------- + | + | URL to your media root. Typically this will be your base URL, + | WITH a trailing slash: + | + | http://cdn.example.com/ + | + */ + public $mediaBaseURL = 'http://127.0.0.2:8080/'; /* |-------------------------------------------------------------------------- diff --git a/app/Controllers/Admin/Podcast.php b/app/Controllers/Admin/Podcast.php index afac930d4a8f1bcc79fe81ae0e3054cff66eb726..542f4f921e474000c1335d1d7bcb08893c36935d 100644 --- a/app/Controllers/Admin/Podcast.php +++ b/app/Controllers/Admin/Podcast.php @@ -361,9 +361,10 @@ class Podcast extends BaseController ? $nsItunes->subtitle . "\n" . $nsItunes->summary : $item->description) ), - 'image' => empty($nsItunes->image->attributes()) - ? null - : download_file($nsItunes->image->attributes()), + 'image' => + !$nsItunes->image || empty($nsItunes->image->attributes()) + ? null + : download_file($nsItunes->image->attributes()), 'parental_advisory' => empty($nsItunes->explicit) ? null : (in_array($nsItunes->explicit, ['yes', 'true']) diff --git a/app/Controllers/Analytics.php b/app/Controllers/Analytics.php index 7066c5e340fbd4ca2e7ae373a665c418516778b8..e0cf865c57b012d815c466e29e91810b0b4f2a6a 100644 --- a/app/Controllers/Analytics.php +++ b/app/Controllers/Analytics.php @@ -55,7 +55,7 @@ class Analytics extends Controller ) { helper('media'); - $serviceName = isset($_GET['s']) ? $_GET['s'] : ''; + $serviceName = isset($_GET['_from']) ? $_GET['_from'] : ''; podcast_hit( $podcastId, @@ -64,6 +64,6 @@ class Analytics extends Controller $fileSize, $serviceName ); - return redirect()->to(media_url(implode('/', $filename))); + return redirect()->to(media_base_url($filename)); } } diff --git a/app/Controllers/Feed.php b/app/Controllers/Feed.php index 0170e05ea3fa9c35f2bc2dc1e76546447604ab95..5b74f70a867c6714f7fa885215f0df0765935dc4 100644 --- a/app/Controllers/Feed.php +++ b/app/Controllers/Feed.php @@ -38,10 +38,7 @@ class Feed extends Controller ($service ? "_{$service['slug']}" : ''); if (!($found = cache($cacheName))) { - $found = get_rss_feed( - $podcast, - $service ? '?s=' . urlencode($service['name']) : '' - ); + $found = get_rss_feed($podcast, $service ? $service['name'] : ''); // 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( diff --git a/app/Controllers/Install.php b/app/Controllers/Install.php index 4d71879ec52423904dcb0f8fc0406810ae406ad0..1d53f7247ff02a761c86ecd36ecd7451dc0dfe2e 100644 --- a/app/Controllers/Install.php +++ b/app/Controllers/Install.php @@ -150,6 +150,7 @@ class Install extends Controller { $rules = [ 'hostname' => 'required|validate_url', + 'media_base_url' => 'permit_empty|validate_url', 'admin_gateway' => 'required', 'auth_gateway' => 'required|differs[admin_gateway]', ]; @@ -165,8 +166,12 @@ class Install extends Controller } $baseUrl = $this->request->getPost('hostname'); + $mediaBaseUrl = $this->request->getPost('media_base_url'); self::writeEnv([ 'app.baseURL' => $baseUrl, + 'app.mediaBaseURL' => empty($mediaBaseUrl) + ? $baseUrl + : $mediaBaseUrl, 'app.adminGateway' => $this->request->getPost('admin_gateway'), 'app.authGateway' => $this->request->getPost('auth_gateway'), ]); diff --git a/app/Database/Seeds/FakePodcastsAnalyticsSeeder.php b/app/Database/Seeds/FakePodcastsAnalyticsSeeder.php index e17e177ddc6f93cb48f811e3511838887293b219..f7156f6e2aa8b39be4bfd112c27ad3f6b8fd9340 100644 --- a/app/Database/Seeds/FakePodcastsAnalyticsSeeder.php +++ b/app/Database/Seeds/FakePodcastsAnalyticsSeeder.php @@ -28,6 +28,13 @@ class FakePodcastsAnalyticsSeeder extends Seeder true ); + $jsonRSSUserAgents = json_decode( + file_get_contents( + 'https://raw.githubusercontent.com/opawg/podcast-rss-useragents/master/src/rss-ua.json' + ), + true + ); + if ($podcast) { $firstEpisode = (new EpisodeModel()) ->selectMin('published_at') @@ -67,6 +74,10 @@ class FakePodcastsAnalyticsSeeder extends Seeder $jsonUserAgents[ rand(1, count($jsonUserAgents) - 1) ]; + $service = + $jsonRSSUserAgents[ + rand(1, count($jsonRSSUserAgents) - 1) + ]['name']; $app = isset($player['app']) ? $player['app'] : ''; $device = isset($player['device']) ? $player['device'] @@ -132,6 +143,7 @@ class FakePodcastsAnalyticsSeeder extends Seeder $analytics_podcasts_by_player[] = [ 'podcast_id' => $podcast->id, 'date' => date('Y-m-d', $date), + 'service'=> $service, 'app' => $app, 'device' => $device, 'os' => $os, diff --git a/app/Entities/Episode.php b/app/Entities/Episode.php index 23f7737c24273c01c771e7176d628ef9e0937b63..3ba5b7e0cd6871d7c60788696d59e6b8ddd77305 100644 --- a/app/Entities/Episode.php +++ b/app/Entities/Episode.php @@ -45,6 +45,11 @@ class Episode extends Entity */ protected $enclosure_url; + /** + * @var string + */ + protected $enclosure_web_url; + /** * @var string */ @@ -194,6 +199,11 @@ class Episode extends Entity ); } + public function getWebEnclosureUrl() + { + return $this->getEnclosureUrl() . '?_from=-+Website+-'; + } + public function getLink() { return base_url( diff --git a/app/Helpers/media_helper.php b/app/Helpers/media_helper.php index a3b28279e285c68abe46ff91745b2200e1be5252..77d77478f7a777bd26d4a867f4c651cf636a0365 100644 --- a/app/Helpers/media_helper.php +++ b/app/Helpers/media_helper.php @@ -74,3 +74,18 @@ function media_url($uri = '', string $protocol = null): string { return base_url(config('App')->mediaRoot . '/' . $uri, $protocol); } + +function media_base_url($uri = '') +{ + // convert segment array to string + if (is_array($uri)) { + $uri = implode('/', $uri); + } + $uri = trim($uri, '/'); + + return rtrim(config('App')->mediaBaseURL, '/') . + '/' . + config('App')->mediaRoot . + '/' . + $uri; +} diff --git a/app/Helpers/rss_helper.php b/app/Helpers/rss_helper.php index 9627c3d128688858b99a5b29951532a0abdf39fc..265841ce9b0c2d48425fff950b36480f5d09961f 100644 --- a/app/Helpers/rss_helper.php +++ b/app/Helpers/rss_helper.php @@ -103,7 +103,11 @@ function get_rss_feed($podcast, $serviceName = '') $item->addChild('title', $episode->title); $enclosure = $item->addChild('enclosure'); - $enclosure->addAttribute('url', $episode->enclosure_url . $serviceName); + $enclosure->addAttribute( + 'url', + $episode->enclosure_url . + (empty($serviceName) ? '' : '?_from=' . urlencode($serviceName)) + ); $enclosure->addAttribute('length', $episode->enclosure_filesize); $enclosure->addAttribute('type', $episode->enclosure_mimetype); diff --git a/app/Language/en/Install.php b/app/Language/en/Install.php index 131fc20691f52409a585eb17816889ad6520f2de..5a5af49e416b9cd56c96079556ea863be72b3937 100644 --- a/app/Language/en/Install.php +++ b/app/Language/en/Install.php @@ -13,6 +13,9 @@ return [ 'form' => [ 'instance_config' => 'Instance configuration', 'hostname' => 'Hostname', + 'media_base_url' => 'Media base URL', + 'media_base_url_hint' => + 'If you use a CDN and/or an external analytics service, you may set them here.', 'admin_gateway' => 'Admin gateway', 'admin_gateway_hint' => 'The route to access the admin area (eg. https://example.com/cp-admin). It is set by default as cp-admin, we recommend you change it for security reasons.', diff --git a/app/Language/fr/Install.php b/app/Language/fr/Install.php index dfff116fa9277cfc8f19bd3d4ad97fffa9a536df..e721ebb441b36632e147704aa8bbaf8c12e3f4e8 100644 --- a/app/Language/fr/Install.php +++ b/app/Language/fr/Install.php @@ -13,6 +13,9 @@ return [ 'form' => [ 'instance_config' => 'Paramètres de l’instance', 'hostname' => 'Nom d’hôte', + 'media_base_url' => 'Adresse racine des médias', + 'media_base_url_hint' => + 'Si vous utilisez un CDN et/ou un service de mesure d’audience externe, vous pouvez les définir ici.', 'admin_gateway' => 'Adresse d’administration', 'admin_gateway_hint' => 'Le chemin pour accéder à l’administration (par exemple https://example.com/cp-admin). Il est défini par défaut à cp-admin, nous vous recommandons de le changer par mesure de sécurité.', diff --git a/app/Views/_assets/styles/charts.css b/app/Views/_assets/styles/charts.css index 37b526afae0a6495bb4d98610ec85a31b88a921d..b1bb0294dda5919e8a3ab9bee3b0b06ec14c9b5c 100644 --- a/app/Views/_assets/styles/charts.css +++ b/app/Views/_assets/styles/charts.css @@ -1,9 +1,9 @@ .chart-map { - height: 800px; + height: 600px; border: solid 10px #eee; } .chart-pie { - height: 400px; + height: 450px; width: 100%; border: solid 1px #eee; } diff --git a/app/Views/_layout.php b/app/Views/_layout.php index dd3ea6e984c8240ab8dd014b91c487eac451cb8e..831f2cb0b682eb1bbea719fcefaef7831c03603e 100644 --- a/app/Views/_layout.php +++ b/app/Views/_layout.php @@ -14,7 +14,9 @@ <body class="flex flex-col min-h-screen mx-auto"> <header class="border-b"> <div class="container flex items-center justify-between px-2 py-4 mx-auto"> - <a href="<?= route_to('home') ?>" class="text-2xl">Castopod</a> + <a href="<?= route_to('home') ?>" class="text-2xl"><?= isset($page) + ? $page->title + : 'Castopod' ?></a> </div> </header> <main class="container flex-1 px-4 py-10 mx-auto"> diff --git a/app/Views/admin/podcast/analytics/locations.php b/app/Views/admin/podcast/analytics/locations.php index d4620e3a814fb98be95b07c48d054136b126cdce..3504529bc257701177b2b0a3d3af44bbe2b0ab65 100644 --- a/app/Views/admin/podcast/analytics/locations.php +++ b/app/Views/admin/podcast/analytics/locations.php @@ -10,7 +10,7 @@ <?= $this->section('content') ?> -<div class="grid grid-cols-2 divide-x"> +<div class="lg:divide-x lg:grid lg:grid-cols-2"> <div class="mb-12 mr-6 text-center"> <h2><?= lang('Charts.by_country_weekly') ?></h2> <div class="chart-pie" id="by-country-pie" data-chart-type="pie-chart" data-chart-url="<?= route_to( diff --git a/app/Views/admin/podcast/analytics/players.php b/app/Views/admin/podcast/analytics/players.php index fcc2e83bf7bdec3f1b305f8564b8c94f359bb8b5..b48ae316c7b4df950b4b64667ec7eb7da3e58a66 100644 --- a/app/Views/admin/podcast/analytics/players.php +++ b/app/Views/admin/podcast/analytics/players.php @@ -10,7 +10,7 @@ <?= $this->section('content') ?> -<div class="grid grid-cols-2 divide-x"> +<div class="lg:divide-x lg:grid lg:grid-cols-2"> <div class="mb-12 mr-6 text-center"> <h2><?= lang('Charts.by_player_weekly') ?></h2> <div class="chart-pie" id="by-app-weekly-pie" data-chart-type="pie-chart" data-chart-url="<?= route_to( diff --git a/app/Views/admin/podcast/analytics/webpages.php b/app/Views/admin/podcast/analytics/webpages.php index 9b0a0142252363790753bd92e464af9dbfcba322..5ec0f99a8b4ebc4e818a1c692bbff10f9c6e0329 100644 --- a/app/Views/admin/podcast/analytics/webpages.php +++ b/app/Views/admin/podcast/analytics/webpages.php @@ -10,7 +10,7 @@ <?= $this->section('content') ?> -<div class="grid grid-cols-2 divide-x"> +<div class="lg:divide-x lg:grid lg:grid-cols-2"> <div class="mb-12 mr-6 text-center"> <h2><?= lang('Charts.by_domain_weekly') ?></h2> diff --git a/app/Views/episode.php b/app/Views/episode.php index c3c9e7f8a618ba2b1fc474014a8d67e86237f3a1..4e52bca255d64d1dbffa6a7508c479fa3f011825 100644 --- a/app/Views/episode.php +++ b/app/Views/episode.php @@ -96,7 +96,7 @@ </time> </div> <audio controls preload="none" class="w-full mt-auto"> - <source src="<?= $episode->enclosure_url ?>" type="<?= $episode->enclosure_type ?>"> + <source src="<?= $episode->enclosure_web_url ?>" type="<?= $episode->enclosure_type ?>"> Your browser does not support the audio tag. </audio> </div> diff --git a/app/Views/install/instance_config.php b/app/Views/install/instance_config.php index bdd63deecf345430cfc2d6e293f7faa4eaa588fb..ef6d64289599057db1495ee5b77e2b4b3315d5a5 100644 --- a/app/Views/install/instance_config.php +++ b/app/Views/install/instance_config.php @@ -22,6 +22,21 @@ 'required' => 'required', ]) ?> + +<?= form_label( + lang('Install.form.media_base_url'), + 'media_base_url', + [], + lang('Install.form.media_base_url_hint'), + true +) ?> +<?= form_input([ + 'id' => 'media_base_url', + 'name' => 'media_base_url', + 'class' => 'form-input mb-4', + 'value' => old('media_base_url', ''), +]) ?> + <?= form_label( lang('Install.form.admin_gateway'), 'admin_gateway', @@ -51,7 +66,12 @@ ]) ?> <?= button( - lang('Install.form.next') . icon('arrow-right', 'ml-2'), + lang('Install.form.next') . + icon( + 'arrow-right', + + 'ml-2' + ), null, ['variant' => 'primary'], ['type' => 'submit', 'class' => 'self-end'] diff --git a/app/Views/podcast.php b/app/Views/podcast.php index c70db9fa25da936366b8fea7f885149502a0df9f..52329cd3535d6a4394d2275028ae535a6870f922 100644 --- a/app/Views/podcast.php +++ b/app/Views/podcast.php @@ -144,7 +144,7 @@ </time> </div> <audio controls preload="none" class="w-full mt-auto"> - <source src="<?= $episode->enclosure_url ?>" type="<?= $episode->enclosure_type ?>"> + <source src="<?= $episode->enclosure_web_url ?>" type="<?= $episode->enclosure_type ?>"> Your browser does not support the audio tag. </audio> </div> diff --git a/sha1sum b/sha1sum new file mode 100644 index 0000000000000000000000000000000000000000..9daeafb9864cf43055ae93beb0afd6c7d144bfa4 --- /dev/null +++ b/sha1sum @@ -0,0 +1 @@ +test