From 09abd75df7eccb072bcd1481e822c450d595a6f8 Mon Sep 17 00:00:00 2001 From: Kaspars Dambis Date: Sat, 16 May 2026 10:53:33 +0300 Subject: [PATCH 1/6] Ensure the expected return type Signed-off-by: Kaspars Dambis --- inc/default-repo/namespace.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/inc/default-repo/namespace.php b/inc/default-repo/namespace.php index 7cf89b7e..49b3e420 100644 --- a/inc/default-repo/namespace.php +++ b/inc/default-repo/namespace.php @@ -26,7 +26,7 @@ function bootstrap() { */ function get_default_repo_domain() : string { if ( defined( 'FAIR_DEFAULT_REPO_DOMAIN' ) ) { - return FAIR_DEFAULT_REPO_DOMAIN; + return (string) \FAIR_DEFAULT_REPO_DOMAIN; } return 'api.aspirecloud.net'; From df45edd9b22cb4643a7e351dc9c6ae691b3add42 Mon Sep 17 00:00:00 2001 From: Kaspars Dambis Date: Sat, 16 May 2026 11:33:28 +0300 Subject: [PATCH 2/6] =?UTF-8?q?Account=20for=20package=20metadata=20that?= =?UTF-8?q?=20doesn=E2=80=99t=20include=20filename?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This sample metadata doesn’t have filename returned, for reference https://github.com/fairpm/fair-plugin/releases/latest/download/fair-metadata.json Signed-off-by: Kaspars Dambis --- inc/packages/namespace.php | 31 ++-- .../tests/Packages/GetHashedFilenameTest.php | 144 ++++++++++++++++++ 2 files changed, 164 insertions(+), 11 deletions(-) create mode 100644 tests/phpunit/tests/Packages/GetHashedFilenameTest.php diff --git a/inc/packages/namespace.php b/inc/packages/namespace.php index 28ee8886..baebcfa2 100644 --- a/inc/packages/namespace.php +++ b/inc/packages/namespace.php @@ -687,21 +687,30 @@ function get_banners( $banners ) : array { * @return string */ function get_hashed_filename( $metadata ) : string { - $filename = $metadata->filename; - $type = str_replace( 'wp-', '', $metadata->type ); $did_hash = '-' . get_did_hash( $metadata->id ); - list( $slug, $file ) = explode( '/', $filename, 2 ); - if ( 'plugin' === $type ) { - if ( ! str_contains( $slug, $did_hash ) ) { - $slug .= $did_hash; - } - $filename = $slug . '/' . $file; - } else { - $filename = $slug . $did_hash; + // Use the slug from the filename, if present. + list( $slug, $file ) = array_pad( explode( '/', $metadata->filename ?? '', 2 ), 2, '' ); + if ( '' === $slug ) { + $slug = $metadata->slug ?? ''; + } + + // Default to slug matching the plugin filename, if not specified. + if ( '' === $file ) { + $file = $slug . '.php'; + } + + // Append DID hash to slug if not already present. + if ( ! str_contains( $slug, $did_hash ) ) { + $slug .= $did_hash; + } + + // WP plugins must include the plugin filename. + if ( 'wp-plugin' === $metadata->type ?? '' ) { + return $slug . '/' . $file; } - return $filename; + return $slug; } /** diff --git a/tests/phpunit/tests/Packages/GetHashedFilenameTest.php b/tests/phpunit/tests/Packages/GetHashedFilenameTest.php new file mode 100644 index 00000000..808b54a3 --- /dev/null +++ b/tests/phpunit/tests/Packages/GetHashedFilenameTest.php @@ -0,0 +1,144 @@ +create_metadata_document( + [ + 'filename' => 'example/example.php', + 'slug' => 'example', + 'type' => 'wp-plugin', + ] + ); + + $this->assertSame( 'example-' . get_did_hash( $metadata->id ) . '/example.php', get_hashed_filename( $metadata ) ); + } + + /** + * Test that missing plugin filenames fall back to the slug. + */ + public function test_should_fall_back_to_slug_when_plugin_filename_missing() { + $metadata = $this->create_metadata_document( + [ + 'filename' => null, + 'slug' => 'example', + 'type' => 'wp-plugin', + ] + ); + + $this->assertSame( 'example-' . get_did_hash( $metadata->id ) . '/example.php', get_hashed_filename( $metadata ) ); + } + + /** + * Test that malformed plugin filenames still produce a valid hashed path. + */ + public function test_should_recover_when_plugin_filename_has_no_main_file() { + $metadata = $this->create_metadata_document( + [ + 'filename' => 'example', + 'slug' => 'example', + 'type' => 'wp-plugin', + ] + ); + + $this->assertSame( 'example-' . get_did_hash( $metadata->id ) . '/example.php', get_hashed_filename( $metadata ) ); + } + + /** + * Test that theme filenames append the DID hash to the slug. + */ + public function test_should_hash_theme_slug() { + $metadata = $this->create_metadata_document( + [ + 'filename' => 'example', + 'slug' => 'example', + 'type' => 'wp-theme', + ] + ); + + $this->assertSame( 'example-' . get_did_hash( $metadata->id ), get_hashed_filename( $metadata ) ); + } + + /** + * Test that missing non-plugin filenames still fall back to the slug. + */ + public function test_should_fall_back_to_slug_for_non_plugin_when_filename_missing() { + $metadata = $this->create_metadata_document( + [ + 'filename' => null, + 'slug' => 'example', + 'type' => 'wp-theme', + ] + ); + + $this->assertSame( 'example-' . get_did_hash( $metadata->id ), get_hashed_filename( $metadata ) ); + } + + /** + * Test that a pre-hashed plugin slug is not hashed twice. + */ + public function test_should_not_append_hash_twice_for_plugin_slug() { + $hash = get_did_hash( 'did:plc:example1234567890123456789' ); + $metadata = $this->create_metadata_document( + [ + 'filename' => 'example-' . $hash . '/example.php', + 'slug' => 'example-' . $hash, + 'type' => 'wp-plugin', + ] + ); + + $this->assertSame( 'example-' . $hash . '/example.php', get_hashed_filename( $metadata ) ); + } + + /** + * Test that empty plugin filenames behave the same as missing filenames. + */ + public function test_should_fall_back_to_slug_when_plugin_filename_is_empty_string() { + $metadata = $this->create_metadata_document( + [ + 'filename' => '', + 'slug' => 'example', + 'type' => 'wp-plugin', + ] + ); + + $this->assertSame( 'example-' . get_did_hash( $metadata->id ) . '/example.php', get_hashed_filename( $metadata ) ); + } + + /** + * Create a metadata document for testing. + * + * @param array $overrides Document overrides. + * @return MetadataDocument + */ + private function create_metadata_document( array $overrides ) : MetadataDocument { + $metadata = new MetadataDocument(); + $metadata->id = 'did:plc:example1234567890123456789'; + $metadata->type = 'wp-plugin'; + $metadata->slug = 'example'; + $metadata->filename = 'example/example.php'; + + foreach ( $overrides as $key => $value ) { + $metadata->{$key} = $value; + } + + return $metadata; + } +} From a56991c929a19d7a04cdf960adc328f0f0122a0f Mon Sep 17 00:00:00 2001 From: Kaspars Dambis Date: Sat, 16 May 2026 22:04:32 +0300 Subject: [PATCH 3/6] evaluate before comparing Co-authored-by: Colin Stewart <79332690+costdev@users.noreply.github.com> Signed-off-by: Kaspars Dambis --- inc/packages/namespace.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/inc/packages/namespace.php b/inc/packages/namespace.php index baebcfa2..58753c10 100644 --- a/inc/packages/namespace.php +++ b/inc/packages/namespace.php @@ -706,7 +706,7 @@ function get_hashed_filename( $metadata ) : string { } // WP plugins must include the plugin filename. - if ( 'wp-plugin' === $metadata->type ?? '' ) { + if ( 'wp-plugin' === ( $metadata->type ?? '' ) ) { return $slug . '/' . $file; } From 76d28701619df28024b0af224faffc66882380b9 Mon Sep 17 00:00:00 2001 From: Kaspars Dambis Date: Sat, 16 May 2026 22:06:10 +0300 Subject: [PATCH 4/6] Ensure we match against the tail Signed-off-by: Kaspars Dambis --- inc/packages/namespace.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/inc/packages/namespace.php b/inc/packages/namespace.php index 58753c10..c16c5bfa 100644 --- a/inc/packages/namespace.php +++ b/inc/packages/namespace.php @@ -701,7 +701,7 @@ function get_hashed_filename( $metadata ) : string { } // Append DID hash to slug if not already present. - if ( ! str_contains( $slug, $did_hash ) ) { + if ( ! str_ends_with( $slug, $did_hash ) ) { $slug .= $did_hash; } From e66cc29fba768942d0df06cca11f96d31d069575 Mon Sep 17 00:00:00 2001 From: Kaspars Dambis Date: Sat, 16 May 2026 22:10:32 +0300 Subject: [PATCH 5/6] Test missing metadata type property Signed-off-by: Kaspars Dambis --- .../tests/Packages/GetHashedFilenameTest.php | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/tests/phpunit/tests/Packages/GetHashedFilenameTest.php b/tests/phpunit/tests/Packages/GetHashedFilenameTest.php index 808b54a3..43085bbb 100644 --- a/tests/phpunit/tests/Packages/GetHashedFilenameTest.php +++ b/tests/phpunit/tests/Packages/GetHashedFilenameTest.php @@ -122,6 +122,23 @@ public function test_should_fall_back_to_slug_when_plugin_filename_is_empty_stri $this->assertSame( 'example-' . get_did_hash( $metadata->id ) . '/example.php', get_hashed_filename( $metadata ) ); } + /** + * Test that a missing type is treated as non-plugin metadata. + * + * This covers the operator precedence difference between + * `'wp-plugin' === ( $metadata->type ?? '' )` and + * `'wp-plugin' === $metadata->type ?? ''`. + */ + public function test_should_treat_missing_type_as_non_plugin_metadata() { + $metadata = (object) [ + 'id' => 'did:plc:example1234567890123456789', + 'slug' => 'example', + 'filename' => 'example/example.php', + ]; + + $this->assertSame( 'example-' . get_did_hash( $metadata->id ), get_hashed_filename( $metadata ) ); + } + /** * Create a metadata document for testing. * From a29caf53ec45d65666c92c8b78edaf41860228f6 Mon Sep 17 00:00:00 2001 From: Kaspars Dambis Date: Sat, 16 May 2026 22:18:11 +0300 Subject: [PATCH 6/6] Test that a hash-like substring in the middle of the slug still gets the DID hash appended Signed-off-by: Kaspars Dambis --- .../tests/Packages/GetHashedFilenameTest.php | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/tests/phpunit/tests/Packages/GetHashedFilenameTest.php b/tests/phpunit/tests/Packages/GetHashedFilenameTest.php index 43085bbb..63a19353 100644 --- a/tests/phpunit/tests/Packages/GetHashedFilenameTest.php +++ b/tests/phpunit/tests/Packages/GetHashedFilenameTest.php @@ -107,6 +107,23 @@ public function test_should_not_append_hash_twice_for_plugin_slug() { $this->assertSame( 'example-' . $hash . '/example.php', get_hashed_filename( $metadata ) ); } + /** + * Test that a hash-like substring in the middle of the slug still gets the DID hash appended. + */ + public function test_should_append_hash_when_same_value_appears_in_middle_of_plugin_slug() { + $hash = get_did_hash( 'did:plc:example1234567890123456789' ); + $slug = 'vendor-' . $hash . '-plugin'; + $metadata = $this->create_metadata_document( + [ + 'filename' => $slug . '/' . $slug . '.php', + 'slug' => $slug, + 'type' => 'wp-plugin', + ] + ); + + $this->assertSame( $slug . '-' . $hash . '/' . $slug . '.php', get_hashed_filename( $metadata ) ); + } + /** * Test that empty plugin filenames behave the same as missing filenames. */