From 621cd5cf252c9671f110184be2650ef2df36d749 Mon Sep 17 00:00:00 2001 From: Claude Date: Sun, 9 Aug 2026 11:10:15 +0000 Subject: [PATCH 1/6] Error on invalid boolean values for `--show-in-rest` `filter_var()` with `FILTER_NULL_ON_FAILURE` returns null for an unparseable value such as `--show-in-rest=bogus`. The guard then skipped the comparison entirely, so the filter silently became a no-op and the command listed every registered ability. Asking to filter and receiving the full set back is worse than an error. Extract the parsing into `parse_bool_filter()` and fail with a clear message instead. `true`/`false`/`1`/`0`/`yes`/`no`, the bare flag, and `--no-show-in-rest` all keep working as before. Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_0162D8GNP2TsQyHUQKNoQv3a --- features/ability.feature | 8 ++++++++ src/Ability_Command.php | 30 ++++++++++++++++++++++++++---- 2 files changed, 34 insertions(+), 4 deletions(-) diff --git a/features/ability.feature b/features/ability.feature index 4ccaae4..f930b0e 100644 --- a/features/ability.feature +++ b/features/ability.feature @@ -396,6 +396,14 @@ Feature: Manage abilities registered via the WordPress Abilities API. test-plugin/private-ability """ + @require-wp-6.9 + Scenario: Reject invalid boolean filter values. + When I try `wp ability list --show-in-rest=bogus` + Then STDERR should be: + """ + Error: Invalid boolean value for --show-in-rest. Use 'true' or 'false'. + """ + @require-wp-6.9 Scenario: Display ability annotations. Given a wp-content/mu-plugins/test-ability.php file: diff --git a/src/Ability_Command.php b/src/Ability_Command.php index 48fc9a4..33ec908 100644 --- a/src/Ability_Command.php +++ b/src/Ability_Command.php @@ -182,7 +182,7 @@ public function list_( $args, $assoc_args ): void { $abilities = wp_get_abilities(); $category_slug = Utils\get_flag_value( $assoc_args, 'category' ); $namespace = Utils\get_flag_value( $assoc_args, 'namespace' ); - $show_in_rest = Utils\get_flag_value( $assoc_args, 'show-in-rest' ); + $show_in_rest = $this->parse_bool_filter( $assoc_args, 'show-in-rest' ); $items = []; @@ -205,9 +205,8 @@ public function list_( $args, $assoc_args ): void { // Filter by show_in_rest if specified. if ( null !== $show_in_rest ) { - $show_in_rest_bool = filter_var( $show_in_rest, FILTER_VALIDATE_BOOLEAN, FILTER_NULL_ON_FAILURE ); - $ability_rest = '1' === $ability_data['show_in_rest']; - if ( null !== $show_in_rest_bool && $show_in_rest_bool !== $ability_rest ) { + $ability_rest = '1' === $ability_data['show_in_rest']; + if ( $show_in_rest !== $ability_rest ) { continue; } } @@ -694,6 +693,29 @@ private function get_annotations( $ability ) { ]; } + /** + * Parses a boolean filter flag. + * + * @param array $assoc_args Associative arguments. + * @param string $flag The flag name. + * @return bool|null The parsed value, or null when the flag was not provided. + */ + private function parse_bool_filter( $assoc_args, $flag ) { + $value = Utils\get_flag_value( $assoc_args, $flag ); + + if ( null === $value ) { + return null; + } + + $parsed = filter_var( $value, FILTER_VALIDATE_BOOLEAN, FILTER_NULL_ON_FAILURE ); + + if ( null === $parsed ) { + WP_CLI::error( "Invalid boolean value for --{$flag}. Use 'true' or 'false'." ); + } + + return $parsed; + } + /** * Formats an annotation value for output. * From b6922bab1d25430caa4afb5381eeb6e352afe919 Mon Sep 17 00:00:00 2001 From: Claude Date: Sun, 9 Aug 2026 11:10:41 +0000 Subject: [PATCH 2/6] Add support for the `meta.public` flag WordPress 7.1 introduces `meta.public` as the high-level client exposure setting, with channel-specific flags taking precedence. Core resolves the cascade at registration, so `show_in_rest` already reports effective REST exposure -- but `meta.public` itself was invisible, and there was no way to tell whether REST exposure came from the high-level flag or the channel flag. Add `public` as an optional field on `list` and a default field on `get`, ordered before `show_in_rest`, plus a `--public=` filter on `list`. Add `meta` as an optional field on `get`, rendering the raw metadata as JSON the way `wp ability category get` already does. It is the only way to inspect channel-specific settings such as `mcp.public`, which no dedicated field covers. Nothing here reimplements the precedence rules, and `list` keeps reporting every registered ability regardless of exposure. WP-CLI runs with full trust; these flags are display and filter concerns, not access control. Fixes https://github.com/wp-cli/ability-command/issues/9 Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_0162D8GNP2TsQyHUQKNoQv3a --- features/ability.feature | 159 +++++++++++++++++++++++++++++++++++++++ src/Ability_Command.php | 85 ++++++++++++++++++++- 2 files changed, 243 insertions(+), 1 deletion(-) diff --git a/features/ability.feature b/features/ability.feature index f930b0e..47d1a76 100644 --- a/features/ability.feature +++ b/features/ability.feature @@ -398,12 +398,171 @@ Feature: Manage abilities registered via the WordPress Abilities API. @require-wp-6.9 Scenario: Reject invalid boolean filter values. + When I try `wp ability list --public=bogus` + Then STDERR should be: + """ + Error: Invalid boolean value for --public. Use 'true' or 'false'. + """ + When I try `wp ability list --show-in-rest=bogus` Then STDERR should be: """ Error: Invalid boolean value for --show-in-rest. Use 'true' or 'false'. """ + @require-wp-7.1 + Scenario: Display the public flag and its precedence over channel flags. + Given a wp-content/mu-plugins/test-ability.php file: + """ + 'Test Category', + 'description' => 'A test category.', + ) ); + } ); + + add_action( 'wp_abilities_api_init', function() { + wp_register_ability( 'test-plugin/public-ability', array( + 'label' => 'Public Ability', + 'description' => 'A public ability.', + 'category' => 'test-category', + 'execute_callback' => function( $input ) { + return array( 'result' => 'done' ); + }, + 'permission_callback' => '__return_true', + 'meta' => array( 'public' => true ), + ) ); + + wp_register_ability( 'test-plugin/public-not-rest', array( + 'label' => 'Public But Not REST', + 'description' => 'A public ability hidden from REST.', + 'category' => 'test-category', + 'execute_callback' => function( $input ) { + return array( 'result' => 'done' ); + }, + 'permission_callback' => '__return_true', + 'meta' => array( 'public' => true, 'show_in_rest' => false ), + ) ); + + wp_register_ability( 'test-plugin/plain-ability', array( + 'label' => 'Plain Ability', + 'description' => 'An ability without exposure metadata.', + 'category' => 'test-category', + 'execute_callback' => function( $input ) { + return array( 'result' => 'done' ); + }, + 'permission_callback' => '__return_true', + ) ); + } ); + """ + + When I run `wp ability list --namespace=test-plugin --fields=name,public,show_in_rest` + Then STDOUT should be a table containing rows: + | name | public | show_in_rest | + | test-plugin/public-ability | 1 | 1 | + | test-plugin/public-not-rest | 1 | 0 | + | test-plugin/plain-ability | 0 | 0 | + + When I run `wp ability get test-plugin/public-ability --format=json` + Then STDOUT should be JSON containing: + """ + {"public":"1","show_in_rest":"1"} + """ + + When I run `wp ability get test-plugin/public-not-rest --format=json` + Then STDOUT should be JSON containing: + """ + {"public":"1","show_in_rest":"0"} + """ + + @require-wp-7.1 + Scenario: Filter abilities by public. + Given a wp-content/mu-plugins/test-ability.php file: + """ + 'Test Category', + 'description' => 'A test category.', + ) ); + } ); + + add_action( 'wp_abilities_api_init', function() { + wp_register_ability( 'test-plugin/public-ability', array( + 'label' => 'Public Ability', + 'description' => 'A public ability.', + 'category' => 'test-category', + 'execute_callback' => function( $input ) { + return array( 'result' => 'done' ); + }, + 'permission_callback' => '__return_true', + 'meta' => array( 'public' => true ), + ) ); + + wp_register_ability( 'test-plugin/internal-ability', array( + 'label' => 'Internal Ability', + 'description' => 'An ability kept away from clients.', + 'category' => 'test-category', + 'execute_callback' => function( $input ) { + return array( 'result' => 'done' ); + }, + 'permission_callback' => '__return_true', + 'meta' => array( 'public' => false ), + ) ); + } ); + """ + + When I run `wp ability list --namespace=test-plugin --public=true --field=name` + Then STDOUT should be: + """ + test-plugin/public-ability + """ + + When I run `wp ability list --namespace=test-plugin --public=false --field=name` + Then STDOUT should be: + """ + test-plugin/internal-ability + """ + + @require-wp-6.9 + Scenario: Inspect raw ability meta. + Given a wp-content/mu-plugins/test-ability.php file: + """ + 'Test Category', + 'description' => 'A test category.', + ) ); + } ); + + add_action( 'wp_abilities_api_init', function() { + wp_register_ability( 'test-plugin/channel-ability', array( + 'label' => 'Channel Ability', + 'description' => 'An ability with channel-specific metadata.', + 'category' => 'test-category', + 'execute_callback' => function( $input ) { + return array( 'result' => 'done' ); + }, + 'permission_callback' => '__return_true', + 'meta' => array( 'mcp' => array( 'public' => false ) ), + ) ); + } ); + """ + + When I run `wp ability get test-plugin/channel-ability --field=meta` + Then STDOUT should contain: + """ + "mcp":{"public":false} + """ + + When I run `wp ability get test-plugin/channel-ability` + Then STDOUT should not contain: + """ + mcp + """ + @require-wp-6.9 Scenario: Display ability annotations. Given a wp-content/mu-plugins/test-ability.php file: diff --git a/src/Ability_Command.php b/src/Ability_Command.php index 33ec908..b29fac2 100644 --- a/src/Ability_Command.php +++ b/src/Ability_Command.php @@ -28,7 +28,7 @@ * +---------------------------+----------------------+----------+------------------------------------------+ * * # Get details of a specific ability. - * $ wp ability get core/get-site-info --fields=name,label,category,readonly,show_in_rest + * $ wp ability get core/get-site-info --fields=name,label,category,readonly,public,show_in_rest * +---------------+----------------------+ * | Field | Value | * +---------------+----------------------+ @@ -36,6 +36,7 @@ * | label | Get Site Information | * | category | site | * | readonly | 1 | + * | public | 1 | * | show_in_rest | 1 | * +---------------+----------------------+ * @@ -92,6 +93,7 @@ class Ability_Command extends WP_CLI_Command { 'readonly', 'destructive', 'idempotent', + 'public', 'show_in_rest', ]; @@ -106,6 +108,9 @@ class Ability_Command extends WP_CLI_Command { * [--namespace=] * : Filter abilities by namespace prefix (e.g., 'core' for 'core/*' abilities). * + * [--public=] + * : Filter abilities by the high-level client exposure flag. + * * [--show-in-rest=] * : Filter abilities by REST API exposure. * @@ -142,8 +147,15 @@ class Ability_Command extends WP_CLI_Command { * * readonly * * destructive * * idempotent + * * public * * show_in_rest * + * The `public` field reports the high-level client exposure flag as declared + * by the ability. As of WordPress 7.1 it seeds the default for channel-specific + * flags such as `show_in_rest`, which take precedence when set explicitly. + * On earlier versions the flag is stored but has no effect, so `public` and + * `show_in_rest` may disagree. + * * ## EXAMPLES * * # List all abilities. @@ -164,6 +176,12 @@ class Ability_Command extends WP_CLI_Command { * # List abilities exposed to REST API. * $ wp ability list --show-in-rest=true * + * # List abilities meant to be available to clients. + * $ wp ability list --public=true + * + * # Find abilities that opt out of REST despite being public. + * $ wp ability list --public=true --show-in-rest=false + * * # List abilities as JSON. * $ wp ability list --format=json * @@ -182,6 +200,7 @@ public function list_( $args, $assoc_args ): void { $abilities = wp_get_abilities(); $category_slug = Utils\get_flag_value( $assoc_args, 'category' ); $namespace = Utils\get_flag_value( $assoc_args, 'namespace' ); + $public = $this->parse_bool_filter( $assoc_args, 'public' ); $show_in_rest = $this->parse_bool_filter( $assoc_args, 'show-in-rest' ); $items = []; @@ -203,6 +222,14 @@ public function list_( $args, $assoc_args ): void { } } + // Filter by public if specified. + if ( null !== $public ) { + $ability_public = '1' === $ability_data['public']; + if ( $public !== $ability_public ) { + continue; + } + } + // Filter by show_in_rest if specified. if ( null !== $show_in_rest ) { $ability_rest = '1' === $ability_data['show_in_rest']; @@ -214,6 +241,8 @@ public function list_( $args, $assoc_args ): void { $items[] = $ability_data; } + $this->maybe_debug_inert_public_flag( $items ); + $formatter = $this->get_formatter( $assoc_args, $this->default_fields ); $formatter->display_items( $items ); } @@ -245,6 +274,8 @@ public function list_( $args, $assoc_args ): void { * * ## AVAILABLE FIELDS * + * These fields will be displayed by default: + * * * name * * label * * category @@ -254,8 +285,22 @@ public function list_( $args, $assoc_args ): void { * * readonly * * destructive * * idempotent + * * public * * show_in_rest * + * These fields are optionally available: + * + * * meta + * + * The `public` field reports the high-level client exposure flag as declared + * by the ability. As of WordPress 7.1 it seeds the default for channel-specific + * flags such as `show_in_rest`, which take precedence when set explicitly. + * On earlier versions the flag is stored but has no effect, so `public` and + * `show_in_rest` may disagree. + * + * The `meta` field renders the raw metadata as JSON. It is the only way to + * inspect channel-specific settings registered by plugins, such as `mcp`. + * * ## EXAMPLES * * # Get details of a specific ability. @@ -272,9 +317,14 @@ public function list_( $args, $assoc_args ): void { * | readonly | 1 | * | destructive | 0 | * | idempotent | 1 | + * | public | 1 | * | show_in_rest | 1 | * +---------------+----------------------+ * + * # Inspect channel-specific settings that have no field of their own. + * $ wp ability get my-plugin/my-ability --field=meta + * {"annotations":{"readonly":true,"destructive":false,"idempotent":null},"mcp":{"public":false},"show_in_rest":true,"public":true} + * * # Get ability as JSON. * $ wp ability get core/get-site-info --format=json * @@ -295,6 +345,8 @@ public function get( $args, $assoc_args ): void { $ability_data = $this->format_ability_for_get( $ability ); + $this->maybe_debug_inert_public_flag( [ $ability_data ] ); + $formatter = $this->get_formatter( $assoc_args, $this->get_fields ); $formatter->display_item( $ability_data ); } @@ -649,6 +701,7 @@ private function format_ability_for_list( $ability ) { 'readonly' => $this->format_annotation( $annotations['readonly'] ), 'destructive' => $this->format_annotation( $annotations['destructive'] ), 'idempotent' => $this->format_annotation( $annotations['idempotent'] ), + 'public' => $ability->get_meta_item( 'public', false ) ? '1' : '0', 'show_in_rest' => $ability->get_meta_item( 'show_in_rest', false ) ? '1' : '0', ]; } @@ -661,6 +714,7 @@ private function format_ability_for_list( $ability ) { */ private function format_ability_for_get( $ability ) { $annotations = $this->get_annotations( $ability ); + $meta = $ability->get_meta(); return [ 'name' => $ability->get_name(), @@ -672,7 +726,9 @@ private function format_ability_for_get( $ability ) { 'readonly' => $this->format_annotation( $annotations['readonly'] ), 'destructive' => $this->format_annotation( $annotations['destructive'] ), 'idempotent' => $this->format_annotation( $annotations['idempotent'] ), + 'public' => $ability->get_meta_item( 'public', false ) ? '1' : '0', 'show_in_rest' => $ability->get_meta_item( 'show_in_rest', false ) ? '1' : '0', + 'meta' => ! empty( $meta ) ? wp_json_encode( $meta ) : '{}', ]; } @@ -716,6 +772,33 @@ private function parse_bool_filter( $assoc_args, $flag ) { return $parsed; } + /** + * Emits a debug note when the `public` meta flag is declared but inert. + * + * WordPress only resolves `public` into channel-specific flags such as + * `show_in_rest` as of 7.1. On earlier versions the flag is stored as + * declared but has no effect on client exposure, so `public` and + * `show_in_rest` can disagree for no visible reason. + * + * @param array> $items The formatted abilities. + * @return void + */ + private function maybe_debug_inert_public_flag( $items ) { + if ( ! Utils\wp_version_compare( '7.1', '<' ) ) { + return; + } + + foreach ( $items as $item ) { + if ( isset( $item['public'] ) && '1' === $item['public'] ) { + WP_CLI::debug( + 'The `public` meta flag is only resolved into channel-specific flags such as `show_in_rest` as of WordPress 7.1. On this version it is reported as declared but has no effect on client exposure.', + 'ability' + ); + return; + } + } + } + /** * Formats an annotation value for output. * From dc3d2db7b0abba5caccd9ef53d6a1127628684c6 Mon Sep 17 00:00:00 2001 From: Claude Date: Sun, 9 Aug 2026 11:32:38 +0000 Subject: [PATCH 3/6] Reject empty boolean filter values, correct the error message `FILTER_VALIDATE_BOOLEAN` treats an empty string as a valid `false`, so `--public=` and `--show-in-rest=` slipped past the new validation and quietly applied a false filter -- the same footgun the validation was added to close. Reject the empty value before parsing. The error message also claimed only `true` and `false` were accepted while the parser takes the full `FILTER_VALIDATE_BOOLEAN` set. List what is actually accepted rather than narrowing the parser, since `1`/`0` and `yes`/`no` are conventional in WP-CLI flags. Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_0162D8GNP2TsQyHUQKNoQv3a --- features/ability.feature | 22 ++++++++++++++++++++-- src/Ability_Command.php | 10 ++++++++-- 2 files changed, 28 insertions(+), 4 deletions(-) diff --git a/features/ability.feature b/features/ability.feature index 47d1a76..0cb397f 100644 --- a/features/ability.feature +++ b/features/ability.feature @@ -401,15 +401,33 @@ Feature: Manage abilities registered via the WordPress Abilities API. When I try `wp ability list --public=bogus` Then STDERR should be: """ - Error: Invalid boolean value for --public. Use 'true' or 'false'. + Error: Invalid boolean value for --public. Accepted: true, false, 1, 0, yes, no, on, off. """ When I try `wp ability list --show-in-rest=bogus` Then STDERR should be: """ - Error: Invalid boolean value for --show-in-rest. Use 'true' or 'false'. + Error: Invalid boolean value for --show-in-rest. Accepted: true, false, 1, 0, yes, no, on, off. """ + When I try `wp ability list --public=` + Then STDERR should be: + """ + Error: Invalid boolean value for --public. Accepted: true, false, 1, 0, yes, no, on, off. + """ + + When I try `wp ability list --show-in-rest=` + Then STDERR should be: + """ + Error: Invalid boolean value for --show-in-rest. Accepted: true, false, 1, 0, yes, no, on, off. + """ + + When I run `wp ability list --public=yes --format=count` + Then STDOUT should not be empty + + When I run `wp ability list --no-public --format=count` + Then STDOUT should not be empty + @require-wp-7.1 Scenario: Display the public flag and its precedence over channel flags. Given a wp-content/mu-plugins/test-ability.php file: diff --git a/src/Ability_Command.php b/src/Ability_Command.php index b29fac2..3121ce9 100644 --- a/src/Ability_Command.php +++ b/src/Ability_Command.php @@ -763,10 +763,16 @@ private function parse_bool_filter( $assoc_args, $flag ) { return null; } - $parsed = filter_var( $value, FILTER_VALIDATE_BOOLEAN, FILTER_NULL_ON_FAILURE ); + $parsed = null; + + // An empty value is rejected rather than parsed, since + // FILTER_VALIDATE_BOOLEAN would silently accept it as false. + if ( '' !== $value ) { + $parsed = filter_var( $value, FILTER_VALIDATE_BOOLEAN, FILTER_NULL_ON_FAILURE ); + } if ( null === $parsed ) { - WP_CLI::error( "Invalid boolean value for --{$flag}. Use 'true' or 'false'." ); + WP_CLI::error( "Invalid boolean value for --{$flag}. Accepted: true, false, 1, 0, yes, no, on, off." ); } return $parsed; From 49800b1c7fd608d2480787f40d52a1bcf0c89245 Mon Sep 17 00:00:00 2001 From: Claude Date: Sun, 9 Aug 2026 15:09:33 +0000 Subject: [PATCH 4/6] Drop the debug note about the inert `public` flag The `## AVAILABLE FIELDS` docs on both `list` and `get` already state that `public` has no effect before WordPress 7.1 and that it may therefore disagree with `show_in_rest`. That is where someone puzzled by the output will look; `WP_CLI::debug()` output only appears under `--debug`, which is not a flag you reach for when you do not yet know something is wrong. The helper also had a finite life by construction -- it could only ever fire on 6.9 and 7.0 -- while costing a version check and a pass over the displayed items on every run. Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_0162D8GNP2TsQyHUQKNoQv3a --- src/Ability_Command.php | 31 ------------------------------- 1 file changed, 31 deletions(-) diff --git a/src/Ability_Command.php b/src/Ability_Command.php index 3121ce9..180001d 100644 --- a/src/Ability_Command.php +++ b/src/Ability_Command.php @@ -241,8 +241,6 @@ public function list_( $args, $assoc_args ): void { $items[] = $ability_data; } - $this->maybe_debug_inert_public_flag( $items ); - $formatter = $this->get_formatter( $assoc_args, $this->default_fields ); $formatter->display_items( $items ); } @@ -345,8 +343,6 @@ public function get( $args, $assoc_args ): void { $ability_data = $this->format_ability_for_get( $ability ); - $this->maybe_debug_inert_public_flag( [ $ability_data ] ); - $formatter = $this->get_formatter( $assoc_args, $this->get_fields ); $formatter->display_item( $ability_data ); } @@ -778,33 +774,6 @@ private function parse_bool_filter( $assoc_args, $flag ) { return $parsed; } - /** - * Emits a debug note when the `public` meta flag is declared but inert. - * - * WordPress only resolves `public` into channel-specific flags such as - * `show_in_rest` as of 7.1. On earlier versions the flag is stored as - * declared but has no effect on client exposure, so `public` and - * `show_in_rest` can disagree for no visible reason. - * - * @param array> $items The formatted abilities. - * @return void - */ - private function maybe_debug_inert_public_flag( $items ) { - if ( ! Utils\wp_version_compare( '7.1', '<' ) ) { - return; - } - - foreach ( $items as $item ) { - if ( isset( $item['public'] ) && '1' === $item['public'] ) { - WP_CLI::debug( - 'The `public` meta flag is only resolved into channel-specific flags such as `show_in_rest` as of WordPress 7.1. On this version it is reported as declared but has no effect on client exposure.', - 'ability' - ); - return; - } - } - } - /** * Formats an annotation value for output. * From 2db8a663ea3e31c4f1cef69b9b49aed58f0b9e3f Mon Sep 17 00:00:00 2001 From: Claude Date: Sun, 9 Aug 2026 15:26:12 +0000 Subject: [PATCH 5/6] Validate exposure filters through the synopsis instead of in PHP Declaring `options: true/false` on `--public` and `--show-in-rest` lets WP-CLI reject bad values in `Subcommand::validate_args()` before the command runs, which is how `--format` in this same command already works. That covers everything `parse_bool_filter()` was doing by hand, including the empty-value case, so the helper and `filter_var()` both go away. The accepted set narrows to `true` and `false`. `1`/`0`, `yes`/`no`, `on`/`off` and the `--no-` prefix are no longer accepted -- `--no-public` in particular now errors, since WP-CLI's loose `in_array()` check does not match boolean false against the options list. The bare `--public` form does still pass validation as boolean true, so the comparison handles it explicitly rather than silently matching nothing. Errors now come from WP-CLI in its standard form: Error: Parameter errors: Invalid value specified for 'public' (...) Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_0162D8GNP2TsQyHUQKNoQv3a --- features/ability.feature | 22 ++++++---------- src/Ability_Command.php | 56 +++++++++++++++------------------------- 2 files changed, 29 insertions(+), 49 deletions(-) diff --git a/features/ability.feature b/features/ability.feature index 0cb397f..78479eb 100644 --- a/features/ability.feature +++ b/features/ability.feature @@ -399,35 +399,29 @@ Feature: Manage abilities registered via the WordPress Abilities API. @require-wp-6.9 Scenario: Reject invalid boolean filter values. When I try `wp ability list --public=bogus` - Then STDERR should be: + Then STDERR should contain: """ - Error: Invalid boolean value for --public. Accepted: true, false, 1, 0, yes, no, on, off. + Invalid value specified for 'public' """ When I try `wp ability list --show-in-rest=bogus` - Then STDERR should be: + Then STDERR should contain: """ - Error: Invalid boolean value for --show-in-rest. Accepted: true, false, 1, 0, yes, no, on, off. + Invalid value specified for 'show-in-rest' """ When I try `wp ability list --public=` - Then STDERR should be: + Then STDERR should contain: """ - Error: Invalid boolean value for --public. Accepted: true, false, 1, 0, yes, no, on, off. + Invalid value specified for 'public' """ When I try `wp ability list --show-in-rest=` - Then STDERR should be: + Then STDERR should contain: """ - Error: Invalid boolean value for --show-in-rest. Accepted: true, false, 1, 0, yes, no, on, off. + Invalid value specified for 'show-in-rest' """ - When I run `wp ability list --public=yes --format=count` - Then STDOUT should not be empty - - When I run `wp ability list --no-public --format=count` - Then STDOUT should not be empty - @require-wp-7.1 Scenario: Display the public flag and its precedence over channel flags. Given a wp-content/mu-plugins/test-ability.php file: diff --git a/src/Ability_Command.php b/src/Ability_Command.php index 180001d..f388d94 100644 --- a/src/Ability_Command.php +++ b/src/Ability_Command.php @@ -110,9 +110,19 @@ class Ability_Command extends WP_CLI_Command { * * [--public=] * : Filter abilities by the high-level client exposure flag. + * --- + * options: + * - true + * - false + * --- * * [--show-in-rest=] * : Filter abilities by REST API exposure. + * --- + * options: + * - true + * - false + * --- * * [--field=] * : Prints the value of a single field for each ability. @@ -200,8 +210,8 @@ public function list_( $args, $assoc_args ): void { $abilities = wp_get_abilities(); $category_slug = Utils\get_flag_value( $assoc_args, 'category' ); $namespace = Utils\get_flag_value( $assoc_args, 'namespace' ); - $public = $this->parse_bool_filter( $assoc_args, 'public' ); - $show_in_rest = $this->parse_bool_filter( $assoc_args, 'show-in-rest' ); + $public = Utils\get_flag_value( $assoc_args, 'public' ); + $show_in_rest = Utils\get_flag_value( $assoc_args, 'show-in-rest' ); $items = []; @@ -222,18 +232,23 @@ public function list_( $args, $assoc_args ): void { } } - // Filter by public if specified. + /* + * Filter by exposure if specified. The synopsis restricts these to + * 'true' or 'false', which WP-CLI validates before the command runs. + * The bare `--public` and `--show-in-rest` forms arrive as boolean true. + */ if ( null !== $public ) { + $wanted_public = 'true' === $public || true === $public; $ability_public = '1' === $ability_data['public']; - if ( $public !== $ability_public ) { + if ( $wanted_public !== $ability_public ) { continue; } } - // Filter by show_in_rest if specified. if ( null !== $show_in_rest ) { + $wanted_rest = 'true' === $show_in_rest || true === $show_in_rest; $ability_rest = '1' === $ability_data['show_in_rest']; - if ( $show_in_rest !== $ability_rest ) { + if ( $wanted_rest !== $ability_rest ) { continue; } } @@ -745,35 +760,6 @@ private function get_annotations( $ability ) { ]; } - /** - * Parses a boolean filter flag. - * - * @param array $assoc_args Associative arguments. - * @param string $flag The flag name. - * @return bool|null The parsed value, or null when the flag was not provided. - */ - private function parse_bool_filter( $assoc_args, $flag ) { - $value = Utils\get_flag_value( $assoc_args, $flag ); - - if ( null === $value ) { - return null; - } - - $parsed = null; - - // An empty value is rejected rather than parsed, since - // FILTER_VALIDATE_BOOLEAN would silently accept it as false. - if ( '' !== $value ) { - $parsed = filter_var( $value, FILTER_VALIDATE_BOOLEAN, FILTER_NULL_ON_FAILURE ); - } - - if ( null === $parsed ) { - WP_CLI::error( "Invalid boolean value for --{$flag}. Accepted: true, false, 1, 0, yes, no, on, off." ); - } - - return $parsed; - } - /** * Formats an annotation value for output. * From d2a444b8e09c4c01f35957e9f5297893602f8e95 Mon Sep 17 00:00:00 2001 From: Claude Date: Sun, 9 Aug 2026 15:44:08 +0000 Subject: [PATCH 6/6] Declare the exposure filters as plain flags `[--public]` and `[--show-in-rest]` are boolean flags, which is how WP-CLI models these. `Utils\get_flag_value()` then returns true for `--public`, false for `--no-public`, and null when absent, so the filter is a direct boolean comparison with no parsing, no validation code, and no `options` block in the synopsis. Removes the invalid-value scenario along with the parsing it covered, and switches the existing `--show-in-rest=true|false` scenario to the flag form. Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_0162D8GNP2TsQyHUQKNoQv3a --- features/ability.feature | 34 ++++------------------------------ src/Ability_Command.php | 37 +++++++++++-------------------------- 2 files changed, 15 insertions(+), 56 deletions(-) diff --git a/features/ability.feature b/features/ability.feature index 78479eb..b2d602d 100644 --- a/features/ability.feature +++ b/features/ability.feature @@ -384,44 +384,18 @@ Feature: Manage abilities registered via the WordPress Abilities API. } ); """ - When I run `wp ability list --namespace=test-plugin --show-in-rest=true --field=name` + When I run `wp ability list --namespace=test-plugin --show-in-rest --field=name` Then STDOUT should be: """ test-plugin/public-ability """ - When I run `wp ability list --namespace=test-plugin --show-in-rest=false --field=name` + When I run `wp ability list --namespace=test-plugin --no-show-in-rest --field=name` Then STDOUT should be: """ test-plugin/private-ability """ - @require-wp-6.9 - Scenario: Reject invalid boolean filter values. - When I try `wp ability list --public=bogus` - Then STDERR should contain: - """ - Invalid value specified for 'public' - """ - - When I try `wp ability list --show-in-rest=bogus` - Then STDERR should contain: - """ - Invalid value specified for 'show-in-rest' - """ - - When I try `wp ability list --public=` - Then STDERR should contain: - """ - Invalid value specified for 'public' - """ - - When I try `wp ability list --show-in-rest=` - Then STDERR should contain: - """ - Invalid value specified for 'show-in-rest' - """ - @require-wp-7.1 Scenario: Display the public flag and its precedence over channel flags. Given a wp-content/mu-plugins/test-ability.php file: @@ -525,13 +499,13 @@ Feature: Manage abilities registered via the WordPress Abilities API. } ); """ - When I run `wp ability list --namespace=test-plugin --public=true --field=name` + When I run `wp ability list --namespace=test-plugin --public --field=name` Then STDOUT should be: """ test-plugin/public-ability """ - When I run `wp ability list --namespace=test-plugin --public=false --field=name` + When I run `wp ability list --namespace=test-plugin --no-public --field=name` Then STDOUT should be: """ test-plugin/internal-ability diff --git a/src/Ability_Command.php b/src/Ability_Command.php index f388d94..3a21351 100644 --- a/src/Ability_Command.php +++ b/src/Ability_Command.php @@ -108,21 +108,11 @@ class Ability_Command extends WP_CLI_Command { * [--namespace=] * : Filter abilities by namespace prefix (e.g., 'core' for 'core/*' abilities). * - * [--public=] - * : Filter abilities by the high-level client exposure flag. - * --- - * options: - * - true - * - false - * --- + * [--public] + * : Only list abilities flagged for client exposure. Pass --no-public to invert. * - * [--show-in-rest=] - * : Filter abilities by REST API exposure. - * --- - * options: - * - true - * - false - * --- + * [--show-in-rest] + * : Only list abilities exposed to the REST API. Pass --no-show-in-rest to invert. * * [--field=] * : Prints the value of a single field for each ability. @@ -184,13 +174,13 @@ class Ability_Command extends WP_CLI_Command { * $ wp ability list --namespace=core * * # List abilities exposed to REST API. - * $ wp ability list --show-in-rest=true + * $ wp ability list --show-in-rest * * # List abilities meant to be available to clients. - * $ wp ability list --public=true + * $ wp ability list --public * * # Find abilities that opt out of REST despite being public. - * $ wp ability list --public=true --show-in-rest=false + * $ wp ability list --public --no-show-in-rest * * # List abilities as JSON. * $ wp ability list --format=json @@ -232,23 +222,18 @@ public function list_( $args, $assoc_args ): void { } } - /* - * Filter by exposure if specified. The synopsis restricts these to - * 'true' or 'false', which WP-CLI validates before the command runs. - * The bare `--public` and `--show-in-rest` forms arrive as boolean true. - */ + // Filter by public if specified. if ( null !== $public ) { - $wanted_public = 'true' === $public || true === $public; $ability_public = '1' === $ability_data['public']; - if ( $wanted_public !== $ability_public ) { + if ( $public !== $ability_public ) { continue; } } + // Filter by show_in_rest if specified. if ( null !== $show_in_rest ) { - $wanted_rest = 'true' === $show_in_rest || true === $show_in_rest; $ability_rest = '1' === $ability_data['show_in_rest']; - if ( $wanted_rest !== $ability_rest ) { + if ( $show_in_rest !== $ability_rest ) { continue; } }