diff --git a/src/Reporting/Chunk.php b/src/Reporting/Chunk.php index 4d795858..6424d34e 100644 --- a/src/Reporting/Chunk.php +++ b/src/Reporting/Chunk.php @@ -86,6 +86,8 @@ protected function createPage($content) ->withCurrent($content) ->get(); + $data['site'] = $content->locale(); + return new Page($content->id(), $data, $this->report); } } diff --git a/src/Reporting/Page.php b/src/Reporting/Page.php index 6b40b7de..cc37c6be 100644 --- a/src/Reporting/Page.php +++ b/src/Reporting/Page.php @@ -127,6 +127,11 @@ public function url() return $this->get('canonical_url'); } + public function site() + { + return $this->get('site'); + } + public function id() { return $this->id; diff --git a/src/Reporting/Rules/UniqueMetaDescription.php b/src/Reporting/Rules/UniqueMetaDescription.php index fa058d40..93bb1a18 100644 --- a/src/Reporting/Rules/UniqueMetaDescription.php +++ b/src/Reporting/Rules/UniqueMetaDescription.php @@ -57,10 +57,14 @@ public function processPage() protected function groupAllPagesByDescription() { - return Blink::once('seo-pro-report-'.$this->report->id().'-page-descriptions-grouped', function () { - return $this->page->report()->pages()->mapToGroups(function ($page) { - return [$page->get('description') => $page->id()]; - }); + $site = $this->page->site(); + + return Blink::once('seo-pro-report-'.$this->report->id().'-page-descriptions-grouped-'.$site, function () use ($site) { + return $this->page->report()->pages() + ->filter(fn ($page) => $page->site() === $site) + ->mapToGroups(function ($page) { + return [$page->get('description') => $page->id()]; + }); }); } diff --git a/src/Reporting/Rules/UniqueTitleTag.php b/src/Reporting/Rules/UniqueTitleTag.php index 0edfe9af..e9bdb3c7 100644 --- a/src/Reporting/Rules/UniqueTitleTag.php +++ b/src/Reporting/Rules/UniqueTitleTag.php @@ -57,10 +57,14 @@ public function processPage() protected function groupAllPagesByTitle() { - return Blink::once('seo-pro-report-'.$this->report->id().'-page-titles-grouped', function () { - return $this->page->report()->pages()->mapToGroups(function ($page) { - return [$page->get('title') => $page->id()]; - }); + $site = $this->page->site(); + + return Blink::once('seo-pro-report-'.$this->report->id().'-page-titles-grouped-'.$site, function () use ($site) { + return $this->page->report()->pages() + ->filter(fn ($page) => $page->site() === $site) + ->mapToGroups(function ($page) { + return [$page->get('title') => $page->id()]; + }); }); } diff --git a/tests/Localized/ReportTest.php b/tests/Localized/ReportTest.php new file mode 100644 index 00000000..97ca1a05 --- /dev/null +++ b/tests/Localized/ReportTest.php @@ -0,0 +1,87 @@ +filter(fn ($entry) => $entry->hasOrigin())->each->delete(); + Entry::all()->each->delete(); + Term::all()->each->delete(); + + if ($this->files->exists($path = $this->reportsPath())) { + $this->files->deleteDirectory($path); + } + } + + /** + * @see https://github.com/statamic/seo-pro/issues/213 + */ + #[Test] + public function it_does_not_flag_duplicate_titles_and_descriptions_across_sites() + { + $this->makeArticle('default', 'one', 'Shared Title', 'Shared Description'); + $this->makeArticle('french', 'two', 'Shared Title', 'Shared Description'); + + $this->assertEqualsIgnoringLineEndings(0, $this->getReportResult('UniqueTitleTag')); + $this->assertEqualsIgnoringLineEndings(0, $this->getReportResult('UniqueMetaDescription')); + } + + /** + * @see https://github.com/statamic/seo-pro/issues/213 + */ + #[Test] + public function it_still_flags_duplicate_titles_and_descriptions_within_the_same_site() + { + $this->makeArticle('default', 'one', 'Shared Title', 'Shared Description'); + $this->makeArticle('default', 'two', 'Shared Title', 'Shared Description'); + $this->makeArticle('french', 'three', 'Shared Title', 'Shared Description'); + + $this->assertEqualsIgnoringLineEndings(2, $this->getReportResult('UniqueTitleTag')); + $this->assertEqualsIgnoringLineEndings(2, $this->getReportResult('UniqueMetaDescription')); + } + + private function makeArticle($site, $slug, $title, $description) + { + Entry::make() + ->collection('articles') + ->blueprint('articles') + ->locale($site) + ->slug($slug) + ->date('2024-01-01') + ->set('title', $title) + ->set('seo', ['title' => $title, 'description' => $description]) + ->save(); + + return $this; + } + + private function reportsPath($path = null) + { + if ($path) { + $path = Str::ensureLeft($path, '/'); + } + + return storage_path('statamic/seopro/reports').$path; + } + + private function getReportResult($key) + { + Carbon::setTestNow(now()); + + Report::create()->save()->generate(); + + return YAML::file($this->reportsPath('1/report.yaml'))->parse()['results'][$key]; + } +} diff --git a/tests/ReportTest.php b/tests/ReportTest.php index 76a5ee8a..20d49106 100644 --- a/tests/ReportTest.php +++ b/tests/ReportTest.php @@ -466,7 +466,7 @@ public function it_properly_reports_on_description_length() ], $this->getReportResult('IdealMetaDescriptionLength')); } - public function reportsPath($path = null) + private function reportsPath($path = null) { if ($path) { $path = Str::ensureLeft($path, '/'); @@ -475,7 +475,7 @@ public function reportsPath($path = null) return storage_path('statamic/seopro/reports').$path; } - protected function generateEntries($count) + private function generateEntries($count) { collect(range(1, $count))->each(function ($i) { Entry::make() @@ -489,7 +489,7 @@ protected function generateEntries($count) return $this; } - protected function generateTerms($count) + private function generateTerms($count) { collect(range(1, $count))->each(function ($i) { Term::make() @@ -502,7 +502,7 @@ protected function generateTerms($count) return $this; } - protected function getReportResult($key) + private function getReportResult($key) { Carbon::setTestNow($now = now()); @@ -510,18 +510,6 @@ protected function getReportResult($key) return YAML::file($this->reportsPath('1/report.yaml'))->parse()['results'][$key]; } - - /** - * Normalize line endings before performing assertion in windows. - */ - public static function assertEqualsIgnoringLineEndings($needle, $haystack, $message = ''): void - { - parent::assertEquals( - is_string($needle) ? static::normalizeMultilineString($needle) : $needle, - is_string($haystack) ? static::normalizeMultilineString($haystack) : $haystack, - $message - ); - } } class TestChunk extends Chunk diff --git a/tests/TestCase.php b/tests/TestCase.php index 6b7f7ab0..d8f11b38 100644 --- a/tests/TestCase.php +++ b/tests/TestCase.php @@ -138,6 +138,18 @@ public static function assertStringNotContainsStringIgnoringLineEndings($needle, ); } + /** + * Normalize line endings before performing assertion in windows. + */ + public static function assertEqualsIgnoringLineEndings(mixed $expected, mixed $actual, string $message = ''): void + { + parent::assertEquals( + is_string($expected) ? static::normalizeMultilineString($expected) : $expected, + is_string($actual) ? static::normalizeMultilineString($actual) : $actual, + $message + ); + } + protected function assertArrayHasKeys(array $keys, array|\ArrayAccess $array): void { foreach ($keys as $key) {