Файловый менеджер - Редактировать - /home/tuudkjt/globeasy/wp-includes/ID3/blueprint.tar
Назад
blueprint.php 0000777 00000000433 15252167130 0007272 0 ustar 00 <?php /** * Plugin Name: Blueprint * Plugin URI: https://woocommerce.com/ * Description: An empty blueprint definition file to setup wp-env test env. * Version: 0.0.1 * Author: Automattic * Author URI: https://woocommerce.com * Requires at least: 6.4 * Requires PHP: 7.4 */ src/ImportSchema.php 0000777 00000004531 15252167130 0010453 0 ustar 00 <?php namespace Automattic\WooCommerce\Blueprint; use Automattic\WooCommerce\Blueprint\Schemas\JsonSchema; use Opis\JsonSchema\Errors\ErrorFormatter; use Opis\JsonSchema\Validator; /** * Class ImportSchema * * Handles the import schema functionality for WooCommerce. * * @package Automattic\WooCommerce\Blueprint */ class ImportSchema { use UseWPFunctions; /** * JsonSchema object. * * @var JsonSchema The schema instance. */ private JsonSchema $schema; /** * Validator object. * * @var Validator The JSON schema validator instance. */ private Validator $validator; /** * ImportSchema constructor. * * @param JsonSchema $schema The schema instance. * @param Validator|null $validator The validator instance, optional. */ public function __construct( JsonSchema $schema, ?Validator $validator = null ) { $this->schema = $schema; if ( null === $validator ) { $validator = new Validator(); } $this->validator = $validator; } /** * Get the schema. * * @return JsonSchema The schema. */ public function get_schema() { return $this->schema; } /** * Create an ImportSchema instance from a file. * * @param string $file The file path. * @return ImportSchema The created ImportSchema instance. * * @throws \RuntimeException If the JSON file cannot be read. * @throws \InvalidArgumentException If the JSON is invalid or missing 'steps' field. */ public static function create_from_file( $file ) { return self::create_from_json( $file ); } /** * Create an ImportSchema instance from a JSON file. * * @param string $json_path The JSON file path. * @return ImportSchema The created ImportSchema instance. * * @throws \RuntimeException If the JSON file cannot be read. * @throws \InvalidArgumentException If the JSON is invalid or missing 'steps' field. */ public static function create_from_json( $json_path ) { return new self( new JsonSchema( $json_path ) ); } /** * Import the schema steps. * * @return StepProcessorResult[] */ public function import() { $results = array(); $result = StepProcessorResult::success( 'ImportSchema' ); $results[] = $result; foreach ( $this->schema->get_steps() as $step_schema ) { $step_importer = new ImportStep( $step_schema, $this->validator ); $results[] = $step_importer->import(); } return $results; } } src/Cli/ImportCli.php 0000777 00000002460 15252167130 0010470 0 ustar 00 <?php namespace Automattic\WooCommerce\Blueprint\Cli; use Automattic\WooCommerce\Blueprint\ImportSchema; use Automattic\WooCommerce\Blueprint\ResultFormatters\CliResultFormatter; /** * Class ImportCli */ class ImportCli { /** * Schema path * * @var string $schema_path The path to the schema file. */ private $schema_path; /** * ImportCli constructor. * * @param string $schema_path The path to the schema file. */ public function __construct( $schema_path ) { $this->schema_path = $schema_path; } /** * Run the import process. * * @param array $optional_args Optional arguments. * * @return void */ public function run( $optional_args ) { try { $blueprint = ImportSchema::create_from_file( $this->schema_path ); } catch ( \Exception $e ) { \WP_CLI::error( $e->getMessage() ); return; } $results = $blueprint->import(); $result_formatter = new CliResultFormatter( $results ); $is_success = $result_formatter->is_success(); if ( isset( $optional_args['show-messages'] ) ) { $result_formatter->format( $optional_args['show-messages'] ); } if ( $is_success ) { \WP_CLI::success( "$this->schema_path imported successfully" ); } else { \WP_CLI::error( "Failed to import $this->schema_path. Run with --show-messages=all to debug" ); } } } src/Cli/ExportCli.php 0000777 00000002551 15252167130 0010500 0 ustar 00 <?php namespace Automattic\WooCommerce\Blueprint\Cli; use Automattic\WooCommerce\Blueprint\ExportSchema; use Automattic\WooCommerce\Blueprint\UseWPFunctions; /** * Class ExportCli * * This class handles the CLI commands for exporting schemas. * * @package Automattic\WooCommerce\Blueprint\Cli */ class ExportCli { use UseWPFunctions; /** * The path where the exported schema will be saved. * * @var string The path where the exported schema will be saved. */ private string $save_to; /** * ExportCli constructor. * * @param string $save_to The path where the exported schema will be saved. */ public function __construct( $save_to ) { $this->save_to = $save_to; } /** * Run the export process. * * @param array $args The arguments for the export process. */ public function run( $args = array() ) { if ( ! isset( $args['steps'] ) ) { $args['steps'] = array(); } $exporter = new ExportSchema(); $result = $exporter->export( $args['steps'] ); if ( is_wp_error( $result ) ) { \WP_CLI::error( $result->get_error_message() ); return; } $is_saved = $this->wp_filesystem_put_contents( $this->save_to, wp_json_encode( $result, JSON_PRETTY_PRINT ) ); if ( false === $is_saved ) { \WP_CLI::error( "Failed to save to {$this->save_to}" ); } else { \WP_CLI::success( "Exported JSON to {$this->save_to}" ); } } } src/StepProcessorResult.php 0000777 00000007101 15252167130 0012066 0 ustar 00 <?php namespace Automattic\WooCommerce\Blueprint; use InvalidArgumentException; /** * A class returned by StepProcessor classes containing result of the process and messages. */ class StepProcessorResult { const MESSAGE_TYPES = array( 'error', 'info', 'debug', 'warn' ); /** * Messages * * @var array $messages */ private array $messages = array(); /** * Indicate whether the process was success or not * * @var bool $success */ private bool $success; /** * Step name * * @var string $step_name */ private string $step_name; /** * Construct. * * @param bool $success Indicate whether the process was success or not. * @param string $step_name The name of the step. */ public function __construct( bool $success, string $step_name ) { $this->success = $success; $this->step_name = $step_name; } /** * Get messages. * * @param string $step_name The name of the step. * * @return void */ public function set_step_name( $step_name ) { $this->step_name = $step_name; } /** * Create a new instance with $success = true. * * @param string $stp_name The name of the step. * * @return StepProcessorResult */ public static function success( string $stp_name ): self { return ( new self( true, $stp_name ) ); } /** * Add a new message. * * @param string $message message. * @param string $type one of error, info. * * @throws InvalidArgumentException When incorrect type is given. * @return void */ public function add_message( string $message, string $type = 'error' ) { if ( ! in_array( $type, self::MESSAGE_TYPES, true ) ) { // phpcs:ignore throw new InvalidArgumentException( "{$type} is not allowed. Type must be one of " . implode( ',', self::MESSAGE_TYPES ) ); } $this->messages[] = compact( 'message', 'type' ); } /** * Merge messages from another StepProcessorResult instance. * * @param StepProcessorResult $other The other StepProcessorResult instance. * * @return void */ public function merge_messages( StepProcessorResult $other ) { $this->messages = array_merge( $this->messages, $other->get_messages() ); } /** * Add a new error message. * * @param string $message message. * * @return void */ public function add_error( string $message ) { $this->add_message( $message ); } /** * Add a new debug message. * * @param string $message message. * * @return void */ public function add_debug( string $message ) { $this->add_message( $message, 'debug' ); } /** * Add a new info message. * * @param string $message message. * * @return void */ public function add_info( string $message ) { $this->add_message( $message, 'info' ); } /** * Add a new warn message. * * @param string $message message. * * @return void */ public function add_warn( string $message ) { $this->add_message( $message, 'warn' ); } /** * Filter messages. * * @param string $type one of all, error, and info. * * @return array */ public function get_messages( string $type = 'all' ): array { if ( 'all' === $type ) { return $this->messages; } return array_filter( $this->messages, function ( $message ) use ( $type ) { return $type === $message['type']; } ); } /** * Check to see if the result was success. * * @return bool */ public function is_success(): bool { return true === $this->success && 0 === count( $this->get_messages( 'error' ) ); } /** * Get the name of the step. * * @return string The name of the step. */ public function get_step_name() { return $this->step_name; } } src/ResultFormatters/CliResultFormatter.php 0000777 00000003175 15252167130 0015202 0 ustar 00 <?php namespace Automattic\WooCommerce\Blueprint\ResultFormatters; use Automattic\WooCommerce\Blueprint\StepProcessorResult; use function WP_CLI\Utils\format_items; /** * Class CliResultFormatter */ class CliResultFormatter { /** * The results to format. * * @var StepProcessorResult[] */ private array $results; /** * CliResultFormatter constructor. * * @param array $results The results to format. */ public function __construct( array $results ) { $this->results = $results; } /** * Format the results. * * @param string $message_type The message type to format. * * @return void * * @throws \Exception If WP CLI Utils is not found. */ public function format( $message_type = 'debug' ) { $header = array( 'Step Processor', 'Type', 'Message' ); $items = array(); foreach ( $this->results as $result ) { $step_name = $result->get_step_name(); foreach ( $result->get_messages( $message_type ) as $message ) { $items[] = array( 'Step Processor' => $step_name, 'Type' => $message['type'], 'Message' => $message['message'], ); } } $format_items_exist = function_exists( '\WP_CLI\Utils\format_items' ); if ( ! $format_items_exist ) { throw new \Exception( 'WP CLI Utils not found' ); } format_items( 'table', $items, $header ); } /** * Check if all results are successful. * * @return bool True if all results are successful, false otherwise. */ public function is_success() { foreach ( $this->results as $result ) { $is_success = $result->is_success(); if ( ! $is_success ) { return false; } } return true; } } src/ResultFormatters/JsonResultFormatter.php 0000777 00000002754 15252167130 0015406 0 ustar 00 <?php namespace Automattic\WooCommerce\Blueprint\ResultFormatters; use Automattic\WooCommerce\Blueprint\StepProcessorResult; /** * Class JsonResultFormatter */ class JsonResultFormatter { /** * The results to format. * * @var StepProcessorResult[] */ private array $results; /** * JsonResultFormatter constructor. * * @param array $results The results to format. */ public function __construct( array $results ) { $this->results = $results; } /** * Format the results. * * @param string $message_type The message type to format. * * @return array */ public function format( $message_type = 'all' ) { $data = array( 'is_success' => $this->is_success(), 'messages' => array(), ); foreach ( $this->results as $result ) { $step_name = $result->get_step_name(); foreach ( $result->get_messages( $message_type ) as $message ) { if ( ! isset( $data['messages'][ $message['type'] ] ) ) { $data['messages'][ $message['type'] ] = array(); } $data['messages'][ $message['type'] ][] = array( 'step' => $step_name, 'type' => $message['type'], 'message' => $message['message'], ); } } return $data; } /** * Check if all results are successful. * * @return bool True if all results are successful, false otherwise. */ public function is_success() { foreach ( $this->results as $result ) { $is_success = $result->is_success(); if ( ! $is_success ) { return false; } } return true; } } src/Exporters/StepExporter.php 0000777 00000001455 15252167130 0012521 0 ustar 00 <?php namespace Automattic\WooCommerce\Blueprint\Exporters; use Automattic\WooCommerce\Blueprint\Steps\Step; /** * Interface StepExporter * * A Step Exporter is responsible collecting data needed for a Step object and exporting it. * Refer to the Step class for the data needed as each step may require different data. */ interface StepExporter { /** * Collect data needed for a Step object and export it. * * @return Step */ public function export(); /** * Returns the name of the step class it exports. * * @return string */ public function get_step_name(); /** * Check if the current user has the required capabilities for this step. * * @return bool True if the user has the required capabilities. False otherwise. */ public function check_step_capabilities(): bool; } src/Exporters/ExportInstallThemeSteps.php 0000777 00000003617 15252167130 0014671 0 ustar 00 <?php namespace Automattic\WooCommerce\Blueprint\Exporters; use Automattic\WooCommerce\Blueprint\Steps\InstallTheme; use Automattic\WooCommerce\Blueprint\UseWPFunctions; /** * Class ExportInstallThemeSteps * * Exporter for the InstallTheme step. * * @package Automattic\WooCommerce\Blueprint\Exporters */ class ExportInstallThemeSteps implements StepExporter { use UseWPFunctions; /** * Filter callback. * * @var callable */ private $filter_callback; /** * Register a filter callback to filter the plugins to export. * * @param callable $callback Filter callback. * * @return void */ public function filter( callable $callback ) { $this->filter_callback = $callback; } /** * Export the steps. * * @return array */ public function export() { $steps = array(); $themes = $this->wp_get_themes(); if ( is_callable( $this->filter_callback ) ) { $themes = call_user_func( $this->filter_callback, $themes ); } $active_theme = $this->wp_get_theme(); foreach ( $themes as $slug => $theme ) { // Check if the theme is active. $is_active = $theme->get( 'Name' ) === $active_theme->get( 'Name' ); $info = $this->wp_themes_api( 'theme_information', array( 'slug' => $slug, 'fields' => array( 'sections' => false, ), ) ); if ( isset( $info->download_link ) ) { $steps[] = new InstallTheme( $slug, 'wordpress.org/themes', array( 'activate' => $is_active, ) ); } } return $steps; } /** * Get the step name. * * @return string */ public function get_step_name() { return InstallTheme::get_step_name(); } /** * Check if the current user has the required capabilities for this step. * * @return bool True if the user has the required capabilities. False otherwise. */ public function check_step_capabilities(): bool { return current_user_can( 'switch_themes' ); } } src/Exporters/HasAlias.php 0000777 00000002075 15252167130 0011541 0 ustar 00 <?php namespace Automattic\WooCommerce\Blueprint\Exporters; /** * Allows a step to have an alias. * * An alias is useful for selective export. * * Let's say you have three exporters and all of them use `setSiteOptions` step. * * Step A: Exports options from WooCommerce -> Settings * Step B: Exports options for the core profiler selection. * Step C: Exports options for the task list. * * You also have a UI where a client can select which steps to export. In this case, we have three checkboxes. * * [ ] WooCommerce Settings * [ ] WooCommerce Core Profiler * [ ] WooCommerce Task List * * Without alias, the client would see three `setSiteOptions` steps and it's not possible * to distinguish between them from the ExportSchema class. * * With alias, you can give each step a unique alias while keeping the step name the same. * * @todo Link to an example class that uses this interface. * * Interface HasAlias */ interface HasAlias { /** * Get the alias for the step. * * @return string The alias for the step. */ public function get_alias(); } src/Exporters/ExportInstallPluginSteps.php 0000777 00000010116 15252167130 0015055 0 ustar 00 <?php namespace Automattic\WooCommerce\Blueprint\Exporters; use Automattic\WooCommerce\Blueprint\Steps\InstallPlugin; use Automattic\WooCommerce\Blueprint\UseWPFunctions; /** * Class ExportInstallPluginSteps * * @package Automattic\WooCommerce\Blueprint\Exporters */ class ExportInstallPluginSteps implements StepExporter { use UseWPFunctions; /** * Filter callback. * * @var callable */ private $filter_callback; /** * Whether to include private plugins in the export. * * @var bool Whether to include private plugins in the export. */ private bool $include_private_plugins = false; /** * Set whether to include private plugins in the export. * * @param bool $boolean Whether to include private plugins. */ public function include_private_plugins( bool $boolean ) { $this->include_private_plugins = $boolean; } /** * Register a filter callback to filter the plugins to export. * * @param callable $callback Filter callback. * * @return void */ public function filter( callable $callback ) { $this->filter_callback = $callback; } /** * Export the steps required to install plugins. * * @return array The array of InstallPlugin steps. */ public function export() { $plugins = $this->sort_plugins_by_dep( $this->wp_get_plugins() ); if ( is_callable( $this->filter_callback ) ) { $plugins = call_user_func( $this->filter_callback, $plugins ); } // @todo temporary fix for JN site -- it includes WooCommerce as a custom plugin // since JN sites are using a different slug. $exclude = array( 'WooCommerce Beta Tester' ); $steps = array(); foreach ( $plugins as $path => $plugin ) { if ( in_array( $plugin['Name'], $exclude, true ) ) { continue; } $slug = dirname( $path ); // single-file plugin. if ( '.' === $slug ) { $slug = pathinfo( $path )['filename']; } $info = $this->wp_plugins_api( 'plugin_information', array( 'slug' => $slug, 'fields' => array( 'sections' => false, ), ) ); $has_download_link = isset( $info->download_link ); if ( false === $this->include_private_plugins && ! $has_download_link ) { continue; } $resource = $has_download_link ? 'wordpress.org/plugins' : 'self/plugins'; $steps[] = new InstallPlugin( $slug, $resource, array( 'activate' => true, ) ); } return $steps; } /** * Sort plugins by dependencies -- put the dependencies at the top. * * @param array $plugins List of plugins to sort (from wp_get_plugins function). * * @return array */ public function sort_plugins_by_dep( array $plugins ) { $sorted = array(); $visited = array(); // Create a mapping of lowercase titles to plugin keys for quick lookups. $title_map = array_reduce( array_keys( $plugins ), function ( $carry, $key ) use ( $plugins ) { $title = strtolower( $plugins[ $key ]['Title'] ?? '' ); if ( $title ) { $carry[ $title ] = $key; } return $carry; }, array() ); // Recursive function for topological sort. $visit = function ( $plugin_key ) use ( &$visit, &$sorted, &$visited, $plugins, $title_map ) { if ( isset( $visited[ $plugin_key ] ) ) { return; } $visited[ $plugin_key ] = true; $requires = $plugins[ $plugin_key ]['RequiresPlugins'] ?? array(); foreach ( (array) $requires as $dependency ) { $dependency_key = $title_map[ strtolower( $dependency ) ] ?? null; if ( $dependency_key ) { $visit( $dependency_key ); } } $sorted[ $plugin_key ] = $plugins[ $plugin_key ]; }; // Perform sort for each plugin. foreach ( array_keys( $plugins ) as $plugin_key ) { $visit( $plugin_key ); } return $sorted; } /** * Get the name of the step. * * @return string The step name. */ public function get_step_name() { return InstallPlugin::get_step_name(); } /** * Check if the current user has the required capabilities for this step. * * @return bool True if the user has the required capabilities. False otherwise. */ public function check_step_capabilities(): bool { return current_user_can( 'activate_plugins' ); } } src/Importers/ImportActivatePlugin.php 0000777 00000003052 15252167130 0014153 0 ustar 00 <?php namespace Automattic\WooCommerce\Blueprint\Importers; use Automattic\WooCommerce\Blueprint\StepProcessor; use Automattic\WooCommerce\Blueprint\StepProcessorResult; use Automattic\WooCommerce\Blueprint\Steps\ActivatePlugin; use Automattic\WooCommerce\Blueprint\UsePluginHelpers; /** * Class ImportActivatePlugin */ class ImportActivatePlugin implements StepProcessor { use UsePluginHelpers; /** * Process the schema. * * @param object $schema The schema to process. * * @return StepProcessorResult */ public function process( $schema ): StepProcessorResult { $result = StepProcessorResult::success( ActivatePlugin::get_step_name() ); // Not snake case because it's a property of the schema. $plugin_path = $schema->pluginPath; // phpcs:ignore WordPress.NamingConventions.ValidVariableName.UsedPropertyNotSnakeCase $activate = $this->wp_activate_plugin( $plugin_path ); if ( $this->is_wp_error( $activate ) ) { $result->add_error( "Unable to activate {$plugin_path}." ); } else { $result->add_info( "Activated {$plugin_path}." ); } return $result; } /** * Get the step class. * * @return string */ public function get_step_class(): string { return ActivatePlugin::class; } /** * Check if the current user has the required capabilities for this step. * * @param object $schema The schema to process. * * @return bool True if the user has the required capabilities. False otherwise. */ public function check_step_capabilities( $schema ): bool { return current_user_can( 'activate_plugins' ); } } src/Importers/ImportActivateTheme.php 0000777 00000003121 15252167130 0013754 0 ustar 00 <?php namespace Automattic\WooCommerce\Blueprint\Importers; use Automattic\WooCommerce\Blueprint\StepProcessor; use Automattic\WooCommerce\Blueprint\StepProcessorResult; use Automattic\WooCommerce\Blueprint\Steps\ActivateTheme; use Automattic\WooCommerce\Blueprint\UsePluginHelpers; use Automattic\WooCommerce\Blueprint\UseWPFunctions; /** * Class ImportActivateTheme * * @package Automattic\WooCommerce\Blueprint\Importers */ class ImportActivateTheme implements StepProcessor { use UsePluginHelpers; use UseWPFunctions; /** * Process the step. * * @param object $schema The schema for the step. * * @return StepProcessorResult */ public function process( $schema ): StepProcessorResult { $result = StepProcessorResult::success( ActivateTheme::get_step_name() ); // phpcs:ignore $name = $schema->themeName; $this->wp_switch_theme( $name ); $current_theme = $this->wp_get_theme()->get_stylesheet(); if ( $current_theme === $name ) { $result->add_debug( "Switched theme to '$name'." ); } return $result; } /** * Returns the class name of the step this processor handles. * * @return string The class name of the step this processor handles. */ public function get_step_class(): string { return ActivateTheme::class; } /** * Check if the current user has the required capabilities for this step. * * @param object $schema The schema to process. * * @return bool True if the user has the required capabilities. False otherwise. */ public function check_step_capabilities( $schema ): bool { return current_user_can( 'switch_themes' ); } } src/Importers/ImportInstallTheme.php 0000777 00000010635 15252167130 0013632 0 ustar 00 <?php namespace Automattic\WooCommerce\Blueprint\Importers; use Automattic\WooCommerce\Blueprint\ResourceStorages; use Automattic\WooCommerce\Blueprint\StepProcessor; use Automattic\WooCommerce\Blueprint\StepProcessorResult; use Automattic\WooCommerce\Blueprint\Steps\InstallTheme; use Automattic\WooCommerce\Blueprint\UseWPFunctions; /** * Class ImportInstallTheme * * This class handles the import process for installing themes. * * @package Automattic\WooCommerce\Blueprint\Importers */ class ImportInstallTheme implements StepProcessor { use UseWPFunctions; /** * Collection of resource storages. * * @var ResourceStorages The resource storage used for downloading themes. */ private ResourceStorages $storage; /** * The result of the step processing. * * @var StepProcessorResult The result of the step processing. */ private StepProcessorResult $result; /** * ImportInstallTheme constructor. * * @param ResourceStorages $storage The resource storage used for downloading themes. */ public function __construct( ResourceStorages $storage ) { $this->result = StepProcessorResult::success( InstallTheme::get_step_name() ); $this->storage = $storage; } /** * Process the schema to install the theme. * * @param object $schema The schema containing theme installation details. * * @return StepProcessorResult The result of the step processing. */ public function process( $schema ): StepProcessorResult { $installed_themes = $this->wp_get_themes(); // phpcs:ignore $theme = $schema->themeData; if ( 'wordpress.org/themes' !== $theme->resource ) { $this->result->add_info( "Skipped installing a theme. Unsupported resource type. Only 'wordpress.org/themes' is supported at the moment." ); return $this->result; } if ( ! isset( $schema->options ) ) { $schema->options = new \stdClass(); } if ( isset( $installed_themes[ $theme->slug ] ) ) { $this->activate_theme( $schema ); $this->result->add_info( "Skipped installing {$theme->slug}. It is already installed." ); return $this->result; } if ( $this->storage->is_supported_resource( $theme->resource ) === false ) { $this->result->add_error( "Invalid resource type for {$theme->slug}" ); return $this->result; } $downloaded_path = $this->storage->download( $theme->slug, $theme->resource ); if ( ! $downloaded_path ) { $this->result->add_error( "Unable to download {$theme->slug} with {$theme->resource} resource type." ); return $this->result; } $this->result->add_debug( "'$theme->slug' has been downloaded in $downloaded_path" ); $install = $this->install( $downloaded_path ); if ( $install ) { $this->result->add_debug( "Theme '$theme->slug' installed successfully." ); } else { $this->result->add_error( "Failed to install theme '$theme->slug'." ); } $this->activate_theme( $schema ); return $this->result; } /** * Attempt to activate the theme if the schema specifies to do so. * * @param object $schema installTheme schema. * * @return void */ protected function activate_theme( $schema ) { // phpcs:ignore $theme = $schema->themeData; if ( isset( $schema->options->activate ) && true === $schema->options->activate ) { $this->wp_switch_theme( $theme->slug ); $current_theme = $this->wp_get_theme()->get_stylesheet(); if ( $current_theme === $theme->slug ) { $this->result->add_info( "Switched theme to '$theme->slug'." ); } else { $this->result->add_error( "Failed to switch theme to '$theme->slug'." ); } } } /** * Install the theme from the local path. * * @param string $local_path The local path of the theme to be installed. * * @return bool True if the installation was successful, false otherwise. */ protected function install( $local_path ) { $unzip_result = $this->wp_unzip_file( $local_path, $this->wp_get_theme_root() ); if ( $this->is_wp_error( $unzip_result ) ) { return false; } return true; } /** * Get the class name of the step. * * @return string The class name of the step. */ public function get_step_class(): string { return InstallTheme::class; } /** * Check if the current user has the required capabilities for this step. * * @param object $schema The schema to process. * * @return bool True if the user has the required capabilities. False otherwise. */ public function check_step_capabilities( $schema ): bool { return current_user_can( 'install_themes' ); } } src/Importers/ImportRunSql.php 0000777 00000021610 15252167130 0012460 0 ustar 00 <?php namespace Automattic\WooCommerce\Blueprint\Importers; use Automattic\WooCommerce\Blueprint\StepProcessor; use Automattic\WooCommerce\Blueprint\StepProcessorResult; use Automattic\WooCommerce\Blueprint\Steps\RunSql; use Automattic\WooCommerce\Blueprint\UsePluginHelpers; use Automattic\WooCommerce\Blueprint\UseWPFunctions; /** * Processes SQL execution steps in the Blueprint. * * Handles the execution of SQL queries with safety checks to prevent * unauthorized modifications to sensitive WordPress data. * * @package Automattic\WooCommerce\Blueprint\Importers */ class ImportRunSql implements StepProcessor { use UsePluginHelpers; use UseWPFunctions; /** * List of allowed SQL query types. * * @var array */ private const ALLOWED_QUERY_TYPES = array( 'INSERT', 'UPDATE', 'REPLACE INTO', ); /** * Process the SQL execution step. * * Validates and executes the SQL query while ensuring: * 1. Only allowed query types are executed * 2. No modifications to admin users or roles * 3. No unauthorized changes to user capabilities * * @param object $schema The schema containing the SQL query to execute. * @return StepProcessorResult The result of the SQL execution. */ public function process( $schema ): StepProcessorResult { global $wpdb; $result = StepProcessorResult::success( RunSql::get_step_name() ); $sql = trim( $schema->sql->contents ); // Check if the query type is allowed. if ( ! $this->is_allowed_query_type( $sql ) ) { $result->add_error( sprintf( 'Only %s queries are allowed.', implode( ', ', self::ALLOWED_QUERY_TYPES ) ) ); return $result; } // Check for SQL comments that might be hiding malicious code. if ( $this->contains_suspicious_comments( $sql ) ) { $result->add_error( 'SQL query contains suspicious comment patterns.' ); return $result; } // Detect SQL injection patterns. if ( $this->contains_sql_injection_patterns( $sql ) ) { $result->add_error( 'SQL query contains potential injection patterns.' ); return $result; } // Check if the query affects protected tables. if ( $this->affects_protected_tables( $sql ) ) { $result->add_error( 'Modifications to admin users or roles are not allowed.' ); return $result; } // Check if the query affects user capabilities in wp_options. if ( $this->affects_user_capabilities( $sql ) ) { $result->add_error( 'Modifications to user roles or capabilities are not allowed.' ); return $result; } $wpdb->suppress_errors( true ); $wpdb->query( 'START TRANSACTION' ); try { $query_result = $wpdb->query( $sql ); // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared $last_error = $wpdb->last_error; if ( $last_error ) { $wpdb->query( 'ROLLBACK' ); $result->add_error( 'Error executing SQL: ' . $last_error ); } else { $wpdb->query( 'COMMIT' ); $result->add_debug( "Executed SQL ({$schema->sql->name}): Affected {$query_result} rows" ); } } catch ( \Throwable $e ) { $wpdb->query( 'ROLLBACK' ); $result->add_error( "Exception executing SQL: {$e->getMessage()}" ); } return $result; } /** * Returns the class name of the step this processor handles. * * @return string The class name of the step this processor handles. */ public function get_step_class(): string { return RunSql::class; } /** * Check if the current user has the required capabilities for this step. * * @param object $schema The schema to process. * * @return bool True if the user has the required capabilities. False otherwise. */ public function check_step_capabilities( $schema ): bool { if ( ! current_user_can( 'manage_options' ) ) { return false; } if ( ! current_user_can( 'edit_posts' ) ) { return false; } if ( ! current_user_can( 'edit_users' ) ) { return false; } return true; } /** * Check if the SQL query type is allowed. * * @param string $sql_content The SQL query to check. * @return bool True if the query type is allowed, false otherwise. */ private function is_allowed_query_type( string $sql_content ): bool { $uppercase_sql_content = strtoupper( trim( $sql_content ) ); foreach ( self::ALLOWED_QUERY_TYPES as $query_type ) { if ( 0 === stripos( $uppercase_sql_content, $query_type ) ) { return true; } } return false; } /** * Check for suspicious comment patterns that might hide malicious code. * * This method detects various types of SQL comments that might be used * to hide malicious SQL commands or bypass security filters. * * @param string $sql_content The SQL query to check. * @return bool True if suspicious comments found, false otherwise. */ private function contains_suspicious_comments( string $sql_content ): bool { // Quick check if there are any comments at all before running regex. if ( strpos( $sql_content, '--' ) === false && strpos( $sql_content, '/*' ) === false && strpos( $sql_content, '#' ) === false ) { return false; } // List of potentially dangerous SQL commands to check for in comments. $dangerous_commands = array( 'DELETE', 'DROP', 'ALTER', 'CREATE', 'TRUNCATE', 'GRANT', 'REVOKE', 'EXEC', 'EXECUTE', 'CALL', 'INTO OUTFILE', 'INTO DUMPFILE', 'LOAD_FILE', 'LOAD DATA', 'BENCHMARK', 'SLEEP', 'INFORMATION_SCHEMA', 'USER\\(', 'DATABASE\\(', 'SCHEMA\\(', ); $dangerous_pattern = implode( '|', $dangerous_commands ); // Check for SQL comments that might be hiding malicious code. $patterns = array( // Single-line comments (-- style) containing dangerous commands. '/--.*?(' . $dangerous_pattern . ')/i', // Single-line comments (# style) containing dangerous commands. '/#.*?(' . $dangerous_pattern . ')/i', // Multi-line comments hiding dangerous commands. '/\/\*.*?(' . $dangerous_pattern . ').*?\*\//is', // MySQL-specific execution comments (version-specific code execution). '/\/\*![0-9]*.*?\*\//', ); foreach ( $patterns as $pattern ) { if ( preg_match( $pattern, $sql_content ) ) { return true; } } return false; } /** * Check for common SQL injection patterns. * * @param string $sql_content The SQL query to check. * @return bool True if potential injection patterns found, false otherwise. */ private function contains_sql_injection_patterns( string $sql_content ): bool { $patterns = array( '/UNION\s+(?:ALL\s+)?SELECT/i', // UNION-based injections. '/OR\s+1\s*=\s*1/i', // OR 1=1 condition. '/AND\s+0\s*=\s*0/i', // AND 0=0 condition. '/;\s*--/i', // Inline comment terminations. '/SLEEP\s*\(/i', // Time-based injections. '/BENCHMARK\s*\(/i', // Benchmark-based injections. '/LOAD_FILE\s*\(/i', // File access. '/INTO\s+OUTFILE/i', // File write. '/INTO\s+DUMPFILE/i', // File dump. '/CREATE\s+(?:TEMPORARY\s+)?TABLE/i', // Table creation. '/DROP\s+TABLE/i', // Table deletion. '/ALTER\s+TABLE/i', // Table alteration. '/INFORMATION_SCHEMA/i', // Database metadata access. '/EXEC\s*\(/i', // Stored procedure execution. '/SCHEMA_NAME/i', // Schema access. '/DATABASE\(\)/i', // Current database name. '/CHR\s*\(/i', // Character function for evasion. '/CHAR\s*\(/i', // Character function for evasion. '/FROM\s+mysql\./i', // Direct MySQL system table access. '/FROM\s+information_schema\./i', // Direct information schema access. ); foreach ( $patterns as $pattern ) { if ( preg_match( $pattern, $sql_content ) ) { return true; } } return false; } /** * Check if the SQL query affects protected user tables. * * @param string $sql_content The SQL query to check. * @return bool True if the query affects protected tables, false otherwise. */ private function affects_protected_tables( string $sql_content ): bool { global $wpdb; $protected_tables = array( $wpdb->users, $wpdb->usermeta, ); foreach ( $protected_tables as $table ) { if ( preg_match( '/\b' . preg_quote( $table, '/' ) . '\b/i', $sql_content ) ) { return true; } } return false; } /** * Check if the SQL query affects user capabilities in wp_options. * * @param string $sql_content The SQL query to check. * @return bool True if the query affects user capabilities, false otherwise. */ private function affects_user_capabilities( string $sql_content ): bool { global $wpdb; // Check if the query affects user capabilities in wp_options. if ( stripos( $sql_content, $wpdb->prefix . 'options' ) !== false ) { $option_patterns = array( 'user_roles', 'capabilities', 'wp_user_', 'role_', 'administrator', ); foreach ( $option_patterns as $pattern ) { if ( stripos( $sql_content, $pattern ) !== false ) { return true; } } } return false; } } src/Importers/ImportSetSiteOptions.php 0000777 00000004674 15252167130 0014203 0 ustar 00 <?php namespace Automattic\WooCommerce\Blueprint\Importers; use Automattic\WooCommerce\Blueprint\StepProcessor; use Automattic\WooCommerce\Blueprint\StepProcessorResult; use Automattic\WooCommerce\Blueprint\Steps\SetSiteOptions; use Automattic\WooCommerce\Blueprint\UseWPFunctions; /** * Class ImportSetSiteOptions * * Importer for the SetSiteOptions step. * * @package Automattic\WooCommerce\Blueprint\Importers */ class ImportSetSiteOptions implements StepProcessor { use UseWPFunctions; /** * List of WordPress options that should not be modified. * * @var array<string> */ private const RESTRICTED_OPTIONS = array( 'siteurl', 'home', 'active_plugins', 'template', 'stylesheet', 'admin_email', 'unfiltered_html', 'users_can_register', 'default_role', 'db_version', 'cron', 'rewrite_rules', 'wp_user_roles', ); /** * Process the step. * * @param object $schema The schema to process. * * @return StepProcessorResult */ public function process( $schema ): StepProcessorResult { $result = StepProcessorResult::success( SetSiteOptions::get_step_name() ); foreach ( $schema->options as $key => $value ) { // Skip if the option should not be modified. if ( in_array( $key, self::RESTRICTED_OPTIONS, true ) ) { $result->add_warn( "Cannot modify '{$key}' option: Modifying is restricted for this key." ); continue; } $value = json_decode( wp_json_encode( $value ), true ); $updated = $this->wp_update_option( $key, $value ); $current_value = $this->wp_get_option( $key ); if ( $current_value !== $value ) { $result->add_warn( "{$key} was intended to be set, but the stored value may have been overridden by a hook." ); continue; } if ( $updated ) { $result->add_info( "{$key} has been updated." ); continue; } if ( $current_value === $value ) { $result->add_info( "{$key} has not been updated because the current value is already up to date." ); } } return $result; } /** * Get the step class. * * @return string */ public function get_step_class(): string { return SetSiteOptions::class; } /** * Check if the current user has the required capabilities for this step. * * @param object $schema The schema to process. * * @return bool True if the user has the required capabilities. False otherwise. */ public function check_step_capabilities( $schema ): bool { return current_user_can( 'manage_options' ); } } src/Importers/ImportInstallPlugin.php 0000777 00000012456 15252167130 0014031 0 ustar 00 <?php namespace Automattic\WooCommerce\Blueprint\Importers; use Automattic\WooCommerce\Blueprint\ResourceStorages; use Automattic\WooCommerce\Blueprint\StepProcessor; use Automattic\WooCommerce\Blueprint\StepProcessorResult; use Automattic\WooCommerce\Blueprint\Steps\InstallPlugin; use Automattic\WooCommerce\Blueprint\UseWPFunctions; use Plugin_Upgrader; /** * Class ImportInstallPlugin * * Handles the installation and activation of plugins based on a schema. */ class ImportInstallPlugin implements StepProcessor { use UseWPFunctions; /** * Resource storage instance for handling plugin files. * * @var ResourceStorages */ private ResourceStorages $storage; /** * Array of paths to installed plugins. * * @var array */ private array $installed_plugin_paths = array(); /** * Constructor. * * @param ResourceStorages $storage Resource storage instance. */ public function __construct( ResourceStorages $storage ) { $this->storage = $storage; } /** * Processes the schema to install and optionally activate a plugin. * * @param object $schema Schema object containing plugin information. * @return StepProcessorResult Result of the processing. */ public function process( $schema ): StepProcessorResult { $result = StepProcessorResult::success( InstallPlugin::get_step_name() ); $installed_plugins = $this->get_installed_plugins_paths(); // phpcs:ignore $plugin = $schema->pluginData; // We only support CorePluginReference at the moment. if ( 'wordpress.org/plugins' !== $plugin->resource ) { $result->add_info( "Skipped installing a plugin. Unsupported resource type. Only 'wordpress.org/plugins' is supported at the moment." ); return $result; } // If the plugin is already installed, skip the installation. if ( isset( $installed_plugins[ $plugin->slug ] ) ) { $result->add_info( "Skipped installing {$plugin->slug}. It is already installed." ); return $result; } // If the resource type is not supported, return an error. if ( $this->storage->is_supported_resource( $plugin->resource ) === false ) { $result->add_error( "Invalid resource type for {$plugin->slug}." ); return $result; } // Download the plugin. $downloaded_path = $this->storage->download( $plugin->slug, $plugin->resource ); if ( ! $downloaded_path ) { $result->add_error( "Unable to download {$plugin->slug} with {$plugin->resource} resource type." ); return $result; } // Install the plugin. $install = $this->install( $downloaded_path ); if ( is_wp_error( $install ) ) { $result->add_error( "Failed to install {$plugin->slug}." ); return $result; } $result->add_info( "Installed {$plugin->slug}." ); // If the plugin should be activated, activate it. $should_activate = isset( $schema->options, $schema->options->activate ) && true === $schema->options->activate; if ( $should_activate ) { $activate = $this->activate( $plugin->slug ); if ( $activate instanceof \WP_Error ) { $result->add_error( "Failed to activate {$plugin->slug}." ); return $result; } $result->add_info( "Activated {$plugin->slug}." ); } return $result; } /** * Installs a plugin from the given local path. * * @param string $local_plugin_path Path to the local plugin file. * @return bool|WP_Error True on success, WP_Error on failure. */ protected function install( $local_plugin_path ) { if ( ! class_exists( 'Plugin_Upgrader' ) ) { include_once ABSPATH . '/wp-admin/includes/class-wp-upgrader.php'; include_once ABSPATH . '/wp-admin/includes/class-plugin-upgrader.php'; } $upgrader = new \Plugin_Upgrader( new \Automatic_Upgrader_Skin() ); return $upgrader->install( $local_plugin_path ); } /** * Activates an installed plugin by its slug. * * @param string $slug Plugin slug. * @return \WP_Error|null WP_Error on failure, null on success. */ protected function activate( $slug ) { if ( empty( $this->installed_plugin_paths ) ) { $this->installed_plugin_paths = $this->get_installed_plugins_paths(); } $path = $this->installed_plugin_paths[ $slug ] ?? false; if ( ! $path ) { return new \WP_Error( 'plugin_not_installed', "Plugin {$slug} is not installed." ); } return $this->wp_activate_plugin( $path ); } /** * Retrieves an array of installed plugins and their paths. * * @return array Array of installed plugins and their paths. */ protected function get_installed_plugins_paths() { if ( ! function_exists( 'get_plugins' ) ) { require_once ABSPATH . 'wp-admin/includes/plugin.php'; } $plugins = get_plugins(); $installed_plugins = array(); foreach ( $plugins as $path => $plugin ) { $path_parts = explode( '/', $path ); $slug = $path_parts[0]; $installed_plugins[ $slug ] = $path; } return $installed_plugins; } /** * Returns the class name of the step being processed. * * @return string Class name of the step. */ public function get_step_class(): string { return InstallPlugin::class; } /** * Check if the current user has the required capabilities for this step. * * @param object $schema The schema to process. * * @return bool True if the user has the required capabilities. False otherwise. */ public function check_step_capabilities( $schema ): bool { return current_user_can( 'install_plugins' ); } } src/UsePubSub.php 0000777 00000003011 15252167130 0007725 0 ustar 00 <?php namespace Automattic\WooCommerce\Blueprint; trait UsePubSub { /** * Subscribers. * * @var array */ private array $subscribers = array(); /** * Subscribe to an event with a callback. * * @param string $event The event name. * @param callable $callback The callback to execute when the event is published. * @return void */ public function subscribe( string $event, callable $callback ): void { if ( ! isset( $this->subscribers[ $event ] ) ) { $this->subscribers[ $event ] = array(); } $this->subscribers[ $event ][] = $callback; } /** * Publish an event to all subscribers. * * @param string $event The event name. * @param mixed ...$args Arguments to pass to the callbacks. * @return void */ public function publish( string $event, ...$args ): void { if ( ! isset( $this->subscribers[ $event ] ) ) { return; } foreach ( $this->subscribers[ $event ] as $callback ) { call_user_func( $callback, ...$args ); } } /** * Unsubscribe a specific callback from an event. * * @param string $event The event name. * @param callable $callback The callback to remove. * @return void */ public function unsubscribe( string $event, callable $callback ): void { if ( ! isset( $this->subscribers[ $event ] ) ) { return; } $this->subscribers[ $event ] = array_filter( $this->subscribers[ $event ], fn( $subscriber ) => $subscriber !== $callback ); if ( empty( $this->subscribers[ $event ] ) ) { unset( $this->subscribers[ $event ] ); } } } src/docs/json-examples/activatePlugin.json 0000777 00000000076 15252167130 0014736 0 ustar 00 { "step": "activatePlugin", "pluginName": "woocommerce" } src/docs/json-examples/installTheme.json 0000777 00000000230 15252167130 0014400 0 ustar 00 { "step": "installTheme", "themeZipFile": { "resource": "wordpress.org/themes", "slug": "twentytwenty" }, "options": { "activate": true } } src/docs/json-examples/setSiteOptions.json 0000777 00000000132 15252167130 0014744 0 ustar 00 { "step": "setSiteOptions", "options": { "woocommerce_allow_tracking": true } } src/docs/json-examples/installPlugin.json 0000777 00000000226 15252167130 0014601 0 ustar 00 { "step": "installPlugin", "pluginZipFile": { "resource": "wordpress.org/plugins", "slug": "akismet" }, "options": { "activate": true } } src/Schemas/JsonSchema.php 0000777 00000003676 15252167130 0011506 0 ustar 00 <?php namespace Automattic\WooCommerce\Blueprint\Schemas; use Automattic\WooCommerce\Blueprint\UseWPFunctions; /** * Class JsonSchema */ class JsonSchema { use UseWPFunctions; /** * The schema. * * @var object The schema. */ protected $schema; /** * JsonSchema constructor. * * @param string $json_path The path to the JSON file. * * @throws \RuntimeException If the JSON file cannot be read. * @throws \InvalidArgumentException If the JSON is invalid or missing 'steps' field. */ public function __construct( $json_path ) { $real_path = realpath( $json_path ); if ( false === $real_path ) { throw new \InvalidArgumentException( 'Invalid schema path' ); } $contents = $this->wp_filesystem_get_contents( $real_path ); if ( false === $contents ) { throw new \RuntimeException( "Failed to read the JSON file at {$real_path}." ); } $schema = json_decode( $contents ); $this->schema = $schema; if ( ! $this->validate() ) { throw new \InvalidArgumentException( "Invalid JSON or missing 'steps' field." ); } } /** * Returns the steps from the schema. * * @return array */ public function get_steps() { return $this->schema->steps; } /** * Returns steps by name. * * @param string $name The name of the step. * * @return array */ public function get_step( $name ) { $steps = array(); foreach ( $this->schema->steps as $step ) { if ( $step->step === $name ) { $steps[] = $step; } } return $steps; } /** * Just makes sure that the JSON contains 'steps' field. * * We're going to validate 'steps' later because we can't know the exact schema * ahead of time. 3rd party plugins can add their step processors. * * @return bool[ */ public function validate() { if ( json_last_error() !== JSON_ERROR_NONE ) { return false; } if ( ! isset( $this->schema->steps ) || ! is_array( $this->schema->steps ) ) { return false; } return true; } } src/Logger.php 0000777 00000010104 15252167130 0007270 0 ustar 00 <?php namespace Automattic\WooCommerce\Blueprint; use Automattic\WooCommerce\Blueprint\UseWPFunctions; /** * Class Logger */ class Logger { use UseWPFunctions; /** * WooCommerce logger class instance. * * @var \WC_Logger_Interface */ private $logger; /** * Constructor. */ public function __construct() { $this->logger = wc_get_logger(); } /** * Log a message as a debug log entry. * * @param string $message The message to log. * @param string $level The log level. * @param array $context The context of the log. */ public function log( string $message, string $level = \WC_Log_Levels::DEBUG, $context = array() ) { $this->logger->log( $level, $message, array_merge( array( 'source' => 'wc-blueprint', 'user_id' => $this->wp_get_current_user_id(), ), $context ) ); } /** * Log the start of an export operation. * * @param array $exporters Array of exporters. */ public function start_export( array $exporters ) { $export_data = $this->get_export_data( $exporters ); $this->log( sprintf( 'Starting export of %d steps', count( $export_data['steps'] ) ), \WC_Log_Levels::INFO, array( 'steps' => $export_data['steps'], 'exporters' => $export_data['exporters'], ) ); } /** * Log the completion of an export operation. * * @param array $exporters Array of exporters. */ public function complete_export( array $exporters ) { $export_data = $this->get_export_data( $exporters ); $this->log( sprintf( 'Export of %d steps completed', count( $export_data['steps'] ) ), \WC_Log_Levels::INFO, array( 'steps' => $export_data['steps'], 'exporters' => $export_data['exporters'], ) ); } /** * Extract export step names and exporter classes from exporters. * * @param array $exporters Array of exporters. * @return array Associative array with 'steps' and 'exporters' keys. */ private function get_export_data( array $exporters ) { $export_steps = array(); $exporter_classes = array(); foreach ( $exporters as $exporter ) { $step_name = method_exists( $exporter, 'get_alias' ) ? $exporter->get_alias() : $exporter->get_step_name(); $export_steps[] = $step_name; $exporter_classes[] = get_class( $exporter ); } return array( 'steps' => $export_steps, 'exporters' => $exporter_classes, ); } /** * Log an export step failure. * * @param string $step_name The name of the step that failed. * @param \Throwable $exception The exception that was thrown. */ public function export_step_failed( string $step_name, \Throwable $exception ) { $this->log( sprintf( 'Export "%s" step failed', $step_name ), \WC_Log_Levels::ERROR, array( 'error' => $exception->getMessage(), ) ); } /** * Log the start of an import step. * * @param string $step_name The name of the step being imported. * @param string $importer_class The class name of the importer. */ public function start_import( string $step_name, string $importer_class ) { $this->log( sprintf( 'Starting import "%s" step', $step_name ), \WC_Log_Levels::INFO, array( 'importer' => $importer_class, ) ); } /** * Log the successful completion of an import step. * * @param string $step_name The name of the step that was imported. * @param StepProcessorResult $result The result of the import. */ public function complete_import( string $step_name, StepProcessorResult $result ) { $this->log( sprintf( 'Import "%s" step completed', $step_name ), \WC_Log_Levels::INFO, array( 'messages' => $result->get_messages( 'info' ), ) ); } /** * Log an import step failure. * * @param string $step_name The name of the step that failed. * @param StepProcessorResult $result The result of the import. */ public function import_step_failed( string $step_name, StepProcessorResult $result ) { $this->log( sprintf( 'Import "%s" step failed', $step_name ), \WC_Log_Levels::ERROR, array( 'messages' => $result->get_messages( 'error' ), ) ); } } src/Steps/InstallTheme.php 0000777 00000005767 15252167130 0011563 0 ustar 00 <?php namespace Automattic\WooCommerce\Blueprint\Steps; /** * Class InstallTheme * * This class represents a step in the installation process of a WooCommerce theme. * It includes methods to prepare the data for the theme installation step and to provide * the schema for the JSON representation of this step. * * @package Automattic\WooCommerce\Blueprint\Steps */ class InstallTheme extends Step { /** * The slug of the theme to be installed. * * @var string The slug of the theme to be installed. */ private string $slug; /** * The resource URL or path to the theme's ZIP file. * * @var string The resource URL or path to the theme's ZIP file. */ private string $resource; /** * Additional options for the theme installation. * * @var array Additional options for the theme installation. */ private array $options; /** * InstallTheme constructor. * * @param string $slug The slug of the theme to be installed. * @param string $resource The resource URL or path to the theme's ZIP file. * @param array $options Additional options for the theme installation. */ // phpcs:ignore public function __construct( $slug, $resource, array $options = array() ) { $this->slug = $slug; $this->resource = $resource; $this->options = $options; } /** * Prepares an associative array for JSON encoding. * * @return array The JSON-encoded array representing this installation step. */ public function prepare_json_array(): array { return array( 'step' => static::get_step_name(), 'themeData' => array( 'resource' => $this->resource, 'slug' => $this->slug, ), 'options' => $this->options, ); } /** * Returns the schema for the JSON representation of this step. * * @param int $version The version of the schema to return. * @return array The schema array. */ public static function get_schema( int $version = 1 ): array { return array( 'type' => 'object', 'properties' => array( 'step' => array( 'type' => 'string', 'enum' => array( static::get_step_name() ), ), 'themeData' => array( 'anyOf' => array( require __DIR__ . '/schemas/definitions/VFSReference.php', require __DIR__ . '/schemas/definitions/LiteralReference.php', require __DIR__ . '/schemas/definitions/CorePluginReference.php', require __DIR__ . '/schemas/definitions/CoreThemeReference.php', require __DIR__ . '/schemas/definitions/UrlReference.php', require __DIR__ . '/schemas/definitions/GitDirectoryReference.php', require __DIR__ . '/schemas/definitions/DirectoryLiteralReference.php', ), ), 'options' => array( 'type' => 'object', 'properties' => array( 'activate' => array( 'type' => 'boolean', ), ), ), ), 'required' => array( 'step', 'themeData' ), ); } /** * Returns the name of this step. * * @return string The step name. */ public static function get_step_name(): string { return 'installTheme'; } } src/Steps/schemas/definitions/UrlReference.php 0000777 00000001075 15252167130 0015475 0 ustar 00 <?php return array( 'type' => 'object', 'properties' => array( 'resource' => array( 'type' => 'string', 'const' => 'url', 'description' => 'Identifies the file resource as a URL', ), 'url' => array( 'type' => 'string', 'description' => 'The URL of the file', ), 'caption' => array( 'type' => 'string', 'description' => 'Optional caption for displaying a progress message', ), ), 'required' => array( 'resource', 'url', ), 'additionalProperties' => false, ); src/Steps/schemas/definitions/DirectoryLiteralReference.php 0000777 00000000756 15252167130 0020221 0 ustar 00 <?php return array( 'type' => 'object', 'additionalProperties' => false, 'properties' => array( 'resource' => array( 'type' => 'string', 'const' => 'literal:directory', 'description' => 'Identifies the file resource as a git directory', ), 'files' => array( '$ref' => '#/definitions/FileTree', ), 'name' => array( 'type' => 'string', ), ), 'required' => array( 'files', 'name', 'resource', ), ); src/Steps/schemas/definitions/GitDirectoryReference.php 0000777 00000001335 15252167130 0017342 0 ustar 00 <?php return array( 'type' => 'object', 'properties' => array( 'resource' => array( 'type' => 'string', 'const' => 'git:directory', 'description' => 'Identifies the file resource as a git directory', ), 'url' => array( 'type' => 'string', 'description' => 'The URL of the git repository', ), 'ref' => array( 'type' => 'string', 'description' => 'The branch of the git repository', ), 'path' => array( 'type' => 'string', 'description' => 'The path to the directory in the git repository', ), ), 'required' => array( 'resource', 'url', 'ref', 'path', ), 'additionalProperties' => false, ); src/Steps/schemas/definitions/CorePluginReference.php 0000777 00000000760 15252167130 0017002 0 ustar 00 <?php return array( 'type' => 'object', 'properties' => array( 'resource' => array( 'type' => 'string', 'const' => 'wordpress.org/plugins', 'description' => 'Identifies the file resource as a WordPress Core plugin', ), 'slug' => array( 'type' => 'string', 'description' => 'The slug of the WordPress Core plugin', ), ), 'required' => array( 'resource', 'slug', ), 'additionalProperties' => false, ); src/Steps/schemas/definitions/VFSReference.php 0000777 00000000724 15252167130 0015371 0 ustar 00 <?php return array( 'type' => 'object', 'properties' => array( 'resource' => array( 'type' => 'string', 'const' => 'vfs', 'description' => 'Identifies the file resource as Virtual File System (VFS)', ), 'path' => array( 'type' => 'string', 'description' => 'The path to the file in the VFS', ), ), 'required' => array( 'resource', 'path' ), 'additionalProperties' => false, ); src/Steps/schemas/definitions/CoreThemeReference.php 0000777 00000000755 15252167130 0016612 0 ustar 00 <?php return array( 'type' => 'object', 'properties' => array( 'resource' => array( 'type' => 'string', 'const' => 'wordpress.org/themes', 'description' => 'Identifies the file resource as a WordPress Core theme', ), 'slug' => array( 'type' => 'string', 'description' => 'The slug of the WordPress Core theme', ), ), 'required' => array( 'resource', 'slug', ), 'additionalProperties' => false, ); src/Steps/schemas/definitions/LiteralReference.php 0000777 00000003021 15252167130 0016320 0 ustar 00 <?php return array( 'type' => 'object', 'properties' => array( 'resource' => array( 'type' => 'string', 'const' => 'literal', 'description' => 'Identifies the file resource as a literal file', ), 'name' => array( 'type' => 'string', 'description' => 'The name of the file', ), 'contents' => array( 'anyOf' => array( array( 'type' => 'string', ), array( 'type' => 'object', 'properties' => array( 'BYTES_PER_ELEMENT' => array( 'type' => 'number', ), 'buffer' => array( 'type' => 'object', 'properties' => array( 'byteLength' => array( 'type' => 'number', ), ), 'required' => array( 'byteLength', ), 'additionalProperties' => false, ), 'byteLength' => array( 'type' => 'number', ), 'byteOffset' => array( 'type' => 'number', ), 'length' => array( 'type' => 'number', ), ), 'required' => array( 'BYTES_PER_ELEMENT', 'buffer', 'byteLength', 'byteOffset', 'length', ), 'additionalProperties' => array( 'type' => 'number', ), ), ), 'description' => 'The contents of the file', ), ), 'required' => array( 'resource', 'name', 'contents', ), 'additionalProperties' => false, ); src/Steps/ActivateTheme.php 0000777 00000003041 15252167130 0011674 0 ustar 00 <?php namespace Automattic\WooCommerce\Blueprint\Steps; /** * Class ActivateTheme * * @package Automattic\WooCommerce\Blueprint\Steps */ class ActivateTheme extends Step { /** * The name of the theme to be activated. * * @var string The name of the theme to be activated. */ private string $theme_folder_name; /** * ActivateTheme constructor. * * @param string $theme_folder_name The name of the theme to be activated. */ public function __construct( $theme_folder_name ) { $this->theme_folder_name = $theme_folder_name; } /** * Returns the name of this step. * * @return string The step name. */ public static function get_step_name(): string { return 'activateTheme'; } /** * Returns the schema for the JSON representation of this step. * * @param int $version The version of the schema to return. * @return array The schema array. */ public static function get_schema( int $version = 1 ): array { return array( 'type' => 'object', 'properties' => array( 'step' => array( 'type' => 'string', 'enum' => array( static::get_step_name() ), ), 'themeFolderName' => array( 'type' => 'string', ), ), 'required' => array( 'step', 'themeFolderName' ), ); } /** * Prepares an associative array for JSON encoding. * * @return array Array of data to be encoded as JSON. */ public function prepare_json_array(): array { return array( 'step' => static::get_step_name(), 'themeFolderName' => $this->theme_folder_name, ); } } src/Steps/SetSiteOptions.php 0000777 00000002276 15252167130 0012116 0 ustar 00 <?php namespace Automattic\WooCommerce\Blueprint\Steps; /** * Set site options step. */ class SetSiteOptions extends Step { /** * Site options. * * @var array site options */ private array $options; /** * Constructor. * * @param array $options site options. */ public function __construct( array $options = array() ) { $this->options = $options; } /** * Get the name of the step. * * @return string step name */ public static function get_step_name(): string { return 'setSiteOptions'; } /** * Get the schema for the step. * * @param int $version schema version. * * @return array schema for the step */ public static function get_schema( int $version = 1 ): array { return array( 'type' => 'object', 'properties' => array( 'step' => array( 'type' => 'string', 'enum' => array( static::get_step_name() ), ), ), 'required' => array( 'step' ), ); } /** * Prepare the step for JSON serialization. * * @return array array representation of the step */ public function prepare_json_array(): array { return array( 'step' => static::get_step_name(), 'options' => (object) $this->options, ); } } src/Steps/Step.php 0000777 00000002662 15252167130 0010074 0 ustar 00 <?php namespace Automattic\WooCommerce\Blueprint\Steps; /** * Abstract class Step * * This class defines the structure for a Step that requires arguments to perform an action. * You can think it as a function described in JSON format. * * A Step should also be capable of returning formatted data that can be imported later. * Additionally, a Step can validate data. */ abstract class Step { /** * Meta values for the step. * * @var array $meta_values */ protected array $meta_values = array(); /** * Get the step name. * * @return string */ abstract public static function get_step_name(): string; /** * Get the schema for this step. * * @param int $version The schema version. * * @return array */ abstract public static function get_schema( int $version = 1 ): array; /** * Prepare the JSON array for this step. * * @return array The JSON array for the step. */ abstract public function prepare_json_array(): array; /** * Set meta values for the step. * * @param array $meta_values The meta values. * * @return void */ public function set_meta_values( array $meta_values ) { $this->meta_values = $meta_values; } /** * Get the JSON array for the step. * * @return mixed */ public function get_json_array() { $json_array = $this->prepare_json_array(); if ( ! empty( $this->meta_values ) ) { $json_array['meta'] = $this->meta_values; } return $json_array; } } src/Steps/RunSql.php 0000777 00000003617 15252167130 0010406 0 ustar 00 <?php namespace Automattic\WooCommerce\Blueprint\Steps; /** * Class RunSql * * @package Automattic\WooCommerce\Blueprint\Steps */ class RunSql extends Step { /** * Sql code to run. * * @var string */ protected string $sql = ''; /** * Name of the sql file. * * @var string */ protected string $name = 'schema.sql'; /** * Constructor. * * @param string $sql Sql code to run. * @param string $name Name of the sql file. */ public function __construct( string $sql, $name = 'schema.sql' ) { $this->sql = $sql; $this->name = $name; } /** * Returns the name of this step. * * @return string The step name. */ public static function get_step_name(): string { return 'runSql'; } /** * Returns the schema for the JSON representation of this step. * * @param int $version The version of the schema to return. * @return array The schema array. */ public static function get_schema( int $version = 1 ): array { return array( 'type' => 'object', 'properties' => array( 'step' => array( 'type' => 'string', 'enum' => array( static::get_step_name() ), ), 'sql' => array( 'type' => 'object', 'required' => array( 'contents', 'resource', 'name' ), 'properties' => array( 'resource' => array( 'type' => 'string', 'enum' => array( 'literal' ), ), 'name' => array( 'type' => 'string', ), 'contents' => array( 'type' => 'string', ), ), ), ), 'required' => array( 'step', 'sql' ), ); } /** * Prepares an associative array for JSON encoding. * * @return array Array of data to be encoded as JSON. */ public function prepare_json_array(): array { return array( 'step' => static::get_step_name(), 'sql' => array( 'resource' => 'literal', 'name' => $this->name, 'contents' => $this->sql, ), ); } } src/Steps/ActivatePlugin.php 0000777 00000003745 15252167130 0012103 0 ustar 00 <?php namespace Automattic\WooCommerce\Blueprint\Steps; /** * Class ActivatePlugin * * @package Automattic\WooCommerce\Blueprint\Steps */ class ActivatePlugin extends Step { /** * The name of the plugin to be activated. * * @var string The name of the plugin to be activated. */ private string $plugin_name; /** * The path to the plugin file relative to the plugins directory. * * @var string The path to the plugin file relative to the plugins directory. */ private string $plugin_path; /** * ActivatePlugin constructor. * * @param string $plugin_path Path to the plugin file relative to the plugins directory. * @param string $plugin_name The name of the plugin to be activated. */ public function __construct( $plugin_path, $plugin_name = '' ) { $this->plugin_name = $plugin_name; $this->plugin_path = $plugin_path; } /** * Returns the name of this step. * * @return string The step name. */ public static function get_step_name(): string { return 'activatePlugin'; } /** * Returns the schema for the JSON representation of this step. * * @param int $version The version of the schema to return. * @return array The schema array. */ public static function get_schema( int $version = 1 ): array { return array( 'type' => 'object', 'properties' => array( 'step' => array( 'type' => 'string', 'enum' => array( static::get_step_name() ), ), 'pluginName' => array( 'type' => 'string', ), 'pluginPath' => array( 'type' => 'string', ), ), 'required' => array( 'step', 'pluginPath' ), ); } /** * Prepares an associative array for JSON encoding. * * @return array Array of data to be encoded as JSON. */ public function prepare_json_array(): array { $data = array( 'step' => static::get_step_name(), 'pluginPath' => $this->plugin_path, ); if ( ! empty( $this->plugin_name ) ) { $data['pluginName'] = $this->plugin_name; } return $data; } } src/Steps/InstallPlugin.php 0000777 00000005774 15252167130 0011755 0 ustar 00 <?php namespace Automattic\WooCommerce\Blueprint\Steps; /** * Class InstallPlugin * * This class represents a step in the installation process of a WooCommerce plugin. * It includes methods to prepare the data for the plugin installation step and to provide * the schema for the JSON representation of this step. * * @package Automattic\WooCommerce\Blueprint\Steps */ class InstallPlugin extends Step { /** * The slug of the plugin to be installed. * * @var string The slug of the plugin to be installed. */ private string $slug; /** * The resource URL or path to the plugin's ZIP file. * * @var string The resource URL or path to the plugin's ZIP file. */ private string $resource; /** * Additional options for the plugin installation. * * @var array Additional options for the plugin installation. */ private array $options; /** * InstallPlugin constructor. * * @param string $slug The slug of the plugin to be installed. * @param string $resource The resource URL or path to the plugin's ZIP file. * @param array $options Additional options for the plugin installation. */ // phpcs:ignore public function __construct( $slug, $resource, array $options = array() ) { $this->slug = $slug; $this->resource = $resource; $this->options = $options; } /** * Prepares an associative array for JSON encoding. * * @return array Array representing this installation step. */ public function prepare_json_array(): array { return array( 'step' => static::get_step_name(), 'pluginData' => array( 'resource' => $this->resource, 'slug' => $this->slug, ), 'options' => $this->options, ); } /** * Returns the schema for the JSON representation of this step. * * @param int $version The version of the schema to return. * @return array The schema array. */ public static function get_schema( int $version = 1 ): array { return array( 'type' => 'object', 'properties' => array( 'step' => array( 'type' => 'string', 'enum' => array( static::get_step_name() ), ), 'pluginData' => array( 'anyOf' => array( require __DIR__ . '/schemas/definitions/VFSReference.php', require __DIR__ . '/schemas/definitions/LiteralReference.php', require __DIR__ . '/schemas/definitions/CorePluginReference.php', require __DIR__ . '/schemas/definitions/CoreThemeReference.php', require __DIR__ . '/schemas/definitions/UrlReference.php', require __DIR__ . '/schemas/definitions/GitDirectoryReference.php', require __DIR__ . '/schemas/definitions/DirectoryLiteralReference.php', ), ), 'options' => array( 'type' => 'object', 'properties' => array( 'activate' => array( 'type' => 'boolean', ), ), ), ), 'required' => array( 'step', 'pluginData' ), ); } /** * Returns the name of this step. * * @return string The step name. */ public static function get_step_name(): string { return 'installPlugin'; } } src/BuiltInExporters.php 0000777 00000000732 15252167130 0011341 0 ustar 00 <?php namespace Automattic\WooCommerce\Blueprint; use Automattic\WooCommerce\Blueprint\Exporters\ExportInstallPluginSteps; use Automattic\WooCommerce\Blueprint\Exporters\ExportInstallThemeSteps; /** * Built-in exporters. */ class BuiltInExporters { /** * Get all built-in exporters. * * @return array List of all built-in exporters. */ public function get_all() { return array( new ExportInstallPluginSteps(), new ExportInstallThemeSteps(), ); } } src/Util.php 0000777 00000010613 15252167130 0006773 0 ustar 00 <?php namespace Automattic\WooCommerce\Blueprint; use RecursiveArrayIterator; use RecursiveIteratorIterator; /** * Utility functions. */ class Util { /** * Ensure that the given path is a valid path within the WP_CONTENT_DIR. * * @param string $path The path to be validated. * * @return string * @throws \InvalidArgumentException If the path is invalid. */ public static function ensure_wp_content_path( $path ) { $path = realpath( $path ); if ( false === $path || strpos( $path, WP_CONTENT_DIR ) !== 0 ) { throw new \InvalidArgumentException( "Invalid path: $path" ); } return $path; } /** * Convert an array to an insert SQL query. * * @param array $row Array row with key and value. * @param string $table Name of the table. * @param string $type One of insert, insert ignore, replace into. * * @return false|string */ public static function array_to_insert_sql( $row, $table, $type = 'insert ignore' ) { if ( empty( $row ) || ! is_array( $row ) ) { return false; // Return false if input data is empty or not an array. } $allowed_types = array( 'insert', 'insert ignore', 'replace into' ); if ( ! in_array( $type, $allowed_types, true ) ) { return false; // Return false if input type is not valid. } // Get column names and values. $columns = '`' . implode( '`, `', array_keys( $row ) ) . '`'; $escaped_values = array_map( fn( $value ) => "'" . addslashes( $value ) . "'", $row ); $values = implode( ', ', $escaped_values ); // Construct final SQL query. return "{$type} `$table` ($columns) VALUES ($values);"; } /** * Convert a string from snake_case to camelCase. * * @param string $string_to_convert The string to be converted. * * @return string */ public static function snake_to_camel( $string_to_convert ) { // Split the string by underscores. $words = explode( '_', $string_to_convert ); // Capitalize the first letter of each word. $words = array_map( 'ucfirst', $words ); // Join the words back together. return implode( '', $words ); } /** * Flatten an array. * * @param array $array_to_flatten The array to be flattened. * * @return \RecursiveIteratorIterator */ public static function array_flatten( $array_to_flatten ) { return new RecursiveIteratorIterator( new RecursiveArrayIterator( $array_to_flatten ) ); } /** * Convert a string from camelCase to snake_case. * * @param string $input The string to be converted. * * @return string */ public static function camel_to_snake( $input ) { // Replace all uppercase letters with an underscore followed by the lowercase version of the letter. $pattern = '/([a-z])([A-Z])/'; $replacement = '$1_$2'; $snake = preg_replace( $pattern, $replacement, $input ); // Replace spaces with underscores. $snake = str_replace( ' ', '_', $snake ); // Convert the entire string to lowercase. return strtolower( $snake ); } /** * Index an array using a callback function. * * @param array $array The array to be indexed. * @param callable $callback The callback function to be called for each element. * * @return array */ // phpcs:ignore public static function index_array( $array, $callback ) { $result = array(); foreach ( $array as $key => $value ) { $new_key = $callback( $key, $value ); $result[ $new_key ] = $value; } return $result; } /** * Check to see if given string is a valid WordPress plugin slug. * * @param string $slug The slug to be validated. * * @return bool */ public static function is_valid_wp_plugin_slug( $slug ) { // Check if the slug only contains allowed characters. if ( preg_match( '/^[a-z0-9-]+$/', $slug ) ) { return true; } return false; } /** * Recursively delete a directory. * * @param string $dir_path The path to the directory. * * @return void * @throws \InvalidArgumentException If $dir_path is not a directory. */ public static function delete_dir( $dir_path ) { if ( ! is_dir( $dir_path ) ) { throw new \InvalidArgumentException( "$dir_path must be a directory" ); } if ( substr( $dir_path, strlen( $dir_path ) - 1, 1 ) !== '/' ) { $dir_path .= '/'; } $files = glob( $dir_path . '*', GLOB_MARK ); foreach ( $files as $file ) { if ( is_dir( $file ) ) { static::delete_dir( $file ); } else { // phpcs:ignore unlink( $file ); } } // phpcs:ignore rmdir( $dir_path ); } } src/ResourceStorages/LocalPluginResourceStorage.php 0000777 00000002406 15252167130 0016624 0 ustar 00 <?php namespace Automattic\WooCommerce\Blueprint\ResourceStorages; /** * Class LocalPluginResourceStorage */ class LocalPluginResourceStorage implements ResourceStorage { /** * Paths to the directories containing the plugins. * * @var array The paths to the directories containing the plugins. */ protected array $paths = array(); /** * Suffix of the plugin files. * * @var string The suffix of the plugin files. */ protected string $suffix = 'plugins'; /** * LocalPluginResourceStorage constructor. * * @param string $path The path to the directory containing the plugins. */ public function __construct( $path ) { $this->paths[] = $path; } /** * Local plugins are already included (downloaded) in the zip file. * Return the full path. * * @param string $slug The slug of the plugin to be downloaded. * * @return string|null */ public function download( $slug ): ?string { foreach ( $this->paths as $path ) { $full_path = $path . "/{$this->suffix}/" . $slug . '.zip'; if ( is_file( $full_path ) ) { return $full_path; } } return null; } /** * Get the supported resource. * * @return string The supported resource. */ public function get_supported_resource(): string { return 'self/plugins'; } } src/ResourceStorages/OrgThemeResourceStorage.php 0000777 00000001546 15252167130 0016131 0 ustar 00 <?php namespace Automattic\WooCommerce\Blueprint\ResourceStorages; /** * Class OrgThemeResourceStorage */ class OrgThemeResourceStorage extends OrgPluginResourceStorage { /** * Get the download link. * * @param string $slug The slug of the theme to be downloaded. * * @return string|null The download link. */ protected function get_download_link( $slug ): ?string { $info = $this->wp_themes_api( 'theme_information', array( 'slug' => $slug, 'fields' => array( 'sections' => false, ), ) ); if ( is_wp_error( $info ) ) { return null; } if ( isset( $info->download_link ) ) { return $info->download_link; } return null; } /** * Get the supported resource. * * @return string The supported resource. */ public function get_supported_resource(): string { return 'wordpress.org/themes'; } } src/ResourceStorages/OrgPluginResourceStorage.php 0000777 00000003632 15252167130 0016323 0 ustar 00 <?php namespace Automattic\WooCommerce\Blueprint\ResourceStorages; use Automattic\WooCommerce\Blueprint\UseWPFunctions; /** * Class OrgPluginResourceStorage * * This class handles the storage and downloading of plugins from wordpress.org. * * @package Automattic\WooCommerce\Blueprint\ResourceStorages */ class OrgPluginResourceStorage implements ResourceStorage { use UseWPFunctions; /** * Download the plugin from wordpress.org * * @param string $slug The slug of the plugin to be downloaded. * * @return string|false The path to the downloaded plugin file, or false on failure. */ public function download( $slug ): ?string { $download_link = $this->get_download_link( $slug ); if ( ! $download_link ) { return false; } $result = $this->download_url( $download_link ); if ( is_wp_error( $result ) ) { return false; } return $result; } /** * Download the file from the given URL. * * @param string $url The URL to download the file from. * * @return string|WP_Error The path to the downloaded file, or WP_Error on failure. */ protected function download_url( $url ) { return $this->wp_download_url( $url ); } /** * Get the download link for a plugin from wordpress.org. * * @param string $slug The slug of the plugin. * * @return string|null The download link, or null if not found. */ protected function get_download_link( $slug ): ?string { $info = $this->wp_plugins_api( 'plugin_information', array( 'slug' => $slug, 'fields' => array( 'sections' => false, ), ) ); if ( is_wp_error( $info ) ) { return null; } if ( is_object( $info ) && isset( $info->download_link ) ) { return $info->download_link; } return null; } /** * Get the supported resource type. * * @return string The supported resource type. */ public function get_supported_resource(): string { return 'wordpress.org/plugins'; } } src/ResourceStorages/LocalThemeResourceStorage.php 0000777 00000000700 15252167130 0016423 0 ustar 00 <?php namespace Automattic\WooCommerce\Blueprint\ResourceStorages; /** * Class LocalThemeResourceStorage */ class LocalThemeResourceStorage extends LocalPluginResourceStorage { /** * The suffix. * * @var string The suffix. */ protected string $suffix = 'themes'; /** * Get the supported resource. * * @return string The supported resource. */ public function get_supported_resource(): string { return 'self/themes'; } } src/ResourceStorages/ResourceStorage.php 0000777 00000001321 15252167130 0014465 0 ustar 00 <?php namespace Automattic\WooCommerce\Blueprint\ResourceStorages; /** * Interface ResourceStorage * * ResourceStorage is an abstraction layer for various storages for WordPress files * such as plugins and themes. It provides a common interface for downloading * the files whether they are stored locally or remotely. * * @package Automattic\WooCommerce\Blueprint\ResourceStorages */ interface ResourceStorage { /** * Return supported resource type. * * @return string */ public function get_supported_resource(): string; /** * Download the resource. * * @param string $slug resource slug. * * @return string|null downloaded local path. */ public function download( $slug ): ?string; } src/ImportStep.php 0000777 00000010734 15252167130 0010170 0 ustar 00 <?php namespace Automattic\WooCommerce\Blueprint; use Opis\JsonSchema\Errors\ErrorFormatter; use Opis\JsonSchema\Validator; use Automattic\WooCommerce\Blueprint\Logger; /** * Class ImportStep * * Import a single step from a JSON definition. * * @package Automattic\WooCommerce\Blueprint */ class ImportStep { use UseWPFunctions; /** * Step definition. * * @var object The step definition. */ private object $step_definition; /** * Validator object. * * @var Validator The JSON schema validator instance. */ private Validator $validator; /** * Built-in step processors. * * @var BuiltInStepProcessors The built-in step processors instance. */ private BuiltInStepProcessors $builtin_step_processors; /** * Importers. * * @var array|mixed The importers. */ private array $importers; /** * Indexed importers. * * @var array The indexed importers by step name. */ private array $indexed_importers; /** * ImportStep constructor. * * @param object $step_definition The step definition. * @param Validator|null $validator The validator instance, optional. */ public function __construct( $step_definition, ?Validator $validator = null ) { $this->step_definition = $step_definition; if ( null === $validator ) { $validator = new Validator(); } $this->validator = $validator; $this->importers = $this->wp_apply_filters( 'wooblueprint_importers', ( ( new BuiltInStepProcessors() )->get_all() ) ); $this->indexed_importers = Util::index_array( $this->importers, function ( $key, $importer ) { return $importer->get_step_class()::get_step_name(); } ); } /** * Import the schema steps. * * @return StepProcessorResult */ public function import() { $result = StepProcessorResult::success( $this->step_definition->step ); if ( ! $this->can_import( $result ) ) { return $result; } $importer = $this->indexed_importers[ $this->step_definition->step ]; $logger = new Logger(); $logger->start_import( $this->step_definition->step, get_class( $importer ) ); $importer_result = $importer->process( $this->step_definition ); if ( $importer_result->is_success() ) { $logger->complete_import( $this->step_definition->step, $importer_result ); } else { $logger->import_step_failed( $this->step_definition->step, $importer_result ); } $result->merge_messages( $importer_result ); return $result; } /** * Check if the step can be imported. * * @param StepProcessorResult $result The result object to add messages to. * * @return bool True if the step can be imported, false otherwise. */ protected function can_import( &$result ) { // Check if the importer exists. if ( ! isset( $this->indexed_importers[ $this->step_definition->step ] ) ) { $result->add_error( 'Unable to find an importer' ); return false; } $importer = $this->indexed_importers[ $this->step_definition->step ]; // Validate importer is a step processor before processing. if ( ! $importer instanceof StepProcessor ) { $result->add_error( 'Incorrect importer type' ); return false; } // Validate steps schemas before processing. if ( ! $this->validate_step_schemas( $importer, $result ) ) { $result->add_error( 'Schema validation failed for step' ); return false; } // Validate step capabilities before processing. if ( ! $importer->check_step_capabilities( $this->step_definition ) ) { $result->add_error( 'User does not have the required capabilities to run step' ); return false; } return true; } /** * Validate the step schemas. * * @param StepProcessor $importer The importer. * @param StepProcessorResult $result The result object to add messages to. * * @return bool True if the step schemas are valid, false otherwise. */ protected function validate_step_schemas( StepProcessor $importer, StepProcessorResult $result ) { $step_schema = call_user_func( array( $importer->get_step_class(), 'get_schema' ) ); $validate = $this->validator->validate( $this->step_definition, wp_json_encode( $step_schema ) ); if ( ! $validate->isValid() ) { $result->add_error( "Schema validation failed for step {$this->step_definition->step}" ); $errors = ( new ErrorFormatter() )->format( $validate->error() ); $formatted_errors = array(); foreach ( $errors as $value ) { $formatted_errors[] = implode( "\n", $value ); } $result->add_error( implode( "\n", $formatted_errors ) ); return false; } return true; } } src/Cli.php 0000777 00000003437 15252167130 0006573 0 ustar 00 <?php namespace Automattic\WooCommerce\Blueprint; use Automattic\WooCommerce\Blueprint\Cli\ExportCli; use Automattic\WooCommerce\Blueprint\Cli\ImportCli; $autoload_path = __DIR__ . '/../vendor/autoload.php'; if ( file_exists( $autoload_path ) ) { require_once $autoload_path; } /** * Class Cli. * * This class is included and execute from WC_CLI(class-wc-cli.php) to register * WP CLI commands. */ class Cli { /** * Register WP CLI commands. * * @return void */ public static function register_commands() { \WP_CLI::add_command( 'wc blueprint import', function ( $args, $assoc_args ) { $import = new ImportCli( $args[0] ); $import->run( $assoc_args ); }, array( 'synopsis' => array( array( 'type' => 'positional', 'name' => 'schema-path', 'optional' => false, ), array( 'type' => 'assoc', 'name' => 'show-messages', 'optional' => true, 'options' => array( 'all', 'error', 'info', 'debug' ), ), ), 'when' => 'after_wp_load', ) ); \WP_CLI::add_command( 'wc blueprint export', function ( $args, $assoc_args ) { $export = new ExportCli( $args[0] ); $steps = array(); if ( isset( $assoc_args['steps'] ) ) { $steps = array_map( function ( $step ) { return trim( $step ); }, explode( ',', $assoc_args['steps'] ) ); } $export->run( array( 'steps' => $steps, 'format' => 'json', ) ); }, array( 'synopsis' => array( array( 'type' => 'positional', 'name' => 'save-to', 'optional' => false, ), array( 'type' => 'assoc', 'name' => 'steps', 'optional' => true, ), ), 'when' => 'after_wp_load', ) ); } } src/BuiltInStepProcessors.php 0000777 00000003474 15252167130 0012352 0 ustar 00 <?php namespace Automattic\WooCommerce\Blueprint; use Automattic\WooCommerce\Blueprint\Importers\ImportActivatePlugin; use Automattic\WooCommerce\Blueprint\Importers\ImportActivateTheme; use Automattic\WooCommerce\Blueprint\Importers\ImportInstallPlugin; use Automattic\WooCommerce\Blueprint\Importers\ImportInstallTheme; use Automattic\WooCommerce\Blueprint\Importers\ImportRunSql; use Automattic\WooCommerce\Blueprint\Importers\ImportSetSiteOptions; use Automattic\WooCommerce\Blueprint\ResourceStorages\OrgPluginResourceStorage; use Automattic\WooCommerce\Blueprint\ResourceStorages\OrgThemeResourceStorage; /** * Class BuiltInStepProcessors * * @package Automattic\WooCommerce\Blueprint */ class BuiltInStepProcessors { /** * BuiltInStepProcessors constructor. */ public function __construct() { } /** * Returns an array of all step processors. * * @return array The array of step processors. */ public function get_all() { return array( $this->create_install_plugins_processor(), $this->create_install_themes_processor(), new ImportSetSiteOptions(), new ImportActivatePlugin(), new ImportActivateTheme(), new ImportRunSql(), ); } /** * Creates the processor for installing plugins. * * @return ImportInstallPlugin The processor for installing plugins. */ private function create_install_plugins_processor() { $storages = new ResourceStorages(); $storages->add_storage( new OrgPluginResourceStorage() ); return new ImportInstallPlugin( $storages ); } /** * Creates the processor for installing themes. * * @return ImportInstallTheme The processor for installing themes. */ private function create_install_themes_processor() { $storage = new ResourceStorages(); $storage->add_storage( new OrgThemeResourceStorage() ); return new ImportInstallTheme( $storage ); } } src/ExportSchema.php 0000777 00000010464 15252167130 0010464 0 ustar 00 <?php namespace Automattic\WooCommerce\Blueprint; use Automattic\WooCommerce\Blueprint\Exporters\StepExporter; use Automattic\WooCommerce\Blueprint\Exporters\HasAlias; use Automattic\WooCommerce\Blueprint\Logger; use Automattic\WooCommerce\Blueprint\Steps\Step; use WP_Error; /** * Class ExportSchema * * Handles the export schema functionality for WooCommerce. * * @package Automattic\WooCommerce\Blueprint */ class ExportSchema { use UseWPFunctions; use UsePubSub; /** * Step exporters. * * @var StepExporter[] Array of step exporters. */ protected array $exporters = array(); /** * ExportSchema constructor. * * @param StepExporter[] $exporters Array of step exporters. */ public function __construct( $exporters = array() ) { $this->exporters = $exporters; } /** * Export the schema steps. * * @param string[] $steps Array of step names to export, optional. * * @return array|WP_Error The exported schema array or a WP_Error if the export fails. */ public function export( $steps = array() ) { $loading_page_path = $this->wp_apply_filters( 'wooblueprint_export_landingpage', '/' ); /** * Validate that the landing page path is a valid relative local URL path. * * Accepts: * - / * - /path/to/page * * Rejects: * - http://example.com/path/to/page * - invalid-path */ if ( ! preg_match( '#^/$|^/[^/].*#', $loading_page_path ) ) { return new WP_Error( 'wooblueprint_invalid_landing_page_path', 'Invalid loading page path.' ); } $schema = array( 'landingPage' => $loading_page_path, 'steps' => array(), ); $built_in_exporters = ( new BuiltInExporters() )->get_all(); /** * Filters the step exporters. * * Allows adding/removing custom step exporters. * * @param StepExporter[] $exporters Array of step exporters. * * @since 0.0.1 */ $exporters = $this->wp_apply_filters( 'wooblueprint_exporters', array_merge( $this->exporters, $built_in_exporters ) ); // Validate that the exporters are instances of StepExporter. $exporters = array_filter( $exporters, function ( $exporter ) { return $exporter instanceof StepExporter; } ); // Filter out any exporters that are not in the list of steps to export. if ( count( $steps ) ) { foreach ( $exporters as $key => $exporter ) { $name = $exporter->get_step_name(); $alias = $exporter instanceof HasAlias ? $exporter->get_alias() : $name; if ( ! in_array( $name, $steps, true ) && ! in_array( $alias, $steps, true ) ) { unset( $exporters[ $key ] ); } } } // Make sure the user has the required capabilities to export the steps. foreach ( $exporters as $exporter ) { if ( ! $exporter->check_step_capabilities() ) { return new WP_Error( 'wooblueprint_insufficient_permissions', 'Insufficient permissions to export for step: ' . $exporter->get_step_name() ); } } $logger = new Logger(); $logger->start_export( $exporters ); foreach ( $exporters as $exporter ) { try { $this->publish( 'onBeforeExport', $exporter ); $step = $exporter->export(); $this->add_result_to_schema( $schema, $step ); } catch ( \Throwable $e ) { $step_name = $exporter instanceof HasAlias ? $exporter->get_alias() : $exporter->get_step_name(); $logger->export_step_failed( $step_name, $e ); return new WP_Error( 'wooblueprint_export_step_failed', 'Export step failed: ' . $e->getMessage() ); } } $logger->complete_export( $exporters ); return $schema; } /** * Subscribe to the onBeforeExport event. * * @param string $step_name The step name to subscribe to. * @param callable $callback The callback to execute. */ public function on_before_export( $step_name, $callback ) { $this->subscribe( 'onBeforeExport', function ( $exporter ) use ( $step_name, $callback ) { if ( $step_name === $exporter->get_step_name() ) { $callback( $exporter ); } } ); } /** * Add export result to the schema array. * * @param array $schema Schema array to add steps to. * @param array|Step $step Step or array of steps to add. */ private function add_result_to_schema( array &$schema, $step ): void { if ( is_array( $step ) ) { foreach ( $step as $_step ) { $schema['steps'][] = $_step->get_json_array(); } return; } $schema['steps'][] = $step->get_json_array(); } } src/StepProcessor.php 0000777 00000001250 15252167130 0010666 0 ustar 00 <?php namespace Automattic\WooCommerce\Blueprint; /** * Interface StepProcessor */ interface StepProcessor { /** * Process the schema. * * @param object $schema The schema to process. * * @return StepProcessorResult */ public function process( $schema ): StepProcessorResult; /** * Get the step class. * * @return string */ public function get_step_class(): string; /** * Check if the current user has the required capabilities for this step. * * @param object $schema The schema to process. * * @return bool True if the user has the required capabilities. False otherwise. */ public function check_step_capabilities( $schema ): bool; } src/ResourceStorages.php 0000777 00000002717 15252167130 0011363 0 ustar 00 <?php namespace Automattic\WooCommerce\Blueprint; use Automattic\WooCommerce\Blueprint\ResourceStorages\ResourceStorage; /** * Class ResourceStorages */ class ResourceStorages { /** * Storage collection. * * @var ResourceStorages[] */ protected array $storages = array(); /** * Add a downloader. * * @param ResourceStorage $downloader The downloader to add. * * @return void */ public function add_storage( ResourceStorage $downloader ) { $supported_resource = $downloader->get_supported_resource(); if ( ! isset( $this->storages[ $supported_resource ] ) ) { $this->storages[ $supported_resource ] = array(); } $this->storages[ $supported_resource ][] = $downloader; } /** * Check if the resource is supported. * * @param string $resource_type The resource type to check. * * @return bool */ public function is_supported_resource( $resource_type ) { return isset( $this->storages[ $resource_type ] ); } /** * Download the resource. * * @param string $slug The slug of the resource to download. * @param string $resource_type The resource type to download. * * @return false|string */ public function download( $slug, $resource_type ) { if ( ! isset( $this->storages[ $resource_type ] ) ) { return false; } $storages = $this->storages[ $resource_type ]; foreach ( $storages as $storage ) { $found = $storage->download( $slug ); if ( $found ) { return $found; } } return false; } } src/UsePluginHelpers.php 0000777 00000006202 15252167130 0011313 0 ustar 00 <?php namespace Automattic\WooCommerce\Blueprint; trait UsePluginHelpers { use UseWPFunctions; /** * Activate a plugin by its slug. * * Searches for the plugin with the specified slug in the installed plugins * and activates it. * * @param string $slug The slug of the plugin to activate. * * @return false|null|WP_Error Null on success, WP_Error on invalid file, false if not found. */ public function activate_plugin_by_slug( $slug ) { // Get all installed plugins. $all_plugins = $this->wp_get_plugins(); // Loop through all plugins to find the one with the specified slug. foreach ( $all_plugins as $plugin_path => $plugin_info ) { // Check if the plugin path contains the slug. if ( strpos( $plugin_path, $slug . '/' ) === 0 ) { // Deactivate the plugin. return $this->wp_activate_plugin( $plugin_path ); } } return false; } /** * Check if a plugin with the specified slug is installed. * * @param string $slug The slug of the plugin to check. * * @return bool */ public function is_plugin_dir( $slug ) { $all_plugins = $this->wp_get_plugins(); foreach ( $all_plugins as $plugin_file => $plugin_data ) { // Extract the directory name from the plugin file path. $plugin_dir = explode( '/', $plugin_file )[0]; // Check for an exact match with the slug. if ( $plugin_dir === $slug ) { return true; } } return false; } /** * Deactivate and delete a plugin by its slug. * * Searches for the plugin with the specified slug in the installed plugins, * deactivates it if active, and then deletes it. * * @param string $slug The slug of the plugin to delete. * * @return bool|WP_Error True if the plugin was deleted, false otherwise. */ public function delete_plugin_by_slug( $slug ) { // Get all installed plugins. $all_plugins = $this->wp_get_plugins(); // Loop through all plugins to find the one with the specified slug. foreach ( $all_plugins as $plugin_path => $plugin_info ) { // Check if the plugin path contains the slug. if ( strpos( $plugin_path, $slug . '/' ) === 0 ) { // Deactivate the plugin. if ( $this->deactivate_plugin_by_slug( $slug ) ) { // Delete the plugin. return $this->wp_delete_plugins( array( $plugin_path ) ); } } } return false; } /** * Deactivate a plugin by its slug. * * Searches for the plugin with the specified slug in the installed plugins * and deactivates it. * * @param string $slug The slug of the plugin to deactivate. * * @return bool True if the plugin was deactivated, false otherwise. */ public function deactivate_plugin_by_slug( $slug ) { // Get all installed plugins. $all_plugins = $this->wp_get_plugins(); // Loop through all plugins to find the one with the specified slug. foreach ( $all_plugins as $plugin_path => $plugin_info ) { // Check if the plugin path contains the slug. if ( strpos( $plugin_path, $slug . '/' ) === 0 ) { // Deactivate the plugin. deactivate_plugins( $plugin_path ); // Check if the plugin has been deactivated. if ( ! is_plugin_active( $plugin_path ) ) { return true; } } } return false; } } src/ClassExtractor.php 0000777 00000014070 15252167130 0011020 0 ustar 00 <?php namespace Automattic\WooCommerce\Blueprint; /** * Class ClassExtractor * * Provides functionality to manipulate PHP class files by replacing variables, * adding prefixes, and removing strict types declarations. * * This class is used to generate 'code' part for runPHP step from a template file. */ class ClassExtractor { /** * Path to the PHP file being processed. * * @var string */ private string $file_path; /** * Whether the file contains a strict types declaration. * * @var bool */ private bool $has_strict_types_declaration = false; /** * PHP code to prefix to the final output. * * @var string */ private string $prefix = ''; /** * Replacements for class variables. * * @var array */ private array $class_variable_replacements = array(); /** * Replacements for method variables. * * @var array */ private array $method_variable_replacements = array(); /** * Constructor. * * @param string $file_path Path to the PHP file to process. * * @throws \InvalidArgumentException If the file does not exist. */ public function __construct( string $file_path ) { if ( ! file_exists( $file_path ) ) { throw new \InvalidArgumentException( "File not found: $file_path" ); } $this->file_path = $file_path; } /** * Adds a prefix to include the WordPress wp-load.php file. * * @return $this */ public function with_wp_load() { $this->prefix .= "<?php require_once 'wordpress/wp-load.php'; "; return $this; } /** * Replaces a class variable with a new value. * * @param string $variable_name Name of the class variable. * @param mixed $new_value The new value to assign to the variable. * * @return $this */ public function replace_class_variable( $variable_name, $new_value ) { $this->class_variable_replacements[ $variable_name ] = $new_value; return $this; } /** * Replaces a variable inside a method with a new value. * * @param string $method_name Name of the method. * @param string $variable_name Name of the variable to replace. * @param mixed $new_value The new value to assign to the variable. * * @return $this */ public function replace_method_variable( $method_name, $variable_name, $new_value ) { $this->method_variable_replacements[] = array( 'method' => $method_name, 'variable' => $variable_name, 'value' => $new_value, ); return $this; } /** * Generates the processed PHP code with applied replacements and prefixes. * * @return string The modified PHP code. */ public function get_code() { // Security check: Check if we can replace this with a more secure function. $file_content = file_get_contents( $this->file_path ); // phpcs:ignore WordPress.WP.AlternativeFunctions.file_get_contents_file_get_contents $file_content = preg_replace( '/<\?php\s*/', '', $file_content ); if ( preg_match( '/declare\s*\(\s*strict_types\s*=\s*1\s*\)\s*;/', $file_content ) ) { $this->has_strict_types_declaration = true; $file_content = preg_replace( '/declare\s*\(\s*strict_types\s*=\s*1\s*\)\s*;/', '', $file_content ); } $file_content = preg_replace( '/\/\*.*?\*\/|\/\/.*?(?=\r?\n)/s', '', $file_content ); foreach ( $this->class_variable_replacements as $variable => $value ) { $file_content = $this->apply_class_variable_replacement( $file_content, $variable, $value ); } foreach ( $this->method_variable_replacements as $replacement ) { $file_content = $this->apply_variable_replacement( $file_content, $replacement['method'], $replacement['variable'], $replacement['value'] ); } return $this->prefix . trim( $file_content ); } /** * Applies a replacement to a class variable in the file content. * * @param string $file_content The content of the PHP file. * @param string $variable_name The name of the variable to replace. * @param mixed $new_value The new value for the variable. * * @return string The updated file content. */ private function apply_class_variable_replacement( $file_content, $variable_name, $new_value ) { // Security check: Check if it's necessary to use var_export. $replacement_value = var_export( $new_value, true ); // phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_var_export $pattern = '/(protected|private|public)\s+\$' . preg_quote( $variable_name, '/' ) . '\s*=\s*.*?;|' . '(protected|private|public)\s+\$' . preg_quote( $variable_name, '/' ) . '\s*;?/'; $replacement = "$1 \$$variable_name = $replacement_value;"; return preg_replace( $pattern, $replacement, $file_content, 1 ); } /** * Applies a replacement to a variable in a specific method. * * @param string $file_content The content of the PHP file. * @param string $method_name The name of the method containing the variable. * @param string $variable_name The name of the variable to replace. * @param mixed $new_value The new value for the variable. * * @return string The updated file content. */ private function apply_variable_replacement( $file_content, $method_name, $variable_name, $new_value ) { $pattern = '/function\s+' . preg_quote( $method_name, '/' ) . '\s*\([^)]*\)\s*\{\s*(.*?)\s*\}/s'; if ( preg_match( $pattern, $file_content, $matches ) ) { $method_body = $matches[1]; // Security check: Check if it's necessary to use var_export. $new_value_exported = var_export( $new_value, true ); // phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_var_export $variable_pattern = '/\$' . preg_quote( $variable_name, '/' ) . '\s*=\s*[^;]+;/'; $replacement = '$' . $variable_name . ' = ' . $new_value_exported . ';'; $updated_method_body = preg_replace( $variable_pattern, $replacement, $method_body, 1 ); if ( null !== $updated_method_body ) { $file_content = str_replace( $method_body, $updated_method_body, $file_content ); } } return $file_content; } /** * Checks if the file has a strict types declaration. * * @return bool True if the file has a strict types declaration, false otherwise. */ public function has_strict_type_declaration() { return $this->has_strict_types_declaration; } } src/UseWPFunctions.php 0000777 00000023336 15252167130 0010760 0 ustar 00 <?php namespace Automattic\WooCommerce\Blueprint; use WP_Error; use WP_Theme; /** * Trait UseWPFunctions */ trait UseWPFunctions { /** * Whether the filesystem has been initialized. * * @var bool */ private $filesystem_initialized = false; /** * Adds a filter to a specified tag. * * @param string $tag The name of the filter to hook the $function_to_add to. * @param callable $function_to_add The callback to be run when the filter is applied. * @param int $priority Optional. Used to specify the order in which the functions * associated with a particular action are executed. Default 10. * @param int $accepted_args Optional. The number of arguments the function accepts. Default 1. */ public function wp_add_filter( $tag, $function_to_add, $priority = 10, $accepted_args = 1 ) { add_filter( $tag, $function_to_add, $priority, $accepted_args ); } /** * Adds an action to a specified tag. * * @param string $tag The name of the action to hook the $function_to_add to. * @param callable $function_to_add The callback to be run when the action is triggered. * @param int $priority Optional. Used to specify the order in which the functions * associated with a particular action are executed. Default 10. * @param int $accepted_args Optional. The number of arguments the function accepts. Default 1. */ public function wp_add_action( $tag, $function_to_add, $priority = 10, $accepted_args = 1 ) { add_action( $tag, $function_to_add, $priority, $accepted_args ); } /** * Calls the functions added to a filter hook. * * @param string $tag The name of the filter hook. * @param mixed $value The value on which the filters hooked to $tag are applied on. * @return mixed The filtered value after all hooked functions are applied to it. */ // phpcs:ignore public function wp_apply_filters( $tag, $value ) { $args = func_get_args(); return call_user_func_array( 'apply_filters', $args ); } /** * Executes the functions hooked on a specific action hook. * * @param string $tag The name of the action to be executed. * @param mixed ...$args Optional. Additional arguments which are passed on to the functions hooked to the action. */ public function wp_do_action( $tag, ...$args ) { // phpcs:ignore do_action( $tag, ...$args ); } /** * Checks if a plugin is active. * * @param string $plugin Path to the plugin file relative to the plugins directory. * @return bool True if the plugin is active, false otherwise. */ public function wp_is_plugin_active( string $plugin ) { if ( ! function_exists( 'is_plugin_active' ) || ! function_exists( 'get_plugins' ) ) { require_once ABSPATH . 'wp-admin/includes/plugin.php'; } return is_plugin_active( $plugin ); } /** * Retrieves plugin information from the WordPress Plugin API. * * @param string $action The type of information to retrieve from the API. * @param array $args Optional. Arguments to pass to the API. * @return object|WP_Error The API response object or WP_Error on failure. */ public function wp_plugins_api( $action, $args = array() ) { if ( ! function_exists( 'plugins_api' ) ) { require_once ABSPATH . 'wp-admin/includes/plugin-install.php'; } return plugins_api( $action, $args ); } /** * Retrieves all plugins. * * @param string $plugin_folder Optional. Path to the plugin folder to scan. * @return array Array of plugins. */ public function wp_get_plugins( string $plugin_folder = '' ) { if ( ! function_exists( 'get_plugins' ) ) { require_once ABSPATH . 'wp-admin/includes/plugin.php'; } return get_plugins( $plugin_folder ); } /** * Retrieves all themes. * * @param array $args Optional. Arguments to pass to the API. * @return array Array of themes. */ public function wp_get_themes( $args = array() ) { return wp_get_themes( $args ); } /** * Retrieves a theme. * * @param string|null $stylesheet Optional. The theme's stylesheet name. * @return WP_Theme The theme object. */ public function wp_get_theme( $stylesheet = null ) { if ( ! function_exists( 'wp_get_theme' ) ) { require_once ABSPATH . 'wp-admin/includes/theme.php'; } return wp_get_theme( $stylesheet ); } /** * Retrieves theme information from the WordPress Theme API. * * @param string $action The type of information to retrieve from the API. * @param array $args Optional. Arguments to pass to the API. * @return object|WP_Error The API response object or WP_Error on failure. */ public function wp_themes_api( $action, $args = array() ) { if ( ! function_exists( 'themes_api' ) ) { require_once ABSPATH . 'wp-admin/includes/theme.php'; } return themes_api( $action, $args ); } /** * Activates a plugin. * * @param string $plugin Path to the plugin file relative to the plugins directory. * @param string $redirect Optional. URL to redirect to after activation. * @param bool $network_wide Optional. Whether to enable the plugin for all sites in the network. * @param bool $silent Optional. Whether to prevent calling activation hooks. * @return WP_Error|null WP_Error on failure, null on success. */ public function wp_activate_plugin( $plugin, $redirect = '', $network_wide = false, $silent = false ) { if ( ! function_exists( 'activate_plugin' ) ) { require_once ABSPATH . 'wp-admin/includes/plugin.php'; } return activate_plugin( $plugin, $redirect, $network_wide, $silent ); } /** * Deletes plugins. * * @param array $plugins List of plugins to delete. * @return array|WP_Error|null Array of results or WP_Error on failure, null if filesystem credentials are required to proceed. */ public function wp_delete_plugins( $plugins ) { if ( ! function_exists( 'delete_plugins' ) ) { require_once ABSPATH . 'wp-admin/includes/plugin.php'; } return delete_plugins( $plugins ); } /** * Updates an option in the database. * * @param string $option Name of the option to update. * @param mixed $value New value for the option. * @param string|null $autoload Optional. Whether to load the option when WordPress starts up. * @return bool True if option was updated, false otherwise. */ public function wp_update_option( $option, $value, $autoload = null ) { return update_option( $option, $value, $autoload ); } /** * Retrieves an option from the database. * * @param string $option Name of the option to retrieve. * @param mixed $default_value Optional. Default value to return if the option does not exist. * @return mixed Value of the option or $default if the option does not exist. */ public function wp_get_option( $option, $default_value = false ) { return get_option( $option, $default_value ); } /** * Switches the current theme. * * @param string $name The name of the theme to switch to. */ public function wp_switch_theme( $name ) { if ( ! function_exists( 'switch_theme' ) ) { require_once ABSPATH . 'wp-admin/includes/theme.php'; } switch_theme( $name ); } /** * Initializes the WordPress filesystem. * * @return bool */ public function wp_init_filesystem() { if ( ! $this->filesystem_initialized ) { if ( ! class_exists( 'WP_Filesystem' ) ) { require_once ABSPATH . 'wp-admin/includes/file.php'; } $initialized = WP_Filesystem(); $this->filesystem_initialized = $initialized; return $initialized; } return true; } /** * Unzips a file to a specified location. * * @param string $path Path to the ZIP file. * @param string $to Destination directory. * @return bool|WP_Error True on success, WP_Error on failure. */ public function wp_unzip_file( $path, $to ) { $this->wp_init_filesystem(); return unzip_file( $path, $to ); } /** * Retrieves the upload directory information. * * @return array Array of upload directory information. */ public function wp_upload_dir() { return \wp_upload_dir(); } /** * Retrieves the root directory of the current theme. * * @return string The root directory of the current theme. */ public function wp_get_theme_root() { return \get_theme_root(); } /** * Checks if a variable is a WP_Error. * * @param mixed $thing Variable to check. * @return bool True if the variable is a WP_Error, false otherwise. */ public function is_wp_error( $thing ) { return is_wp_error( $thing ); } /** * Downloads a file from a URL. * * @param string $url The URL of the file to download. * @return string|WP_Error The local file path on success, WP_Error on failure. */ public function wp_download_url( $url ) { if ( ! function_exists( 'download_url' ) ) { include ABSPATH . '/wp-admin/includes/file.php'; } return download_url( $url ); } /** * Alias for WP_Filesystem::put_contents(). * * @param string $file_path The path to the file to write. * @param mixed $content The data to write to the file. * * @return bool True on success, false on failure. */ public function wp_filesystem_put_contents( $file_path, $content ) { global $wp_filesystem; if ( ! $this->wp_init_filesystem() ) { return false; } return $wp_filesystem->put_contents( $file_path, $content ); } /** * Alias for WP_Filesystem::get_contents(). * * @param string $file_path The path to the file to read. * @return string|false The contents of the file, or false on failure. */ public function wp_filesystem_get_contents( $file_path ) { global $wp_filesystem; if ( ! $this->wp_init_filesystem() ) { return false; } return $wp_filesystem->get_contents( $file_path ); } /** * Retrieves the current user's ID. * * @return int The current user's ID. */ public function wp_get_current_user_id() { return get_current_user_id(); } } composer.json 0000777 00000002475 15252167130 0007307 0 ustar 00 { "name": "woocommerce/blueprint", "version": "0.0.1", "type": "wordpress-plugin", "autoload": { "psr-4": { "Automattic\\WooCommerce\\Blueprint\\": "src/" } }, "autoload-dev": { "psr-4": { "Automattic\\WooCommerce\\Blueprint\\Tests\\": "tests/" } }, "require": { "opis/json-schema": "^2.3" }, "scripts": { "test:setup": "wp-env start", "test:unit": "wp-env run tests-cli --env-cwd=wp-content/plugins/blueprint ./vendor/bin/phpunit", "phpcs": [ "phpcs -s -p" ], "phpcbf": [ "phpcbf -p" ] }, "require-dev": { "phpunit/phpunit": "^9", "mockery/mockery": "^1.6", "automattic/jetpack-changelogger": "3.3.0", "woocommerce/woocommerce-sniffs": "^1.0.0", "yoast/phpunit-polyfills": "^2.0" }, "extra": { "changelogger": { "formatter": { "filename": "../../../tools/changelogger/class-legacy-core-formatter.php" }, "types": { "fix": "Fixes an existing bug", "add": "Adds functionality", "update": "Update existing functionality", "dev": "Development related task", "tweak": "A minor adjustment to the codebase", "performance": "Address performance issues", "enhancement": "Improve existing functionality" }, "changelog": "changelog.md" } }, "config": { "allow-plugins": { "dealerdirect/phpcodesniffer-composer-installer": true } } }
| ver. 1.6 |
Github
|
.
| PHP 8.1.34 | Генерация страницы: 0 |
proxy
|
phpinfo
|
Настройка