Commit 53e313c06b9 for woocommerce
commit 53e313c06b91116fc7e59b7b581fe1611a9e0ff2
Author: Luis Felipe Zaguini <26530524+zaguiini@users.noreply.github.com>
Date: Mon Nov 2 08:18:53 2026 -0300
Make the steps of write abilities protected (#70307)
* Make the steps of write abilities protected
Any code could call load(), change() or save() on a write ability
directly. A direct call to save() skips the extension fields and the
validators, so an extension's rules could be bypassed with no warning.
The steps and prepare_response() are now protected. Only
execute_change(), the ability run, calls them. Subclasses of Core's
write abilities still override the steps, and a new test runs one
end to end.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
* Check that the validator runs in the subclass test
The subclass adds the suffix before validation, so the old assertions
passed even without a validator call. The test now records the call
and asserts it.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
* Remove the subclass test and the protected-steps docblock sentence
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
---------
Co-authored-by: Claude Fable 5.1 <noreply@anthropic.com>
diff --git a/plugins/woocommerce/changelog/wooai-protect-ability-steps b/plugins/woocommerce/changelog/wooai-protect-ability-steps
new file mode 100644
index 00000000000..a566b96234a
--- /dev/null
+++ b/plugins/woocommerce/changelog/wooai-protect-ability-steps
@@ -0,0 +1,4 @@
+Significance: patch
+Type: update
+
+Make the load, change and save steps of write abilities protected, so only the ability run can call them.
diff --git a/plugins/woocommerce/src/Abilities/ActionableAbility.php b/plugins/woocommerce/src/Abilities/ActionableAbility.php
index b10b50058e5..0366f0f0754 100644
--- a/plugins/woocommerce/src/Abilities/ActionableAbility.php
+++ b/plugins/woocommerce/src/Abilities/ActionableAbility.php
@@ -50,7 +50,7 @@ abstract class ActionableAbility extends \WP_Ability {
* @param array $input Ability input.
* @return \WC_Data|\WP_Error
*/
- abstract public function load( array $input );
+ abstract protected function load( array $input );
/**
* Change the object in memory.
@@ -61,7 +61,7 @@ abstract class ActionableAbility extends \WP_Ability {
* @param array $input Ability input.
* @return null|\WP_Error A WP_Error rejects the change, and nothing is saved.
*/
- abstract public function change( $subject, array $input );
+ abstract protected function change( $subject, array $input );
/**
* The ability output for the saved object. It must match the output schema
@@ -72,7 +72,7 @@ abstract class ActionableAbility extends \WP_Ability {
* @param \WC_Data $subject Saved object.
* @return mixed
*/
- abstract public function prepare_response( $subject );
+ abstract protected function prepare_response( $subject );
/**
* Save the changed object. The default calls WC_Data::save(). When the save
@@ -85,7 +85,7 @@ abstract class ActionableAbility extends \WP_Ability {
* @param \WC_Data $subject Changed object.
* @return null|\WP_Error Null when the object was saved, a WP_Error when it was not.
*/
- public function save( $subject ) {
+ protected function save( $subject ) {
try {
$subject->save();
} catch ( \Exception $e ) {
diff --git a/plugins/woocommerce/src/Internal/Abilities/Domain/ProductCreate.php b/plugins/woocommerce/src/Internal/Abilities/Domain/ProductCreate.php
index dd161bfaa7a..103b047c00f 100644
--- a/plugins/woocommerce/src/Internal/Abilities/Domain/ProductCreate.php
+++ b/plugins/woocommerce/src/Internal/Abilities/Domain/ProductCreate.php
@@ -74,7 +74,7 @@ class ProductCreate extends ActionableAbility {
* @param array $input Ability input.
* @return \WC_Product|\WP_Error
*/
- public function load( array $input ) {
+ protected function load( array $input ) {
$product_config = self::get_product_config_for_alias( $input['product_type_alias'] ?? 'physical' );
if ( is_wp_error( $product_config ) ) {
@@ -101,7 +101,7 @@ class ProductCreate extends ActionableAbility {
* @param array $input Ability input.
* @return null|\WP_Error
*/
- public function change( $subject, array $input ) {
+ protected function change( $subject, array $input ) {
$product_config = self::get_product_config_for_alias( $input['product_type_alias'] ?? 'physical' );
if ( is_wp_error( $product_config ) ) {
@@ -123,7 +123,7 @@ class ProductCreate extends ActionableAbility {
* @param \WC_Product $subject Changed product.
* @return null|\WP_Error
*/
- public function save( $subject ) {
+ protected function save( $subject ) {
return self::save_product( $subject, 'woocommerce_product_create_failed' );
}
@@ -133,7 +133,7 @@ class ProductCreate extends ActionableAbility {
* @param \WC_Product $subject Saved product.
* @return array
*/
- public function prepare_response( $subject ): array {
+ protected function prepare_response( $subject ): array {
return array(
'product' => self::format_product_for_response( $subject ),
);
diff --git a/plugins/woocommerce/src/Internal/Abilities/Domain/ProductUpdate.php b/plugins/woocommerce/src/Internal/Abilities/Domain/ProductUpdate.php
index f80c5828b86..0cbbbb01628 100644
--- a/plugins/woocommerce/src/Internal/Abilities/Domain/ProductUpdate.php
+++ b/plugins/woocommerce/src/Internal/Abilities/Domain/ProductUpdate.php
@@ -75,7 +75,7 @@ class ProductUpdate extends ActionableAbility {
* @param array $input Ability input.
* @return \WC_Product|\WP_Error
*/
- public function load( array $input ) {
+ protected function load( array $input ) {
$product = self::get_product_from_input( $input );
if ( is_wp_error( $product ) ) {
@@ -141,7 +141,7 @@ class ProductUpdate extends ActionableAbility {
* @param array $input Ability input.
* @return null|\WP_Error
*/
- public function change( $subject, array $input ) {
+ protected function change( $subject, array $input ) {
$product_config = isset( $input['product_type_alias'] )
? self::get_product_config_for_alias( $input['product_type_alias'] )
: self::get_product_config_for_product( $subject );
@@ -167,7 +167,7 @@ class ProductUpdate extends ActionableAbility {
* @param \WC_Product $subject Changed product.
* @return null|\WP_Error
*/
- public function save( $subject ) {
+ protected function save( $subject ) {
return self::save_product( $subject, 'woocommerce_product_update_failed' );
}
@@ -177,7 +177,7 @@ class ProductUpdate extends ActionableAbility {
* @param \WC_Product $subject Saved product.
* @return array
*/
- public function prepare_response( $subject ): array {
+ protected function prepare_response( $subject ): array {
return array(
'product' => self::format_product_for_response( $subject ),
);
diff --git a/plugins/woocommerce/tests/php/src/Internal/AbilitiesApi/TestOrderStatusAbility.php b/plugins/woocommerce/tests/php/src/Internal/AbilitiesApi/TestOrderStatusAbility.php
index effef82c948..0c499d69c69 100644
--- a/plugins/woocommerce/tests/php/src/Internal/AbilitiesApi/TestOrderStatusAbility.php
+++ b/plugins/woocommerce/tests/php/src/Internal/AbilitiesApi/TestOrderStatusAbility.php
@@ -29,7 +29,7 @@ class TestOrderStatusAbility extends ActionableAbility {
* @param array $input Ability input.
* @return \WC_Order|\WP_Error
*/
- public function load( array $input ) {
+ protected function load( array $input ) {
$order = wc_get_order( $input['id'] );
return $order ? $order : new \WP_Error( 'test_not_found', 'Not found.', array( 'status' => 404 ) );
}
@@ -41,7 +41,7 @@ class TestOrderStatusAbility extends ActionableAbility {
* @param array $input Ability input.
* @return null
*/
- public function change( $subject, array $input ) {
+ protected function change( $subject, array $input ) {
$subject->set_status( $input['status'] );
return null;
}
@@ -52,7 +52,7 @@ class TestOrderStatusAbility extends ActionableAbility {
* @param \WC_Order $subject Saved order.
* @return array
*/
- public function prepare_response( $subject ) {
+ protected function prepare_response( $subject ) {
return array( 'status' => $subject->get_status() );
}
}
diff --git a/plugins/woocommerce/tests/php/src/Internal/AbilitiesApi/TestProductRenameAbility.php b/plugins/woocommerce/tests/php/src/Internal/AbilitiesApi/TestProductRenameAbility.php
index a42a503d35c..ee6c735454b 100644
--- a/plugins/woocommerce/tests/php/src/Internal/AbilitiesApi/TestProductRenameAbility.php
+++ b/plugins/woocommerce/tests/php/src/Internal/AbilitiesApi/TestProductRenameAbility.php
@@ -30,7 +30,7 @@ class TestProductRenameAbility extends ActionableAbility {
* @param array $input Ability input.
* @return \WC_Product|\WP_Error
*/
- public function load( array $input ) {
+ protected function load( array $input ) {
$product = wc_get_product( $input['id'] );
return $product ? $product : new \WP_Error( 'test_not_found', 'Not found.', array( 'status' => 404 ) );
}
@@ -42,7 +42,7 @@ class TestProductRenameAbility extends ActionableAbility {
* @param array $input Ability input.
* @return null
*/
- public function change( $subject, array $input ) {
+ protected function change( $subject, array $input ) {
$subject->set_name( $input['name'] );
return null;
}
@@ -53,7 +53,7 @@ class TestProductRenameAbility extends ActionableAbility {
* @param \WC_Product $subject Saved product.
* @return array
*/
- public function prepare_response( $subject ) {
+ protected function prepare_response( $subject ) {
return ExtensibleAbilities::add_fields_to_object( array( 'id' => $subject->get_id() ), 'product', $subject );
}
}