Commit cac3015c8f7 for woocommerce
commit cac3015c8f724ca001e2d7a2107d2f0acad48757
Author: Ján Mikláš <neosinner@gmail.com>
Date: Mon Sep 14 09:47:04 2026 +0200
Guard the Orders report export against a missing customer name (#68393)
* fix: guard the Orders report export against a missing customer name
prepare_item_for_export() reads extended_info.customer for every row, but
that key defaults to an empty array when the order has no analytics
customer record, and a guest order carries only the name read off the
order itself. get_customer_name() indexed first_name and last_name
unguarded, so those rows raised "Undefined array key" and exported a lone
space instead of a blank cell.
Read both keys defensively and trim the result.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SEcWbxtTF4Lmy1uWR8RGYw
* Better changelog
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
* Clearer docblock
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
* Fix Orders export docblock spacing
* test: use order status constant in export fixture
---------
Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
diff --git a/plugins/woocommerce/changelog/fix-orders-report-export-customer-name-guard b/plugins/woocommerce/changelog/fix-orders-report-export-customer-name-guard
new file mode 100644
index 00000000000..51b757f1d33
--- /dev/null
+++ b/plugins/woocommerce/changelog/fix-orders-report-export-customer-name-guard
@@ -0,0 +1,4 @@
+Significance: patch
+Type: fix
+
+Prevent the Orders Analytics CSV export from logging warnings and writing a lone space when an order has no customer name.
diff --git a/plugins/woocommerce/src/Admin/API/Reports/Orders/Controller.php b/plugins/woocommerce/src/Admin/API/Reports/Orders/Controller.php
index d2d49f9d316..d0b2075f22c 100644
--- a/plugins/woocommerce/src/Admin/API/Reports/Orders/Controller.php
+++ b/plugins/woocommerce/src/Admin/API/Reports/Orders/Controller.php
@@ -414,11 +414,14 @@ class Controller extends GenericController implements ExportableInterface {
/**
* Get customer name column export value.
*
+ * Analytics rows can contain an empty customer array when the customer record is missing, and guest
+ * orders may include only a first or last name, so neither key is guaranteed.
+ *
* @param array $customer Customer from report row.
* @return string
*/
protected function get_customer_name( $customer ) {
- return $customer['first_name'] . ' ' . $customer['last_name'];
+ return trim( ( $customer['first_name'] ?? '' ) . ' ' . ( $customer['last_name'] ?? '' ) );
}
/**
diff --git a/plugins/woocommerce/tests/php/src/Admin/API/Reports/Orders/ControllerTest.php b/plugins/woocommerce/tests/php/src/Admin/API/Reports/Orders/ControllerTest.php
new file mode 100644
index 00000000000..ea69505bdfe
--- /dev/null
+++ b/plugins/woocommerce/tests/php/src/Admin/API/Reports/Orders/ControllerTest.php
@@ -0,0 +1,87 @@
+<?php
+declare( strict_types = 1 );
+
+namespace Automattic\WooCommerce\Tests\Admin\API\Reports\Orders;
+
+use Automattic\WooCommerce\Admin\API\Reports\Orders\Controller;
+use Automattic\WooCommerce\Enums\OrderStatus;
+use WC_Unit_Test_Case;
+
+/**
+ * Tests for the Orders report export methods.
+ */
+class ControllerTest extends WC_Unit_Test_Case {
+
+ /**
+ * The System Under Test.
+ *
+ * @var Controller
+ */
+ private $sut;
+
+ /**
+ * Set up test fixtures.
+ */
+ public function setUp(): void {
+ parent::setUp();
+ $this->sut = new Controller();
+ }
+
+ /**
+ * @testdox The customer name export column should be empty for an order with no customer record.
+ */
+ public function test_customer_name_is_empty_when_the_order_has_no_customer_record(): void {
+ $export_item = $this->sut->prepare_item_for_export( $this->get_item( array() ) );
+
+ $this->assertSame( '', $export_item['customer_name'] );
+ }
+
+ /**
+ * @testdox The customer name export column should hold the name a guest order carries on its own.
+ */
+ public function test_customer_name_is_read_from_a_partial_customer_record(): void {
+ $export_item = $this->sut->prepare_item_for_export(
+ $this->get_item(
+ array(
+ 'first_name' => 'Ada',
+ 'last_name' => 'Lovelace',
+ )
+ )
+ );
+
+ $this->assertSame( 'Ada Lovelace', $export_item['customer_name'] );
+ }
+
+ /**
+ * @testdox The customer name export column should hold a first name on its own.
+ */
+ public function test_customer_name_holds_a_first_name_on_its_own(): void {
+ $export_item = $this->sut->prepare_item_for_export( $this->get_item( array( 'first_name' => 'Ada' ) ) );
+
+ $this->assertSame( 'Ada', $export_item['customer_name'] );
+ }
+
+ /**
+ * Build a report row carrying the given customer record.
+ *
+ * @param array $customer Customer record for the row.
+ * @return array
+ */
+ private function get_item( array $customer ): array {
+ return array(
+ 'date' => '2026-09-04 10:00:00',
+ 'order_number' => '123',
+ 'total_formatted' => '10.00',
+ 'status' => OrderStatus::COMPLETED,
+ 'customer_type' => 'new',
+ 'num_items_sold' => 1,
+ 'net_total' => 10.00,
+ 'extended_info' => array(
+ 'products' => array(),
+ 'coupons' => array(),
+ 'customer' => $customer,
+ 'attribution' => array( 'origin' => 'Direct' ),
+ ),
+ );
+ }
+}