diff --git a/.github/workflows/metrics.yml b/.github/workflows/metrics.yml
index da5de8e5312..61d5f3efede 100644
--- a/.github/workflows/metrics.yml
+++ b/.github/workflows/metrics.yml
@@ -51,3 +51,11 @@ jobs:
with:
name: performance-results
path: ${{ env.WP_ARTIFACTS_PATH }}/*.performance-results*.json
+
+ - name: Publish performance results
+ if: github.event_name == 'push'
+ env:
+ CODEVITALS_PROJECT_TOKEN: ${{ secrets.CODEVITALS_PROJECT_TOKEN }}
+ run: |
+ COMMITTED_AT=$(git show -s $GITHUB_SHA --format="%cI")
+ cd tools/compare-perf && pnpm run log $CODEVITALS_PROJECT_TOKEN trunk $GITHUB_SHA 19f3d0884617d7ecdcf37664f648a51e2987cada $COMMITTED_AT
diff --git a/phpcs.xml b/phpcs.xml
index fd355d33463..7324030f0aa 100644
--- a/phpcs.xml
+++ b/phpcs.xml
@@ -107,18 +107,24 @@
src/Internal/Admin/
src/Admin/
+ src/Blocks/
+ src/StoreApi/
src/Internal/Admin/
src/Admin/
+ src/Blocks/
+ src/StoreApi/
src/Internal/Admin/
src/Admin/
+ src/Blocks/
+ src/StoreApi/
diff --git a/plugins/woocommerce/changelog/add-blocks-injection-rule b/plugins/woocommerce/changelog/add-blocks-injection-rule
new file mode 100644
index 00000000000..df513a6a407
--- /dev/null
+++ b/plugins/woocommerce/changelog/add-blocks-injection-rule
@@ -0,0 +1,4 @@
+Significance: minor
+Type: update
+
+Add InternalInjection sniff exceptions for blocks
diff --git a/plugins/woocommerce/changelog/fix-41249 b/plugins/woocommerce/changelog/fix-41249
new file mode 100644
index 00000000000..762c0a25ed5
--- /dev/null
+++ b/plugins/woocommerce/changelog/fix-41249
@@ -0,0 +1,4 @@
+Significance: patch
+Type: fix
+
+Delete trashed orders after `EMPTY_TRASH_DAYS` as defined by WordPress (HPOS)
diff --git a/plugins/woocommerce/src/Internal/DataStores/Orders/DataSynchronizer.php b/plugins/woocommerce/src/Internal/DataStores/Orders/DataSynchronizer.php
index 5d6a383b636..235f4787816 100644
--- a/plugins/woocommerce/src/Internal/DataStores/Orders/DataSynchronizer.php
+++ b/plugins/woocommerce/src/Internal/DataStores/Orders/DataSynchronizer.php
@@ -80,6 +80,13 @@ class DataSynchronizer implements BatchProcessorInterface {
*/
private $error_logger;
+ /**
+ * The instance of the LegacyProxy object to use.
+ *
+ * @var LegacyProxy
+ */
+ private $legacy_proxy;
+
/**
* The order cache controller.
*
@@ -103,6 +110,7 @@ class DataSynchronizer implements BatchProcessorInterface {
self::add_action( 'woocommerce_refund_created', array( $this, 'handle_updated_order' ), 100 );
self::add_action( 'woocommerce_update_order', array( $this, 'handle_updated_order' ), 100 );
self::add_action( 'wp_scheduled_auto_draft_delete', array( $this, 'delete_auto_draft_orders' ), 9 );
+ self::add_action( 'wp_scheduled_delete', array( $this, 'delete_trashed_orders' ), 9 );
self::add_filter( 'updated_option', array( $this, 'process_updated_option' ), 999, 3 );
self::add_filter( 'added_option', array( $this, 'process_added_option' ), 999, 2 );
self::add_filter( 'deleted_option', array( $this, 'process_deleted_option' ), 999 );
@@ -136,6 +144,7 @@ class DataSynchronizer implements BatchProcessorInterface {
$this->data_store = $data_store;
$this->database_util = $database_util;
$this->posts_to_cot_migrator = $posts_to_cot_migrator;
+ $this->legacy_proxy = $legacy_proxy;
$this->error_logger = $legacy_proxy->call_function( 'wc_get_logger' );
$this->order_cache_controller = $order_cache_controller;
$this->batch_processing_controller = $batch_processing_controller;
@@ -966,6 +975,41 @@ ORDER BY orders.id ASC
do_action( 'woocommerce_scheduled_auto_draft_delete' );
}
+ /**
+ * Handles deletion of trashed orders after `EMPTY_TRASH_DAYS` as defined by WordPress.
+ *
+ * @since 8.5.0
+ *
+ * @return void
+ */
+ private function delete_trashed_orders() {
+ if ( ! $this->custom_orders_table_is_authoritative() ) {
+ return;
+ }
+
+ $delete_timestamp = $this->legacy_proxy->call_function( 'time' ) - ( DAY_IN_SECONDS * EMPTY_TRASH_DAYS );
+ $args = array(
+ 'status' => 'trash',
+ 'limit' => self::ORDERS_SYNC_BATCH_SIZE,
+ 'date_modified' => '<' . $delete_timestamp,
+ );
+
+ $orders = wc_get_orders( $args );
+ if ( ! $orders || ! is_array( $orders ) ) {
+ return;
+ }
+
+ foreach ( $orders as $order ) {
+ if ( $order->get_status() !== 'trash' ) {
+ continue;
+ }
+ if ( $order->get_date_modified()->getTimestamp() >= $delete_timestamp ) {
+ continue;
+ }
+ $order->delete( true );
+ }
+ }
+
/**
* Handle the 'woocommerce_feature_description_tip' filter.
*
diff --git a/plugins/woocommerce/tests/php/src/Internal/DataStores/Orders/DataSynchronizerTests.php b/plugins/woocommerce/tests/php/src/Internal/DataStores/Orders/DataSynchronizerTests.php
index d64ae7ae3b3..52a4eeab8c6 100644
--- a/plugins/woocommerce/tests/php/src/Internal/DataStores/Orders/DataSynchronizerTests.php
+++ b/plugins/woocommerce/tests/php/src/Internal/DataStores/Orders/DataSynchronizerTests.php
@@ -25,13 +25,18 @@ class DataSynchronizerTests extends HposTestCase {
*/
public function setUp(): void {
parent::setUp();
+
+ $this->reset_legacy_proxy_mocks();
+ $container = wc_get_container();
+ $container->reset_all_resolved();
+
// Remove the Test Suiteās use of temporary tables https://wordpress.stackexchange.com/a/220308.
remove_filter( 'query', array( $this, '_create_temporary_tables' ) );
remove_filter( 'query', array( $this, '_drop_temporary_tables' ) );
OrderHelper::delete_order_custom_tables(); // We need this since non-temporary tables won't drop automatically.
OrderHelper::create_order_custom_table_if_not_exist();
OrderHelper::toggle_cot_feature_and_usage( false );
- $this->sut = wc_get_container()->get( DataSynchronizer::class );
+ $this->sut = $container->get( DataSynchronizer::class );
}
/**
@@ -530,6 +535,53 @@ class DataSynchronizerTests extends HposTestCase {
$this->assertNotContains( $order1->get_id(), $orders );
}
+ /**
+ * Test that trashed orders are deleted after the time set in `EMPTY_TRASH_DAYS`.
+ */
+ public function test_trashed_order_deletion(): void {
+ $this->toggle_cot_authoritative( true );
+ $this->disable_cot_sync();
+
+ $order = new WC_Order();
+ $order->save();
+
+ // Ensure the placeholder post is there.
+ $placeholder = get_post( $order->get_id() );
+ $this->assertEquals( $order->get_id(), $placeholder->ID );
+
+ // Trashed orders should be deleted by the collection mechanism.
+ $order->get_data_store()->delete( $order );
+ $this->assertEquals( $order->get_status(), 'trash' );
+ $order->save();
+
+ // Run scheduled deletion.
+ do_action( 'wp_scheduled_delete' ); // phpcs:ignore WooCommerce.Commenting.CommentHooks.HookCommentWrongStyle
+
+ // Refresh order and ensure it's *not* gone.
+ $order = wc_get_order( $order->get_id() );
+ $this->assertNotNull( $order );
+
+ // Time-travel into the future so that the time required to delete a trashed order has passed.
+ $this->register_legacy_proxy_function_mocks(
+ array(
+ 'time' => function() {
+ return time() + DAY_IN_SECONDS * EMPTY_TRASH_DAYS + 1;
+ },
+ )
+ );
+
+ // Run scheduled deletion.
+ do_action( 'wp_scheduled_delete' ); // phpcs:ignore WooCommerce.Commenting.CommentHooks.HookCommentWrongStyle
+
+ // Ensure the placeholder post is gone.
+ $placeholder = get_post( $order->get_id() );
+ $this->assertNull( $placeholder );
+
+ // Refresh order and ensure it's gone.
+ $order = wc_get_order( $order->get_id() );
+ $this->assertFalse( $order );
+ }
+
/**
* @testDox When HPOS is enabled, the custom orders table is created.
*/
diff --git a/tools/compare-perf/log-to-codevitals.js b/tools/compare-perf/log-to-codevitals.js
new file mode 100644
index 00000000000..3b4e31a6a1e
--- /dev/null
+++ b/tools/compare-perf/log-to-codevitals.js
@@ -0,0 +1,86 @@
+#!/usr/bin/env node
+/* eslint-disable no-console */
+const fs = require( 'fs' );
+const path = require( 'path' );
+const https = require( 'https' );
+const [ token, branch, hash, baseHash, timestamp ] = process.argv.slice( 2 );
+
+const resultsFiles = [
+ {
+ file: 'editor.performance-results.json',
+ metricsPrefix: 'editor-',
+ },
+];
+
+const performanceResults = resultsFiles.map( ( { file } ) =>
+ JSON.parse(
+ fs.readFileSync(
+ path.join( process.env.WP_ARTIFACTS_PATH, file ),
+ 'utf8'
+ )
+ )
+);
+
+const data = new TextEncoder().encode(
+ JSON.stringify( {
+ branch,
+ hash,
+ baseHash,
+ timestamp,
+ metrics: resultsFiles.reduce( ( result, { metricsPrefix }, index ) => {
+ return {
+ ...result,
+ ...Object.fromEntries(
+ Object.entries(
+ performanceResults[ index ][ hash ] ?? {}
+ ).map( ( [ key, value ] ) => [
+ metricsPrefix + key,
+ value,
+ ] )
+ ),
+ };
+ }, {} ),
+ baseMetrics: resultsFiles.reduce(
+ ( result, { metricsPrefix }, index ) => {
+ return {
+ ...result,
+ ...Object.fromEntries(
+ Object.entries(
+ performanceResults[ index ][ baseHash ] ?? {}
+ ).map( ( [ key, value ] ) => [
+ metricsPrefix + key,
+ value,
+ ] )
+ ),
+ };
+ },
+ {}
+ ),
+ } )
+);
+
+const options = {
+ hostname: 'www.codevitals.run',
+ port: 443,
+ path: '/api/log?token=' + token,
+ method: 'POST',
+ headers: {
+ 'Content-Type': 'application/json',
+ 'Content-Length': data.length,
+ },
+};
+
+const req = https.request( options, ( res ) => {
+ console.log( `statusCode: ${ res.statusCode }` );
+
+ res.on( 'data', ( d ) => {
+ process.stdout.write( d );
+ } );
+} );
+
+req.on( 'error', ( error ) => {
+ console.error( error );
+} );
+
+req.write( data );
+req.end();
diff --git a/tools/compare-perf/package.json b/tools/compare-perf/package.json
index 77c193c56ff..36ae6fe2746 100644
--- a/tools/compare-perf/package.json
+++ b/tools/compare-perf/package.json
@@ -7,7 +7,8 @@
"license": "GPLv2",
"repository": "woocommerce/woocommerce",
"scripts": {
- "compare": "node index.js"
+ "compare": "node index.js",
+ "log": "node log-to-codevitals.js"
},
"dependencies": {
"@wordpress/env": "^8.13.0",