From 972bcbf65ee119b8641ca3c4e5c0e8cf9ca8dd4f Mon Sep 17 00:00:00 2001
From: Benjamin Bellamy <ben@podlibre.org>
Date: Mon, 26 Oct 2020 16:13:43 +0000
Subject: [PATCH] feat: add CDN url

feat: rename ?s to ?_from to match podcastindex recommendation
feat: adust map height
feat: display pie chart on 1 column for small and medium screens
feat: handle empty rss user agent
fix: correct bug when importing episode with empty image
fix: add service to FakePodcastsAnalyticsSeeder, resize pie charts so that all lines fit
fix: set page title to ->title when it exists
fix: replace %20 with +

closes #37
---
 .env.example                                  |  3 ++-
 DEPENDENCIES.md                               |  5 ++++-
 app/Config/App.php                            | 15 ++++++++++++-
 app/Controllers/Admin/Podcast.php             |  7 +++---
 app/Controllers/Analytics.php                 |  4 ++--
 app/Controllers/Feed.php                      |  5 +----
 app/Controllers/Install.php                   |  5 +++++
 .../Seeds/FakePodcastsAnalyticsSeeder.php     | 12 ++++++++++
 app/Entities/Episode.php                      | 10 +++++++++
 app/Helpers/media_helper.php                  | 15 +++++++++++++
 app/Helpers/rss_helper.php                    |  6 ++++-
 app/Language/en/Install.php                   |  3 +++
 app/Language/fr/Install.php                   |  3 +++
 app/Views/_assets/styles/charts.css           |  4 ++--
 app/Views/_layout.php                         |  4 +++-
 .../admin/podcast/analytics/locations.php     |  2 +-
 app/Views/admin/podcast/analytics/players.php |  2 +-
 .../admin/podcast/analytics/webpages.php      |  2 +-
 app/Views/episode.php                         |  2 +-
 app/Views/install/instance_config.php         | 22 ++++++++++++++++++-
 app/Views/podcast.php                         |  2 +-
 sha1sum                                       |  1 +
 22 files changed, 112 insertions(+), 22 deletions(-)
 create mode 100644 sha1sum

diff --git a/.env.example b/.env.example
index a1b2a68103..0ac3f1b0d4 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 d4c1c29cc1..f987fd2785 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 c81f2d891a..ffad1a4188 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 afac930d4a..542f4f921e 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 7066c5e340..e0cf865c57 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 0170e05ea3..5b74f70a86 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 4d71879ec5..1d53f7247f 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 e17e177ddc..f7156f6e2a 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 23f7737c24..3ba5b7e0cd 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 a3b28279e2..77d77478f7 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 9627c3d128..265841ce9b 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 131fc20691..5a5af49e41 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 dfff116fa9..e721ebb441 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 37b526afae..b1bb0294dd 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 dd3ea6e984..831f2cb0b6 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 d4620e3a81..3504529bc2 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 fcc2e83bf7..b48ae316c7 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 9b0a014225..5ec0f99a8b 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 c3c9e7f8a6..4e52bca255 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 bdd63deecf..ef6d642895 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 c70db9fa25..52329cd353 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 0000000000..9daeafb986
--- /dev/null
+++ b/sha1sum
@@ -0,0 +1 @@
+test
-- 
GitLab