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'; diff --git a/inc/packages/namespace.php b/inc/packages/namespace.php index 28ee8886..c16c5bfa 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_ends_with( $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..63a19353 --- /dev/null +++ b/tests/phpunit/tests/Packages/GetHashedFilenameTest.php @@ -0,0 +1,178 @@ +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 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. + */ + 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 ) ); + } + + /** + * 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. + * + * @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; + } +}