Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
80 changes: 80 additions & 0 deletions features/theme-mod.feature
Original file line number Diff line number Diff line change
Expand Up @@ -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:
"""
Expand All @@ -21,19 +37,83 @@ 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:
| key | value |
| 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

Expand Down
140 changes: 103 additions & 37 deletions src/Theme_Mod_Command.php
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
<?php

use WP_CLI\Utils;

/**
* Sets, gets, and removes theme mods.
*
Expand Down Expand Up @@ -80,57 +82,121 @@ class Theme_Mod_Command extends WP_CLI_Command {
*/
public function get( $args, $assoc_args ) {

if ( ! \WP_CLI\Utils\get_flag_value( $assoc_args, 'all' ) && empty( $args ) ) {
if ( ! Utils\get_flag_value( $assoc_args, 'all' ) && empty( $args ) ) {
WP_CLI::error( 'You must specify at least one mod or use --all.' );
}

if ( \WP_CLI\Utils\get_flag_value( $assoc_args, 'all' ) ) {
if ( Utils\get_flag_value( $assoc_args, 'all' ) ) {
$args = array();
}

$list = array();
$mods = get_theme_mods();
if ( ! is_array( $mods ) ) {
// If no mods are set (perhaps new theme), make sure foreach still works.
$mods = array();
// 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, 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();
}
foreach ( $mods as $k => $v ) {

// 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 ) {
// If mods were given, skip the others.
if ( ! empty( $args ) && ! in_array( $k, $args, true ) ) {
if ( ! empty( $args ) && ! in_array( $key, $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,
];
}
$this->mod_to_string( $key, $value, $mod_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 ( Utils\get_flag_value( $assoc_args, 'format' ) ) {
// For tables we use a double space to indent child items.
case 'table':
$mod_list = array_map(
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;
}
return [
'key' => $new_key,
'value' => $item['value'],
];
},
$mod_list
);
break;

// For JSON, CSV, and YAML formats we use dot notation to show the hierarchy.
case 'csv':
case 'yaml':
case 'json':
$mod_list = array_map(
static function ( $item ) use ( $separator ) {
return [
'key' => str_replace( $separator, '.', $item['key'] ),
'value' => $item['value'],
];
},
$mod_list
);
break;
Comment on lines +137 to +150
Copy link

Copilot AI Feb 3, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The new flattening and dot-notation logic for CSV/YAML/JSON formats introduces non-trivial behavior but currently has no dedicated acceptance tests (unlike features/theme-mod-list.feature, which covers those formats for the list subcommand). Given that issue #96 was caused by malformed non-tabular output, it would be valuable to add Behat coverage for wp theme mod get --all --format=csv|json|yaml with nested theme-mod structures to ensure this formatter behavior is exercised and guarded against regressions.

Copilot uses AI. Check for mistakes.
}

// 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 );
}

/**
* 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 $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, &$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 ) ) {
$mod_list[] = array(
'key' => $key,
'value' => '[empty array]',
);
return;
}

// Arrays get their own entry in the list to allow for sensible table output.
$mod_list[] = array(
'key' => $key,
'value' => '',
);

foreach ( $value as $child_key => $child_value ) {
$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]';
}

$mod_list[] = array(
'key' => $key,
'value' => $value,
);
}
}

/**
Expand Down Expand Up @@ -206,11 +272,11 @@ public function list_( $args, $assoc_args ) {
*/
public function remove( $args, $assoc_args ) {

if ( ! \WP_CLI\Utils\get_flag_value( $assoc_args, 'all' ) && empty( $args ) ) {
if ( ! Utils\get_flag_value( $assoc_args, 'all' ) && empty( $args ) ) {
WP_CLI::error( 'You must specify at least one mod or use --all.' );
}

if ( \WP_CLI\Utils\get_flag_value( $assoc_args, 'all' ) ) {
if ( Utils\get_flag_value( $assoc_args, 'all' ) ) {
remove_theme_mods();
WP_CLI::success( 'Theme mods removed.' );
return;
Expand Down
Loading