$response = Client::wpcom_json_api_request_as_blog(
empty( $named_args['api_version'] ) ? Client::WPCOM_JSON_API_VERSION : $named_args['api_version'],
empty( $decoded_body ) ? null : $decoded_body,
empty( $named_args['base_api_path'] ) ? 'rest' : $named_args['base_api_path']
if ( is_wp_error( $response ) ) {
/* translators: %1$s is an endpoint route (ex. /sites/123456), %2$d is an error code, %3$s is an error message. */
__( 'Request to %1$s returned an error: (%2$d) %3$s.', 'jetpack' ),
$response->get_error_code(),
$response->get_error_message()
if ( 200 !== wp_remote_retrieve_response_code( $response ) ) {
/* translators: %1$s is an endpoint route (ex. /sites/123456), %2$d is an HTTP status code. */
__( 'Request to %1$s returned a non-200 response code: %2$d.', 'jetpack' ),
wp_remote_retrieve_response_code( $response )
$output = wp_remote_retrieve_body( $response );
if ( isset( $named_args['pretty'] ) ) {
$decoded_output = json_decode( $output );
$output = wp_json_encode( $decoded_output, JSON_PRETTY_PRINT );
if ( isset( $named_args['strip-success'] ) ) {
WP_CLI::success( $output );
* Allows uploading SSH Credentials to the current site for backups, restores, and security scanning.
* : The SSH server's address.
* : The username to use to log in to the SSH server.
* : The password used to log in, if using a password. (optional)
* : The private key used to log in, if using a private key. (optional)
* : Will pretty print the results of a successful API call. (optional)
* : Will remove the green success label from successful API calls. (optional)
* wp jetpack upload_ssh_creds --host=example.com --ssh-user=example --pass=password
* wp jetpack updload_ssh_creds --host=example.com --ssh-user=example --kpri=key
* @param array $args Positional args.
* @param array $named_args Named args.
public function upload_ssh_creds( $args, $named_args ) {
if ( ! Jetpack::is_connection_ready() ) {
WP_CLI::error( __( 'Jetpack is not currently connected to WordPress.com', 'jetpack' ) );
foreach ( $required_args as $arg ) {
if ( empty( $named_args[ $arg ] ) ) {
/* translators: %s is a slug, such as 'host'. */
__( '`%s` cannot be empty.', 'jetpack' ),
if ( empty( $named_args['pass'] ) && empty( $named_args['kpri'] ) ) {
WP_CLI::error( __( 'Both `pass` and `kpri` fields cannot be blank.', 'jetpack' ) );
'site_url' => get_site_url(),
'host' => $named_args['host'],
'user' => $named_args['ssh-user'],
'pass' => empty( $named_args['pass'] ) ? '' : $named_args['pass'],
'kpri' => empty( $named_args['kpri'] ) ? '' : $named_args['kpri'],
$named_args = wp_parse_args(
'resource' => '/activity-log/%d/update-credentials',
'body' => wp_json_encode( $values ),
self::call_api( $args, $named_args );
* API wrapper for getting stats from the WordPress.com API for the current site.
* [--quantity=<quantity>]
* : The number of units to include.
* : The unit of time to query stats for.
* : The latest date to return stats for. Ex. - 2018-01-01.
* : Will pretty print the results of a successful API call.
* : Will remove the green success label from successful API calls.
* @param array $args Positional args.
* @param array $named_args Named args.
public function get_stats( $args, $named_args ) {
$selected_args = array_intersect_key(
// The API expects unit, but period seems to be more correct.
$selected_args['unit'] = $named_args['period'];
'jetpack call_api --resource=/sites/%d/stats/%s',
Jetpack_Options::get_option( 'id' ),
add_query_arg( $selected_args, 'visits' )
if ( isset( $named_args['pretty'] ) ) {
if ( isset( $named_args['strip-success'] ) ) {
$command .= ' --strip-success';
'launch' => false, // Use the current process.
* Allows management of publicize connections.
* : The action to perform.
* : The connection ID or service to perform an action on.
* : Whether to ignore connections cache.
* : Allows overriding the output of the command when listing connections.
* # List all publicize connections.
* $ wp jetpack publicize list
* # List all publicize connections, ignoring the cache.
* $ wp jetpack publicize list --ignore-cache
* # List publicize connections for a given service.
* $ wp jetpack publicize list linkedin
* # List all publicize connections for a given user.
* $ wp --user=1 jetpack publicize list
* # List all publicize connections for a given user and service.
* $ wp --user=1 jetpack publicize list linkedin
* # Display details for a given connection.
* $ wp jetpack publicize list 123456
* # Diconnection a given connection.
* $ wp jetpack publicize disconnect 123456
* # Disconnect all connections.
* $ wp jetpack publicize disconnect all
* # Disconnect all connections for a given service.
* $ wp jetpack publicize disconnect linkedin
* @param array $args Positional args.
* @param array $named_args Named args.
public function publicize( $args, $named_args ) {
if ( ! Jetpack::connection()->has_connected_owner() ) {
WP_CLI::error( __( 'Jetpack Social requires a user-level connection to WordPress.com', 'jetpack' ) );
if ( ! Jetpack::is_module_active( 'publicize' ) ) {
WP_CLI::error( __( 'The Jetpack Social module is not active.', 'jetpack' ) );
if ( ( new Status() )->is_offline_mode() ) {
! defined( 'JETPACK_DEV_DEBUG' ) &&
! has_filter( 'jetpack_development_mode' ) &&
! has_filter( 'jetpack_offline_mode' ) &&
! str_contains( site_url(), '.' )
WP_CLI::error( __( "Jetpack is current in offline mode because the site url does not contain a '.', which often occurs when dynamically setting the WP_SITEURL constant. While in offline mode, the Jetpack Social module will not load.", 'jetpack' ) );
WP_CLI::error( __( 'Jetpack is currently in offline mode, so the Jetpack Social module will not load.', 'jetpack' ) );
if ( ! class_exists( Publicize::class ) ) {
WP_CLI::error( __( 'The Jetpack Social module is not loaded.', 'jetpack' ) );
$publicize = new Publicize();
$identifier = ! empty( $args[1] ) ? $args[1] : false;
$services = array_keys( $publicize->get_services() );
$id_is_service = in_array( $identifier, $services, true );
'ignore_cache' => $named_args['ignore-cache'] ?? false,
// For the CLI command, let's return all connections when a user isn't specified. This
// differs from the logic in the Publicize class.
$connections_to_return = is_user_logged_in()
? Connections::get_all_for_user( $_args )
: Connections::get_all( $_args );
if ( $id_is_service && ! empty( $identifier ) && ! empty( $connections_to_return ) ) {
$temp_connections = $connections_to_return;
$connections_to_return = array();
foreach ( $temp_connections as $connection ) {
if ( $identifier === $connection['service_name'] ) {
$connections_to_return[] = $connection;
if ( $identifier && ! $id_is_service && ! empty( $connections_to_return ) ) {
$connections_to_return = wp_list_filter( $connections_to_return, array( 'connection_id' => $identifier ) );
// Somehow, a test site ended up in a state where $connections_to_return looked like:
// array( array( array( 'id' => 0, 'service' => 0 ) ) ) // phpcs:ignore Squiz.PHP.CommentedOutCode.Found
// This caused the CLI command to error when running WP_CLI\Utils\format_items() below. So
// to minimize future issues, this nested loop will remove any connections that don't contain
// any keys that we expect.
foreach ( (array) $connections_to_return as $connection_key => $connection ) {
foreach ( $expected_keys as $expected_key ) {
if ( ! isset( $connection[ $expected_key ] ) ) {
unset( $connections_to_return[ $connection_key ] );
if ( empty( $connections_to_return ) ) {
WP_CLI\Utils\format_items( $named_args['format'], $connections_to_return, $expected_keys );
WP_CLI::error( __( 'A connection ID must be passed in order to disconnect.', 'jetpack' ) );
// If the connection ID is 'all' then delete all connections. If the connection ID
// matches a service, delete all connections for that service.
if ( 'all' === $identifier || $id_is_service ) {
if ( 'all' === $identifier ) {
WP_CLI::log( __( "You're about to delete all Jetpack Social connections.", 'jetpack' ) );
/* translators: %s is a lowercase string for a social network. */
WP_CLI::log( sprintf( __( "You're about to delete all Jetpack Social connections to %s.", 'jetpack' ), $identifier ) );
jetpack_cli_are_you_sure();
$connections = is_user_logged_in()
? Connections::get_all_for_user()
: Connections::get_all();
if ( 'all' !== $service ) {
$connections = wp_list_filter( $connections, array( 'service_name' => $service ) );
if ( ! empty( $connections ) ) {
$count = is_countable( $connections ) ? count( $connections ) : 0;
$progress = \WP_CLI\Utils\make_progress_bar(
/* translators: %s is a lowercase string for a social network. */
sprintf( __( 'Disconnecting all connections to %s.', 'jetpack' ), $service ),
foreach ( $connections as $connection ) {
$id = $connection['connection_id'];
if ( false === $publicize->disconnect( false, $id ) ) {
/* translators: %1$d is a numeric ID and %2$s is a lowercase string for a social network. */
__( 'Jetpack Social connection %d could not be disconnected', 'jetpack' ),
// @phan-suppress-next-line PhanUndeclaredClassMethod - Class is missing from php-stubs/wp-cli-stubs 🤷
// @phan-suppress-next-line PhanUndeclaredClassMethod - Class is missing from php-stubs/wp-cli-stubs 🤷
if ( 'all' === $service ) {
WP_CLI::success( __( 'All Jetpack Social connections were successfully disconnected.', 'jetpack' ) );
/* translators: %s is a lowercase string for a social network. */
WP_CLI::success( sprintf( __( 'All Jetpack Social connections to %s were successfully disconnected.', 'jetpack' ), $service ) );
} elseif ( false !== $publicize->disconnect( false, $identifier ) ) {
/* translators: %d is a numeric ID. Example: 1234. */
WP_CLI::success( sprintf( __( 'Jetpack Social connection %d has been disconnected.', 'jetpack' ), $identifier ) );
/* translators: %d is a numeric ID. Example: 1234. */
WP_CLI::error( sprintf( __( 'Jetpack Social connection %d could not be disconnected.', 'jetpack' ), $identifier ) );
private function get_api_host() {
$env_api_host = getenv( 'JETPACK_START_API_HOST', true );
return $env_api_host ? 'https://' . $env_api_host : JETPACK__WPCOM_JSON_API_BASE;
* Log and exit on a partner provision error.
* @param WP_Error $error Error.
private function partner_provision_error( $error ) {
'error_code' => $error->get_error_code(),
'error_message' => $error->get_error_message(),
* Creates the essential files in Jetpack to start building a Gutenberg block or plugin.
* block: it creates a Jetpack block. All files will be created in a directory under extensions/blocks named based on the block title or a specific given slug.
* The first parameter is the block title and it's not associative. Add it wrapped in quotes.
* The title is also used to create the slug and the edit PHP class name. If it's something like "Logo gallery", the slug will be 'logo-gallery' and the class name will be LogoGalleryEdit.
* --slug: Specific slug to identify the block that overrides the one generated based on the title.
* --description: Allows to provide a text description of the block.
* --keywords: Provide up to three keywords separated by comma so users can find this block when they search in Gutenberg's inserter.
* --variation: Allows to decide whether the block should be a production block, experimental, or beta. Defaults to Beta when arg not provided.
* wp jetpack scaffold block "Cool Block"
* wp jetpack scaffold block "Amazing Rock" --slug="good-music" --description="Rock the best music on your site"
* wp jetpack scaffold block "Jukebox" --keywords="music, audio, media"
* wp jetpack scaffold block "Jukebox" --variation="experimental"
* @subcommand scaffold block
* @synopsis <type> <title> [--slug] [--description] [--keywords] [--variation]
* @param array $args Positional parameters, when strings are passed, wrap them in quotes.
* @param array $assoc_args Associative parameters like --slug="nice-block".
public function scaffold( $args, $assoc_args ) {
// It's ok not to check if it's set, because otherwise WPCLI exits earlier.
$this->block( $args, $assoc_args );
/* translators: %s is the subcommand */
WP_CLI::error( sprintf( esc_html__( 'Invalid subcommand %s.', 'jetpack' ), $args[0] ) . ' 👻' );