Skip to content

Commit

Permalink
Refactor generate installation commands
Browse files Browse the repository at this point in the history
  • Loading branch information
JulianBustamante committed Aug 8, 2024
1 parent d51f435 commit 41b8fbb
Show file tree
Hide file tree
Showing 7 changed files with 318 additions and 251 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -12,17 +12,14 @@
class Marketplace_Command_Helper {

/**
* Generate the commands that install a plugin, some dependencies, and a possible theme.
* Generate the commands to install the dependencies of a product.
*
* @param string|null $plugin_slug_or_url The plugin slug or URL to install.
* @param array<string> $plugin_dependencies List of plugins the product depends on.
* @param array<string> $theme_dependencies List of themes the product depends on.
* @param string|null $theme_slug The theme slug to install.
* @param bool|null $use_managed Whether to use the managed version of the product or not.
* @param array<string> $plugin_dependencies List of plugins the product depends on.
* @param array<string> $theme_dependencies List of themes the product depends on.
*
* @return string[]
*/
public function generate_install_commands( ?string $plugin_slug_or_url, array $plugin_dependencies, array $theme_dependencies, ?string $theme_slug = null, ?bool $use_managed = true ): array {
public function generate_dependency_install_commands( array $plugin_dependencies, array $theme_dependencies ) {
/**
* This can be extended in the future to support advanced dependency trees or multiple types of products.
*
Expand All @@ -32,191 +29,139 @@ public function generate_install_commands( ?string $plugin_slug_or_url, array $p
$plugin_dependency_commands = \array_map( fn( $plugin ) => \sprintf( $dependency_command_template, $plugin, 'plugin' ), $plugin_dependencies );
$theme_dependency_commands = \array_map( fn( $theme ) => \sprintf( $dependency_command_template, $theme, 'theme' ), $theme_dependencies );

$required_plugins_regex = \array_map( fn( $plugin ) => \sprintf( "-e '^%1\$s$'", $plugin ), $plugin_dependencies );
$required_plugins_regex = implode( ' ', $required_plugins_regex );

$required_themes_regex = \array_map( fn( $theme ) => \sprintf( "-e '^%1\$s$'", $theme ), $theme_dependencies );
$required_themes_regex = implode( ' ', $required_themes_regex );
return array_merge( $plugin_dependency_commands, $theme_dependency_commands );
}

$dependency_commands = array_merge( $plugin_dependency_commands, $theme_dependency_commands );
/**
* Get the list of installed plugins.
*
* @return string
*/
public function get_installed_plugins_names_command(): string {
return '--skip-themes --skip-plugins plugin list --field=name';
}

if ( empty( $plugin_slug_or_url ) && empty( $theme_slug ) ) {
return array( ...$dependency_commands );
}
/**
* Get the list of installed themes.
*
* @return string
*/
public function get_installed_themes_names_command(): string {
return '--skip-themes --skip-plugins theme list --field=name';
}

/**
* Generate the command that installs and activates the plugins. This handles plugins that are from wp.org repo, managed or paid from remote urls.
*
* To make sure that there are no 3rd party plugins that might interfere in the installation process, this command is executing by skipping non-required plugins and themes.
* 1. We get a list of themes or plugins by name
* 2. We pipe the result to grep to exclude dependencies that are required
* 3. We pipe the result to tr to escape space characters
* 4. We pipe the result to tr to created a comma separated list
*/
$command_template = '';
/**
* Generate the commands to install a plugin.
*
* @param string $software_slug_or_url The plugin slug or URL to install.
* @param bool $is_managed Whether to use the managed version of the plugin or not.
* @param array $skip_plugins List of plugins to skip when installing the plugin.
* @param array $skip_themes List of themes to skip when installing the plugin.
*
* @return string[]
*/
public function generate_plugin_install_commands( string $software_slug_or_url, bool $is_managed, array $skip_plugins, array $skip_themes ) {
$skip_plugins = implode( ',', $skip_plugins );
$skip_themes = implode( ',', $skip_themes );

if ( array() === $theme_dependencies ) {
$command_template .= ' --skip-themes';
} else {
$command_template .= " --skip-themes=\"$(wp --skip-themes --skip-plugins theme list --field=name | grep -v %2\$s | tr '\n' ',')\"";
$skip_plugins_parameter = '--skip-plugins';
if ( ! empty( $skip_plugins ) ) {
$skip_plugins_parameter .= '="' . $skip_plugins . '" ';
}

if ( array() === $plugin_dependencies ) {
$command_template .= ' --skip-plugins';
} else {
$command_template .= " --skip-plugins=\"$(wp --skip-themes --skip-plugins plugin list --field=name | grep -v %3\$s | tr '\n' ',')\"";
$skip_themes_parameter = '--skip-themes';
if ( ! empty( $skip_themes ) ) {
$skip_themes_parameter .= '="' . $skip_themes . '" ';
}

$commands = array(
...$dependency_commands,
$plugin_install_commands = array(
'add_managed_plugin_command' => ' atomic plugin use-managed %1$s --remove-existing',
'activate_plugin_command' => ' plugin activate %1$s',
);

// If we have a plugin slug, install and activate the plugin.
if ( ! empty( $plugin_slug_or_url ) ) {
$software_commands = array(
'add_managed_plugin_command' => ' atomic plugin use-managed %1$s --remove-existing',
'activate_plugin_command' => ' plugin activate %1$s',
);

if ( ! $use_managed ) {
$software_commands = array(
'add_remote_plugin_command' => ' plugin install "%1$s" --activate --force',
);
}

// Append the dependencies first so that they are installed before the actual product.
$commands = array(
...$dependency_commands,
...array_map( fn( $command ) => sprintf( $command_template . $command, $plugin_slug_or_url, $required_themes_regex, $required_plugins_regex ), array_values( $software_commands ) ),
if ( ! $is_managed ) {
$plugin_install_commands = array(
'add_remote_plugin_command' => ' plugin install "%1$s" --activate --force',
);
}

// We can bail early if we don't need to install a theme.
if ( empty( $theme_slug ) ) {
return $commands;
}

// Install a theme either from remote url or use managed version and activate
if ( ! $use_managed ) {
$commands[] = sprintf( $command_template . ' theme install "%1$s" --force', $theme_slug, $required_themes_regex, $required_plugins_regex );
} else {
$commands[] = sprintf( $command_template . ' atomic theme use-managed %1$s --remove-existing', $theme_slug, $required_themes_regex, $required_plugins_regex );
}

return $commands;
return array(
...array_map(
fn( $command ) => sprintf( $skip_plugins_parameter . $skip_themes_parameter . $command, $software_slug_or_url ),
array_values( $plugin_install_commands )
),
);
}

/**
* Verify that the product software is installed and activated.
* Generate the command to install a theme.
*
* @param array $expected_plugins A list of plugins that will be checked.
* @param array $expected_themes A list of themes that will be checked.
* @param string|null $expected_theme The theme that will be checked.
* @param string $theme_slug The theme slug to install.
* @param bool $is_managed Whether to use the managed version of the theme or not.
* @param array $skip_plugins List of plugins to skip when installing the theme.
* @param array $skip_themes List of themes to skip when installing the theme.
*
* @return bool|WP_Error
* @return string
*/
public function verify_installation( array $expected_plugins, array $expected_themes, string $expected_theme = null ) {
$plugins_verification = $this->verify_plugins_software_is_installed( $expected_plugins, $expected_themes );

if ( ! $plugins_verification || \is_wp_error( $plugins_verification ) ) {
return new \WP_Error(
'software_not_installed',
'Failed to verify plugin activation',
array(
'verification_result' => $plugins_verification,
)
);
public function generate_theme_install_command( string $theme_slug, bool $is_managed, array $skip_plugins, array $skip_themes ) {
$skip_plugins = implode( ',', $skip_plugins );
$skip_themes = implode( ',', $skip_themes );

$skip_plugins_parameter = '--skip-plugins';
if ( ! empty( $skip_plugins ) ) {
$skip_plugins_parameter .= '="' . $skip_plugins . '" ';
}

if ( null !== $expected_theme ) {
$theme_verification = $this->verify_theme_software_is_installed( $expected_theme );

if ( false === $theme_verification || \is_wp_error( $theme_verification ) ) {
return new \WP_Error(
'software_not_installed',
'Failed to verify theme activation',
array(
'verification_result' => $theme_verification,
)
);
}
$skip_themes_parameter = '--skip-themes';
if ( ! empty( $skip_themes ) ) {
$skip_themes_parameter .= '="' . $skip_themes . '" ';
}

return true;
if ( ! $is_managed ) {
$command = sprintf( $skip_plugins_parameter . $skip_themes_parameter . ' theme install "%s" --force', $theme_slug );
} else {
$command = sprintf( $skip_plugins_parameter . $skip_themes_parameter . ' atomic theme use-managed %s --remove-existing', $theme_slug );
}

return $command;
}

/**
* Check if the given plugins are activated on the Atomic site.
* Generate the commands to verify the installation of the plugins.
*
* @param array $expected_plugins A list of plugins that will be checked.
* @param array $expected_themes A list of themes that will be checked.
*
* @return bool|WP_Error
*/
private function verify_plugins_software_is_installed( array $expected_plugins, array $expected_themes ) {
public function generate_verify_plugin_installation_commands( array $expected_plugins, array $expected_themes ) {
$expected_plugins = \array_filter( $expected_plugins );

if ( empty( $expected_plugins ) ) {
return true;
}

$plugin_commands = \array_map(
fn( $plugin_slug ) => 'wp --skip-themes --skip-plugins plugin get ' . $plugin_slug . ' --field=status',
fn( $plugin_slug ) => '--skip-themes --skip-plugins plugin get ' . $plugin_slug . ' --field=status',
$expected_plugins
);

$theme_commands = \array_map(
fn( $plugin_slug ) => 'wp --skip-themes --skip-plugins theme get ' . $plugin_slug . ' --field=status',
fn( $plugin_slug ) => '--skip-themes --skip-plugins theme get ' . $plugin_slug . ' --field=status',
$expected_themes
);
$commands = array( ...$plugin_commands, ...$theme_commands );

return $this->run_verify_commands( $commands );
return array( ...$plugin_commands, ...$theme_commands );
}

/**
* Check if the given theme is activated on the Atomic site.
* Generate the command to verify the theme installation.
*
* @param string $expected_theme The theme that will be checked.
*
* @return bool|WP_Error
*/
private function verify_theme_software_is_installed( string $expected_theme ) {
$commands = array( 'wp --skip-themes --skip-plugins theme get ' . $expected_theme . ' --field=status' );

return $this->run_verify_commands( $commands, true );
}

/**
* Run the commands that verifies if the software is either installed/active or installed/inactive.
*
* @param array $commands The list of verification commands to be run.
* @param bool $verify_only_installation Whether to verify only installation or installation and activation.
*
* @return bool|WP_Error
* @return string
*/
private function run_verify_commands( array $commands, bool $verify_only_installation = false ) {
$command = \implode( ' && ', $commands );

WP_CLI::debug( 'Running command: ' . $command );
$result = WP_CLI::runcommand(
$command,
array(
'launch' => false,
'exit_error' => false,
)
);

if ( \is_wp_error( $result ) ) {
return $result;
}

if ( $verify_only_installation ) {
// Only verify installation, we can return true as the command did not return any errors
return true;
}

// Returns true if the output displays "active" for each $software item.
return \substr_count( $result->stdout, 'active' ) === \count( $commands );
public function generate_verify_theme_installation_command( string $expected_theme ) {
return '--skip-themes --skip-plugins theme get ' . $expected_theme . ' --field=status';
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -86,11 +86,11 @@ public static function get_product_installer( Marketplace_Product_Software $prod
$command_helper = new Marketplace_Command_Helper();

if ( $product_software instanceof Marketplace_Plugin_Software ) {
return new Marketplace_Plugin_Installer( $command_helper );
return new Marketplace_Plugin_Installer( $command_helper, $product_software );
}

if ( $product_software instanceof Marketplace_Theme_Software ) {
return new Marketplace_Theme_Installer( $command_helper );
return new Marketplace_Theme_Installer( $command_helper, $product_software );
}

return new WP_Error( 'invalid_product_type', 'Invalid product type.' );
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ public function install_marketplace_software() {
$installer = Marketplace_Software_Factory::get_product_installer( $product_software );
$installation = $installer->install( $product_software );
if ( is_wp_error( $installation ) ) {
WPCOMSH_Log::unsafe_direct_log( $installation->get_error_message() );
WPCOMSH_Log::unsafe_direct_log( $installation->get_error_message(), $installation->get_error_data() );
}
}

Expand All @@ -46,16 +46,12 @@ public function install_marketplace_software() {

/**
* Get the marketplace software from Atomic Persist Data. This data is persisted by the
* woa_post_transfer_wpcomsh_cli_flags_install_marketplace_software_filter.
* woa_post_transfer_wpcomsh_cli_flags_install_marketplace_software_filter on WPCOM.
*
* @return array|WP_Error
*/
protected function get_apd_marketplace_software() {
$atomic_persist_data = new Atomic_Persistent_Data();
if ( ! $atomic_persist_data ) {
return new WP_Error( 'atomic_persist_data_not_found', 'Atomic persist data not found.' );
}

if ( empty( $atomic_persist_data->WPCOM_MARKETPLACE_SOFTWARE ) ) { // phpcs:ignore WordPress.NamingConventions.ValidVariableName.UsedPropertyNotSnakeCase
return new WP_Error( 'marketplace_software_not_found', 'WPCOM_Marketplace_Software Atomic persist data is empty. No Marketplace Software installed.' );
}
Expand Down
Loading

0 comments on commit 41b8fbb

Please sign in to comment.