diff --git a/plugins/woocommerce/changelog/fix-35612 b/plugins/woocommerce/changelog/fix-35612 new file mode 100644 index 00000000000..c05815b0379 --- /dev/null +++ b/plugins/woocommerce/changelog/fix-35612 @@ -0,0 +1,4 @@ +Significance: patch +Type: fix + +Support inserting NULL values for strict DB mode for DataBase Util's insert_on_duplicate_key_update method. diff --git a/plugins/woocommerce/src/Internal/DataStores/Orders/OrdersTableDataStore.php b/plugins/woocommerce/src/Internal/DataStores/Orders/OrdersTableDataStore.php index 7eb110ae613..1ab638bfc3d 100644 --- a/plugins/woocommerce/src/Internal/DataStores/Orders/OrdersTableDataStore.php +++ b/plugins/woocommerce/src/Internal/DataStores/Orders/OrdersTableDataStore.php @@ -1656,9 +1656,10 @@ FROM $order_meta_table if ( 'create' === $context ) { $post_id = wp_insert_post( array( - 'post_type' => $data_sync->data_sync_is_enabled() ? $order->get_type() : $data_sync::PLACEHOLDER_ORDER_POST_TYPE, - 'post_status' => 'draft', - 'post_parent' => $order->get_changes()['parent_id'] ?? $order->get_data()['parent_id'] ?? 0, + 'post_type' => $data_sync->data_sync_is_enabled() ? $order->get_type() : $data_sync::PLACEHOLDER_ORDER_POST_TYPE, + 'post_status' => 'draft', + 'post_parent' => $order->get_changes()['parent_id'] ?? $order->get_data()['parent_id'] ?? 0, + 'post_date_gmt' => current_time( 'mysql', 1 ), // We set the date to prevent invalid date errors when using MySQL strict mode. ) ); @@ -2287,6 +2288,10 @@ FROM $order_meta_table $order->set_date_created( time() ); } + if ( ! $order->get_date_modified( 'edit' ) ) { + $order->set_date_modified( current_time( 'mysql' ) ); + } + $this->update_order_meta( $order ); $this->persist_order_to_db( $order, $force_all_fields ); @@ -2370,7 +2375,7 @@ FROM $order_meta_table $changes = $order->get_changes(); if ( ! isset( $changes['date_modified'] ) ) { - $order->set_date_modified( time() ); + $order->set_date_modified( current_time( 'mysql' ) ); } $this->persist_order_to_db( $order ); diff --git a/plugins/woocommerce/src/Internal/Utilities/DatabaseUtil.php b/plugins/woocommerce/src/Internal/Utilities/DatabaseUtil.php index 1402bf8460d..3bd824f450c 100644 --- a/plugins/woocommerce/src/Internal/Utilities/DatabaseUtil.php +++ b/plugins/woocommerce/src/Internal/Utilities/DatabaseUtil.php @@ -233,19 +233,35 @@ class DatabaseUtil { */ public function insert_on_duplicate_key_update( $table_name, $data, $format ) : int { global $wpdb; + if ( empty( $data ) ) { + return 0; + } - $columns = array_keys( $data ); + $columns = array_keys( $data ); + $value_format = array(); + $values = array(); + $index = 0; + // Directly use NULL for placeholder if the value is NULL, since otherwise $wpdb->prepare will convert it to empty string. + foreach ( $data as $key => $value ) { + if ( is_null( $value ) ) { + $value_format[] = 'NULL'; + } else { + $values[] = $value; + $value_format[] = $format[ $index ]; + } + $index++; + } $column_clause = '`' . implode( '`, `', $columns ) . '`'; - $value_placeholders = implode( ', ', array_values( $format ) ); + $value_format_clause = implode( ', ', $value_format ); $on_duplicate_clause = $this->generate_on_duplicate_statement_clause( $columns ); // phpcs:disable WordPress.DB.PreparedSQL.InterpolatedNotPrepared, WordPress.DB.PreparedSQLPlaceholders.UnfinishedPrepare -- Values are escaped in $wpdb->prepare. $sql = $wpdb->prepare( " INSERT INTO $table_name ( $column_clause ) -VALUES ( $value_placeholders ) +VALUES ( $value_format_clause ) $on_duplicate_clause ", - array_values( $data ) + $values ); // phpcs:enable // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared -- $sql is prepared. diff --git a/plugins/woocommerce/tests/php/src/Internal/DataStores/Orders/OrdersTableDataStoreTests.php b/plugins/woocommerce/tests/php/src/Internal/DataStores/Orders/OrdersTableDataStoreTests.php index 47295dc657c..7834eb53777 100644 --- a/plugins/woocommerce/tests/php/src/Internal/DataStores/Orders/OrdersTableDataStoreTests.php +++ b/plugins/woocommerce/tests/php/src/Internal/DataStores/Orders/OrdersTableDataStoreTests.php @@ -68,6 +68,7 @@ class OrdersTableDataStoreTests extends HposTestCase { * Destroys system under test. */ public function tearDown(): void { + global $wpdb; //phpcs:ignore WordPress.DateTime.RestrictedFunctions.timezone_change_date_default_timezone_set -- We need to change the timezone to test the date sync fields. update_option( 'timezone_string', $this->original_time_zone ); $this->toggle_cot_feature_and_usage( $this->cot_state ); @@ -1996,6 +1997,37 @@ class OrdersTableDataStoreTests extends HposTestCase { $this->assertEquals( $modified_date->format( 'Y-m-d H:i:s' ), $post->post_modified_gmt ); } + /** + * @testDox Test that inserting with strict SQL mode is also supported. + */ + public function test_order_create_with_strict_mode_and_null_values() { + global $wpdb; + $this->toggle_cot_feature_and_usage( true ); + $sql_mode = $wpdb->get_var( 'SELECT @@sql_mode' ); + // Set SQL mode to strict to disallow 0 dates. + $wpdb->query( "SET sql_mode = 'TRADITIONAL'" ); + + $order = new WC_Order(); + $this->switch_data_store( $order, $this->sut ); + $order->save(); + + $this->assertTrue( $order->get_id() > 0 ); + + // Let's also repeat with sync off. + $this->enable_cot_sync(); + $order = new WC_Order(); + $this->switch_data_store( $order, $this->sut ); + $order->save(); + + $this->assertTrue( $order->get_id() > 0 ); + $order = wc_get_order( $order->get_id() ); + $post = get_post( $order->get_id() ); + $this->assertEquals( $order->get_date_modified()->format( 'Y-m-d H:i:s' ), $post->post_date ); + + // phpcs:ignore -- Hardcoded value. + $wpdb->query( "SET sql_mode = '$sql_mode' " ); + } + /** * @testDox Test that multiple calls to read don't try to sync again. */