File: //usr/local/wp/vendor/wp-cli/widget-command/src/Widget_Command.php
<?php
use WP_CLI\Utils;
use WP_CLI\Formatter;
/**
* Manages widgets, including adding and moving them within sidebars.
*
* A [widget](https://developer.wordpress.org/themes/functionality/widgets/) adds content and features to a widget area (also called a [sidebar](https://developer.wordpress.org/themes/functionality/sidebars/)).
*
* ## EXAMPLES
*
* # List widgets on a given sidebar
* $ wp widget list sidebar-1
* +----------+------------+----------+----------------------+
* | name | id | position | options |
* +----------+------------+----------+----------------------+
* | meta | meta-6 | 1 | {"title":"Meta"} |
* | calendar | calendar-2 | 2 | {"title":"Calendar"} |
* +----------+------------+----------+----------------------+
*
* # Add a calendar widget to the second position on the sidebar
* $ wp widget add calendar sidebar-1 2
* Success: Added widget to sidebar.
*
* # Update option(s) associated with a given widget
* $ wp widget update calendar-1 --title="Calendar"
* Success: Widget updated.
*
* # Delete one or more widgets entirely
* $ wp widget delete calendar-2 archive-1
* Success: 2 widgets removed from sidebar.
*/
class Widget_Command extends WP_CLI_Command {
private $fields = [
'name',
'id',
'position',
'options',
];
/**
* Lists widgets associated with a sidebar.
*
* ## OPTIONS
*
* <sidebar-id>
* : ID for the corresponding sidebar.
*
* [--fields=<fields>]
* : Limit the output to specific object fields.
*
* [--format=<format>]
* : Render output in a particular format.
* ---
* default: table
* options:
* - table
* - csv
* - ids
* - json
* - count
* - yaml
* ---
*
* ## AVAILABLE FIELDS
*
* These fields will be displayed by default for each widget:
*
* * name
* * id
* * position
* * options
*
* There are no optionally available fields.
*
* ## EXAMPLES
*
* $ wp widget list sidebar-1 --fields=name,id --format=csv
* name,id
* meta,meta-5
* search,search-3
*
* @subcommand list
*/
public function list_( $args, $assoc_args ) {
list( $sidebar_id ) = $args;
$this->validate_sidebar( $sidebar_id );
$output_widgets = $this->get_sidebar_widgets( $sidebar_id );
if ( ! empty( $assoc_args['format'] ) && 'ids' === $assoc_args['format'] ) {
$output_widgets = wp_list_pluck( $output_widgets, 'id' );
}
$formatter = new Formatter( $assoc_args, $this->fields );
$formatter->display_items( $output_widgets );
}
/**
* Adds a widget to a sidebar.
*
* Creates a new widget entry in the database, and associates it with the
* sidebar.
*
* ## OPTIONS
*
* <name>
* : Widget name.
*
* <sidebar-id>
* : ID for the corresponding sidebar.
*
* [<position>]
* : Widget's current position within the sidebar. Defaults to last
*
* [--<field>=<value>]
* : Widget option to add, with its new value
*
* ## EXAMPLES
*
* # Add a new calendar widget to sidebar-1 with title "Calendar"
* $ wp widget add calendar sidebar-1 2 --title="Calendar"
* Success: Added widget to sidebar.
*
* @subcommand add
*/
public function add( $args, $assoc_args ) {
list( $name, $sidebar_id ) = $args;
$this->validate_sidebar( $sidebar_id );
$position = count( $args ) > 2
? $args[2] - 1
: count( $this->get_sidebar_widgets( $sidebar_id ) );
$widget = $this->get_widget_obj( $name );
if ( false === $widget ) {
WP_CLI::error( 'Invalid widget type.' );
}
/*
* Adding a widget is as easy as:
* 1. Creating a new widget option
* 2. Adding the widget to the sidebar
* 3. Positioning appropriately
*/
$widget_options = $this->get_widget_options( $name );
$option_keys = $widget_options;
if ( ! isset( $widget_options['_multiwidget'] ) ) {
$widget_options['_multiwidget'] = 1;
}
unset( $option_keys['_multiwidget'] );
$option_keys = array_keys( $option_keys );
$last_key = array_pop( $option_keys );
$option_index = $last_key + 1;
$widget_options[ $option_index ] = $this->sanitize_widget_options( $name, $assoc_args, array() );
$this->update_widget_options( $name, $widget_options );
$widget_id = $name . '-' . $option_index;
$this->move_sidebar_widget( $widget_id, null, $sidebar_id, null, $position );
WP_CLI::success( 'Added widget to sidebar.' );
}
/**
* Updates options for an existing widget.
*
* ## OPTIONS
*
* <widget-id>
* : Unique ID for the widget
*
* [--<field>=<value>]
* : Field to update, with its new value
*
* ## EXAMPLES
*
* # Change calendar-1 widget title to "Our Calendar"
* $ wp widget update calendar-1 --title="Our Calendar"
* Success: Widget updated.
*
* @subcommand update
*/
public function update( $args, $assoc_args ) {
list( $widget_id ) = $args;
if ( ! $this->validate_sidebar_widget( $widget_id ) ) {
WP_CLI::error( "Widget doesn't exist." );
}
if ( empty( $assoc_args ) ) {
WP_CLI::error( 'No options specified to update.' );
}
list( $name, $option_index ) = $this->get_widget_data( $widget_id );
$widget_options = $this->get_widget_options( $name );
$clean_options = $this->sanitize_widget_options( $name, $assoc_args, $widget_options[ $option_index ] );
$widget_options[ $option_index ] = array_merge( (array) $widget_options[ $option_index ], $clean_options );
$this->update_widget_options( $name, $widget_options );
WP_CLI::success( 'Widget updated.' );
}
/**
* Moves the position of a widget.
*
* Changes the order of a widget in its existing sidebar, or moves it to a
* new sidebar.
*
* ## OPTIONS
*
* <widget-id>
* : Unique ID for the widget
*
* [--position=<position>]
* : Assign the widget to a new position.
*
* [--sidebar-id=<sidebar-id>]
* : Assign the widget to a new sidebar
*
* ## EXAMPLES
*
* # Change position of widget
* $ wp widget move recent-comments-2 --position=2
* Success: Widget moved.
*
* # Move widget to Inactive Widgets
* $ wp widget move recent-comments-2 --sidebar-id=wp_inactive_widgets
* Success: Widget moved.
*
* @subcommand move
*/
public function move( $args, $assoc_args ) {
list( $widget_id ) = $args;
if ( ! $this->validate_sidebar_widget( $widget_id ) ) {
WP_CLI::error( "Widget doesn't exist." );
}
if ( empty( $assoc_args['position'] ) && empty( $assoc_args['sidebar-id'] ) ) {
WP_CLI::error( 'A new position or new sidebar must be specified.' );
}
list( $name, $option_index, $current_sidebar_id, $current_sidebar_index ) = $this->get_widget_data( $widget_id );
$new_sidebar_id = ! empty( $assoc_args['sidebar-id'] ) ? $assoc_args['sidebar-id'] : $current_sidebar_id;
$this->validate_sidebar( $new_sidebar_id );
$new_sidebar_index = ! empty( $assoc_args['position'] ) ? $assoc_args['position'] - 1 : $current_sidebar_index;
// Moving between sidebars adds to the top.
if ( $new_sidebar_id !== $current_sidebar_id && $new_sidebar_index === $current_sidebar_index ) {
// Human-readable positions are different than numerically indexed array.
$new_sidebar_index = 0;
}
$this->move_sidebar_widget( $widget_id, $current_sidebar_id, $new_sidebar_id, $current_sidebar_index, $new_sidebar_index );
WP_CLI::success( 'Widget moved.' );
}
/**
* Deactivates one or more widgets from an active sidebar.
*
* Moves widgets to Inactive Widgets.
*
* ## OPTIONS
*
* <widget-id>...
* : Unique ID for the widget(s)
*
* ## EXAMPLES
*
* # Deactivate the recent-comments-2 widget.
* $ wp widget deactivate recent-comments-2
* Success: 1 widget deactivated.
*
* @subcommand deactivate
*/
public function deactivate( $args, $assoc_args ) {
$count = 0;
$errors = 0;
foreach ( $args as $widget_id ) {
if ( ! $this->validate_sidebar_widget( $widget_id ) ) {
WP_CLI::warning( "Widget '{$widget_id}' doesn't exist." );
++$errors;
continue;
}
list( $name, $option_index, $sidebar_id, $sidebar_index ) = $this->get_widget_data( $widget_id );
if ( 'wp_inactive_widgets' === $sidebar_id ) {
WP_CLI::warning( sprintf( "'%s' is already deactivated.", $widget_id ) );
continue;
}
$this->move_sidebar_widget(
$widget_id,
$sidebar_id,
'wp_inactive_widgets',
$sidebar_index,
count( $this->get_sidebar_widgets( 'wp_inactive_widgets' ) )
);
++$count;
}
Utils\report_batch_operation_results( 'widget', 'deactivate', count( $args ), $count, $errors );
}
/**
* Deletes one or more widgets from a sidebar.
*
* ## OPTIONS
*
* <widget-id>...
* : Unique ID for the widget(s)
*
* ## EXAMPLES
*
* # Delete the recent-comments-2 widget from its sidebar.
* $ wp widget delete recent-comments-2
* Success: Deleted 1 of 1 widgets.
*
* @subcommand delete
*/
public function delete( $args, $assoc_args ) {
$count = 0;
$errors = 0;
foreach ( $args as $widget_id ) {
if ( ! $this->validate_sidebar_widget( $widget_id ) ) {
WP_CLI::warning( "Widget '{$widget_id}' doesn't exist." );
++$errors;
continue;
}
// Remove the widget's settings.
list( $name, $option_index, $sidebar_id, $sidebar_index ) = $this->get_widget_data( $widget_id );
$widget_options = $this->get_widget_options( $name );
unset( $widget_options[ $option_index ] );
$this->update_widget_options( $name, $widget_options );
// Remove the widget from the sidebar.
$all_widgets = $this->wp_get_sidebars_widgets();
unset( $all_widgets[ $sidebar_id ][ $sidebar_index ] );
$all_widgets[ $sidebar_id ] = array_values( $all_widgets[ $sidebar_id ] );
update_option( 'sidebars_widgets', $all_widgets );
++$count;
}
Utils\report_batch_operation_results( 'widget', 'delete', count( $args ), $count, $errors );
}
/**
* Resets sidebar.
*
* Removes all widgets from the sidebar and places them in Inactive Widgets.
*
* ## OPTIONS
*
* [<sidebar-id>...]
* : One or more sidebars to reset.
*
* [--all]
* : If set, all sidebars will be reset.
*
* ## EXAMPLES
*
* # Reset a sidebar
* $ wp widget reset sidebar-1
* Success: Sidebar 'sidebar-1' reset.
*
* # Reset multiple sidebars
* $ wp widget reset sidebar-1 sidebar-2
* Success: Sidebar 'sidebar-1' reset.
* Success: Sidebar 'sidebar-2' reset.
*
* # Reset all sidebars
* $ wp widget reset --all
* Success: Sidebar 'sidebar-1' reset.
* Success: Sidebar 'sidebar-2' reset.
* Success: Sidebar 'sidebar-3' reset.
*/
public function reset( $args, $assoc_args ) {
global $wp_registered_sidebars;
$all = Utils\get_flag_value( $assoc_args, 'all', false );
// Bail if no arguments and no all flag.
if ( ! $all && empty( $args ) ) {
WP_CLI::error( 'Please specify one or more sidebars, or use --all.' );
}
// Fetch all sidebars if all flag is set.
if ( $all ) {
$args = array_keys( $wp_registered_sidebars );
}
// Sidebar ID wp_inactive_widgets is reserved by WP core for inactive widgets.
if ( isset( $args['wp_inactive_widgets'] ) ) {
unset( $args['wp_inactive_widgets'] );
}
// Check if no registered sidebar.
if ( empty( $args ) ) {
WP_CLI::error( 'No sidebar registered.' );
}
$count = 0;
$errors = 0;
foreach ( $args as $sidebar_id ) {
if ( ! array_key_exists( $sidebar_id, $wp_registered_sidebars ) ) {
WP_CLI::warning( sprintf( 'Invalid sidebar: %s', $sidebar_id ) );
++$errors;
continue;
}
$widgets = $this->get_sidebar_widgets( $sidebar_id );
if ( empty( $widgets ) ) {
WP_CLI::warning( sprintf( "Sidebar '%s' is already empty.", $sidebar_id ) );
} else {
foreach ( $widgets as $widget ) {
$widget_id = $widget->id;
list( $name, $option_index, $new_sidebar_id, $sidebar_index ) = $this->get_widget_data( $widget_id );
$this->move_sidebar_widget(
$widget_id,
$new_sidebar_id,
'wp_inactive_widgets',
$sidebar_index,
count( $this->get_sidebar_widgets( 'wp_inactive_widgets' ) )
);
}
WP_CLI::log( sprintf( "Sidebar '%s' reset.", $sidebar_id ) );
++$count;
}
}
Utils\report_batch_operation_results( 'sidebar', 'reset', count( $args ), $count, $errors );
}
/**
* Checks whether a sidebar is a valid sidebar
*
* @param string $sidebar_id
*/
private function validate_sidebar( $sidebar_id ) {
global $wp_registered_sidebars;
Utils\wp_register_unused_sidebar();
if ( ! array_key_exists( $sidebar_id, $wp_registered_sidebars ) ) {
WP_CLI::error( 'Invalid sidebar.' );
}
}
/**
* Checks whether the specified widget is on the sidebar
*
* @param string $widget_id
*/
private function validate_sidebar_widget( $widget_id ) {
$sidebars_widgets = $this->wp_get_sidebars_widgets();
$widget_exists = false;
foreach ( $sidebars_widgets as $sidebar_id => $widgets ) {
if ( in_array( $widget_id, $widgets, true ) ) {
$widget_exists = true;
break;
}
}
return $widget_exists;
}
/**
* Gets the widgets (and their associated data) for a given sidebar
*
* @param string $sidebar_id
* @return array
*/
private function get_sidebar_widgets( $sidebar_id ) {
$all_widgets = $this->wp_get_sidebars_widgets();
if ( empty( $all_widgets[ $sidebar_id ] ) ) {
return array();
}
$prepared_widgets = array();
foreach ( $all_widgets[ $sidebar_id ] as $key => $widget_id ) {
$prepared_widget = new stdClass();
$parts = explode( '-', $widget_id );
$option_index = array_pop( $parts );
$widget_name = implode( '-', $parts );
$prepared_widget->name = $widget_name;
$prepared_widget->id = $widget_id;
$prepared_widget->position = $key + 1;
$widget_options = get_option( 'widget_' . $widget_name );
$prepared_widget->options = $widget_options[ $option_index ];
$prepared_widgets[] = $prepared_widget;
}
return $prepared_widgets;
}
/**
* Re-implementation of wp_get_sidebars_widgets()
* because the original has a nasty global component
*/
private function wp_get_sidebars_widgets() {
$sidebars_widgets = get_option( 'sidebars_widgets', array() );
if ( is_array( $sidebars_widgets ) && isset( $sidebars_widgets['array_version'] ) ) {
unset( $sidebars_widgets['array_version'] );
}
return $sidebars_widgets;
}
/**
* Gets the widget's name, option index, sidebar, and sidebar index from its ID
*
* @param string $widget_id
* @return array
*/
private function get_widget_data( $widget_id ) {
$parts = explode( '-', $widget_id );
$option_index = array_pop( $parts );
$name = implode( '-', $parts );
$sidebar_id = false;
$sidebar_index = false;
$all_widgets = $this->wp_get_sidebars_widgets();
foreach ( $all_widgets as $s_id => &$widgets ) {
$key = array_search( $widget_id, $widgets, true );
if ( false !== $key ) {
$sidebar_id = $s_id;
$sidebar_index = $key;
break;
}
}
return array( $name, $option_index, $sidebar_id, $sidebar_index );
}
/**
* Gets the options for a given widget
*
* @param string $name
* @return array
*/
private function get_widget_options( $name ) {
return get_option( 'widget_' . $name, array() );
}
/**
* Updates the options for a given widget
*
* @param string $name
* @param mixed
*/
private function update_widget_options( $name, $value ) {
update_option( 'widget_' . $name, $value );
}
/**
* Repositions a widget within a sidebar or move to another sidebar.
*
* @param string $widget_id
* @param string|null $current_sidebar_id
* @param string $new_sidebar_id
* @param int|null $current_index
* @param int $new_index
*/
private function move_sidebar_widget( $widget_id, $current_sidebar_id, $new_sidebar_id, $current_index, $new_index ) {
$all_widgets = $this->wp_get_sidebars_widgets();
$needs_placement = true;
// Existing widget
if ( $current_sidebar_id && ! is_null( $current_index ) ) {
$widgets = $all_widgets[ $current_sidebar_id ];
if ( $current_sidebar_id !== $new_sidebar_id ) {
unset( $widgets[ $current_index ] );
} else {
$part = array_splice( $widgets, $current_index, 1 );
array_splice( $widgets, $new_index, 0, $part );
$needs_placement = false;
}
$all_widgets[ $current_sidebar_id ] = array_values( $widgets );
}
if ( $needs_placement ) {
$widgets = ! empty( $all_widgets[ $new_sidebar_id ] ) ? $all_widgets[ $new_sidebar_id ] : array();
$before = array_slice( $widgets, 0, $new_index, true );
$after = array_slice( $widgets, $new_index, count( $widgets ), true );
$widgets = array_merge( $before, array( $widget_id ), $after );
$all_widgets[ $new_sidebar_id ] = array_values( $widgets );
}
update_option( 'sidebars_widgets', $all_widgets );
}
/**
* Gets a widget's instantiated object based on its name
*
* @param string $id_base Name of the widget
* @return WP_Widget|false
*/
private function get_widget_obj( $id_base ) {
global $wp_widget_factory;
$widget = wp_filter_object_list( $wp_widget_factory->widgets, array( 'id_base' => $id_base ) );
if ( empty( $widget ) ) {
return false;
}
return array_pop( $widget );
}
/**
* Cleans up a widget's options based on its update callback
*
* @param string $id_base Name of the widget
* @param mixed $dirty_options
* @param mixed $old_options
* @return mixed
*/
private function sanitize_widget_options( $id_base, $dirty_options, $old_options ) {
$widget = $this->get_widget_obj( $id_base );
if ( empty( $widget ) ) {
return array();
}
// No easy way to determine expected array keys for $dirty_options
// because Widget API dependent on the form fields
// phpcs:ignore WordPress.PHP.NoSilencedErrors.Discouraged -- Whitelisting due to above reason.
return @$widget->update( $dirty_options, $old_options );
}
}