From 588d8b9be1b044c8d516473a694ca5e4a4a18898 Mon Sep 17 00:00:00 2001 From: Vincenzo Buttazzo Date: Fri, 11 Apr 2025 17:55:01 +0200 Subject: [PATCH 1/4] `wp post create`: Add JSON input support for `tax_input` Fixes this 10 year old issue https://github.com/wp-cli/wp-cli/issues/1323 (closed, but not fixed). `wp post create` parameter `--tax_input` is supposed to be an array, but no JSON parsing is done and therefore is unusable. --- src/Post_Command.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Post_Command.php b/src/Post_Command.php index a4a5ba42d..9a3f87485 100644 --- a/src/Post_Command.php +++ b/src/Post_Command.php @@ -186,7 +186,7 @@ public function create( $args, $assoc_args ) { WP_CLI::warning( "The 'meta_input' field was only introduced in WordPress 4.4 so will have no effect." ); } - $array_arguments = [ 'meta_input' ]; + $array_arguments = [ 'meta_input', 'tax_input' ]; $assoc_args = Utils\parse_shell_arrays( $assoc_args, $array_arguments ); if ( isset( $assoc_args['from-post'] ) ) { From f6e928ea6c1127febe995f9c52b98fa991d6dcb7 Mon Sep 17 00:00:00 2001 From: Pascal Birchler Date: Tue, 6 May 2025 20:33:10 +0200 Subject: [PATCH 2/4] Add (failing) test --- features/post.feature | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/features/post.feature b/features/post.feature index c3f72a522..078d212e6 100644 --- a/features/post.feature +++ b/features/post.feature @@ -395,6 +395,25 @@ Feature: Manage WordPress posts post-2 """ + Scenario: Creating/updating posts with taxonomies + When I run `wp term create category "First Category" --porcelain` + And save STDOUT as {CAT_1} + And I run `wp term create category "Second Category" --porcelain` + And save STDOUT as {CAT_2} + And I run `wp term create post_tag "Term One" --porcelain` + And I run `wp term create post_tag "Term Two" --porcelain` + When I run `wp post create --post_title='Test Post' --post_content='Test post content' --tax_input='{"category":[{CAT_1},{CAT_2}],"post_tag":["term-one", "term-two"]}' --porcelain` + Then STDOUT should be a number + And save STDOUT as {POST_ID} + + When I run `wp post term list {POST_ID} category post_tag --format=table --fields=name,taxonomy` + Then STDOUT should be a table containing rows: + | name | taxonomy | + | First Category | category | + | Second Category | category | + | Term One | post_tag | + | Term Two | post_tag | + Scenario: Update categories on a post When I run `wp term create category "Test Category" --porcelain` Then save STDOUT as {TERM_ID} From 3d21eefe46763bd7dd424ad264ead1501889a00f Mon Sep 17 00:00:00 2001 From: Pascal Birchler Date: Tue, 11 Nov 2025 14:16:20 +0100 Subject: [PATCH 3/4] Fix Gherkin lint issue --- features/post.feature | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/features/post.feature b/features/post.feature index 44e488645..1db5bcc18 100644 --- a/features/post.feature +++ b/features/post.feature @@ -402,7 +402,7 @@ Feature: Manage WordPress posts And save STDOUT as {CAT_2} And I run `wp term create post_tag "Term One" --porcelain` And I run `wp term create post_tag "Term Two" --porcelain` - When I run `wp post create --post_title='Test Post' --post_content='Test post content' --tax_input='{"category":[{CAT_1},{CAT_2}],"post_tag":["term-one", "term-two"]}' --porcelain` + And I run `wp post create --post_title='Test Post' --post_content='Test post content' --tax_input='{"category":[{CAT_1},{CAT_2}],"post_tag":["term-one", "term-two"]}' --porcelain` Then STDOUT should be a number And save STDOUT as {POST_ID} From 3e8991081ba172903736f16275a79a017e92629e Mon Sep 17 00:00:00 2001 From: Pascal Birchler Date: Fri, 13 Mar 2026 11:27:38 +0100 Subject: [PATCH 4/4] Add workaround --- features/post.feature | 11 ++++++ src/Post_Command.php | 78 +++++++++++++++++++++++++++++++++++++++++-- 2 files changed, 86 insertions(+), 3 deletions(-) diff --git a/features/post.feature b/features/post.feature index ae7b6d315..c480a6f93 100644 --- a/features/post.feature +++ b/features/post.feature @@ -440,6 +440,17 @@ Feature: Manage WordPress posts | Second Category | category | | Term One | post_tag | | Term Two | post_tag | + When I run `wp post update {POST_ID} --tax_input='{"category":[{CAT_1}],"post_tag":["term-one"]}'` + Then STDOUT should contain: + """ + Success: Updated post {POST_ID}. + """ + + When I run `wp post term list {POST_ID} category post_tag --format=table --fields=name,taxonomy` + Then STDOUT should be a table containing rows: + | name | taxonomy | + | First Category | category | + | Term One | post_tag | Scenario: Update categories on a post When I run `wp term create category "Test Category" --porcelain` diff --git a/src/Post_Command.php b/src/Post_Command.php index a0b28cc06..5e10c546b 100644 --- a/src/Post_Command.php +++ b/src/Post_Command.php @@ -122,6 +122,8 @@ public function __construct() { * [--tax_input=] * : Array of taxonomy terms keyed by their taxonomy name. Default empty. * + * Note: In WordPress core, this normally requires a user context to satisfy capability checks. WP-CLI bypasses this for convenience. See https://core.trac.wordpress.org/ticket/19373 + * * [--meta_input=] * : Array in JSON format of post meta values keyed by their post meta key. Default empty. * @@ -212,7 +214,41 @@ public function create( $args, $assoc_args ) { $args, $assoc_args, function ( $params ) { - return wp_insert_post( $params, true ); + $filter_callback = null; + + if ( 0 === get_current_user_id() && ! empty( $params['tax_input'] ) ) { + $allowed_caps = []; + /** + * @var string $taxonomy + */ + foreach ( array_keys( $params['tax_input'] ) as $taxonomy ) { + $tax_obj = get_taxonomy( $taxonomy ); + if ( $tax_obj ) { + $primitive_caps = map_meta_cap( $tax_obj->cap->assign_terms, 0 ); + $allowed_caps = array_merge( $allowed_caps, $primitive_caps ); + } + } + + if ( ! empty( $allowed_caps ) ) { + $filter_callback = function ( $allcaps, $caps ) use ( $allowed_caps ) { + foreach ( $caps as $cap ) { + if ( in_array( $cap, $allowed_caps, true ) ) { + $allcaps[ $cap ] = true; + } + } + return $allcaps; + }; + add_filter( 'user_has_cap', $filter_callback, 10, 2 ); + } + } + + $result = wp_insert_post( $params, true ); + + if ( $filter_callback ) { + remove_filter( 'user_has_cap', $filter_callback ); + } + + return $result; } ); } @@ -297,6 +333,8 @@ function ( $params ) { * [--tax_input=] * : Array of taxonomy terms keyed by their taxonomy name. Default empty. * + * Note: In WordPress core, this normally requires a user context to satisfy capability checks. WP-CLI bypasses this for convenience. See https://core.trac.wordpress.org/ticket/19373 + * * [--meta_input=] * : Array in JSON format of post meta values keyed by their post meta key. Default empty. * @@ -348,7 +386,7 @@ public function update( $args, $assoc_args ) { $assoc_args['post_category'] = $this->get_category_ids( $assoc_args['post_category'] ); } - $array_arguments = [ 'meta_input' ]; + $array_arguments = [ 'meta_input', 'tax_input' ]; $assoc_args = Utils\parse_shell_arrays( $assoc_args, $array_arguments ); $assoc_args = wp_slash( $assoc_args ); @@ -356,7 +394,41 @@ public function update( $args, $assoc_args ) { $args, $assoc_args, function ( $params ) { - return wp_update_post( $params, true ); + $filter_callback = null; + + if ( 0 === get_current_user_id() && ! empty( $params['tax_input'] ) ) { + $allowed_caps = []; + /** + * @var string $taxonomy + */ + foreach ( array_keys( $params['tax_input'] ) as $taxonomy ) { + $tax_obj = get_taxonomy( $taxonomy ); + if ( $tax_obj ) { + $primitive_caps = map_meta_cap( $tax_obj->cap->assign_terms, 0 ); + $allowed_caps = array_merge( $allowed_caps, $primitive_caps ); + } + } + + if ( ! empty( $allowed_caps ) ) { + $filter_callback = function ( $allcaps, $caps ) use ( $allowed_caps ) { + foreach ( $caps as $cap ) { + if ( in_array( $cap, $allowed_caps, true ) ) { + $allcaps[ $cap ] = true; + } + } + return $allcaps; + }; + add_filter( 'user_has_cap', $filter_callback, 10, 2 ); + } + } + + $result = wp_update_post( $params, true ); + + if ( $filter_callback ) { + remove_filter( 'user_has_cap', $filter_callback ); + } + + return $result; } ); }