From faebc2e0e2897a8f5f0a609abb6bd6b165bde3c7 Mon Sep 17 00:00:00 2001 From: Christoph Daum Date: Sun, 31 May 2026 14:46:00 +0200 Subject: [PATCH] feat: emit hreflang x-default with multiple translations get_alternate_links() only returned an x-default alternate when a single translation existed; with two or more it was dropped. x-default is the recommended fallback for multilingual pages, so emit it in the multi-alternate case too. The x-default link runs through the existing msls_output_get_alternate_links_default filter, so it stays customisable and can be suppressed by returning an empty string. Single-translation behaviour is unchanged. --- includes/MslsOutput.php | 5 ++++ tests/phpunit/TestMslsOutput.php | 51 ++++++++++++++++++++++++++++++++ 2 files changed, 56 insertions(+) diff --git a/includes/MslsOutput.php b/includes/MslsOutput.php index 7d3061e38..0034c79d6 100644 --- a/includes/MslsOutput.php +++ b/includes/MslsOutput.php @@ -138,6 +138,11 @@ public function get_alternate_links() { return apply_filters( self::MSLS_ALTERNATE_LINKS_DEFAULT_HOOK, $default ); } + $default = apply_filters( self::MSLS_ALTERNATE_LINKS_DEFAULT_HOOK, $default ); + if ( '' !== $default ) { + array_unshift( $arr, $default ); + } + $arr = (array) apply_filters( self::MSLS_ALTERNATE_LINKS_ARR_HOOK, $arr ); return implode( PHP_EOL, $arr ); diff --git a/tests/phpunit/TestMslsOutput.php b/tests/phpunit/TestMslsOutput.php index 280f5f7dc..8d5d1036c 100644 --- a/tests/phpunit/TestMslsOutput.php +++ b/tests/phpunit/TestMslsOutput.php @@ -65,6 +65,57 @@ public function test_get_alternate_links_two_url(): void { Functions\expect( 'get_queried_object_id' )->once()->andReturn( 42 ); Functions\expect( 'get_option' )->once()->andReturn( array() ); + Filters\expectApplied( 'msls_output_get_alternate_links_default' )->once(); + Filters\expectApplied( 'msls_output_get_alternate_links_arr' )->once(); + + $expected = + '' . PHP_EOL . + '' . PHP_EOL . + ''; + + $test = $this->MslsOutputFactory(); + + $this->assertEquals( $expected, $test->get_alternate_links() ); + } + + public function test_get_alternate_links_x_default_suppressed(): void { + $blogs = array(); + + $a = \Mockery::mock( MslsBlog::class ); + $a->shouldReceive( 'get_alpha2' )->andReturn( 'de' ); + $a->shouldReceive( 'get_language' )->andReturn( 'de_DE' ); + $a->shouldReceive( 'get_url' )->andReturn( 'https://example.de/' ); + $a->shouldReceive( 'get_description' )->andReturn( 'Deutsch' ); + + $blogs[] = $a; + + $b = \Mockery::mock( MslsBlog::class ); + $b->shouldReceive( 'get_alpha2' )->andReturn( 'it' ); + $b->shouldReceive( 'get_language' )->andReturn( 'it_IT' ); + $b->shouldReceive( 'get_url' )->andReturn( 'https://example.it/' ); + $b->shouldReceive( 'get_description' )->andReturn( 'Italiano' ); + + $blogs[] = $b; + + $collection = \Mockery::mock( MslsBlogCollection::class ); + $collection->shouldReceive( 'get_objects' )->andReturn( $blogs ); + + Functions\expect( 'msls_blog_collection' )->once()->andReturn( $collection ); + Functions\expect( 'is_admin' )->once()->andReturn( false ); + Functions\expect( 'is_front_page' )->once()->andReturn( false ); + Functions\expect( 'is_search' )->once()->andReturn( false ); + Functions\expect( 'is_404' )->once()->andReturn( false ); + Functions\expect( 'is_category' )->once()->andReturn( false ); + Functions\expect( 'is_tag' )->once()->andReturn( false ); + Functions\expect( 'is_tax' )->once()->andReturn( false ); + Functions\expect( 'is_date' )->once()->andReturn( false ); + Functions\expect( 'is_author' )->once()->andReturn( false ); + Functions\expect( 'is_post_type_archive' )->once()->andReturn( false ); + Functions\expect( 'get_queried_object_id' )->once()->andReturn( 42 ); + Functions\expect( 'get_option' )->once()->andReturn( array() ); + + // Returning an empty string from the filter opts out of x-default. + Filters\expectApplied( 'msls_output_get_alternate_links_default' )->once()->andReturn( '' ); Filters\expectApplied( 'msls_output_get_alternate_links_arr' )->once(); $expected =