From 09abd75df7eccb072bcd1481e822c450d595a6f8 Mon Sep 17 00:00:00 2001 From: Kaspars Dambis Date: Sat, 16 May 2026 10:53:33 +0300 Subject: [PATCH 01/12] 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 02/12] =?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 03/12] 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 04/12] 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 05/12] 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 06/12] 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. */ From 28a3d505c008f490b331df4e5bf9efca2d00a24d Mon Sep 17 00:00:00 2001 From: Kaspars Dambis Date: Mon, 18 May 2026 21:46:15 +0300 Subject: [PATCH 07/12] we only need the slug since file is loaded dynamically by WP for plugins only --- inc/packages/namespace.php | 24 +++------- ...t.php => GetHashedPackageIdentityTest.php} | 44 +++++++++---------- 2 files changed, 29 insertions(+), 39 deletions(-) rename tests/phpunit/tests/Packages/{GetHashedFilenameTest.php => GetHashedPackageIdentityTest.php} (71%) diff --git a/inc/packages/namespace.php b/inc/packages/namespace.php index c16c5bfa..30c560d9 100644 --- a/inc/packages/namespace.php +++ b/inc/packages/namespace.php @@ -680,36 +680,26 @@ function get_banners( $banners ) : array { } /** - * Get hashed file name from MetadataDocument. + * Get hashed slug from MetadataDocument. * * @param MetadataDocument $metadata MetadataDocument. * * @return string */ -function get_hashed_filename( $metadata ) : string { +function get_hashed_slug( $metadata ) : string { $did_hash = '-' . get_did_hash( $metadata->id ); // Use the slug from the filename, if present. - list( $slug, $file ) = array_pad( explode( '/', $metadata->filename ?? '', 2 ), 2, '' ); + list( $slug ) = array_pad( explode( '/', $metadata->filename ?? '', 2 ), 1, '' ); 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 $slug; } @@ -731,7 +721,7 @@ function get_package_data( $did ) { } $required_versions = version_requirements( $release ); - $filename = get_hashed_filename( $metadata ); + $hashed_slug = get_hashed_slug( $metadata ); $type = str_replace( 'wp-', '', $metadata->type ); $sections = (array) $metadata->sections; $description = trim( $sections['description'] ?? '' ); @@ -741,9 +731,9 @@ function get_package_data( $did ) { 'author' => $metadata->authors[0]->name, 'author_uri' => $metadata->authors[0]->url, 'slug' => $metadata->slug, - 'slug_didhash' => $metadata->slug . '-' . get_did_hash( $did ), $type => $filename, 'file' => $filename, + 'slug_didhash' => $hashed_slug, 'url' => $metadata->url ?? $metadata->slug, 'sections' => $sections, 'description' => $description, @@ -874,7 +864,7 @@ function maybe_rename_on_package_download( $source, string $remote_source, WP_Up return $source; } - $new_source = trailingslashit( $remote_source ) . $metadata->slug . '-' . get_did_hash( $did ); + $new_source = trailingslashit( $remote_source ) . get_hashed_slug( $metadata ); if ( trailingslashit( strtolower( $source ) ) !== trailingslashit( strtolower( $new_source ) ) ) { $wp_filesystem->move( $source, $new_source, true ); @@ -926,7 +916,7 @@ function move_package_during_install( $source, string $remote_source, WP_Upgrade // Cannot guarantee a slug-didhash format. dir-didhash is the best achievable. $new_source = untrailingslashit( $source ) . "-{$did_hash}/"; } else { - $new_source = dirname( untrailingslashit( $source ), 2 ) . "/{$metadata->slug}-{$did_hash}/"; + $new_source = dirname( untrailingslashit( $source ), 2 ) . '/' . get_hashed_slug( $metadata ) . '/'; } // Core must be able to find the new source directory. diff --git a/tests/phpunit/tests/Packages/GetHashedFilenameTest.php b/tests/phpunit/tests/Packages/GetHashedPackageIdentityTest.php similarity index 71% rename from tests/phpunit/tests/Packages/GetHashedFilenameTest.php rename to tests/phpunit/tests/Packages/GetHashedPackageIdentityTest.php index 63a19353..e95f49a7 100644 --- a/tests/phpunit/tests/Packages/GetHashedFilenameTest.php +++ b/tests/phpunit/tests/Packages/GetHashedPackageIdentityTest.php @@ -1,25 +1,25 @@ create_metadata_document( [ 'filename' => 'example/example.php', @@ -28,26 +28,26 @@ public function test_should_hash_plugin_directory_name() { ] ); - $this->assertSame( 'example-' . get_did_hash( $metadata->id ) . '/example.php', get_hashed_filename( $metadata ) ); + $this->assertSame( 'example-' . get_did_hash( $metadata->id ), get_hashed_slug( $metadata ) ); } /** - * Test that missing plugin filenames fall back to the slug. + * Test that plugin slugs hash independently of the bootstrap filename. */ - public function test_should_fall_back_to_slug_when_plugin_filename_missing() { + public function test_should_ignore_plugin_bootstrap_filename_when_hashing_slug() { $metadata = $this->create_metadata_document( [ - 'filename' => null, + 'filename' => 'example/custom-bootstrap.php', 'slug' => 'example', 'type' => 'wp-plugin', ] ); - $this->assertSame( 'example-' . get_did_hash( $metadata->id ) . '/example.php', get_hashed_filename( $metadata ) ); + $this->assertSame( 'example-' . get_did_hash( $metadata->id ), get_hashed_slug( $metadata ) ); } /** - * Test that malformed plugin filenames still produce a valid hashed path. + * Test that malformed plugin filenames still produce a valid hashed slug. */ public function test_should_recover_when_plugin_filename_has_no_main_file() { $metadata = $this->create_metadata_document( @@ -58,11 +58,11 @@ public function test_should_recover_when_plugin_filename_has_no_main_file() { ] ); - $this->assertSame( 'example-' . get_did_hash( $metadata->id ) . '/example.php', get_hashed_filename( $metadata ) ); + $this->assertSame( 'example-' . get_did_hash( $metadata->id ), get_hashed_slug( $metadata ) ); } /** - * Test that theme filenames append the DID hash to the slug. + * Test that theme slugs append the DID hash. */ public function test_should_hash_theme_slug() { $metadata = $this->create_metadata_document( @@ -73,7 +73,7 @@ public function test_should_hash_theme_slug() { ] ); - $this->assertSame( 'example-' . get_did_hash( $metadata->id ), get_hashed_filename( $metadata ) ); + $this->assertSame( 'example-' . get_did_hash( $metadata->id ), get_hashed_slug( $metadata ) ); } /** @@ -88,7 +88,7 @@ public function test_should_fall_back_to_slug_for_non_plugin_when_filename_missi ] ); - $this->assertSame( 'example-' . get_did_hash( $metadata->id ), get_hashed_filename( $metadata ) ); + $this->assertSame( 'example-' . get_did_hash( $metadata->id ), get_hashed_slug( $metadata ) ); } /** @@ -104,7 +104,7 @@ public function test_should_not_append_hash_twice_for_plugin_slug() { ] ); - $this->assertSame( 'example-' . $hash . '/example.php', get_hashed_filename( $metadata ) ); + $this->assertSame( 'example-' . $hash, get_hashed_slug( $metadata ) ); } /** @@ -121,11 +121,11 @@ public function test_should_append_hash_when_same_value_appears_in_middle_of_plu ] ); - $this->assertSame( $slug . '-' . $hash . '/' . $slug . '.php', get_hashed_filename( $metadata ) ); + $this->assertSame( $slug . '-' . $hash, get_hashed_slug( $metadata ) ); } /** - * Test that empty plugin filenames behave the same as missing filenames. + * Test that empty plugin filenames behave the same as missing filenames for slug hashing. */ public function test_should_fall_back_to_slug_when_plugin_filename_is_empty_string() { $metadata = $this->create_metadata_document( @@ -136,7 +136,7 @@ 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 ) ); + $this->assertSame( 'example-' . get_did_hash( $metadata->id ), get_hashed_slug( $metadata ) ); } /** @@ -153,7 +153,7 @@ public function test_should_treat_missing_type_as_non_plugin_metadata() { 'filename' => 'example/example.php', ]; - $this->assertSame( 'example-' . get_did_hash( $metadata->id ), get_hashed_filename( $metadata ) ); + $this->assertSame( 'example-' . get_did_hash( $metadata->id ), get_hashed_slug( $metadata ) ); } /** From 1473beddca21a5849cf3fc7e9e05c7564b05898c Mon Sep 17 00:00:00 2001 From: Kaspars Dambis Date: Mon, 18 May 2026 22:12:06 +0300 Subject: [PATCH 08/12] not really needed --- inc/packages/namespace.php | 2 -- 1 file changed, 2 deletions(-) diff --git a/inc/packages/namespace.php b/inc/packages/namespace.php index 30c560d9..6b1ceee5 100644 --- a/inc/packages/namespace.php +++ b/inc/packages/namespace.php @@ -731,8 +731,6 @@ function get_package_data( $did ) { 'author' => $metadata->authors[0]->name, 'author_uri' => $metadata->authors[0]->url, 'slug' => $metadata->slug, - $type => $filename, - 'file' => $filename, 'slug_didhash' => $hashed_slug, 'url' => $metadata->url ?? $metadata->slug, 'sections' => $sections, From 967b8f114d63166a22361bf01fc6ee714eec12eb Mon Sep 17 00:00:00 2001 From: Kaspars Dambis Date: Mon, 18 May 2026 22:42:54 +0300 Subject: [PATCH 09/12] use the actual plugin basename reported by WP instead of guessing it also none of these functions are used elsewhere, keep inline for now --- inc/packages/wp-cli/compat/namespace.php | 112 +++++++---------------- 1 file changed, 32 insertions(+), 80 deletions(-) diff --git a/inc/packages/wp-cli/compat/namespace.php b/inc/packages/wp-cli/compat/namespace.php index a625b8cb..c442e77b 100644 --- a/inc/packages/wp-cli/compat/namespace.php +++ b/inc/packages/wp-cli/compat/namespace.php @@ -8,6 +8,7 @@ namespace FAIR\Packages\WP_CLI\Compat; use FAIR\Packages as Packages; +use function FAIR\Updater\get_packages; use function WP_CLI\Utils\get_flag_value as get_flag_value; use WP_CLI; @@ -89,13 +90,6 @@ function ( $did ) { * @return void */ function handle_command( string $command, string $subcommand, array $args, array $assoc_args, array $items, array $dids ): void { - $hashed_items = replace_dids_with_hashed_filenames( $items, $dids ); - if ( $hashed_items === array_values( $items ) ) { - return; - } - - force_detection_by_did( $dids ); - switch ( $subcommand ) { case 'activate': case 'deactivate': @@ -107,7 +101,37 @@ function handle_command( string $command, string $subcommand, array $args, array case 'toggle': case 'uninstall': case 'update': - $args = array_merge( [ $command, $subcommand ], $hashed_items ); + $packages = get_packages(); + $plugin_basename_by_did = []; + + // Get the plugin basenames for the DIDs. + foreach ( $dids as $did ) { + if ( ! empty( $packages['plugins'][ $did ] ) ) { + $plugin_basename_by_did[ $did ] = plugin_basename( $packages['plugins'][ $did ] ); + } + } + + // Replace positional DIDs with plugin basenames where possible. + $plugin_items = array_map( + fn ( $item ) => $plugin_basename_by_did[ $item ] ?? $item, + $items + ); + + // Match DIDs to plugin information too. + add_filter( + 'all_plugins', + function ( $all_plugins ) use ( $plugin_basename_by_did ) { + foreach ( $plugin_basename_by_did as $did => $plugin_file ) { + if ( isset( $all_plugins[ $plugin_file ] ) ) { + $all_plugins[ $did ] = $all_plugins[ $plugin_file ]; + } + } + + return $all_plugins; + } + ); + + $args = array_merge( [ $command, $subcommand ], $plugin_items ); run_command_and_halt( $args, $assoc_args ); break; case 'search': @@ -127,49 +151,9 @@ function handle_command( string $command, string $subcommand, array $args, array WP_CLI::log( __( 'The verify-checksums command is not currently supported for DIDs.', 'fair' ) ); WP_CLI::halt( 1 ); break; - default: - // Do nothing. - break; } } -/** - * Force WP to detect plugins by their DIDs. - * - * This adds a filter to 'all_plugins' that duplicates entries - * for the hashed filenames to also be accessible by their DIDs. - * - * @param string[] $dids The DIDs to force detection for. - * @return void - */ -function force_detection_by_did( array $dids ): void { - add_filter( - 'all_plugins', - function ( $all_plugins ) use ( $dids ) { - foreach ( $dids as $did ) { - $metadata = Packages\fetch_package_metadata( $did ); - if ( is_wp_error( $metadata ) ) { - WP_CLI::warning( - sprintf( - /* translators: 1: The DID, 2: The error message. */ - __( 'Could not retrieve metadata for %1$s - %2$s', 'fair' ), - $did, - $metadata->get_error_message() - ) - ); - continue; - } - - $filename = Packages\get_hashed_filename( $metadata ); - if ( isset( $all_plugins[ $filename ] ) ) { - $all_plugins[ $did ] = $all_plugins[ $filename ]; - } - } - return $all_plugins; - } - ); -} - /** * Prime the environment for the search command. * @@ -258,35 +242,3 @@ function run_command_and_halt( array $args, array $assoc_args = [] ): void { } } } - -/** - * Replace DIDs in an array of items with their hashed filenames. - * - * @param string[] $items The command line items. - * @param string[] $dids The DIDs to replace. - * @return string[] The modified items. - */ -function replace_dids_with_hashed_filenames( array $items, array $dids ): array { - return array_map( - function ( $item ) use ( $dids ) { - if ( in_array( $item, $dids, true ) ) { - $metadata = Packages\fetch_package_metadata( $item ); - if ( is_wp_error( $metadata ) ) { - WP_CLI::warning( - sprintf( - /* translators: 1: The DID, 2: The error message. */ - __( 'Could not retrieve metadata for %1$s - %2$s', 'fair' ), - $item, - $metadata->get_error_message() - ) - ); - return $item; - } - - return Packages\get_hashed_filename( $metadata ); - } - return $item; - }, - $items - ); -} From c5f8cc649189a6cc0ec1905842a4189cb8459d6c Mon Sep 17 00:00:00 2001 From: Kaspars Dambis Date: Mon, 18 May 2026 22:43:52 +0300 Subject: [PATCH 10/12] no more --- tests/phpstan-baseline.neon | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/phpstan-baseline.neon b/tests/phpstan-baseline.neon index 7bacc7c8..5fa1d3bb 100644 --- a/tests/phpstan-baseline.neon +++ b/tests/phpstan-baseline.neon @@ -69,7 +69,7 @@ parameters: - message: '#^Call to static method warning\(\) on an unknown class WP_CLI\.$#' identifier: class.notFound - count: 3 + count: 1 path: ../inc/packages/wp-cli/compat/namespace.php - From 17e97ec8180a6a59b2856c8214f56c04f08baf8b Mon Sep 17 00:00:00 2001 From: Kaspars Dambis Date: Mon, 18 May 2026 23:07:18 +0300 Subject: [PATCH 11/12] Add the plugin type and basename where we know it --- inc/updater/class-updater.php | 3 +++ 1 file changed, 3 insertions(+) diff --git a/inc/updater/class-updater.php b/inc/updater/class-updater.php index d822d100..44dc0fe5 100644 --- a/inc/updater/class-updater.php +++ b/inc/updater/class-updater.php @@ -279,8 +279,11 @@ private static function update_site_transient( $transient, array $packages ) { } $rel_path = $package->get_relative_path(); + $meta = $package->get_metadata(); + $package_type = str_replace( 'wp-', '', $meta->type ); $response['slug'] = $response['slug_didhash']; + $response[ $package_type ] = $rel_path; $is_compatible = Packages\check_requirements( $release ); From 4f3b070590f262f4c14a683862f3044a1f5c1543 Mon Sep 17 00:00:00 2001 From: Kaspars Dambis Date: Mon, 18 May 2026 23:23:11 +0300 Subject: [PATCH 12/12] Inline where not re-used --- inc/packages/namespace.php | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/inc/packages/namespace.php b/inc/packages/namespace.php index 6b1ceee5..874d96c8 100644 --- a/inc/packages/namespace.php +++ b/inc/packages/namespace.php @@ -721,9 +721,6 @@ function get_package_data( $did ) { } $required_versions = version_requirements( $release ); - $hashed_slug = get_hashed_slug( $metadata ); - $type = str_replace( 'wp-', '', $metadata->type ); - $sections = (array) $metadata->sections; $description = trim( $sections['description'] ?? '' ); $response = [ @@ -731,9 +728,9 @@ function get_package_data( $did ) { 'author' => $metadata->authors[0]->name, 'author_uri' => $metadata->authors[0]->url, 'slug' => $metadata->slug, - 'slug_didhash' => $hashed_slug, + 'slug_didhash' => get_hashed_slug( $metadata ), 'url' => $metadata->url ?? $metadata->slug, - 'sections' => $sections, + 'sections' => (array) $metadata->sections, 'description' => $description, 'short_description' => substr( strip_tags( $description ), 0, 147 ) . '...', 'icons' => isset( $release->artifacts->icon ) ? get_icons( $release->artifacts->icon ) : [], @@ -754,7 +751,8 @@ function get_package_data( $did ) { 'active_installs' => 0, '_fair' => $metadata, ]; - if ( 'theme' === $type ) { + + if ( 'wp-theme' === $metadata->type ) { $response['theme_uri'] = $response['url']; }