Commit 8cb8ad18c82 for woocommerce
commit 8cb8ad18c82d09803eb06917781044d69c555917
Author: James Kemp <me@jckemp.com>
Date: Wed May 6 15:09:57 2026 +0100
Fix PHP 8.5 fatal error in StoreApi AbstractSchema (#63905)
* Fix PHP 8.5 fatal error in AbstractSchema::remove_arg_options()
Add type check to prevent unsetting string offsets in PHP 8.4+.
The method now returns non-array properties unchanged instead of
attempting to unset array keys on them.
Fixes WOOPLUG-6417
Co-authored-by: James Kemp <jamesckemp@users.noreply.github.com>
* Add changefile(s) from automation for the following project(s): woocommerce
* Remove unused ReflectionMethod import from AbstractSchemaTest
Co-authored-by: James Kemp <jamesckemp@users.noreply.github.com>
* Remove useless tearDown override in AbstractSchemaTest
Co-authored-by: James Kemp <jamesckemp@users.noreply.github.com>
* Fix AbstractSchemaTest: use actual ExtendSchema instance instead of mock
ExtendSchema is declared final and cannot be mocked. Create actual
instances of Formatters and ExtendSchema to avoid mock failures.
Co-authored-by: James Kemp <jamesckemp@users.noreply.github.com>
* Fix missing trailing newline in changelog entry
https://claude.ai/code/session_017Mwo2ZfL4zQ3Pdnvf8sJ1G
* Add changefile(s) from automation for the following project(s): woocommerce
* Fix check-changelogger-use.php regex to match pnpm-workspace.yaml
Add `m` flag to the `^packages:` regex so it matches `packages:` at the
start of any line, not just the start of the file. Since the workspace
YAML now begins with `useNodeVersion:` and `catalogs:`, the old regex
never matched, causing all projects to be silently ignored and the
changelog validation to fail.
https://claude.ai/code/session_017Mwo2ZfL4zQ3Pdnvf8sJ1G
---------
Co-authored-by: Cursor Agent <cursoragent@cursor.com>
Co-authored-by: James Kemp <jamesckemp@users.noreply.github.com>
Co-authored-by: woocommercebot <woocommercebot@users.noreply.github.com>
Co-authored-by: Claude <noreply@anthropic.com>
diff --git a/plugins/woocommerce/changelog/63905-cursor-WOOPLUG-6417-php-8-5-unset-string-4251 b/plugins/woocommerce/changelog/63905-cursor-WOOPLUG-6417-php-8-5-unset-string-4251
new file mode 100644
index 00000000000..2d28a96ce6f
--- /dev/null
+++ b/plugins/woocommerce/changelog/63905-cursor-WOOPLUG-6417-php-8-5-unset-string-4251
@@ -0,0 +1,4 @@
+Significance: patch
+Type: fix
+
+Fix PHP 8.5 fatal error when unsetting string offsets in StoreApi AbstractSchema
\ No newline at end of file
diff --git a/plugins/woocommerce/src/StoreApi/Schemas/V1/AbstractSchema.php b/plugins/woocommerce/src/StoreApi/Schemas/V1/AbstractSchema.php
index 482de6ebfa1..83894ac28c9 100644
--- a/plugins/woocommerce/src/StoreApi/Schemas/V1/AbstractSchema.php
+++ b/plugins/woocommerce/src/StoreApi/Schemas/V1/AbstractSchema.php
@@ -90,6 +90,9 @@ abstract class AbstractSchema {
protected function remove_arg_options( $properties ) {
return array_map(
function( $property ) {
+ if ( ! is_array( $property ) ) {
+ return $property;
+ }
if ( isset( $property['properties'] ) ) {
$property['properties'] = $this->remove_arg_options( $property['properties'] );
} elseif ( isset( $property['items']['properties'] ) ) {
diff --git a/plugins/woocommerce/tests/php/src/StoreApi/Schemas/V1/AbstractSchemaTest.php b/plugins/woocommerce/tests/php/src/StoreApi/Schemas/V1/AbstractSchemaTest.php
new file mode 100644
index 00000000000..354418b3a9f
--- /dev/null
+++ b/plugins/woocommerce/tests/php/src/StoreApi/Schemas/V1/AbstractSchemaTest.php
@@ -0,0 +1,167 @@
+<?php
+declare( strict_types = 1 );
+
+namespace Automattic\WooCommerce\Tests\StoreApi\Schemas\V1;
+
+use Automattic\WooCommerce\StoreApi\Schemas\V1\AbstractSchema;
+use Automattic\WooCommerce\StoreApi\SchemaController;
+use Automattic\WooCommerce\StoreApi\Schemas\ExtendSchema;
+use Automattic\WooCommerce\StoreApi\Formatters;
+use WC_Unit_Test_Case;
+use ReflectionClass;
+
+/**
+ * Tests for the AbstractSchema class.
+ *
+ * @since 10.8.0
+ */
+class AbstractSchemaTest extends WC_Unit_Test_Case {
+
+ /**
+ * The System Under Test.
+ *
+ * @var AbstractSchema
+ */
+ private $sut;
+
+ /**
+ * Set up test fixtures.
+ */
+ public function setUp(): void {
+ parent::setUp();
+
+ $formatters = new Formatters();
+ $extend = new ExtendSchema( $formatters );
+ $controller = $this->createMock( SchemaController::class );
+
+ $this->sut = $this->getMockForAbstractClass(
+ AbstractSchema::class,
+ array( $extend, $controller )
+ );
+ }
+
+ /**
+ * @testdox Should remove arg_options from properties array.
+ */
+ public function test_removes_arg_options_from_properties(): void {
+ $properties = array(
+ 'name' => array(
+ 'type' => 'string',
+ 'arg_options' => array( 'default' => 'test' ),
+ ),
+ );
+
+ $result = $this->invoke_remove_arg_options( $properties );
+
+ $this->assertArrayNotHasKey( 'arg_options', $result['name'], 'arg_options should be removed' );
+ $this->assertSame( 'string', $result['name']['type'], 'type property should remain' );
+ }
+
+ /**
+ * @testdox Should handle nested properties recursively.
+ */
+ public function test_handles_nested_properties_recursively(): void {
+ $properties = array(
+ 'address' => array(
+ 'type' => 'object',
+ 'properties' => array(
+ 'city' => array(
+ 'type' => 'string',
+ 'arg_options' => array( 'default' => 'Los Angeles' ),
+ ),
+ ),
+ ),
+ );
+
+ $result = $this->invoke_remove_arg_options( $properties );
+
+ $this->assertArrayNotHasKey( 'arg_options', $result['address']['properties']['city'], 'nested arg_options should be removed' );
+ $this->assertSame( 'string', $result['address']['properties']['city']['type'], 'nested type property should remain' );
+ }
+
+ /**
+ * @testdox Should handle items with properties recursively.
+ */
+ public function test_handles_items_properties_recursively(): void {
+ $properties = array(
+ 'tags' => array(
+ 'type' => 'array',
+ 'items' => array(
+ 'type' => 'object',
+ 'properties' => array(
+ 'name' => array(
+ 'type' => 'string',
+ 'arg_options' => array( 'default' => 'tag' ),
+ ),
+ ),
+ ),
+ ),
+ );
+
+ $result = $this->invoke_remove_arg_options( $properties );
+
+ $this->assertArrayNotHasKey( 'arg_options', $result['tags']['items']['properties']['name'], 'items properties arg_options should be removed' );
+ $this->assertSame( 'string', $result['tags']['items']['properties']['name']['type'], 'items properties type should remain' );
+ }
+
+ /**
+ * @testdox Should handle non-array property values without errors in PHP 8.4+.
+ */
+ public function test_handles_non_array_property_values(): void {
+ $properties = array(
+ 'title' => 'Product Schema',
+ 'description' => array(
+ 'type' => 'string',
+ 'arg_options' => array( 'default' => 'test' ),
+ ),
+ );
+
+ $result = $this->invoke_remove_arg_options( $properties );
+
+ $this->assertSame( 'Product Schema', $result['title'], 'string property should remain unchanged' );
+ $this->assertArrayNotHasKey( 'arg_options', $result['description'], 'arg_options should be removed from array property' );
+ }
+
+ /**
+ * @testdox Should handle empty properties array.
+ */
+ public function test_handles_empty_properties_array(): void {
+ $properties = array();
+
+ $result = $this->invoke_remove_arg_options( $properties );
+
+ $this->assertIsArray( $result, 'result should be an array' );
+ $this->assertEmpty( $result, 'result should be empty' );
+ }
+
+ /**
+ * @testdox Should handle properties without arg_options.
+ */
+ public function test_handles_properties_without_arg_options(): void {
+ $properties = array(
+ 'id' => array(
+ 'type' => 'integer',
+ 'readonly' => true,
+ ),
+ );
+
+ $result = $this->invoke_remove_arg_options( $properties );
+
+ $this->assertSame( 'integer', $result['id']['type'], 'type property should remain' );
+ $this->assertTrue( $result['id']['readonly'], 'readonly property should remain' );
+ }
+
+ /**
+ * Invoke the protected remove_arg_options method.
+ *
+ * @param array $properties Properties array.
+ * @return array Result from remove_arg_options.
+ */
+ private function invoke_remove_arg_options( array $properties ): array {
+ $reflection = new ReflectionClass( $this->sut );
+ $method = $reflection->getMethod( 'remove_arg_options' );
+ $method->setAccessible( true );
+
+ return $method->invoke( $this->sut, $properties );
+ }
+}
diff --git a/tools/monorepo/check-changelogger-use.php b/tools/monorepo/check-changelogger-use.php
index 87fb69fa0b9..91c2f8c04b7 100644
--- a/tools/monorepo/check-changelogger-use.php
+++ b/tools/monorepo/check-changelogger-use.php
@@ -82,7 +82,7 @@ $base_path = dirname( dirname( __DIR__ ) );
$workspace_paths = array();
$workspace_yaml = file_get_contents( $base_path . '/pnpm-workspace.yaml' );
-if ( preg_match( '/^packages:((\n\s+.+)+)/', $workspace_yaml, $matches ) ) {
+if ( preg_match( '/^packages:((\n\s+.+)+)/m', $workspace_yaml, $matches ) ) {
$packages_config = $matches[1];
if ( preg_match_all( "/^\s+-\s?'([^']+)'/m", $packages_config, $matches ) ) {
$workspace_paths = $matches[1];