diff --git a/app/Config/Routes.php b/app/Config/Routes.php index 878e0cc8..9e1209c0 100644 --- a/app/Config/Routes.php +++ b/app/Config/Routes.php @@ -153,6 +153,14 @@ $routes->group( 'filter' => 'permission:podcasts-view,podcast-view', ] ); + $routes->get( + 'listening-time', + 'Podcast::viewAnalyticsListeningTime/$1', + [ + 'as' => 'podcast-analytics-listening-time', + 'filter' => 'permission:podcasts-view,podcast-view', + ] + ); $routes->get( 'players', 'Podcast::viewAnalyticsPlayers/$1', diff --git a/app/Controllers/Admin/Podcast.php b/app/Controllers/Admin/Podcast.php index 2cf43a47..1d10f9f2 100644 --- a/app/Controllers/Admin/Podcast.php +++ b/app/Controllers/Admin/Podcast.php @@ -90,6 +90,14 @@ class Podcast extends BaseController return view('admin/podcast/analytics/unique_listeners', $data); } + public function viewAnalyticsListeningTime() + { + $data = ['podcast' => $this->podcast]; + + replace_breadcrumb_params([0 => $this->podcast->title]); + return view('admin/podcast/analytics/listening-time', $data); + } + public function viewAnalyticsPlayers() { $data = ['podcast' => $this->podcast]; diff --git a/app/Language/en/Breadcrumb.php b/app/Language/en/Breadcrumb.php index dba5b876..be49e105 100644 --- a/app/Language/en/Breadcrumb.php +++ b/app/Language/en/Breadcrumb.php @@ -27,4 +27,5 @@ return [ 'webpages' => 'Web pages', 'unique-listeners' => 'Unique listeners', 'players' => 'Players', + 'listening-time' => 'Listening time', ]; diff --git a/app/Language/en/Charts.php b/app/Language/en/Charts.php index 5e3e8879..ac27218e 100644 --- a/app/Language/en/Charts.php +++ b/app/Language/en/Charts.php @@ -27,4 +27,6 @@ return [ 'by_domain_yearly' => 'Web pages visits by source (for the past year)', 'by_entry_page' => 'Web pages visits by landing page (for the past week)', 'podcast_bots' => 'Bots (crawlers)', + 'daily_listening_time' => 'Daily cumulative listening time', + 'monthly_listening_time' => 'Monthly cumulative listening time', ]; diff --git a/app/Language/en/PodcastNavigation.php b/app/Language/en/PodcastNavigation.php index 9dae71b3..26288408 100644 --- a/app/Language/en/PodcastNavigation.php +++ b/app/Language/en/PodcastNavigation.php @@ -25,4 +25,5 @@ return [ 'podcast-analytics-locations' => 'Locations', 'podcast-analytics-unique-listeners' => 'Unique listeners', 'podcast-analytics-players' => 'Players', + 'podcast-analytics-listening-time' => 'Listening time', ]; diff --git a/app/Language/fr/Breadcrumb.php b/app/Language/fr/Breadcrumb.php index a27e5278..50405e53 100644 --- a/app/Language/fr/Breadcrumb.php +++ b/app/Language/fr/Breadcrumb.php @@ -27,4 +27,5 @@ return [ 'webpages' => 'Pages web', 'unique-listeners' => 'Auditeurs uniques', 'players' => 'Lecteurs', + 'listening-time' => 'Durée d’écoute', ]; diff --git a/app/Language/fr/Charts.php b/app/Language/fr/Charts.php index 2469f6fb..b878234d 100644 --- a/app/Language/fr/Charts.php +++ b/app/Language/fr/Charts.php @@ -39,4 +39,6 @@ return [ 'by_entry_page' => 'Fréquentation des pages web par page d’entrée (sur la dernière semaine)', 'podcast_bots' => 'Robots (bots)', + 'daily_listening_time' => 'Durée quotidienne d’écoute cumulée', + 'monthly_listening_time' => 'Durée mensuelle d’écoute cumulée', ]; diff --git a/app/Language/fr/PodcastNavigation.php b/app/Language/fr/PodcastNavigation.php index ea483837..9a2e09e9 100644 --- a/app/Language/fr/PodcastNavigation.php +++ b/app/Language/fr/PodcastNavigation.php @@ -25,4 +25,5 @@ return [ 'podcast-analytics-locations' => 'Localisations', 'podcast-analytics-unique-listeners' => 'Auditeurs uniques', 'podcast-analytics-players' => 'Lecteurs', + 'podcast-analytics-listening-time' => 'Durée d’écoute', ]; diff --git a/app/Models/AnalyticsPodcastByEpisodeModel.php b/app/Models/AnalyticsPodcastByEpisodeModel.php index fce15445..29506bda 100644 --- a/app/Models/AnalyticsPodcastByEpisodeModel.php +++ b/app/Models/AnalyticsPodcastByEpisodeModel.php @@ -142,4 +142,71 @@ class AnalyticsPodcastByEpisodeModel extends Model } return $found; } + + /** + * Gets listening-time data for a podcast + * + * @param int $podcastId + * + * @return array + */ + public function getDataTotalListeningTimeByDay(int $podcastId): array + { + if ( + !($found = cache( + "{$podcastId}_analytics_podcast_listening_time_by_day" + )) + ) { + $found = $this->select('date as labels') + ->selectSum('(enclosure_duration * hits)', 'values') + ->join('episodes', 'id = episode_id', 'inner') + ->where([ + $this->table . '.podcast_id' => $podcastId, + 'date >' => date('Y-m-d', strtotime('-60 days')), + ]) + ->groupBy('labels') + ->orderBy('labels', 'ASC') + ->findAll(); + + cache()->save( + "{$podcastId}_analytics_podcast_listening_time_by_day", + $found, + 600 + ); + } + return $found; + } + + /** + * Gets listening-time data for a podcast + * + * @param int $podcastId + * + * @return array + */ + public function getDataTotalListeningTimeByMonth(int $podcastId): array + { + if ( + !($found = cache( + "{$podcastId}_analytics_podcast_listening_time_by_month" + )) + ) { + $found = $this->select('DATE_FORMAT(`date`,"%Y-%m-01") as `labels`') + ->selectSum('(enclosure_duration * hits)', 'values') + ->join('episodes', 'id = episode_id', 'inner') + ->where([ + $this->table . '.podcast_id' => $podcastId, + ]) + ->groupBy('`labels`') + ->orderBy('`labels`', 'ASC') + ->findAll(); + + cache()->save( + "{$podcastId}_analytics_podcast_listening_time_by_month", + $found, + 600 + ); + } + return $found; + } } diff --git a/app/Models/AnalyticsPodcastByRegionModel.php b/app/Models/AnalyticsPodcastByRegionModel.php index 45edbc24..511a0769 100644 --- a/app/Models/AnalyticsPodcastByRegionModel.php +++ b/app/Models/AnalyticsPodcastByRegionModel.php @@ -38,13 +38,11 @@ class AnalyticsPodcastByRegionModel extends Model "{$podcastId}_analytics_podcast_by_region_{$locale}" )) ) { - $found = $this->select( - '`country_code`, `region_code`, `latitude`, `longitude`' - ) + $found = $this->select('`country_code`, `region_code`') ->selectSum('`hits`', '`value`') - ->groupBy( - '`country_code`, `region_code`, `latitude`, `longitude`' - ) + ->selectAvg('`latitude`') + ->selectAvg('`longitude`') + ->groupBy('`country_code`, `region_code`') ->where([ '`podcast_id`' => $podcastId, '`date` >' => date('Y-m-d', strtotime('-1 week')), diff --git a/app/Views/_assets/modules/Charts.ts b/app/Views/_assets/modules/Charts.ts index b8d11207..3266714e 100644 --- a/app/Views/_assets/modules/Charts.ts +++ b/app/Views/_assets/modules/Charts.ts @@ -62,6 +62,41 @@ const drawXYChart = (chartDivId: string, dataUrl: string | null): void => { chart.scrollbarX = new am4core.Scrollbar(); }; +const drawXYDurationChart = (chartDivId: string, dataUrl: string | null): void => { + // Create chart instance + const chart = am4core.create(chartDivId, am4charts.XYChart); + am4core.percent(100); + // Set theme + am4core.useTheme(am4themes_material); + // Create axes + const dateAxis = chart.xAxes.push(new am4charts.DateAxis()); + dateAxis.renderer.minGridDistance = 60; + const yAxis = chart.yAxes.push(new am4charts.DurationAxis()); + yAxis.baseUnit = "second"; + chart.durationFormatter.durationFormat = "hh'h,' mm'mn'"; + // Add data + chart.dataSource.url = dataUrl || ""; + chart.dataSource.parser.options.emptyAs = 0; + // Create series + const series = chart.series.push(new am4charts.LineSeries()); + series.dataFields.valueY = "values"; + series.dataFields.dateX = "labels"; + series.tooltipText = "{valueY.formatDuration()}"; + series.strokeWidth = 2; + // Make bullets grow on hover + const bullet = series.bullets.push(new am4charts.CircleBullet()); + bullet.circle.strokeWidth = 2; + bullet.circle.radius = 4; + bullet.circle.fill = am4core.color("#fff"); + const bullethover = bullet.states.create("hover"); + bullethover.properties.scale = 1.3; + series.tooltip.pointerOrientation = "vertical"; + chart.cursor = new am4charts.XYCursor(); + chart.cursor.snapToSeries = series; + chart.cursor.xAxis = dateAxis; + chart.scrollbarX = new am4core.Scrollbar(); +}; + const drawXYSeriesChart = ( chartDivId: string, dataUrl: string | null @@ -152,6 +187,9 @@ const DrawCharts = (): void => { case "xy-chart": drawXYChart(chartDiv.id, chartDiv.getAttribute("data-chart-url")); break; + case "xy-duration-chart": + drawXYDurationChart(chartDiv.id, chartDiv.getAttribute("data-chart-url")); + break; case "xy-series-chart": drawXYSeriesChart(chartDiv.id, chartDiv.getAttribute("data-chart-url")); break; diff --git a/app/Views/admin/podcast/_sidebar.php b/app/Views/admin/podcast/_sidebar.php index d62dd857..cf637465 100644 --- a/app/Views/admin/podcast/_sidebar.php +++ b/app/Views/admin/podcast/_sidebar.php @@ -13,6 +13,7 @@ $podcastNavigation = [ 'items' => [ 'podcast-analytics', 'podcast-analytics-unique-listeners', + 'podcast-analytics-listening-time', 'podcast-analytics-players', 'podcast-analytics-locations', 'podcast-analytics-webpages', diff --git a/app/Views/admin/podcast/analytics/listening-time.php b/app/Views/admin/podcast/analytics/listening-time.php new file mode 100644 index 00000000..f6cd05d0 --- /dev/null +++ b/app/Views/admin/podcast/analytics/listening-time.php @@ -0,0 +1,34 @@ +extend('admin/_layout') ?> + +section('title') ?> +title ?> +endSection() ?> + +section('pageTitle') ?> +title ?> +endSection() ?> + +section('content') ?> + +
+

+
+
+ +
+

+
+
+ + +endSection() ?>