From bc6035ddb0aabb9c589b7c946b6915e3c96f1d42 Mon Sep 17 00:00:00 2001 From: Philip John Date: Thu, 5 Jun 2025 15:38:27 +0200 Subject: [PATCH 01/21] 96: Fix output formats for the `theme mod get` command --- src/Theme_Mod_Command.php | 122 ++++++++++++++++++++++++++++---------- 1 file changed, 90 insertions(+), 32 deletions(-) diff --git a/src/Theme_Mod_Command.php b/src/Theme_Mod_Command.php index 9aea4c878..e35decbe3 100644 --- a/src/Theme_Mod_Command.php +++ b/src/Theme_Mod_Command.php @@ -85,51 +85,109 @@ public function get( $args = array(), $assoc_args = array() ) { $args = array(); } + // This array will hold the list of theme mods in a format suitable for the WP CLI Formatter. $list = array(); - $mods = get_theme_mods(); + + // If specific mods are requested, filter out any that aren't requested. + $mods = ! empty( $args ) ? array_intersect_key( get_theme_mods(), array_flip( $args ) ) : get_theme_mods(); if ( ! is_array( $mods ) ) { - // If no mods are set (perhaps new theme), make sure foreach still works. + // If no mods are set (perhaps new theme), make sure array_intersect_key still works. $mods = array(); } - foreach ( $mods as $k => $v ) { - // If mods were given, skip the others. - if ( ! empty( $args ) && ! in_array( $k, $args, true ) ) { - continue; - } - if ( is_array( $v ) ) { - $list[] = [ - 'key' => $k, - 'value' => '=>', - ]; - foreach ( $v as $_k => $_v ) { - $list[] = [ - 'key' => " $_k", - 'value' => $_v, - ]; - } - } else { - $list[] = [ - 'key' => $k, - 'value' => $v, - ]; + // Generate the list of items ready for output. We use an initial separator that we can replace later depending on format. + $separator = '!!!'; + array_walk( + $mods, + function( $value, $key ) use ( &$list, $separator ) { + $this->mod_to_string( $key, $value, $list, $separator ); } - } + ); - // For unset mods, show blank value. - foreach ( $args as $mod ) { - if ( ! isset( $mods[ $mod ] ) ) { - $list[] = [ - 'key' => $mod, - 'value' => '', - ]; - } + // Take our Formatter-friendly list and adjust it according to the requested format. + switch ( \WP_CLI\Utils\get_flag_value( $assoc_args, 'format' ) ) { + // For tables we use a double space to indent child items. + case 'table': + $list = array_map( + function( $item ) { + $parts = explode( '!!!', $item['key'] ); + $new_key = array_pop( $parts ); + if ( ! empty( $parts ) ) { + $new_key = str_repeat( ' ', count( $parts ) ) . $new_key; + } + return [ + 'key' => $new_key, + 'value' => $item['value'], + ]; + }, + $list + ); + break; + + // For JSON, CSV, and YAML formats we use dot notation to show the hierarchy. + case 'csv': + case 'yaml': + case 'json': + $list = array_filter( array_map( + function( $item ) { + return [ + 'key' => str_replace( '!!!', '.', $item['key'] ), + 'value' => $item['value'], + ]; + }, + $list + ), function( $item ) { + return ! empty( $item['value'] ); + } ); + break; } + // Output the list using the WP CLI Formatter. $formatter = new \WP_CLI\Formatter( $assoc_args, $this->fields, 'thememods' ); $formatter->display_items( $list ); } + /** + * Convert the theme mods to a flattened array with a string representation of the keys. + * + * @param string $key The mod key + * @param mixed $value The value of the mod. + * @param array $list The list so far, passed by reference. + * @param string $separator A string to separate keys to denote their place in the tree. + */ + private function mod_to_string( $key, $value, &$list, $separator ) { + if ( is_array( $value ) ){ + if ( empty( $value ) ) { + // Explicitly handle empty arrays to ensure they are displayed. + $list[] = array( + 'key' => $key, + 'value' => '[empty array]', + ); + return; + } + + // Arrays get their own entry in the list to allow for sensible table output. + $list[] = array( + 'key' => $key, + 'value' => '', + ); + + foreach ( $value as $child_key => $child_value ) { + $this->mod_to_string( $key . $separator . $child_key, $child_value, $list, $separator ); + } + } else { + // Explicitly handle false values to ensure they are displayed. + if ( false === $value ) { + $value = '[false]'; + } + + $list[] = array( + 'key' => $key, + 'value' => $value, + ); + } + } + /** * Gets a list of theme mods. * From 84891e74bcededdb7a2a033ba868d9ba150f2ee6 Mon Sep 17 00:00:00 2001 From: Philip John Date: Thu, 5 Jun 2025 15:48:26 +0200 Subject: [PATCH 02/21] 96: Handle both booleans --- src/Theme_Mod_Command.php | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/Theme_Mod_Command.php b/src/Theme_Mod_Command.php index e35decbe3..487cb79a2 100644 --- a/src/Theme_Mod_Command.php +++ b/src/Theme_Mod_Command.php @@ -176,9 +176,9 @@ private function mod_to_string( $key, $value, &$list, $separator ) { $this->mod_to_string( $key . $separator . $child_key, $child_value, $list, $separator ); } } else { - // Explicitly handle false values to ensure they are displayed. - if ( false === $value ) { - $value = '[false]'; + // Explicitly handle boolean values to ensure they are displayed correctly. + if ( is_bool( $value ) ) { + $value = $value ? '[true]' : '[false]'; } $list[] = array( From d80db8f743e53eb8c1e1d60a2c6d61e6d535bf16 Mon Sep 17 00:00:00 2001 From: Philip John Date: Thu, 5 Jun 2025 15:57:58 +0200 Subject: [PATCH 03/21] 96: Handle objects --- src/Theme_Mod_Command.php | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/src/Theme_Mod_Command.php b/src/Theme_Mod_Command.php index 487cb79a2..31e42cd11 100644 --- a/src/Theme_Mod_Command.php +++ b/src/Theme_Mod_Command.php @@ -156,9 +156,12 @@ function( $item ) { * @param string $separator A string to separate keys to denote their place in the tree. */ private function mod_to_string( $key, $value, &$list, $separator ) { - if ( is_array( $value ) ){ + if ( is_array( $value ) || is_object( $value ) ) { + // Convert objects to arrays for easier handling. + $value = (array) $value; + + // Explicitly handle empty arrays to ensure they are displayed. if ( empty( $value ) ) { - // Explicitly handle empty arrays to ensure they are displayed. $list[] = array( 'key' => $key, 'value' => '[empty array]', From e3546141730b69538cf2640a4bef0e42c6179bdf Mon Sep 17 00:00:00 2001 From: Philip John Date: Thu, 5 Jun 2025 16:01:53 +0200 Subject: [PATCH 04/21] 96: Fix formatting and rename variable to please the CS Gods --- src/Theme_Mod_Command.php | 51 +++++++++++++++++++++------------------ 1 file changed, 27 insertions(+), 24 deletions(-) diff --git a/src/Theme_Mod_Command.php b/src/Theme_Mod_Command.php index 31e42cd11..cb1f0f4e4 100644 --- a/src/Theme_Mod_Command.php +++ b/src/Theme_Mod_Command.php @@ -86,7 +86,7 @@ public function get( $args = array(), $assoc_args = array() ) { } // This array will hold the list of theme mods in a format suitable for the WP CLI Formatter. - $list = array(); + $mod_list = array(); // If specific mods are requested, filter out any that aren't requested. $mods = ! empty( $args ) ? array_intersect_key( get_theme_mods(), array_flip( $args ) ) : get_theme_mods(); @@ -99,8 +99,8 @@ public function get( $args = array(), $assoc_args = array() ) { $separator = '!!!'; array_walk( $mods, - function( $value, $key ) use ( &$list, $separator ) { - $this->mod_to_string( $key, $value, $list, $separator ); + function ( $value, $key ) use ( &$mod_list, $separator ) { + $this->mod_to_string( $key, $value, $mod_list, $separator ); } ); @@ -108,8 +108,8 @@ function( $value, $key ) use ( &$list, $separator ) { switch ( \WP_CLI\Utils\get_flag_value( $assoc_args, 'format' ) ) { // For tables we use a double space to indent child items. case 'table': - $list = array_map( - function( $item ) { + $mod_list = array_map( + function ( $item ) { $parts = explode( '!!!', $item['key'] ); $new_key = array_pop( $parts ); if ( ! empty( $parts ) ) { @@ -120,7 +120,7 @@ function( $item ) { 'value' => $item['value'], ]; }, - $list + $mod_list ); break; @@ -128,23 +128,26 @@ function( $item ) { case 'csv': case 'yaml': case 'json': - $list = array_filter( array_map( - function( $item ) { - return [ - 'key' => str_replace( '!!!', '.', $item['key'] ), - 'value' => $item['value'], - ]; - }, - $list - ), function( $item ) { - return ! empty( $item['value'] ); - } ); + $mod_list = array_filter( + array_map( + function ( $item ) { + return [ + 'key' => str_replace( '!!!', '.', $item['key'] ), + 'value' => $item['value'], + ]; + }, + $mod_list + ), + function ( $item ) { + return ! empty( $item['value'] ); + } + ); break; } // Output the list using the WP CLI Formatter. $formatter = new \WP_CLI\Formatter( $assoc_args, $this->fields, 'thememods' ); - $formatter->display_items( $list ); + $formatter->display_items( $mod_list ); } /** @@ -152,17 +155,17 @@ function( $item ) { * * @param string $key The mod key * @param mixed $value The value of the mod. - * @param array $list The list so far, passed by reference. + * @param array $mod_list The list so far, passed by reference. * @param string $separator A string to separate keys to denote their place in the tree. */ - private function mod_to_string( $key, $value, &$list, $separator ) { + private function mod_to_string( $key, $value, &$mod_list, $separator ) { if ( is_array( $value ) || is_object( $value ) ) { // Convert objects to arrays for easier handling. $value = (array) $value; // Explicitly handle empty arrays to ensure they are displayed. if ( empty( $value ) ) { - $list[] = array( + $mod_list[] = array( 'key' => $key, 'value' => '[empty array]', ); @@ -170,13 +173,13 @@ private function mod_to_string( $key, $value, &$list, $separator ) { } // Arrays get their own entry in the list to allow for sensible table output. - $list[] = array( + $mod_list[] = array( 'key' => $key, 'value' => '', ); foreach ( $value as $child_key => $child_value ) { - $this->mod_to_string( $key . $separator . $child_key, $child_value, $list, $separator ); + $this->mod_to_string( $key . $separator . $child_key, $child_value, $mod_list, $separator ); } } else { // Explicitly handle boolean values to ensure they are displayed correctly. @@ -184,7 +187,7 @@ private function mod_to_string( $key, $value, &$list, $separator ) { $value = $value ? '[true]' : '[false]'; } - $list[] = array( + $mod_list[] = array( 'key' => $key, 'value' => $value, ); From 561cfc862f7221b8fcd3dc7e99daf243ee0759ca Mon Sep 17 00:00:00 2001 From: Philip John Date: Fri, 6 Jun 2025 10:08:46 +0200 Subject: [PATCH 05/21] 96: Remove unnecessary array check already done by get_theme_mods() --- src/Theme_Mod_Command.php | 4 ---- 1 file changed, 4 deletions(-) diff --git a/src/Theme_Mod_Command.php b/src/Theme_Mod_Command.php index cb1f0f4e4..2eb32b756 100644 --- a/src/Theme_Mod_Command.php +++ b/src/Theme_Mod_Command.php @@ -90,10 +90,6 @@ public function get( $args = array(), $assoc_args = array() ) { // If specific mods are requested, filter out any that aren't requested. $mods = ! empty( $args ) ? array_intersect_key( get_theme_mods(), array_flip( $args ) ) : get_theme_mods(); - if ( ! is_array( $mods ) ) { - // If no mods are set (perhaps new theme), make sure array_intersect_key still works. - $mods = array(); - } // Generate the list of items ready for output. We use an initial separator that we can replace later depending on format. $separator = '!!!'; From 4b0aa30f34e3226e289a858549b14373c2308efd Mon Sep 17 00:00:00 2001 From: Philip John Date: Fri, 6 Jun 2025 10:09:22 +0200 Subject: [PATCH 06/21] 96: Use tab character as separator --- src/Theme_Mod_Command.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Theme_Mod_Command.php b/src/Theme_Mod_Command.php index 2eb32b756..aadfec9e7 100644 --- a/src/Theme_Mod_Command.php +++ b/src/Theme_Mod_Command.php @@ -92,7 +92,7 @@ public function get( $args = array(), $assoc_args = array() ) { $mods = ! empty( $args ) ? array_intersect_key( get_theme_mods(), array_flip( $args ) ) : get_theme_mods(); // Generate the list of items ready for output. We use an initial separator that we can replace later depending on format. - $separator = '!!!'; + $separator = '\t'; array_walk( $mods, function ( $value, $key ) use ( &$mod_list, $separator ) { From 26b9affe6b88a799f2d5e58748f99f16c1e1eca4 Mon Sep 17 00:00:00 2001 From: Philip John Date: Fri, 6 Jun 2025 10:11:44 +0200 Subject: [PATCH 07/21] 96: Import Utils for much more handsome code --- src/Theme_Mod_Command.php | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/src/Theme_Mod_Command.php b/src/Theme_Mod_Command.php index aadfec9e7..bd136de4c 100644 --- a/src/Theme_Mod_Command.php +++ b/src/Theme_Mod_Command.php @@ -1,5 +1,7 @@ Date: Fri, 6 Jun 2025 10:12:39 +0200 Subject: [PATCH 08/21] 96: Make the array_map callbacks static --- src/Theme_Mod_Command.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Theme_Mod_Command.php b/src/Theme_Mod_Command.php index bd136de4c..d69ce35c9 100644 --- a/src/Theme_Mod_Command.php +++ b/src/Theme_Mod_Command.php @@ -107,7 +107,7 @@ function ( $value, $key ) use ( &$mod_list, $separator ) { // For tables we use a double space to indent child items. case 'table': $mod_list = array_map( - function ( $item ) { + static function ( $item ) { $parts = explode( '!!!', $item['key'] ); $new_key = array_pop( $parts ); if ( ! empty( $parts ) ) { @@ -128,7 +128,7 @@ function ( $item ) { case 'json': $mod_list = array_filter( array_map( - function ( $item ) { + static function ( $item ) { return [ 'key' => str_replace( '!!!', '.', $item['key'] ), 'value' => $item['value'], From 76025d7074fe054697ee12f309af13ae3f23cd81 Mon Sep 17 00:00:00 2001 From: Philip John Date: Fri, 6 Jun 2025 10:13:55 +0200 Subject: [PATCH 09/21] 96: Set the separator once --- src/Theme_Mod_Command.php | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/Theme_Mod_Command.php b/src/Theme_Mod_Command.php index d69ce35c9..cbff4a5f1 100644 --- a/src/Theme_Mod_Command.php +++ b/src/Theme_Mod_Command.php @@ -107,8 +107,8 @@ function ( $value, $key ) use ( &$mod_list, $separator ) { // For tables we use a double space to indent child items. case 'table': $mod_list = array_map( - static function ( $item ) { - $parts = explode( '!!!', $item['key'] ); + static function ( $item ) use ( $separator ) { + $parts = explode( $separator, $item['key'] ); $new_key = array_pop( $parts ); if ( ! empty( $parts ) ) { $new_key = str_repeat( ' ', count( $parts ) ) . $new_key; @@ -128,9 +128,9 @@ static function ( $item ) { case 'json': $mod_list = array_filter( array_map( - static function ( $item ) { + static function ( $item ) use ( $separator ) { return [ - 'key' => str_replace( '!!!', '.', $item['key'] ), + 'key' => str_replace( $separator, '.', $item['key'] ), 'value' => $item['value'], ]; }, From 97f2985a6c2b9a113939b5f174b286b9e28181f6 Mon Sep 17 00:00:00 2001 From: Pascal Birchler Date: Thu, 12 Mar 2026 13:39:52 +0100 Subject: [PATCH 10/21] Update src/Theme_Mod_Command.php Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- src/Theme_Mod_Command.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Theme_Mod_Command.php b/src/Theme_Mod_Command.php index 6760f0c7e..cd266ad22 100644 --- a/src/Theme_Mod_Command.php +++ b/src/Theme_Mod_Command.php @@ -140,7 +140,7 @@ static function ( $item ) use ( $separator ) { $mod_list ), function ( $item ) { - return ! empty( $item['value'] ); + return $item['value'] !== '' && $item['value'] !== null; } ); break; From 2d70f93af0269efbba470e82b99dbf4962721fdb Mon Sep 17 00:00:00 2001 From: Pascal Birchler Date: Thu, 19 Mar 2026 13:42:45 +0100 Subject: [PATCH 11/21] Apply suggestions from code review Co-authored-by: Pascal Birchler --- src/Theme_Mod_Command.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Theme_Mod_Command.php b/src/Theme_Mod_Command.php index cd266ad22..883e1453d 100644 --- a/src/Theme_Mod_Command.php +++ b/src/Theme_Mod_Command.php @@ -1,6 +1,6 @@ Date: Thu, 4 Jun 2026 11:21:36 +0100 Subject: [PATCH 12/21] 96: Restore all theme mod keys passed in via arguments --- src/Theme_Mod_Command.php | 23 ++++++++++++++++------- 1 file changed, 16 insertions(+), 7 deletions(-) diff --git a/src/Theme_Mod_Command.php b/src/Theme_Mod_Command.php index 883e1453d..ae68086f4 100644 --- a/src/Theme_Mod_Command.php +++ b/src/Theme_Mod_Command.php @@ -93,17 +93,26 @@ public function get( $args, $assoc_args ) { // This array will hold the list of theme mods in a format suitable for the WP CLI Formatter. $mod_list = array(); - // If specific mods are requested, filter out any that aren't requested. - $mods = ! empty( $args ) ? array_intersect_key( get_theme_mods(), array_flip( $args ) ) : get_theme_mods(); + // If specific mods are requested, fetch only those, setting missing mods to null. Otherwise, fetch all mods. + $mods = array(); + if ( ! empty( $args ) ) { + foreach ( $args as $mod ) { + $mods[ $mod ] = get_theme_mod( $mod, null ); + } + } else { + $mods = get_theme_mods(); + } // Generate the list of items ready for output. We use an initial separator that we can replace later depending on format. $separator = '\t'; - array_walk( - $mods, - function ( $value, $key ) use ( &$mod_list, $separator ) { - $this->mod_to_string( $key, $value, $mod_list, $separator ); + foreach ( $mods as $key => $value) { + // If mods were given, skip the others. + if ( ! empty( $args ) && ! in_array( $key, $args, true ) ) { + continue; } - ); + + $this->mod_to_string( $key, $value, $mod_list, $separator ); + } // Take our Formatter-friendly list and adjust it according to the requested format. switch ( Utils\get_flag_value( $assoc_args, 'format' ) ) { From 11a8de3810cfeb3f64c011ce520931a354d4999f Mon Sep 17 00:00:00 2001 From: Philip John Date: Thu, 4 Jun 2026 11:24:27 +0100 Subject: [PATCH 13/21] 96: Add missing space --- src/Theme_Mod_Command.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Theme_Mod_Command.php b/src/Theme_Mod_Command.php index ae68086f4..27426b419 100644 --- a/src/Theme_Mod_Command.php +++ b/src/Theme_Mod_Command.php @@ -105,7 +105,7 @@ public function get( $args, $assoc_args ) { // Generate the list of items ready for output. We use an initial separator that we can replace later depending on format. $separator = '\t'; - foreach ( $mods as $key => $value) { + foreach ( $mods as $key => $value ) { // If mods were given, skip the others. if ( ! empty( $args ) && ! in_array( $key, $args, true ) ) { continue; From 6cbe2673ae731ba67520f5f0ff8fcca54377dd8e Mon Sep 17 00:00:00 2001 From: Philip John Date: Thu, 4 Jun 2026 13:38:41 +0200 Subject: [PATCH 14/21] 96: Ensure empty values are included in all output formats --- src/Theme_Mod_Command.php | 21 ++++++++------------- 1 file changed, 8 insertions(+), 13 deletions(-) diff --git a/src/Theme_Mod_Command.php b/src/Theme_Mod_Command.php index 27426b419..edeec4fe7 100644 --- a/src/Theme_Mod_Command.php +++ b/src/Theme_Mod_Command.php @@ -138,19 +138,14 @@ static function ( $item ) use ( $separator ) { case 'csv': case 'yaml': case 'json': - $mod_list = array_filter( - array_map( - static function ( $item ) use ( $separator ) { - return [ - 'key' => str_replace( $separator, '.', $item['key'] ), - 'value' => $item['value'], - ]; - }, - $mod_list - ), - function ( $item ) { - return '' !== $item['value'] && null !== $item['value']; - } + $mod_list = array_map( + static function ( $item ) use ( $separator ) { + return [ + 'key' => str_replace( $separator, '.', $item['key'] ), + 'value' => $item['value'], + ]; + }, + $mod_list ); break; } From eff291444d2aff30e283d3dc9787ba1142d887a9 Mon Sep 17 00:00:00 2001 From: Philip John Date: Thu, 4 Jun 2026 13:39:14 +0200 Subject: [PATCH 15/21] 96: Add Behat tests covering all formats for theme get mod --- features/theme-mod.feature | 80 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 80 insertions(+) diff --git a/features/theme-mod.feature b/features/theme-mod.feature index 831256888..336cb8ecf 100644 --- a/features/theme-mod.feature +++ b/features/theme-mod.feature @@ -7,6 +7,22 @@ Feature: Manage WordPress theme mods Then STDOUT should be a table containing rows: | key | value | + When I run `wp theme mod get --all --format=csv` + Then STDOUT should be CSV containing: + | key | value | + + When I run `wp theme mod get --all --format=json` + Then STDOUT should be: + """ + [] + """ + + When I run `wp theme mod get --all --format=yaml` + Then STDOUT should be: + """ + --- + """ + When I try `wp theme mod get` Then STDERR should contain: """ @@ -21,12 +37,52 @@ Feature: Manage WordPress theme mods | key | value | | background_color | 123456 | + When I run `wp theme mod get --all --format=csv` + Then STDOUT should be CSV containing: + | key | value | + | background_color | 123456 | + + + When I run `wp theme mod get --all --format=json` + Then STDOUT should be JSON containing: + """ + [{"key":"background_color","value":"123456"}] + """ + + When I run `wp theme mod get --all --format=yaml` + Then STDOUT should be YAML containing: + """ + --- + -- + key: background_color + value: "123456" + """ + When I run `wp theme mod get background_color --field=value` Then STDOUT should be: """ 123456 """ + When I run `wp theme mod get background_color --field=value --format=csv` + Then STDOUT should be: + """ + 123456 + """ + + When I run `wp theme mod get background_color --field=value --format=json` + Then STDOUT should be: + """ + ["123456"] + """ + + When I run `wp theme mod get background_color --field=value --format=yaml` + Then STDOUT should be YAML containing: + """ + --- + - "123456" + """ + When I run `wp theme mod set background_color 123456` And I run `wp theme mod get background_color header_textcolor` Then STDOUT should be a table containing rows: @@ -34,6 +90,30 @@ Feature: Manage WordPress theme mods | background_color | 123456 | | header_textcolor | | + When I run `wp theme mod get background_color header_textcolor --format=csv` + Then STDOUT should be CSV containing: + | key | value | + | background_color | 123456 | + | header_textcolor | | + + When I run `wp theme mod get background_color header_textcolor --format=json` + Then STDOUT should be JSON containing: + """ + [{"key":"background_color","value":"123456"},{"key":"header_textcolor","value":null}] + """ + + When I run `wp theme mod get background_color header_textcolor --format=yaml` + Then STDOUT should be YAML containing: + """ + --- + -- + key: background_color + value: "123456" + -- + key: header_textcolor + value: null + """ + Scenario: Setting theme mods Given a WP install From 658811d735bfd2f0eaa405c10f4ed033654d0e2b Mon Sep 17 00:00:00 2001 From: Alain Schlesser Date: Tue, 21 Jul 2026 09:07:02 +0200 Subject: [PATCH 16/21] Fix warning when getting mods on a theme with none set get_theme_mods() returns false rather than an empty array when the current theme has no mods stored (e.g. a fresh install on older WP). Iterating that false value emitted an "Invalid argument supplied for foreach()" warning to STDERR, which failed the WP 4.9 acceptance test. Guard the return value and fall back to an empty array. --- src/Theme_Mod_Command.php | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/Theme_Mod_Command.php b/src/Theme_Mod_Command.php index edeec4fe7..c54cd7b81 100644 --- a/src/Theme_Mod_Command.php +++ b/src/Theme_Mod_Command.php @@ -101,6 +101,11 @@ public function get( $args, $assoc_args ) { } } else { $mods = get_theme_mods(); + + // A theme with no mods set returns false rather than an empty array. + if ( ! is_array( $mods ) ) { + $mods = []; + } } // Generate the list of items ready for output. We use an initial separator that we can replace later depending on format. From 55e0d6492981e599b068c766029f87b954310528 Mon Sep 17 00:00:00 2001 From: Alain Schlesser Date: Tue, 21 Jul 2026 09:07:35 +0200 Subject: [PATCH 17/21] Use short array syntax in theme mod get Match the short array notation (`[]`) used across the rest of the codebase instead of the long `array()` form. --- src/Theme_Mod_Command.php | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/src/Theme_Mod_Command.php b/src/Theme_Mod_Command.php index c54cd7b81..c15a8ae6f 100644 --- a/src/Theme_Mod_Command.php +++ b/src/Theme_Mod_Command.php @@ -87,14 +87,14 @@ public function get( $args, $assoc_args ) { } if ( Utils\get_flag_value( $assoc_args, 'all' ) ) { - $args = array(); + $args = []; } // This array will hold the list of theme mods in a format suitable for the WP CLI Formatter. - $mod_list = array(); + $mod_list = []; // If specific mods are requested, fetch only those, setting missing mods to null. Otherwise, fetch all mods. - $mods = array(); + $mods = []; if ( ! empty( $args ) ) { foreach ( $args as $mod ) { $mods[ $mod ] = get_theme_mod( $mod, null ); @@ -175,18 +175,18 @@ private function mod_to_string( $key, $value, &$mod_list, $separator ) { // Explicitly handle empty arrays to ensure they are displayed. if ( empty( $value ) ) { - $mod_list[] = array( + $mod_list[] = [ 'key' => $key, 'value' => '[empty array]', - ); + ]; return; } // Arrays get their own entry in the list to allow for sensible table output. - $mod_list[] = array( + $mod_list[] = [ 'key' => $key, 'value' => '', - ); + ]; foreach ( $value as $child_key => $child_value ) { $this->mod_to_string( $key . $separator . $child_key, $child_value, $mod_list, $separator ); @@ -197,10 +197,10 @@ private function mod_to_string( $key, $value, &$mod_list, $separator ) { $value = $value ? '[true]' : '[false]'; } - $mod_list[] = array( + $mod_list[] = [ 'key' => $key, 'value' => $value, - ); + ]; } } From c8fb93940785e495dace62d9c86517c32df54a92 Mon Sep 17 00:00:00 2001 From: Alain Schlesser Date: Tue, 21 Jul 2026 09:12:39 +0200 Subject: [PATCH 18/21] Use native value types for machine-readable output formats The table format is meant for humans, so booleans and empty arrays keep their readable placeholders ([true], [false], [empty array]). For the json, csv, and yaml formats those placeholders discarded the value type, handing consumers the string "[false]" instead of a real boolean. Keep the native types for the machine-readable formats: booleans and empty arrays now serialize as-is. CSV is a flat format that cannot carry an array, so empty arrays are JSON-encoded there to avoid an "Array to string conversion" warning. --- src/Theme_Mod_Command.php | 42 +++++++++++++++++++++++++++------------ 1 file changed, 29 insertions(+), 13 deletions(-) diff --git a/src/Theme_Mod_Command.php b/src/Theme_Mod_Command.php index c15a8ae6f..8d9f9aeed 100644 --- a/src/Theme_Mod_Command.php +++ b/src/Theme_Mod_Command.php @@ -120,8 +120,10 @@ public function get( $args, $assoc_args ) { } // Take our Formatter-friendly list and adjust it according to the requested format. - switch ( Utils\get_flag_value( $assoc_args, 'format' ) ) { - // For tables we use a double space to indent child items. + $format = Utils\get_flag_value( $assoc_args, 'format', 'table' ); + switch ( $format ) { + // For tables we use a double space to indent child items, and render + // types like booleans and empty arrays as readable placeholders. case 'table': $mod_list = array_map( static function ( $item ) use ( $separator ) { @@ -130,24 +132,39 @@ static function ( $item ) use ( $separator ) { if ( ! empty( $parts ) ) { $new_key = str_repeat( ' ', count( $parts ) ) . $new_key; } + + $value = $item['value']; + if ( is_bool( $value ) ) { + $value = $value ? '[true]' : '[false]'; + } elseif ( is_array( $value ) ) { + $value = '[empty array]'; + } + return [ 'key' => $new_key, - 'value' => $item['value'], + 'value' => $value, ]; }, $mod_list ); break; - // For JSON, CSV, and YAML formats we use dot notation to show the hierarchy. + // For JSON, CSV, and YAML formats we use dot notation to show the + // hierarchy and keep the native value types. CSV is a flat format + // that cannot represent an array, so those are JSON-encoded. case 'csv': case 'yaml': case 'json': $mod_list = array_map( - static function ( $item ) use ( $separator ) { + static function ( $item ) use ( $separator, $format ) { + $value = $item['value']; + if ( 'csv' === $format && is_array( $value ) ) { + $value = (string) wp_json_encode( $value ); + } + return [ 'key' => str_replace( $separator, '.', $item['key'] ), - 'value' => $item['value'], + 'value' => $value, ]; }, $mod_list @@ -173,11 +190,13 @@ private function mod_to_string( $key, $value, &$mod_list, $separator ) { // Convert objects to arrays for easier handling. $value = (array) $value; - // Explicitly handle empty arrays to ensure they are displayed. + // Explicitly handle empty arrays to ensure they are displayed. The + // value is kept native here and only rendered as a placeholder for + // the table format. if ( empty( $value ) ) { $mod_list[] = [ 'key' => $key, - 'value' => '[empty array]', + 'value' => [], ]; return; } @@ -192,11 +211,8 @@ private function mod_to_string( $key, $value, &$mod_list, $separator ) { $this->mod_to_string( $key . $separator . $child_key, $child_value, $mod_list, $separator ); } } else { - // Explicitly handle boolean values to ensure they are displayed correctly. - if ( is_bool( $value ) ) { - $value = $value ? '[true]' : '[false]'; - } - + // Scalars (including booleans) are kept as their native type here. + // The table format renders booleans as readable placeholders later. $mod_list[] = [ 'key' => $key, 'value' => $value, From e9c2d29e793956b6a26518786122e02b4dcfbe9d Mon Sep 17 00:00:00 2001 From: Alain Schlesser Date: Tue, 21 Jul 2026 09:14:35 +0200 Subject: [PATCH 19/21] Add acceptance tests for theme mod value-type formatting Cover booleans and empty arrays across the output formats: the table format renders them as readable placeholders, while json and csv keep the native types. --- features/theme-mod.feature | 28 ++++++++++++++++++++++++++++ 1 file changed, 28 insertions(+) diff --git a/features/theme-mod.feature b/features/theme-mod.feature index 336cb8ecf..c140ad9aa 100644 --- a/features/theme-mod.feature +++ b/features/theme-mod.feature @@ -114,6 +114,34 @@ Feature: Manage WordPress theme mods value: null """ + Scenario: Getting theme mods keeps native value types for machine-readable formats + Given a WP install + + When I run `wp eval 'set_theme_mod( "flag_off", false ); set_theme_mod( "flag_on", true ); set_theme_mod( "empty_thing", [] ); set_theme_mod( "nested", [ "child" => "x" ] );'` + + # The table format is for humans, so types are shown as readable placeholders. + And I run `wp theme mod get --all` + Then STDOUT should be a table containing rows: + | key | value | + | flag_off | [false] | + | flag_on | [true] | + | empty_thing | [empty array] | + + # Machine-readable formats keep the native value types. + When I run `wp theme mod get --all --format=json` + Then STDOUT should be JSON containing: + """ + [{"key":"flag_off","value":false},{"key":"flag_on","value":true},{"key":"empty_thing","value":[]},{"key":"nested","value":""},{"key":"nested.child","value":"x"}] + """ + + When I run `wp theme mod get --all --format=csv` + Then STDOUT should be CSV containing: + | key | value | + | flag_off | false | + | flag_on | true | + | empty_thing | [] | + | nested.child | x | + Scenario: Setting theme mods Given a WP install From 37cbd786841e12f67ff98c282677d79a0e44c372 Mon Sep 17 00:00:00 2001 From: Alain Schlesser Date: Tue, 21 Jul 2026 10:42:51 +0200 Subject: [PATCH 20/21] Use an actual tab character as the key separator The single-quoted '\t' was the two-character string backslash-t rather than a tab, so a hierarchical key containing that literal sequence could be split or flattened incorrectly. Use a real tab ("\t") to avoid the collision. Behavior is otherwise unchanged. --- src/Theme_Mod_Command.php | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/Theme_Mod_Command.php b/src/Theme_Mod_Command.php index 8d9f9aeed..9fd08a0df 100644 --- a/src/Theme_Mod_Command.php +++ b/src/Theme_Mod_Command.php @@ -108,8 +108,9 @@ public function get( $args, $assoc_args ) { } } - // Generate the list of items ready for output. We use an initial separator that we can replace later depending on format. - $separator = '\t'; + // Generate the list of items ready for output. We use an actual tab as an + // initial separator that we can replace later depending on format. + $separator = "\t"; foreach ( $mods as $key => $value ) { // If mods were given, skip the others. if ( ! empty( $args ) && ! in_array( $key, $args, true ) ) { From 16c60ed0240d43867e6d337e17522616901864c9 Mon Sep 17 00:00:00 2001 From: Alain Schlesser Date: Tue, 21 Jul 2026 12:21:23 +0200 Subject: [PATCH 21/21] Seed theme mods via eval-file for Windows compatibility The value-type test seeded mods with an inline `wp eval '...'`, but on Windows the shell interpreted the `>` in the array's `=>` as output redirection, truncating the PHP and producing a parse error. Move the setup into a PHP file run with `wp eval-file` so the code never passes through the shell, keeping the test cross-platform. --- features/theme-mod.feature | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/features/theme-mod.feature b/features/theme-mod.feature index c140ad9aa..24ad1d71f 100644 --- a/features/theme-mod.feature +++ b/features/theme-mod.feature @@ -116,8 +116,16 @@ Feature: Manage WordPress theme mods Scenario: Getting theme mods keeps native value types for machine-readable formats Given a WP install + And a set-mods.php file: + """ + 'x' ] ); + """ - When I run `wp eval 'set_theme_mod( "flag_off", false ); set_theme_mod( "flag_on", true ); set_theme_mod( "empty_thing", [] ); set_theme_mod( "nested", [ "child" => "x" ] );'` + When I run `wp eval-file set-mods.php` # The table format is for humans, so types are shown as readable placeholders. And I run `wp theme mod get --all`