Commit 6904f47f25 for woocommerce
commit 6904f47f2578651459d069991967ba18913ba38c
Author: Chi-Hsuan Huang <chihsuan.tw@gmail.com>
Date: Fri Apr 25 17:15:41 2025 +0800
[Blueprint] Add permission validation for import steps (#57294)
* Refactor ImportSchema and ImportStep classes to enhance permissions validation
- Removed the built-in step processors from ImportSchema, simplifying the class structure.
- Introduced a new ImportStep class to handle step processing, improving modularity.
- Added capability checks in step processors to ensure users have the necessary permissions before executing steps.
- Updated validation methods to return boolean values, enhancing clarity on validation outcomes.
* Update ImportStepTest to extend WP_UnitTestCase and add capability validation test
- Changed ImportStepTest to extend WP_UnitTestCase for better integration with WordPress testing framework.
- Added a new test to validate error handling when user capabilities are insufficient for executing a step, enhancing test coverage and robustness of the import process.
- Introduced a check_step_capabilities method in DummyImporter to support capability validation.
* Fix function existence check in CliResultFormatter to use namespaced format_items function
- Updated the function existence check for format_items to use the correct namespaced path '\WP_CLI\Utils\format_items'.
- Reorganized the conditional logic to throw an exception if the function is not found before calling it, improving error handling and clarity.
* Update ImportStep to use step definition in success result
- Modified the import method to utilize the step definition's step property in the success result, enhancing clarity and ensuring the correct step context is used during the import process.
* Add changefile(s) from automation for the following project(s): packages/php/blueprint
* Refactor capability checks in ImportRunSql to check all permissions
* Refactor capability check formatting in ImportDeletePlugin for improved readability
* [Blueprint] Add permission validation for export steps and REST API (#57303)
* Enhance ExportSchema and related exporters with capability checks and error handling
- Updated the export method in ExportSchema to return WP_Error for invalid landing page paths instead of throwing exceptions, improving error handling.
- Added check_step_capabilities method to various StepExporter implementations to validate user permissions before executing steps, ensuring only authorized users can perform exports.
- Updated CLI export command to handle WP_Error responses gracefully, providing clear error messages to the user.
- Enhanced documentation for methods to clarify expected return types and capabilities checks.
* Refactor permission checks in RestApi for export and import actions
- Updated permission callbacks for export and import methods to use specific checks: `check_export_permission` and `check_import_permission`.
- Enhanced permission validation to ensure only users with appropriate capabilities can perform export and import actions, improving security and clarity in user permissions.
* Improve error handling in ExportSchema and RestApi
- Updated error message in ExportSchema to clarify the context of insufficient permissions during export steps.
- Added handling for WP_Error responses in RestApi to return appropriate HTTP responses, enhancing API robustness and user feedback.
* Improve error handling in slotfill.js for export functionality
- Enhanced error handling in the Blueprint component to differentiate between various error types, including string errors and specific permission-related errors.
- Added user-friendly error messages for insufficient permissions during export, improving clarity for users.
- Ensured that the export functionality is re-enabled after error handling, maintaining a smooth user experience.
* Enhance steps_payload_to_blueprint_steps method in RestApi
- Added checks to ensure that 'settings', 'plugins', and 'themes' arrays are not only set but also contain elements before processing them, improving the robustness of the steps payload handling.
* Refactor error handling in ExportSchema for consistency
- Updated the ExportSchema class to use the WP_Error class consistently without the namespace prefix, improving code readability and maintaining consistency across error handling.
- Clarified return type documentation for the export method to reflect the use of WP_Error, enhancing developer understanding of the method's behavior.
* Initialize error state in exportBlueprint function in slotfill.js
- Added a line to reset the error state to null before starting the export process, ensuring that previous errors do not affect the current export operation.
* Add changelog
* Fix tests
* Enhance DummyExporter with strict types and additional methods
- Added strict types declaration for improved type safety.
- Introduced check_step_capabilities method to validate export capabilities.
- Enhanced documentation for existing methods to clarify their purpose and return types, improving code readability and developer understanding.
* Fix lint
* [Blueprint] Implement logging for export and import operations (#57384)
* Implement logging functionality in ExportSchema and ImportStep classes
- Introduced a Logger class to handle logging of export and import steps, improving traceability and debugging.
- Enhanced ExportSchema to log the start and completion of export steps, as well as errors encountered during the process.
- Updated ImportStep to log the start and result of the import process, including success and error messages.
- Refactored error handling in both classes to utilize the new logging system, ensuring consistent logging practices across the codebase.
* Add changelog
* Fix tests
* Refactor Logger class to utilize WP functions
- Introduced UseWPFunctions trait to encapsulate WordPress function usage.
- Updated Logger class to use wp_get_current_user_id() for retrieving the current user's ID, enhancing consistency with WordPress standards.
- Minor adjustment in ImportStepTest class to ensure proper namespace usage for WP_UnitTestCase.
* Refactor logging in ExportSchema and ImportStep classes
- Updated ExportSchema and ImportStep to utilize new logging methods in the Logger class for starting, completing, and handling failures of export and import steps.
- Removed redundant logging code and centralized logging functionality in the Logger class, enhancing code clarity and maintainability.
* Remove WC_Log_Levels and WC_Logger_Interface stubs; centralize logging in tests
- Deleted the WC_Log_Levels and WC_Logger_Interface stub files to streamline the codebase.
- Introduced a new stubs.php file to provide necessary stubs for testing, ensuring compatibility and reducing redundancy.
- Updated references in the Logger class to use fully qualified class names for WC_Log_Levels, enhancing clarity and consistency.
* Fix tests
* Add changefile(s) from automation for the following project(s): packages/php/blueprint, woocommerce, woocommerce/client/admin
---------
Co-authored-by: github-actions <github-actions@github.com>
diff --git a/packages/php/blueprint/changelog/57294-wooplug-2482-feature-blueprint-require-permission-based-on-the-steps b/packages/php/blueprint/changelog/57294-wooplug-2482-feature-blueprint-require-permission-based-on-the-steps
new file mode 100644
index 0000000000..2da1557574
--- /dev/null
+++ b/packages/php/blueprint/changelog/57294-wooplug-2482-feature-blueprint-require-permission-based-on-the-steps
@@ -0,0 +1,4 @@
+Significance: patch
+Type: enhancement
+
+Add permission validation for import steps
\ No newline at end of file
diff --git a/packages/php/blueprint/changelog/update-blueprint-require-permission-exporters-api b/packages/php/blueprint/changelog/update-blueprint-require-permission-exporters-api
new file mode 100644
index 0000000000..3cda0d466d
--- /dev/null
+++ b/packages/php/blueprint/changelog/update-blueprint-require-permission-exporters-api
@@ -0,0 +1,4 @@
+Significance: patch
+Type: enhancement
+
+Add permission validation for export steps and REST API
diff --git a/packages/php/blueprint/changelog/wooplug-3946-blueprint-centralized-audit-logging b/packages/php/blueprint/changelog/wooplug-3946-blueprint-centralized-audit-logging
new file mode 100644
index 0000000000..33633cf484
--- /dev/null
+++ b/packages/php/blueprint/changelog/wooplug-3946-blueprint-centralized-audit-logging
@@ -0,0 +1,4 @@
+Significance: patch
+Type: add
+
+Implement logging functionality in ExportSchema and ImportStep classes
diff --git a/packages/php/blueprint/src/ExportSchema.php b/packages/php/blueprint/src/ExportSchema.php
index b58f9d4bd8..fa7d532256 100644
--- a/packages/php/blueprint/src/ExportSchema.php
+++ b/packages/php/blueprint/src/ExportSchema.php
@@ -4,6 +4,8 @@ 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;
/**
@@ -74,7 +76,6 @@ class ExportSchema {
* @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,
@@ -94,23 +95,31 @@ class ExportSchema {
}
}
- /**
- * StepExporter.
- *
- * @var StepExporter $exporter
- */
+ // Make sure the user has the required capabilities to export the steps.
foreach ( $exporters as $exporter ) {
- $this->publish( 'onBeforeExport', $exporter );
- $step = $exporter->export();
- if ( is_array( $step ) ) {
- foreach ( $step as $_step ) {
- $schema['steps'][] = $_step->get_json_array();
- }
- } else {
- $schema['steps'][] = $step->get_json_array();
+ 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;
}
@@ -130,4 +139,21 @@ class ExportSchema {
}
);
}
+
+ /**
+ * 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();
+ }
}
diff --git a/packages/php/blueprint/src/Exporters/ExportInstallPluginSteps.php b/packages/php/blueprint/src/Exporters/ExportInstallPluginSteps.php
index c11bd652ae..48ca9bd49b 100644
--- a/packages/php/blueprint/src/Exporters/ExportInstallPluginSteps.php
+++ b/packages/php/blueprint/src/Exporters/ExportInstallPluginSteps.php
@@ -158,4 +158,12 @@ class ExportInstallPluginSteps implements StepExporter {
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' );
+ }
}
diff --git a/packages/php/blueprint/src/Exporters/ExportInstallThemeSteps.php b/packages/php/blueprint/src/Exporters/ExportInstallThemeSteps.php
index db9ee4aeeb..dbd975128f 100644
--- a/packages/php/blueprint/src/Exporters/ExportInstallThemeSteps.php
+++ b/packages/php/blueprint/src/Exporters/ExportInstallThemeSteps.php
@@ -80,4 +80,13 @@ class ExportInstallThemeSteps implements StepExporter {
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' );
+ }
}
diff --git a/packages/php/blueprint/src/Exporters/StepExporter.php b/packages/php/blueprint/src/Exporters/StepExporter.php
index ddbd5f309a..4e263f5b03 100644
--- a/packages/php/blueprint/src/Exporters/StepExporter.php
+++ b/packages/php/blueprint/src/Exporters/StepExporter.php
@@ -24,4 +24,11 @@ interface StepExporter {
* @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;
}
diff --git a/packages/php/blueprint/src/ImportSchema.php b/packages/php/blueprint/src/ImportSchema.php
index f1c913a7a7..0b0c16fc79 100644
--- a/packages/php/blueprint/src/ImportSchema.php
+++ b/packages/php/blueprint/src/ImportSchema.php
@@ -30,12 +30,6 @@ class ImportSchema {
*/
private Validator $validator;
- /**
- * Built-in step processors.
- *
- * @var BuiltInStepProcessors The built-in step processors instance.
- */
- private BuiltInStepProcessors $builtin_step_processors;
/**
* ImportSchema constructor.
@@ -50,8 +44,6 @@ class ImportSchema {
}
$this->validator = $validator;
-
- $this->builtin_step_processors = new BuiltInStepProcessors();
}
/**
@@ -99,89 +91,11 @@ class ImportSchema {
$result = StepProcessorResult::success( 'ImportSchema' );
$results[] = $result;
- $step_processors = $this->builtin_step_processors->get_all();
-
- /**
- * Filters the step processors.
- *
- * Allows adding/removing custom step processors.
- *
- * @param StepProcessor[] $step_processors The step processors.
- *
- * @since 0.0.1
- */
- $step_processors = $this->wp_apply_filters( 'wooblueprint_importers', $step_processors );
-
- // Validate that the step processors are instances of StepProcessor.
- $step_processors = array_filter(
- $step_processors,
- function ( $step_processor ) {
- return $step_processor instanceof StepProcessor;
- }
- );
-
- $indexed_step_processors = Util::index_array(
- $step_processors,
- function ( $key, $step_processor ) {
- return $step_processor->get_step_class()::get_step_name();
- }
- );
-
- // validate steps before processing.
- $this->validate_step_schemas( $indexed_step_processors, $result );
-
- if ( count( $result->get_messages( 'error' ) ) !== 0 ) {
- return $results;
- }
-
foreach ( $this->schema->get_steps() as $step_schema ) {
- $step_processor = $indexed_step_processors[ $step_schema->step ] ?? null;
- if ( ! $step_processor instanceof StepProcessor ) {
- $result->add_error( "Unable to create a step processor for {$step_schema->step}" );
- continue;
- }
-
- $results[] = $step_processor->process( $step_schema );
+ $step_importer = new ImportStep( $step_schema, $this->validator );
+ $results[] = $step_importer->import();
}
return $results;
}
-
- /**
- * Validate the step schemas.
- *
- * @param array $indexed_step_processors Array of step processors indexed by step name.
- * @param StepProcessorResult $result The result object to add messages to.
- *
- * @return void
- */
- protected function validate_step_schemas( array $indexed_step_processors, StepProcessorResult $result ) {
- $step_schemas = array_map(
- function ( $step_processor ) {
- return $step_processor->get_step_class()::get_schema();
- },
- $indexed_step_processors
- );
-
- foreach ( $this->schema->get_steps() as $step_json ) {
- $step_schema = $step_schemas[ $step_json->step ] ?? null;
- if ( ! $step_schema ) {
- $result->add_info( "No schema found for step $step_json->step" );
- continue;
- }
-
- $validate = $this->validator->validate( $step_json, wp_json_encode( $step_schema ) );
-
- if ( ! $validate->isValid() ) {
- $result->add_error( "Schema validation failed for step {$step_json->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 ) );
- }
- }
- }
}
diff --git a/packages/php/blueprint/src/ImportStep.php b/packages/php/blueprint/src/ImportStep.php
index 65b1af972a..c7f28ce6f9 100644
--- a/packages/php/blueprint/src/ImportStep.php
+++ b/packages/php/blueprint/src/ImportStep.php
@@ -4,6 +4,7 @@ namespace Automattic\WooCommerce\Blueprint;
use Opis\JsonSchema\Errors\ErrorFormatter;
use Opis\JsonSchema\Validator;
+use Automattic\WooCommerce\Blueprint\Logger;
/**
* Class ImportStep
@@ -78,31 +79,63 @@ class ImportStep {
* @return StepProcessorResult
*/
public function import() {
- $result = StepProcessorResult::success( 'ImportStep' );
+ $result = StepProcessorResult::success( $this->step_definition->step );
- if ( ! isset( $this->indexed_importers[ $this->step_definition->step ] ) ) {
- $result->add_warn( "Unable to find an importer for {$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 ) );
- // validate importer is a step processor before processing.
- if ( ! $importer instanceof StepProcessor ) {
- $result->add_warn( "Importer {$this->step_definition->step} is not a valid step processor" );
- return $result;
+ $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 );
}
- // validate steps before processing.
- $this->validate_step_schemas( $importer, $result );
+ $result->merge_messages( $importer_result );
- if ( count( $result->get_messages( 'error' ) ) !== 0 ) {
- return $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;
}
- $result->merge_messages( $importer->process( $this->step_definition ) );
+ $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;
+ }
- return $result;
+ // 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;
}
/**
@@ -111,7 +144,7 @@ class ImportStep {
* @param StepProcessor $importer The importer.
* @param StepProcessorResult $result The result object to add messages to.
*
- * @return void
+ * @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' ) );
@@ -127,6 +160,9 @@ class ImportStep {
}
$result->add_error( implode( "\n", $formatted_errors ) );
+
+ return false;
}
+ return true;
}
}
diff --git a/packages/php/blueprint/src/Importers/ImportActivatePlugin.php b/packages/php/blueprint/src/Importers/ImportActivatePlugin.php
index e475b5cb31..53940502c8 100644
--- a/packages/php/blueprint/src/Importers/ImportActivatePlugin.php
+++ b/packages/php/blueprint/src/Importers/ImportActivatePlugin.php
@@ -45,4 +45,15 @@ class ImportActivatePlugin implements StepProcessor {
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' );
+ }
}
diff --git a/packages/php/blueprint/src/Importers/ImportActivateTheme.php b/packages/php/blueprint/src/Importers/ImportActivateTheme.php
index 286c964a39..d046c944a8 100644
--- a/packages/php/blueprint/src/Importers/ImportActivateTheme.php
+++ b/packages/php/blueprint/src/Importers/ImportActivateTheme.php
@@ -4,7 +4,6 @@ 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\Steps\ActivateTheme;
use Automattic\WooCommerce\Blueprint\UsePluginHelpers;
use Automattic\WooCommerce\Blueprint\UseWPFunctions;
@@ -49,4 +48,15 @@ class ImportActivateTheme implements StepProcessor {
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' );
+ }
}
diff --git a/packages/php/blueprint/src/Importers/ImportDeactivatePlugin.php b/packages/php/blueprint/src/Importers/ImportDeactivatePlugin.php
index c3e589693b..fd1b1d7ab1 100644
--- a/packages/php/blueprint/src/Importers/ImportDeactivatePlugin.php
+++ b/packages/php/blueprint/src/Importers/ImportDeactivatePlugin.php
@@ -44,4 +44,15 @@ class ImportDeactivatePlugin implements StepProcessor {
public function get_step_class(): string {
return DeactivatePlugin::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( 'deactivate_plugins' );
+ }
}
diff --git a/packages/php/blueprint/src/Importers/ImportDeletePlugin.php b/packages/php/blueprint/src/Importers/ImportDeletePlugin.php
index a45d5a8bc1..4a8cc2d812 100644
--- a/packages/php/blueprint/src/Importers/ImportDeletePlugin.php
+++ b/packages/php/blueprint/src/Importers/ImportDeletePlugin.php
@@ -44,4 +44,15 @@ class ImportDeletePlugin implements StepProcessor {
public function get_step_class(): string {
return DeletePlugin::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( 'deactivate_plugins' ) && current_user_can( 'delete_plugins' );
+ }
}
diff --git a/packages/php/blueprint/src/Importers/ImportInstallPlugin.php b/packages/php/blueprint/src/Importers/ImportInstallPlugin.php
index 4c969544d6..166c5c664b 100644
--- a/packages/php/blueprint/src/Importers/ImportInstallPlugin.php
+++ b/packages/php/blueprint/src/Importers/ImportInstallPlugin.php
@@ -171,4 +171,15 @@ class ImportInstallPlugin implements StepProcessor {
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' );
+ }
}
diff --git a/packages/php/blueprint/src/Importers/ImportInstallTheme.php b/packages/php/blueprint/src/Importers/ImportInstallTheme.php
index 4ad2e1836f..4233cf170a 100644
--- a/packages/php/blueprint/src/Importers/ImportInstallTheme.php
+++ b/packages/php/blueprint/src/Importers/ImportInstallTheme.php
@@ -7,7 +7,6 @@ use Automattic\WooCommerce\Blueprint\StepProcessor;
use Automattic\WooCommerce\Blueprint\StepProcessorResult;
use Automattic\WooCommerce\Blueprint\Steps\InstallTheme;
use Automattic\WooCommerce\Blueprint\UseWPFunctions;
-use Plugin_Upgrader;
/**
* Class ImportInstallTheme
@@ -144,4 +143,15 @@ class ImportInstallTheme implements StepProcessor {
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' );
+ }
}
diff --git a/packages/php/blueprint/src/Importers/ImportRunSql.php b/packages/php/blueprint/src/Importers/ImportRunSql.php
index d79b7ad57b..3a2fb889bb 100644
--- a/packages/php/blueprint/src/Importers/ImportRunSql.php
+++ b/packages/php/blueprint/src/Importers/ImportRunSql.php
@@ -4,8 +4,6 @@ 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\Steps\ActivateTheme;
use Automattic\WooCommerce\Blueprint\Steps\RunSql;
use Automattic\WooCommerce\Blueprint\UsePluginHelpers;
use Automattic\WooCommerce\Blueprint\UseWPFunctions;
@@ -49,4 +47,27 @@ class ImportRunSql implements StepProcessor {
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;
+ }
}
diff --git a/packages/php/blueprint/src/Importers/ImportSetSiteOptions.php b/packages/php/blueprint/src/Importers/ImportSetSiteOptions.php
index 7d0dab845f..53cd70e791 100644
--- a/packages/php/blueprint/src/Importers/ImportSetSiteOptions.php
+++ b/packages/php/blueprint/src/Importers/ImportSetSiteOptions.php
@@ -51,4 +51,15 @@ class ImportSetSiteOptions implements StepProcessor {
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' );
+ }
}
diff --git a/packages/php/blueprint/src/Logger.php b/packages/php/blueprint/src/Logger.php
new file mode 100644
index 0000000000..e50c1d92bf
--- /dev/null
+++ b/packages/php/blueprint/src/Logger.php
@@ -0,0 +1,169 @@
+<?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' ),
+ )
+ );
+ }
+}
diff --git a/packages/php/blueprint/src/ResultFormatters/CliResultFormatter.php b/packages/php/blueprint/src/ResultFormatters/CliResultFormatter.php
index 9f063f4474..a85cf49109 100644
--- a/packages/php/blueprint/src/ResultFormatters/CliResultFormatter.php
+++ b/packages/php/blueprint/src/ResultFormatters/CliResultFormatter.php
@@ -49,13 +49,13 @@ class CliResultFormatter {
}
}
- $format_items_exist = function_exists( 'format_items' );
+ $format_items_exist = function_exists( '\WP_CLI\Utils\format_items' );
- if ( $format_items_exist ) {
- format_items( 'table', $items, $header );
- } else {
+ if ( ! $format_items_exist ) {
throw new \Exception( 'WP CLI Utils not found' );
}
+
+ format_items( 'table', $items, $header );
}
/**
diff --git a/packages/php/blueprint/src/StepProcessor.php b/packages/php/blueprint/src/StepProcessor.php
index a97f62e5f6..a61743c10d 100644
--- a/packages/php/blueprint/src/StepProcessor.php
+++ b/packages/php/blueprint/src/StepProcessor.php
@@ -21,4 +21,12 @@ interface StepProcessor {
* @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;
}
diff --git a/packages/php/blueprint/src/UseWPFunctions.php b/packages/php/blueprint/src/UseWPFunctions.php
index 27e38a1039..be0cae7b0d 100644
--- a/packages/php/blueprint/src/UseWPFunctions.php
+++ b/packages/php/blueprint/src/UseWPFunctions.php
@@ -311,4 +311,13 @@ trait UseWPFunctions {
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();
+ }
}
diff --git a/packages/php/blueprint/tests/Unit/ExportSchemaTest.php b/packages/php/blueprint/tests/Unit/ExportSchemaTest.php
index 68a0179868..f6b3473d36 100644
--- a/packages/php/blueprint/tests/Unit/ExportSchemaTest.php
+++ b/packages/php/blueprint/tests/Unit/ExportSchemaTest.php
@@ -148,4 +148,20 @@ class ExportSchemaTest extends TestCase {
$this->assertCount( 1, $result['steps'] );
$this->assertEquals( 'setSiteOptions', $result['steps'][0]['step'] );
}
+
+ /**
+ * Test that it returns a WP_Error when the exporter is not capable.
+ */
+ public function test_it_returns_wp_error_when_exporter_is_not_capable() {
+ $exporter = Mockery::mock( EmptySetSiteOptionsExporter::class );
+ $exporter->makePartial();
+ $exporter->shouldReceive( 'check_step_capabilities' )
+ ->andReturn( false );
+
+ $mock = Mock( ExportSchema::class, array( array( $exporter ) ) );
+ $mock->makePartial();
+
+ $result = $mock->export();
+ $this->assertInstanceOf( WP_Error::class, $result );
+ }
}
diff --git a/packages/php/blueprint/tests/Unit/ImportStepTest.php b/packages/php/blueprint/tests/Unit/ImportStepTest.php
index ad2714f662..d76f171daf 100644
--- a/packages/php/blueprint/tests/Unit/ImportStepTest.php
+++ b/packages/php/blueprint/tests/Unit/ImportStepTest.php
@@ -2,14 +2,13 @@
use Automattic\WooCommerce\Blueprint\Tests\stubs\Importers\DummyImporter;
use Automattic\WooCommerce\Blueprint\Tests\stubs\Steps\DummyStep;
-use PHPUnit\Framework\TestCase;
use Automattic\WooCommerce\Blueprint\ImportStep;
use Automattic\WooCommerce\Blueprint\StepProcessorResult;
/**
* Class ImportStepTest
*/
-class ImportStepTest extends TestCase {
+class ImportStepTest extends \WP_UnitTestCase {
/**
* Tear down Mockery after each test.
@@ -51,21 +50,21 @@ class ImportStepTest extends TestCase {
*
* @return void
*/
- public function test_it_returns_warn_when_it_cannot_find_valid_importer() {
+ public function test_it_returns_error_when_it_cannot_find_valid_importer() {
$rand = wp_rand( 1, 99999999 );
$importer = new ImportStep( (object) array( 'step' => 'dummy' . $rand ) );
$result = $importer->import();
- $this->assertCount( 1, $result->get_messages( 'warn' ) );
- $this->assertEquals( 'Unable to find an importer for dummy' . $rand, $result->get_messages( 'warn' )[0]['message'] );
+ $this->assertCount( 1, $result->get_messages( 'error' ) );
+ $this->assertEquals( 'Unable to find an importer', $result->get_messages( 'error' )[0]['message'] );
}
/**
- * Test it returns warn when importer is not a step processor.
+ * Test it returns error when importer is not a step processor.
*
* @return void
*/
- public function test_it_returns_warn_when_importer_is_not_a_step_processor() {
+ public function test_it_returns_error_when_importer_is_not_a_step_processor() {
// Create a filter that adds an invalid importer (not implementing StepProcessor).
add_filter(
'wooblueprint_importers',
@@ -87,8 +86,8 @@ class ImportStepTest extends TestCase {
$importer = new ImportStep( (object) array( 'step' => DummyStep::get_step_name() ) );
$result = $importer->import();
- $this->assertCount( 1, $result->get_messages( 'warn' ) );
- $this->assertEquals( sprintf( 'Importer %s is not a valid step processor', DummyStep::get_step_name() ), $result->get_messages( 'warn' )[0]['message'] );
+ $this->assertCount( 1, $result->get_messages( 'error' ) );
+ $this->assertEquals( 'Incorrect importer type', $result->get_messages( 'error' )[0]['message'] );
}
/**
@@ -108,4 +107,21 @@ class ImportStepTest extends TestCase {
$this->assertNotEmpty( $result->get_messages( 'error' ) );
$this->assertEquals( 'Schema validation failed for step dummy', $result->get_messages( 'error' )[0]['message'] );
}
+
+ /**
+ * Test it returns error when step capabilities are not valid.
+ *
+ * @return void
+ */
+ public function test_it_returns_error_when_step_capabilities_are_not_valid() {
+ // create a user with editor role using wp native function.
+ $user_id = $this->factory->user->create( array( 'role' => 'editor' ) );
+
+ wp_set_current_user( $user_id );
+
+ $importer = new ImportStep( (object) array( 'step' => 'setSiteOptions' ) );
+ $result = $importer->import();
+ $this->assertNotEmpty( $result->get_messages( 'error' ) );
+ $this->assertEquals( 'User does not have the required capabilities to run step', $result->get_messages( 'error' )[0]['message'] );
+ }
}
diff --git a/packages/php/blueprint/tests/bootstrap.php b/packages/php/blueprint/tests/bootstrap.php
index 97fdc8f1f9..9f36108740 100644
--- a/packages/php/blueprint/tests/bootstrap.php
+++ b/packages/php/blueprint/tests/bootstrap.php
@@ -19,3 +19,9 @@ require 'vendor/yoast/phpunit-polyfills/phpunitpolyfills-autoload.php';
// Start up the WP testing environment.
require "{$_tests_dir}/includes/bootstrap.php";
+
+
+define( 'WOO_BLUEPRINT_TESTS', true );
+
+// Stubs.
+require_once __DIR__ . '/stubs/stubs.php';
diff --git a/packages/php/blueprint/tests/stubs/Exporters/EmptySetSiteOptionsExporter.php b/packages/php/blueprint/tests/stubs/Exporters/EmptySetSiteOptionsExporter.php
index f7596b29d4..fa075e872c 100644
--- a/packages/php/blueprint/tests/stubs/Exporters/EmptySetSiteOptionsExporter.php
+++ b/packages/php/blueprint/tests/stubs/Exporters/EmptySetSiteOptionsExporter.php
@@ -28,4 +28,13 @@ class EmptySetSiteOptionsExporter implements StepExporter {
public function get_step_name() {
return SetSiteOptions::get_step_name();
}
+
+ /**
+ * Check if the step is capable of being exported.
+ *
+ * @return bool True if the step is capable of being exported, false otherwise.
+ */
+ public function check_step_capabilities(): bool {
+ return true;
+ }
}
diff --git a/packages/php/blueprint/tests/stubs/Importers/DummyImporter.php b/packages/php/blueprint/tests/stubs/Importers/DummyImporter.php
index 19abe8f70e..6b4559fcf8 100644
--- a/packages/php/blueprint/tests/stubs/Importers/DummyImporter.php
+++ b/packages/php/blueprint/tests/stubs/Importers/DummyImporter.php
@@ -28,4 +28,14 @@ class DummyImporter implements StepProcessor {
public function get_step_class(): string {
return DummyStep::class;
}
+
+ /**
+ * Check the step capabilities.
+ *
+ * @param object $schema The schema to check.
+ * @return bool True if the step capabilities are valid.
+ */
+ public function check_step_capabilities( $schema ): bool {
+ return true;
+ }
}
diff --git a/packages/php/blueprint/tests/stubs/stubs.php b/packages/php/blueprint/tests/stubs/stubs.php
new file mode 100644
index 0000000000..c529280cc3
--- /dev/null
+++ b/packages/php/blueprint/tests/stubs/stubs.php
@@ -0,0 +1,126 @@
+<?php
+/**
+ * Stubs for WooCommerce classes and interfaces.
+ */
+
+// This file should only be loaded during test runs to prevent conflicts in development environments.
+// During local development, files are copied to the WooCommerce vendor directory where the autoloader might attempt to load this file.
+if ( defined( 'WOO_BLUEPRINT_TESTS' ) ) {
+
+ if ( ! class_exists( 'WC_Log_Levels', false ) ) {
+ /**
+ * WC Log Levels Class
+ */
+ class WC_Log_Levels {
+ const EMERGENCY = 'emergency';
+ const ALERT = 'alert';
+ const CRITICAL = 'critical';
+ const ERROR = 'error';
+ const WARNING = 'warning';
+ const NOTICE = 'notice';
+ const INFO = 'info';
+ const DEBUG = 'debug';
+ }
+ }
+
+// phpcs:disable Generic.Files.OneObjectStructurePerFile.MultipleFound
+ if ( ! interface_exists( 'WC_Logger_Interface' ) ) {
+ /**
+ * WC Logger Interface
+ */
+ interface WC_Logger_Interface {
+ /**
+ * Log message with level.
+ *
+ * @param string $level Log level.
+ * @param string $message Log message.
+ * @param array $context Optional. Additional information for log handlers.
+ */
+ public function log( $level, $message, $context = array() );
+
+ /**
+ * Add a log entry.
+ *
+ * @param string $handle Log handle.
+ * @param string $message Log message.
+ * @param string $level Log level.
+ */
+ public function add( $handle, $message, $level = 'notice' );
+
+ /**
+ * Add an emergency level message.
+ *
+ * @param string $message Log message.
+ * @param array $context Log context.
+ */
+ public function emergency( $message, $context = array() );
+
+ /**
+ * Add an alert level message.
+ *
+ * @param string $message Log message.
+ * @param array $context Log context.
+ */
+ public function alert( $message, $context = array() );
+
+ /**
+ * Add a critical level message.
+ *
+ * @param string $message Log message.
+ * @param array $context Log context.
+ */
+ public function critical( $message, $context = array() );
+
+ /**
+ * Add an error level message.
+ *
+ * @param string $message Log message.
+ * @param array $context Log context.
+ */
+ public function error( $message, $context = array() );
+
+ /**
+ * Add a warning level message.
+ *
+ * @param string $message Log message.
+ * @param array $context Log context.
+ */
+ public function warning( $message, $context = array() );
+
+ /**
+ * Add a notice level message.
+ *
+ * @param string $message Log message.
+ * @param array $context Log context.
+ */
+ public function notice( $message, $context = array() );
+
+ /**
+ * Add an info level message.
+ *
+ * @param string $message Log message.
+ * @param array $context Log context.
+ */
+ public function info( $message, $context = array() );
+
+ /**
+ * Add a debug level message.
+ *
+ * @param string $message Log message.
+ * @param array $context Log context.
+ */
+ public function debug( $message, $context = array() );
+ }
+ }
+
+ if ( ! function_exists( 'wc_get_logger' ) ) {
+ /**
+ * Mock wc_get_logger function.
+ *
+ * @return WC_Logger_Interface
+ */
+ function wc_get_logger() { // phpcs:ignore Universal.Files.SeparateFunctionsFromOO.Mixed
+ return Mockery::mock( 'WC_Logger_Interface' )->shouldReceive( 'log' )->getMock();
+ }
+ }
+}
diff --git a/plugins/woocommerce/changelog/57294-wooplug-2482-feature-blueprint-require-permission-based-on-the-steps b/plugins/woocommerce/changelog/57294-wooplug-2482-feature-blueprint-require-permission-based-on-the-steps
new file mode 100644
index 0000000000..2da1557574
--- /dev/null
+++ b/plugins/woocommerce/changelog/57294-wooplug-2482-feature-blueprint-require-permission-based-on-the-steps
@@ -0,0 +1,4 @@
+Significance: patch
+Type: enhancement
+
+Add permission validation for import steps
\ No newline at end of file
diff --git a/plugins/woocommerce/client/admin/client/blueprint/settings/slotfill.js b/plugins/woocommerce/client/admin/client/blueprint/settings/slotfill.js
index b675a810b7..727ed3451e 100644
--- a/plugins/woocommerce/client/admin/client/blueprint/settings/slotfill.js
+++ b/plugins/woocommerce/client/admin/client/blueprint/settings/slotfill.js
@@ -50,6 +50,7 @@ const Blueprint = () => {
);
const exportBlueprint = async ( _steps ) => {
+ setExportError( null );
setExportEnabled( false );
const linkContainer = document.getElementById(
@@ -65,6 +66,7 @@ const Blueprint = () => {
steps: _steps,
},
} );
+
const link = document.createElement( 'a' );
let url = null;
@@ -94,14 +96,41 @@ const Blueprint = () => {
settings_exported: _steps.settings,
} );
} catch ( e ) {
- setExportError( e.message );
-
recordEvent( 'blueprint_export_error', {
error_message: e.message || 'unknown',
} );
- }
- setExportEnabled( true );
+ setExportError( e.message );
+
+ switch ( true ) {
+ case e instanceof Error:
+ setExportError( e.message );
+ break;
+ case typeof e === 'string':
+ setExportError( e );
+ break;
+ case e.errors &&
+ e.errors.wooblueprint_insufficient_permissions &&
+ e.errors.wooblueprint_insufficient_permissions.length > 0:
+ setExportError(
+ __(
+ 'Sorry, you are not allowed to export the selected settings.',
+ 'woocommerce'
+ )
+ );
+ break;
+ default:
+ setExportError(
+ __(
+ 'An unknown error occurred while exporting the settings.',
+ 'woocommerce'
+ )
+ );
+ break;
+ }
+ } finally {
+ setExportEnabled( true );
+ }
};
// Handle checkbox change
diff --git a/plugins/woocommerce/src/Admin/Features/Blueprint/Exporters/ExportWCCoreProfilerOptions.php b/plugins/woocommerce/src/Admin/Features/Blueprint/Exporters/ExportWCCoreProfilerOptions.php
index 60a759e331..271eb00a80 100644
--- a/plugins/woocommerce/src/Admin/Features/Blueprint/Exporters/ExportWCCoreProfilerOptions.php
+++ b/plugins/woocommerce/src/Admin/Features/Blueprint/Exporters/ExportWCCoreProfilerOptions.php
@@ -66,4 +66,13 @@ class ExportWCCoreProfilerOptions implements StepExporter, HasAlias {
public function get_description() {
return __( 'Includes onboarding configuration options', 'woocommerce' );
}
+
+ /**
+ * 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( 'manage_woocommerce' );
+ }
}
diff --git a/plugins/woocommerce/src/Admin/Features/Blueprint/Exporters/ExportWCPaymentGateways.php b/plugins/woocommerce/src/Admin/Features/Blueprint/Exporters/ExportWCPaymentGateways.php
index 7a124e0457..a1aad545f0 100644
--- a/plugins/woocommerce/src/Admin/Features/Blueprint/Exporters/ExportWCPaymentGateways.php
+++ b/plugins/woocommerce/src/Admin/Features/Blueprint/Exporters/ExportWCPaymentGateways.php
@@ -84,4 +84,14 @@ class ExportWCPaymentGateways implements StepExporter {
public function get_description() {
return __( 'Includes all settings in WooCommerce | Settings | Payments.', 'woocommerce' );
}
+
+
+ /**
+ * 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( 'manage_woocommerce' );
+ }
}
diff --git a/plugins/woocommerce/src/Admin/Features/Blueprint/Exporters/ExportWCSettings.php b/plugins/woocommerce/src/Admin/Features/Blueprint/Exporters/ExportWCSettings.php
index 27c05cf3ca..d280a2cf1a 100644
--- a/plugins/woocommerce/src/Admin/Features/Blueprint/Exporters/ExportWCSettings.php
+++ b/plugins/woocommerce/src/Admin/Features/Blueprint/Exporters/ExportWCSettings.php
@@ -73,4 +73,13 @@ abstract class ExportWCSettings implements StepExporter, HasAlias {
public function get_description() {
return __( 'Includes all settings in WooCommerce | Settings | General.', 'woocommerce' );
}
+
+ /**
+ * 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( 'manage_woocommerce' );
+ }
}
diff --git a/plugins/woocommerce/src/Admin/Features/Blueprint/Exporters/ExportWCSettingsSiteVisibility.php b/plugins/woocommerce/src/Admin/Features/Blueprint/Exporters/ExportWCSettingsSiteVisibility.php
index 784e52f19d..aaaa2d4775 100644
--- a/plugins/woocommerce/src/Admin/Features/Blueprint/Exporters/ExportWCSettingsSiteVisibility.php
+++ b/plugins/woocommerce/src/Admin/Features/Blueprint/Exporters/ExportWCSettingsSiteVisibility.php
@@ -68,4 +68,13 @@ class ExportWCSettingsSiteVisibility implements StepExporter, HasAlias {
public function get_step_name() {
return 'setSiteOptions';
}
+
+ /**
+ * 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( 'manage_woocommerce' );
+ }
}
diff --git a/plugins/woocommerce/src/Admin/Features/Blueprint/Exporters/ExportWCShipping.php b/plugins/woocommerce/src/Admin/Features/Blueprint/Exporters/ExportWCShipping.php
index bb1bc295d7..92c461c729 100644
--- a/plugins/woocommerce/src/Admin/Features/Blueprint/Exporters/ExportWCShipping.php
+++ b/plugins/woocommerce/src/Admin/Features/Blueprint/Exporters/ExportWCShipping.php
@@ -187,4 +187,13 @@ class ExportWCShipping implements StepExporter, HasAlias {
)
);
}
+
+ /**
+ * 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( 'manage_woocommerce' );
+ }
}
diff --git a/plugins/woocommerce/src/Admin/Features/Blueprint/Exporters/ExportWCTaskOptions.php b/plugins/woocommerce/src/Admin/Features/Blueprint/Exporters/ExportWCTaskOptions.php
index a98ac9ad89..23b0c360d0 100644
--- a/plugins/woocommerce/src/Admin/Features/Blueprint/Exporters/ExportWCTaskOptions.php
+++ b/plugins/woocommerce/src/Admin/Features/Blueprint/Exporters/ExportWCTaskOptions.php
@@ -68,4 +68,13 @@ class ExportWCTaskOptions implements StepExporter, HasAlias {
public function get_description() {
return __( 'Includes the task configurations for WooCommerce.', 'woocommerce' );
}
+
+ /**
+ * 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( 'manage_woocommerce' );
+ }
}
diff --git a/plugins/woocommerce/src/Admin/Features/Blueprint/Exporters/ExportWCTaxRates.php b/plugins/woocommerce/src/Admin/Features/Blueprint/Exporters/ExportWCTaxRates.php
index f6ffc3f3a9..74e6ca4d42 100644
--- a/plugins/woocommerce/src/Admin/Features/Blueprint/Exporters/ExportWCTaxRates.php
+++ b/plugins/woocommerce/src/Admin/Features/Blueprint/Exporters/ExportWCTaxRates.php
@@ -81,4 +81,13 @@ class ExportWCTaxRates implements StepExporter, HasAlias {
public function get_alias(): string {
return 'setWCTaxRates';
}
+
+ /**
+ * 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( 'manage_woocommerce' );
+ }
}
diff --git a/plugins/woocommerce/src/Admin/Features/Blueprint/RestApi.php b/plugins/woocommerce/src/Admin/Features/Blueprint/RestApi.php
index 9e3a9538fb..52e27da5c2 100644
--- a/plugins/woocommerce/src/Admin/Features/Blueprint/RestApi.php
+++ b/plugins/woocommerce/src/Admin/Features/Blueprint/RestApi.php
@@ -58,7 +58,7 @@ class RestApi {
array(
'methods' => \WP_REST_Server::CREATABLE,
'callback' => array( $this, 'export' ),
- 'permission_callback' => array( $this, 'check_permission' ),
+ 'permission_callback' => array( $this, 'check_export_permission' ),
'args' => array(
'steps' => array(
'description' => __( 'A list of plugins to install', 'woocommerce' ),
@@ -98,7 +98,7 @@ class RestApi {
array(
'methods' => \WP_REST_Server::CREATABLE,
'callback' => array( $this, 'import_step' ),
- 'permission_callback' => array( $this, 'check_permission' ),
+ 'permission_callback' => array( $this, 'check_import_permission' ),
'args' => array(
'step_definition' => array(
'description' => __( 'The step definition to import', 'woocommerce' ),
@@ -113,13 +113,28 @@ class RestApi {
}
/**
- * Check if the current user has permission to perform the request.
+ * General permission check for export requests.
*
* @return bool|\WP_Error
*/
- public function check_permission() {
- if ( ! current_user_can( 'install_plugins' ) ) {
- return new \WP_Error( 'woocommerce_rest_cannot_view', __( 'Sorry, you cannot list resources.', 'woocommerce' ), array( 'status' => rest_authorization_required_code() ) );
+ public function check_export_permission() {
+ if ( ! current_user_can( 'manage_woocommerce' ) ) {
+ return new \WP_Error( 'woocommerce_rest_cannot_view', __( 'Sorry, you cannot export WooCommerce Blueprints.', 'woocommerce' ), array( 'status' => rest_authorization_required_code() ) );
+ }
+ return true;
+ }
+
+ /**
+ * General permission check for import requests.
+ *
+ * @return bool|\WP_Error
+ */
+ public function check_import_permission() {
+ if (
+ ! current_user_can( 'manage_woocommerce' ) ||
+ ! current_user_can( 'manage_options' )
+ ) {
+ return new \WP_Error( 'woocommerce_rest_cannot_view', __( 'Sorry, you cannot import WooCommerce Blueprints.', 'woocommerce' ), array( 'status' => rest_authorization_required_code() ) );
}
return true;
}
@@ -164,6 +179,10 @@ class RestApi {
$data = $exporter->export( $steps );
+ if ( is_wp_error( $data ) ) {
+ return new \WP_REST_Response( $data, 400 );
+ }
+
return new \WP_HTTP_Response(
array(
'data' => $data,
@@ -193,15 +212,15 @@ class RestApi {
private function steps_payload_to_blueprint_steps( $steps ) {
$blueprint_steps = array();
- if ( isset( $steps['settings'] ) ) {
+ if ( isset( $steps['settings'] ) && count( $steps['settings'] ) > 0 ) {
$blueprint_steps = array_merge( $blueprint_steps, $steps['settings'] );
}
- if ( isset( $steps['plugins'] ) ) {
+ if ( isset( $steps['plugins'] ) && count( $steps['plugins'] ) > 0 ) {
$blueprint_steps[] = 'installPlugin';
}
- if ( isset( $steps['themes'] ) ) {
+ if ( isset( $steps['themes'] ) && count( $steps['themes'] ) > 0 ) {
$blueprint_steps[] = 'installTheme';
}
diff --git a/plugins/woocommerce/tests/php/src/Admin/Features/Blueprint/Stubs/DummyExporter.php b/plugins/woocommerce/tests/php/src/Admin/Features/Blueprint/Stubs/DummyExporter.php
index 3e75ff1c0b..d74ba04e0b 100644
--- a/plugins/woocommerce/tests/php/src/Admin/Features/Blueprint/Stubs/DummyExporter.php
+++ b/plugins/woocommerce/tests/php/src/Admin/Features/Blueprint/Stubs/DummyExporter.php
@@ -1,24 +1,58 @@
<?php
+declare(strict_types=1);
+
namespace Automattic\WooCommerce\Tests\Admin\Features\Blueprint\Stubs;
use Automattic\WooCommerce\Blueprint\Exporters\StepExporter;
+/**
+ * Dummy exporter for testing purposes.
+ */
class DummyExporter implements StepExporter {
+ /**
+ * Export the step.
+ *
+ * @return null The result of the export.
+ */
public function export() {
return null;
}
+ /**
+ * Get the description of the step.
+ *
+ * @return string The description of the step.
+ */
public function get_description() {
- return 'description';
+ return 'description';
}
+ /**
+ * Get the label of the step.
+ *
+ * @return string The label of the step.
+ */
public function get_label() {
return 'Dummy';
}
+ /**
+ * Get the name of the step.
+ *
+ * @return string The name of the step.
+ */
public function get_step_name() {
return 'dummy';
}
+
+ /**
+ * Check if the step is capable of being exported.
+ *
+ * @return bool True if the step is capable of being exported, false otherwise.
+ */
+ public function check_step_capabilities(): bool {
+ return true;
+ }
}