From: Vincent Vanwaelscappel Date: Wed, 24 May 2023 17:09:21 +0000 (+0200) Subject: wip #5877 @0.25 X-Git-Url: http://git.cubedesigners.com/?a=commitdiff_plain;h=5e6000c73fd9964d404cedde879e176af96cb563;p=fluidbook-toolbox.git wip #5877 @0.25 --- diff --git a/.docker/images/php-dev/Dockerfile b/.docker/images/php-dev/Dockerfile index 9af96ec91..b2c74b1de 100644 --- a/.docker/images/php-dev/Dockerfile +++ b/.docker/images/php-dev/Dockerfile @@ -5,8 +5,6 @@ WORKDIR "/application" # Fixes some weird terminal issues such as broken clear / CTRL+L ENV TERM=linux -WORKDIR "/application" - # Ensure apt doesn't ask questions when installing stuff ENV DEBIAN_FRONTEND=noninteractive diff --git a/.env.dev b/.env.dev index 7ef4d0582..c95561247 100644 --- a/.env.dev +++ b/.env.dev @@ -5,7 +5,7 @@ APP_DEBUG=true DEBUGBAR_ENABLED=false APP_URL=https://dev.toolbox.fluidbook.com -HEADER_COLOR="#8C0025" +HEADER_COLOR="#df4759" LOG_CHANNEL=stack APP_LOG=daily diff --git a/app/Fluidbook/Stats.php b/app/Fluidbook/Stats.php index 75569700a..87635159e 100644 --- a/app/Fluidbook/Stats.php +++ b/app/Fluidbook/Stats.php @@ -5,7 +5,9 @@ namespace App\Fluidbook; use App\Models\FluidbookPublication; use Carbon\Carbon; use Carbon\CarbonInterface; +use Cubist\Matomo\MatomoUtils; use Cubist\Matomo\Reporting; +use NumberFormatter; class Stats extends Reporting { @@ -38,7 +40,6 @@ class Stats extends Reporting // ID >= 21210 (even numbers) = stats4.fluidbook.com // ID >= 21211 (odd numbers) = stats5.fluidbook.com $fluidbook_id = intval($id); - if ($fluidbook_id < 21210) { return 'stats3.fluidbook.com'; } elseif ($fluidbook_id % 2 === 0) { @@ -94,7 +95,7 @@ class Stats extends Reporting ], ]; - $dates = $date ? $this->parseDate($date) : false; + $dates = $date ? MatomoUtils::parseDate($date) : false; // Make sure that we receive a valid period from the URL $period_override = in_array($period_override, array_keys($period_map)) ? $period_override : null; @@ -467,34 +468,14 @@ class Stats extends Reporting } - private function parseDate($date) - { - // Match possible date strings: - // - YYYY - // - YYYY-MM - // - YYYY-MM-DD - // - YYYY-MM-DD,YYYY-MM-DD - // https://regex101.com/r/BLrqm0/1 - $regex = '/^(?(?2\d{3})-?(?0[1-9]|1[012])?-?(?0[1-9]|[12][0-9]|3[01])?),?(?2\d{3}-(?>0[1-9]|1[012])-(?>0[1-9]|[12][0-9]|3[01]))?/'; - - preg_match($regex, $date, $date_matches); - - extract($date_matches); // Just for easier access to match variables - // Bail out on nonsensical dates - if (isset($start_date) && isset($end_date) && ($start_date > $end_date)) { - return false; - } - - return $date_matches; - } // Since the report date can be in different formats (ie. YYYY, YYYY-MM or a full range) // and this format determines the report mode (range, month or year), we have to figure out // the mode and full starting and ending dates, as necessary public function getReportSettings($date, $fluidbook) { - $date_matches = $this->parseDate($date); + $date_matches = MatomoUtils::parseDate($date); // ... @@ -515,37 +496,7 @@ class Stats extends Reporting $dates['end_date'] = now()->isoFormat('YYYY-MM-DD'); } - // Extract array keys as variables for easier access: - // $start_date, $start_year, $start_month, $start_day, $end_date - extract($dates); - - // At this point, if the end_date still isn't set, it must either be a YYYY or YYYY-MM that was passed in. - // Therefore, we can also assume that $start_year will be set. All that's left to do is set appropriate - // start and end dates so that we can determine the best period based on the overall length of the range. - if (!isset($end_date)) { - if (isset($start_month) && !empty($start_month)) { // Month mode - $start_date = "{$start_year}-{$start_month}-01"; - $end_date = Carbon::parse($start_date)->endOfMonth()->isoFormat('YYYY-MM-DD'); - } else { // Year mode - $start_date = "{$start_year}-01-01"; - $end_date = Carbon::parse($start_date)->endOfYear()->isoFormat('YYYY-MM-DD'); - } - } - - // Count <= 60 days: period = day - // Count 61–180 days: period = week - // Count 180+ days: period = month - // Count 2+ years: period = year - $day_count = Carbon::parse($start_date)->diffInDays($end_date); - - if ($day_count <= 60) { - return 'day'; - } elseif ($day_count <= 180) { - return 'week'; - } elseif ($day_count <= 730) { - return 'month'; - } - return 'year'; + return MatomoUtils::getBestPeriod($dates); } } diff --git a/app/Http/Controllers/Admin/Operations/FluidbookPublication/StatsOperation.php b/app/Http/Controllers/Admin/Operations/FluidbookPublication/StatsOperation.php index 04b971966..6c1281f93 100644 --- a/app/Http/Controllers/Admin/Operations/FluidbookPublication/StatsOperation.php +++ b/app/Http/Controllers/Admin/Operations/FluidbookPublication/StatsOperation.php @@ -24,6 +24,7 @@ trait StatsOperation ->withoutMiddleware([CheckIfAdmin::class]) ->name('stats'); // Named route is used to generate URLs more consistently using route helper + // Since the stats can sometimes be slow to generate, the initial URL displays // a loader while the actual report is fetched via AJAX and injected into the page. Route::get($segment . '/stats-data/{fluidbook_id}_{hash}/{date?}/{period_override?}', $controller . '@statsSummary') @@ -31,11 +32,7 @@ trait StatsOperation ->name('stats-report'); // API testing tool (intended for superadmins only) - Route::get($segment . '/stats/API', $controller . '@statsAPI'); - - // Shortcuts for easier access to hashed URLs - only users with sufficient permissions will be redirected - Route::get($segment . '/stats/{fluidbook_id}', $controller . '@statsRedirect'); - Route::get($segment . '/{fluidbook_id}/stats', $controller . '@statsRedirect'); + Route::get($segment . '/stats/API/{id?}', $controller . '@statsAPI'); } protected function setupStatsDefaults() @@ -43,137 +40,6 @@ trait StatsOperation $this->crud->addButtonFromView('line', 'stats', 'fluidbook_publication.stats', 'end'); } - protected function redirectMatomo($fluidbook_id) - { - $url = 'https://' . Stats::getMatomoServer($fluidbook_id) . '/index.php?module=CoreHome&action=index&idSite=' . $fluidbook_id . '&period=day&date=yesterday'; - return redirect($url); - } - - protected function getMatomoTokens() - { - // Each stats server has a different instance of Matamo, so we need to provide different API tokens for each - // Normally this information would be stored in the .env but there's no good way to do that with an array, so - // it is simpler to keep it here. These are also stored in the shared Bitwarden entry for Matomo. - return [ - 'stats3.fluidbook.com' => '9df722a0bd30878ddc4d737352427502', - 'stats4.fluidbook.com' => '3ffdbe052ae625f065573df9fa9515df', - 'stats5.fluidbook.com' => '85e9cc307b6e5083249949e9472a80b8', - 'stats6.fluidbook.com' => '16f4c1d77cdc4792b807718388db96a0', - ]; - } - - protected function getMatomoServer($fluidbook_id) - { - // Get the appropriate server based on the Fluidbook ID - // Stats are split across different servers depending on the ID: - // ID < 21210 = stats3.fluidbook.com - // ID >= 21210 (even numbers) = stats4.fluidbook.com - // ID >= 21211 (odd numbers) = stats5.fluidbook.com - - $fluidbook_id = intval($fluidbook_id); - - if ($fluidbook_id < 21210) { - return 'stats3.fluidbook.com'; - } elseif ($fluidbook_id % 2 === 0) { - return 'stats4.fluidbook.com'; - } - return 'stats5.fluidbook.com'; - } - - protected function getMatomoToken($server): bool|string - { - $tokens = $this->getMatomoTokens(); - return $tokens[$server] ?? false; - } - - private function getReporting($fluidbook_id): Reporting - { - $server = $this->getMatomoServer($fluidbook_id); - $token = $this->getMatomoToken($server); - return new Reporting("https://{$server}/", $token, $fluidbook_id); - } - - private function parseDate($date) - { - // Match possible date strings: - // - YYYY - // - YYYY-MM - // - YYYY-MM-DD - // - YYYY-MM-DD,YYYY-MM-DD - // https://regex101.com/r/BLrqm0/1 - $regex = '/^(?(?2\d{3})-?(?0[1-9]|1[012])?-?(?0[1-9]|[12][0-9]|3[01])?),?(?2\d{3}-(?>0[1-9]|1[012])-(?>0[1-9]|[12][0-9]|3[01]))?/'; - - preg_match($regex, $date, $date_matches); - - extract($date_matches); // Just for easier access to match variables - - // Bail out on nonsensical dates - if (isset($start_date) && isset($end_date) && ($start_date > $end_date)) { - return false; - } - - return $date_matches; - } - - // Since the report date can be in different formats (ie. YYYY, YYYY-MM or a full range) - // and this format determines the report mode (range, month or year), we have to figure out - // the mode and full starting and ending dates, as necessary - public function getReportSettings($date, $fluidbook) - { - $date_matches = $this->parseDate($date); - - // ... - - // TODO: centralise all mode + start / end date logic here... then in the main report function, we can use the returned mode in a switch() statement to clean things up a lot. - - - } - - // Figure out the best period to use for the stats according to the date range - public function determinePeriod($dates, $fluidbook) - { - - // TODO: refactor this into getReportSettings() so that this function can simply take the determined start and end dates, calculate the number of days and give an answer based on that. No extra logic to try to figure out which mode it is... - - // If no valid date range is given, use the Fluidbook's lifetime - if (empty($dates) || !is_array($dates)) { - $dates['start_date'] = $fluidbook->created_at->isoFormat('YYYY-MM-DD'); - $dates['end_date'] = now()->isoFormat('YYYY-MM-DD'); - } - - // Extract array keys as variables for easier access: - // $start_date, $start_year, $start_month, $start_day, $end_date - extract($dates); - - // At this point, if the end_date still isn't set, it must either be a YYYY or YYYY-MM that was passed in. - // Therefore, we can also assume that $start_year will be set. All that's left to do is set appropriate - // start and end dates so that we can determine the best period based on the overall length of the range. - if (!isset($end_date)) { - if (isset($start_month) && !empty($start_month)) { // Month mode - $start_date = "{$start_year}-{$start_month}-01"; - $end_date = Carbon::parse($start_date)->endOfMonth()->isoFormat('YYYY-MM-DD'); - } else { // Year mode - $start_date = "{$start_year}-01-01"; - $end_date = Carbon::parse($start_date)->endOfYear()->isoFormat('YYYY-MM-DD'); - } - } - - // Count <= 60 days: period = day - // Count 61–180 days: period = week - // Count 180+ days: period = month - // Count 2+ years: period = year - $day_count = Carbon::parse($start_date)->diffInDays($end_date); - - if ($day_count <= 60) { - return 'day'; - } elseif ($day_count <= 180) { - return 'week'; - } elseif ($day_count <= 730) { - return 'month'; - } - - return 'year'; - } protected function statsLoader($fluidbook_id, $hash, $date = null, $period_override = null) { @@ -183,12 +49,11 @@ trait StatsOperation protected function statsSummary($fluidbook_id, $hash, $date = null, $period_override = null) { + $fluidbook = FluidbookPublication::withoutGlobalScopes()->where('id', $fluidbook_id)->where('hash', $hash)->firstOrFail(); $locale = app()->getLocale(); $base_URL = route('stats', compact('fluidbook_id', 'hash')); // Used by date range picker to update report URL - // If the Fluidbook ID + hash combination isn't found, a 404 response will be returned - $fluidbook = FluidbookPublication::where('id', $fluidbook_id)->where('hash', $hash)->firstOrFail(); $fluidbook_settings = json_decode($fluidbook->settings); // The page count setting is sometimes missing for older Fluidbooks @@ -287,10 +152,8 @@ trait StatsOperation $formatted_date_range = $this->formatDateRange($start_date, $end_date); //=== Set up Matomo Reporting API - $report = $this->getReporting($fluidbook_id); - $report->setDate($date_range); - $report->setPeriod($period); - $report->setLanguage($locale); // Matomo will return translated data when relevant (eg. country names) + $report=new Stats($fluidbook->id); + // * Note about visits and page views: // Although Matomo records visits and page views (API: VisitsSummary.get & Actions.get), we don't use these @@ -452,7 +315,6 @@ trait StatsOperation $expanded_stats = $fluidbook_id >= $new_stats_cutoff ? 1 : 0; // Only fetch extra data when it will be used $pagesByPeriod = collect($report->getPageUrls(['expanded' => $expanded_stats])) ->map(function ($item, $date) use ($period, $fluidbook_id, $hash, $eventsByPeriod, $new_stats_cutoff) { - if (empty($item)) { return $item; // Some periods might have no data } @@ -633,26 +495,14 @@ trait StatsOperation } - // Redirect users with sufficient permissions to the full hashed URL - // This was added as a convenience when quickly switching between stats views for Fluidbooks - // If the current user has sufficient permissions, they will be redirected to the hashed URL. - // If not, the FluidbookPublication lookup will fail and further execution will halt. - protected function statsRedirect($fluidbook_id) + protected function redirectMatomo($fluidbook_id) { - $fluidbook = FluidbookPublication::where('id', $fluidbook_id)->firstOrFail(); - - $settings = json_decode($fluidbook->settings); - $hash = $fluidbook->hash; - - if (!$fluidbook->stats) { - return "Statistics are disabled for this Fluidbook #{$fluidbook_id} ({$settings->title})"; - } - - return redirect()->route('stats', compact('fluidbook_id', 'hash')); + $url = 'https://' . Stats::getMatomoServer($fluidbook_id) . '/index.php?module=CoreHome&action=index&idSite=' . $fluidbook_id . '&period=day&date=yesterday'; + return redirect($url); } // https://toolbox.fluidbook.com/fluidbook-publication/stats/API - protected function statsAPI() + protected function statsAPI($id = null) { if (!can('superadmin')) { @@ -661,7 +511,7 @@ trait StatsOperation } $matomo_tokens = json_encode($this->getMatomoTokens()); - return view('fluidbook_stats.API', compact('matomo_tokens')); + return view('fluidbook_stats.API', compact('matomo_tokens', 'id')); } // Format a date range for display in the interface diff --git a/composer.lock b/composer.lock index 81bfdca3e..f13ab670f 100644 --- a/composer.lock +++ b/composer.lock @@ -1364,13 +1364,13 @@ "source": { "type": "git", "url": "git://git.cubedesigners.com/cubedesigners_userdatabase.git", - "reference": "1310f7a85c4741dfc22a6212bfb788ee0fa99910" + "reference": "5217abc590c7a7d5a47dacc0af15a0eeae5790ee" }, "dist": { "type": "tar", - "url": "https://composer.cubedesigners.com/dist/cubedesigners/userdatabase/cubedesigners-userdatabase-dev-backpack5-f9c8c7.tar", - "reference": "1310f7a85c4741dfc22a6212bfb788ee0fa99910", - "shasum": "3e0550a4c23c7e2d4257a13d4fd8b2edafc22f9a" + "url": "https://composer.cubedesigners.com/dist/cubedesigners/userdatabase/cubedesigners-userdatabase-dev-backpack5-5641b0.tar", + "reference": "5217abc590c7a7d5a47dacc0af15a0eeae5790ee", + "shasum": "7f4e97289a746086a2831a5d663395396746155c" }, "require": { "cubist/cms-back": "dev-backpack5", @@ -1402,7 +1402,7 @@ } ], "description": "Cubedesigners common users database", - "time": "2023-04-30T12:44:03+00:00" + "time": "2023-05-24T08:34:53+00:00" }, { "name": "cubist/azuretts", @@ -1452,13 +1452,13 @@ "source": { "type": "git", "url": "git://git.cubedesigners.com/cubist_cms-back.git", - "reference": "1adf6232baf5b6567b72a2a537777bc41dc7674a" + "reference": "662c4a98089d04b66a520165f631cf345008a706" }, "dist": { "type": "tar", - "url": "https://composer.cubedesigners.com/dist/cubist/cms-back/cubist-cms-back-dev-backpack5-f19d9a.tar", - "reference": "1adf6232baf5b6567b72a2a537777bc41dc7674a", - "shasum": "c1cdaf19cd977610cc9ed971b0f852c90bb1b0fb" + "url": "https://composer.cubedesigners.com/dist/cubist/cms-back/cubist-cms-back-dev-backpack5-8e16bf.tar", + "reference": "662c4a98089d04b66a520165f631cf345008a706", + "shasum": "3f6a221290cb172e1568aac8b6213ede11dae019" }, "require": { "backpack/backupmanager": "^v3.0.9", @@ -1537,7 +1537,7 @@ } ], "description": "Cubist Backpack extension", - "time": "2023-05-10T17:43:22+00:00" + "time": "2023-05-24T16:11:13+00:00" }, { "name": "cubist/cms-front", @@ -1795,17 +1795,18 @@ "source": { "type": "git", "url": "git://git.cubedesigners.com/cubist_matomo.git", - "reference": "16b89dc9bd52116a343a0ef120311e06c0f3112f" + "reference": "f9543e92e7fe8a59a21211ef807936b48222bbc1" }, "dist": { "type": "tar", - "url": "https://composer.cubedesigners.com/dist/cubist/matomo/cubist-matomo-dev-master-484885.tar", - "reference": "16b89dc9bd52116a343a0ef120311e06c0f3112f", - "shasum": "c8855c22c5d8a21bbdcdbcfd0c12bcbfea86544b" + "url": "https://composer.cubedesigners.com/dist/cubist/matomo/cubist-matomo-dev-master-868e71.tar", + "reference": "f9543e92e7fe8a59a21211ef807936b48222bbc1", + "shasum": "ac520f2a8d3ab9dca227d3d2a540cc4edde30adc" }, "require": { "ext-json": "*", - "guzzlehttp/guzzle": "^7.4.1", + "guzzlehttp/guzzle": "^7.7", + "illuminate/cache": ">=5", "php": ">=7.2" }, "default-branch": true, @@ -1825,7 +1826,7 @@ } ], "description": "Matomo API", - "time": "2022-09-21T16:20:01+00:00" + "time": "2023-05-24T17:00:51+00:00" }, { "name": "cubist/net", @@ -3557,13 +3558,13 @@ "source": { "type": "git", "url": "git://git.cubedesigners.com/fluidbook_tools.git", - "reference": "67ba5e732fc3caa72bce6f0315595c4f05a6790c" + "reference": "c858c61c414027bf3d62ffc642f620623f492417" }, "dist": { "type": "tar", - "url": "https://composer.cubedesigners.com/dist/fluidbook/tools/fluidbook-tools-dev-master-94943c.tar", - "reference": "67ba5e732fc3caa72bce6f0315595c4f05a6790c", - "shasum": "6a4aceb11c237d34cbc990b489c20bfb8835b403" + "url": "https://composer.cubedesigners.com/dist/fluidbook/tools/fluidbook-tools-dev-master-f43473.tar", + "reference": "c858c61c414027bf3d62ffc642f620623f492417", + "shasum": "ad755be6d01cc2e45a6dfaca90a772f614b66611" }, "require": { "barryvdh/laravel-debugbar": "*", @@ -3597,7 +3598,7 @@ } ], "description": "Fluidbook Tools", - "time": "2023-05-22T13:30:55+00:00" + "time": "2023-05-23T17:58:08+00:00" }, { "name": "fruitcake/php-cors", @@ -4837,16 +4838,16 @@ }, { "name": "laravel/framework", - "version": "v10.11.0", + "version": "v10.12.0", "source": { "type": "git", "url": "https://github.com/laravel/framework.git", - "reference": "21a5b6d9b669f32c10cc8ba776511b5f62599fea" + "reference": "9e6dcff23ab1d4b522bef56074c31625cf077576" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/laravel/framework/zipball/21a5b6d9b669f32c10cc8ba776511b5f62599fea", - "reference": "21a5b6d9b669f32c10cc8ba776511b5f62599fea", + "url": "https://api.github.com/repos/laravel/framework/zipball/9e6dcff23ab1d4b522bef56074c31625cf077576", + "reference": "9e6dcff23ab1d4b522bef56074c31625cf077576", "shasum": "" }, "require": { @@ -5033,7 +5034,7 @@ "issues": "https://github.com/laravel/framework/issues", "source": "https://github.com/laravel/framework" }, - "time": "2023-05-16T13:59:23+00:00" + "time": "2023-05-23T18:04:16+00:00" }, { "name": "laravel/serializable-closure", @@ -15286,16 +15287,16 @@ }, { "name": "psy/psysh", - "version": "v0.11.17", + "version": "v0.11.18", "source": { "type": "git", "url": "https://github.com/bobthecow/psysh.git", - "reference": "3dc5d4018dabd80bceb8fe1e3191ba8460569f0a" + "reference": "4f00ee9e236fa6a48f4560d1300b9c961a70a7ec" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/bobthecow/psysh/zipball/3dc5d4018dabd80bceb8fe1e3191ba8460569f0a", - "reference": "3dc5d4018dabd80bceb8fe1e3191ba8460569f0a", + "url": "https://api.github.com/repos/bobthecow/psysh/zipball/4f00ee9e236fa6a48f4560d1300b9c961a70a7ec", + "reference": "4f00ee9e236fa6a48f4560d1300b9c961a70a7ec", "shasum": "" }, "require": { @@ -15356,9 +15357,9 @@ ], "support": { "issues": "https://github.com/bobthecow/psysh/issues", - "source": "https://github.com/bobthecow/psysh/tree/v0.11.17" + "source": "https://github.com/bobthecow/psysh/tree/v0.11.18" }, - "time": "2023-05-05T20:02:42+00:00" + "time": "2023-05-23T02:31:11+00:00" }, { "name": "sebastian/cli-parser", diff --git a/resources/views/fluidbook_stats/API.blade.php b/resources/views/fluidbook_stats/API.blade.php index 486200b52..68c3a3109 100644 --- a/resources/views/fluidbook_stats/API.blade.php +++ b/resources/views/fluidbook_stats/API.blade.php @@ -91,8 +91,10 @@ - - + + @@ -101,14 +103,14 @@