diff --git a/app/Console/Commands/UpdateArticleViewCounts.php b/app/Console/Commands/UpdateArticleViewCounts.php
index c75aa59a2..c7771f9f4 100644
--- a/app/Console/Commands/UpdateArticleViewCounts.php
+++ b/app/Console/Commands/UpdateArticleViewCounts.php
@@ -43,6 +43,26 @@ public function handle()
protected function getViewCountFor(Article $article): ?int
{
+ $viewCount = $this->getViewCountForUrl(route('articles.show', $article->slug));
+ $canonicalViewCount = ($url = $article->originalUrl()) ? $this->getViewCountForUrl($url) : 0;
+
+ return ($total = $viewCount + $canonicalViewCount) > 0 ? $total : null;
+ }
+
+ protected function getViewCountForUrl(string $url): int
+ {
+ if (! $url = parse_url($url)) {
+ return 0;
+ }
+
+ $scheme = $url['scheme'] ?? null;
+ $host = $url['host'] ?? null;
+ $path = $url['path'] ?? null;
+
+ if (! $scheme || ! $host || ! $path) {
+ return 0;
+ }
+
$response = Http::withToken($this->token)
->get('https://api.usefathom.com/v1/aggregations', [
'date_from' => '2021-03-01 00:00:00', // Fathom data aggregations not accurate prior to this date.
@@ -54,13 +74,18 @@ protected function getViewCountFor(Article $article): ?int
[
'property' => 'pathname',
'operator' => 'is',
- 'value' => "/articles/{$article->slug()}",
+ 'value' => $path,
+ ],
+ [
+ 'property' => 'hostname',
+ 'operator' => 'is',
+ 'value' => "{$scheme}://{$host}",
],
]),
]);
if ($response->failed()) {
- return null;
+ return 0;
}
return $response->json('0.pageviews');
diff --git a/resources/views/layouts/_fathom.blade.php b/resources/views/layouts/_fathom.blade.php
index ab4715acd..09e7850ca 100644
--- a/resources/views/layouts/_fathom.blade.php
+++ b/resources/views/layouts/_fathom.blade.php
@@ -1,5 +1,5 @@
@production
-
+
-@endproduction
+@endproduction
\ No newline at end of file
diff --git a/tests/Feature/CanonicalUrlTest.php b/tests/Feature/CanonicalUrlTest.php
index 9f2bc011e..bc9dd0f10 100644
--- a/tests/Feature/CanonicalUrlTest.php
+++ b/tests/Feature/CanonicalUrlTest.php
@@ -1,13 +1,22 @@
'production');
+}
+
+afterEach(fn () => App::detectEnvironment(fn () => 'testing'));
+
test('pages without a canonical url explicitly set fall back to the current url', function () {
$this->get('/register')
->see('');
@@ -51,3 +60,19 @@
$this->get('?utm_source=twitter&utm_medium=social&utm_term=abc123')
->see('');
});
+
+test('canonical tracking is turned off when using external url', function () {
+ Article::factory()->create(['slug' => 'my-first-article', 'submitted_at' => now(), 'approved_at' => now(), 'original_url' => 'https://example.com/external-path']);
+
+ $this->get('/articles/my-first-article')
+ ->see('data-canonical="false"');
+})->inProduction();
+
+test('canonical tracking is turned on when using external url', function () {
+ App::detectEnvironment(fn () => 'production');
+
+ Article::factory()->create(['slug' => 'my-first-article', 'submitted_at' => now(), 'approved_at' => now()]);
+
+ $this->get('/articles/my-first-article')
+ ->dontSee('data-canonical="false"');
+})->inProduction();
diff --git a/tests/Integration/Commands/UpdateArticleViewCountsTest.php b/tests/Integration/Commands/UpdateArticleViewCountsTest.php
index ad6c0e2c1..79c79e2be 100644
--- a/tests/Integration/Commands/UpdateArticleViewCountsTest.php
+++ b/tests/Integration/Commands/UpdateArticleViewCountsTest.php
@@ -28,6 +28,46 @@
expect($article->fresh()->view_count)->toBe(1234);
});
+test('article view counts can be merged with original url', function () {
+ Http::fake(function () {
+ return Http::response([[
+ 'pageviews' => 1234,
+ ]]);
+ });
+
+ $article = Article::factory()->create([
+ 'title' => 'My First Article',
+ 'slug' => 'my-first-article',
+ 'original_url' => 'https://example.com/my-first-article',
+ 'submitted_at' => now(),
+ 'approved_at' => now(),
+ ]);
+
+ (new UpdateArticleViewCounts)->handle();
+
+ expect($article->fresh()->view_count)->toBe(2468);
+});
+
+test('article view counts are not merged when url is invalid', function () {
+ Http::fake(function () {
+ return Http::response([[
+ 'pageviews' => 1234,
+ ]]);
+ });
+
+ $article = Article::factory()->create([
+ 'title' => 'My First Article',
+ 'slug' => 'my-first-article',
+ 'original_url' => 'erhwerhwerh',
+ 'submitted_at' => now(),
+ 'approved_at' => now(),
+ ]);
+
+ (new UpdateArticleViewCounts)->handle();
+
+ expect($article->fresh()->view_count)->toBe(1234);
+});
+
test('article view counts are not updated if API call fails', function () {
Http::fake(function () {
return Http::response('Uh oh', 500);