diff --git a/docs/docs-manifest.json b/docs/docs-manifest.json index 2e5881e4690..d057b5bd72d 100644 --- a/docs/docs-manifest.json +++ b/docs/docs-manifest.json @@ -559,6 +559,22 @@ "category_slug": "hpos", "category_title": "High Performance Order Storage", "posts": [ + { + "post_title": "HPOS order querying APIs", + "tags": "reference", + "edit_url": "https://github.com/woocommerce/woocommerce/edit/trunk/docs/high-performance-order-storage/wc-order-query-improvements.md", + "hash": "0e7d4f6ed88da1607aef79d72f2f8289ce3717f405f5e028b4be654654a80289", + "url": "https://raw.githubusercontent.com/woocommerce/woocommerce/trunk/docs/high-performance-order-storage/wc-order-query-improvements.md", + "id": "94261c0a5d954f409ce6c207c9ae624dd577cf64" + }, + { + "post_title": "HPOS extension recipe book", + "tags": "how-to", + "edit_url": "https://github.com/woocommerce/woocommerce/edit/trunk/docs/high-performance-order-storage/recipe-book.md", + "hash": "07491c9e8ee2dd78d7f242ad4cc8c3aa9a604837287a6af4eba85017c8d70951", + "url": "https://raw.githubusercontent.com/woocommerce/woocommerce/trunk/docs/high-performance-order-storage/recipe-book.md", + "id": "991c19edd20b1df6683f31f106d74689248cd102" + }, { "post_title": "A large store's guide to enable HPOS on WooCommerce", "menu_title": "Enable HPOS for large stores", @@ -576,6 +592,14 @@ "hash": "c8c75c274eb52a48729589220cfd6b5c091b6699d3bf44e6c808cf62f1680310", "url": "https://raw.githubusercontent.com/woocommerce/woocommerce/trunk/docs/high-performance-order-storage/enable-hpos.md", "id": "9d41c63f3d0cb17ada82a65bf4a26a1978cc34b0" + }, + { + "post_title": "HPOS CLI Tools", + "tags": "reference", + "edit_url": "https://github.com/woocommerce/woocommerce/edit/trunk/docs/high-performance-order-storage/cli-tools.md", + "hash": "8cd823759ce20551d582c39f57ae79f9e0227a8cb0131146e6b7dac5e7312708", + "url": "https://raw.githubusercontent.com/woocommerce/woocommerce/trunk/docs/high-performance-order-storage/cli-tools.md", + "id": "cdd9d9ad5777d978ba953e3478fbb61cab8fdf59" } ], "categories": [] @@ -1242,5 +1266,5 @@ "categories": [] } ], - "hash": "4d3e84c2d89db384ddd068a068dfa2e284e7d339b46bd59e6bda1f0dff53de99" + "hash": "22dc26fbd0e387183beec01e381506771318c236ba6a63b573ffb888dcc1d1d0" } \ No newline at end of file diff --git a/docs/high-performance-order-storage/cli-tools.md b/docs/high-performance-order-storage/cli-tools.md new file mode 100644 index 00000000000..e61035d60cd --- /dev/null +++ b/docs/high-performance-order-storage/cli-tools.md @@ -0,0 +1,358 @@ +--- +post_title: HPOS CLI Tools +tags: reference +--- +## Overview + +With HPOS we introduced a set of [WP-CLI commands](https://developer.woocommerce.com/docs/category/wc-cli/) living under the `wp wc hpos` and `wp wc cot` namespaces. + +The following table provides an overview of what each command does, while more details and examples can be found below. + +Keep in mind that the commands themselves have documentation and examples that can be accessed via WP-CLI's help by passing the `--help` flag. + +|Command|Use this command to...| +|----|-----| +|`wc hpos status`|Get an overview of all HPOS matters on your site.| +|`wc cot enable`|Enable HPOS (and possibly compatibility mode).| +|`wc cot disable`|Disable HPOS (and possibly compatibility mode).| +|`wc cot count_unmigrated`|Get a count of all orders pending sync.| +|`wc cot sync`|Performantly sync orders from the currently active order storage to the other.| +|`wc cot verify_cot_data`|Verify data between datastores.| +|`wc hpos diff`|Get an user friendly version of the differences for an order between both order storages.| +|`wc hpos backfill`|Copy over whole orders or specific bits of order data from any order storage to the other.| +|`wc hpos cleanup`|Remove order data from legacy tables.| + +## Usage and examples + +### `wc hpos status` + +Use this to get an overview of HPOS settings and status on your site. The command will output whether HPOS and compatibility mode are enabled or not, and other useful information such as orders pending sync or subject to cleanup. + +**Note:** Remember that, if desired, orders pending sync can be synced using [`wc cot sync`](#wc-cot-sync) and, similarly, you can perform a cleanup on those subject to cleanup (provided compatibility mode is disabled) by running [`wc hpos cleanup all`](#wc-hpos-cleanup). + + +#### Example 1 - HPOS status output + +```plaintext +$ wp wc hpos status +HPOS enabled?: yes +Compatibility mode enabled?: no +Unsynced orders: 651 +Orders subject to cleanup: 348 +``` + +### `wc cot enable` + +Use this command to enable HPOS and compatibility mode (if desired) from the command line. + +#### Example 1 - Enable HPOS via CLI + +Enables HPOS and compatibility mode too (`--with-sync` flag). + +```plaintext +$ wp wc cot enable --with-sync +Running pre-enable checks... +Success: Sync enabled. +Success: HPOS enabled. +``` + +### `wc cot disable` + +Similarly to the prior command, this can be used to disable HPOS. + +#### Example 1 - Attempt to disable HPOS (with orders pending sync) + +If there are any orders pending sync, you won't be allowed to disable HPOS until those orders have been synced (via `wp wc cot sync`). + +```plaintext +$ wp wc cot disable +Running pre-disable checks... +Error: [Failed] There are orders pending sync. Please run `wp wc cot sync` to sync pending orders. +``` + +#### Example 2 - Disable HPOS + +```plaintext +$ wp wc cot disable +Running pre-disable checks... +Success: HPOS disabled. +``` + +### `wc cot count_unmigrated` + +Prints the number of orders pending sync. + +#### Example 1 - Obtain number of orders pending sync + +```plaintext +$ wp wc cot count_unmigrated +There are 651 orders to be synced. +``` + +### `wc cot sync` + +This command can be used to migrate orders from the posts order storage to HPOS (or viceversa) based on the current settings in WC > Settings > Advanced > Features. +That is, it'll sync orders from your currently selected datastore to the other one. + +Note that enabling compatibility mode in the settings will eventually take care of migrating all orders, but this command can be used to do that more performantly. + +If you need more control over which datastore to use as source (or destination) regardless of settings, or want to migrate just a few orders or properties, use [`wp wc hpos backfill`](#wc-hpos-backfill) instead. + +#### Example 1 - Sync all orders + +```plaintext +$ wp wc cot sync +There are 999 orders to be synced. +Order Data Sync 100% [============================================================================================] 0:08 / 0:08 +Sync completed. +Success: 999 orders were synced in 14 seconds. +``` + +### `wc cot verify_cot_data` + +Use this command to check that order data in both the legacy (posts) datastore and HPOS is in sync. This is only relevant if you have "compatibility mode" enabled and orders might've been modified outside of the usual WooCommerce flows. + +This command operates on all orders. For a user friendlier alternative that operates on individual orders, refer to [`wp wc hpos diff`](#wc-hpos-diff). + +#### Example 1 - Verify data on a migrated site + +All orders are identical between datastores. + +```plaintext +$ wp wc cot verify_cot_data +Order Data Verification 100% [====================================================================================] 0:00 / 0:00 +Verification completed. +Success: 999 orders were verified in 0 seconds. +``` + +#### Example 2 - Verification failures + +An order (with ID 100126) fails verification due to differences in order total, tax, modification date and billing information. + +```plaintext +$ wp wc cot verify_cot_data +Order Data Verification 100% [====================================================================================] 0:00 / 0:00 +Verification completed. +Error: 999 orders were verified in 0 seconds. 1 error found: { + "100126": [ + { + "column": "post_modified_gmt", + "original_value": "2024-04-04 15:32:27", + "new_value": "2024-04-05 15:19:56" + }, + { + "column": "_order_tax", + "original_value": "74", + "new_value": "0" + }, + { + "column": "_order_total", + "original_value": "567.25", + "new_value": "0" + }, + { + "order_id": 100126, + "meta_key": "_billing_address_index", + "orig_meta_values": [ + "Hans Howell Moore Ltd 325 Ross Drive Wilfridhaven WA 23322 NF heidi.koch@example.net +17269674166" + ], + "new_meta_values": [ + "Hans X Howell Moore Ltd 325 Ross Drive Wilfridhaven WA 23322 NF heidi.koch@example.net +17269674166" + ] + } + ] +}. Please review the error above. +``` + +#### Example 3 - Re-migrate during verification + +The verification command also admits a `--re-migrate` flag that will attempt to sync orders that have differences. This could effectively overwrite an order in the database, so use with care. + +```plaintext +$ wp wc cot verify_cot_data --re-migrate +Order Data Verification 100% [====================================================================================] 0:00 / 0:00 +Verification completed. +Success: 999 orders were verified in 0 seconds. +``` + +### `wc hpos diff` + +If you have enabled compatibility mode or migrated orders using `wp wc cot sync`, all of your orders should be in an identical state in both datastores (the legacy one and HPOS), but errors can happen. Also, manually modifying orders in the database or use of HPOS-incompatible plugins can result in orders deviating. + +The `wp wc hpos diff` tool can be used to look into those (possible) differences, which can be useful to determine whether re-migrating to/from either datastore should be done, or a more careful approach needs to be taken. + +The tool itself doesn't reconcile the differences. For that you should use [`wp wc hpos backfill`](#wc-hpos-backfill). + +#### Example 1 - No difference between orders + +Order is the same in both datastores (legacy and HPOS). + +```plaintext +$ wp wc hpos diff 100087 +Success: No differences found. +``` + +#### Example 2 - Mismatch in order properties between datastores + +This examples shows that order `100126` differs in various fields between both datastores. For example, its HPOS version has status `completed` while the post is still in `pending` status. Similarly, there are differences in other fields and there's even some metadata (`post_only_meta`) that only exists in the post/legacy version. + +Any other order fields or metadata not listed are understood to be equal in both order versions. + +```plaintext +$ wp wc hpos diff 100126 +Warning: Differences found for order 100126: ++--------------------+---------------------------+---------------------------+ +| property | hpos | post | ++--------------------+---------------------------+---------------------------+ +| status | completed | pending | +| total | 567.25 | 267.25 | +| date_modified | 2024-04-04T15:32:27+00:00 | 2024-04-04T19:00:26+00:00 | +| billing_first_name | Hans | Jans | +| post_only_meta | | why not? | ++--------------------+---------------------------+---------------------------+ +``` + +#### Example 3 - JSON output + +You can also get the output in various formats (`json`, `csv` or `list` -the default-), which can be useful for exporting differences from various orders to a file. + +```plaintext +$ wp wc hpos diff 100126 --format=json +Warning: Differences found for order 100126: +[{"property":"status","hpos":"completed","post":"pending"},{"property":"total","hpos":"567.25","post":"267.25"},{"property":"date_modified","hpos":"2024-04-04T15:32:27+00:00","post":"2024-04-04T19:00:26+00:00"},{"property":"billing_first_name","hpos":"Hans","post":"Jans"},{"property":"post_only_meta","hpos":"","post":"why not?"}] +``` + +### `wc hpos backfill` + +The backfill command can be used to selectively migrate order data (or whole orders) from either the legacy or HPOS datastore to the other one. It's very useful to reconcile sync or migration mishaps. + +The exact syntax for this command is as follows: + +```plaintext +wp wc hpos backfill --from= --to= [--meta_keys=] [--props=] +``` + +You have to specify which datastore to use as source (either `posts` or `hpos`) and which one to use as destination. The `--meta_keys` and `--props` arguments receive a comma separated list of meta keys and order properties, which can be used to move only certain data from one datastore to the other, instead of the whole order. + +Note that `wp wc hpos backfill` differs from `wp wc cot sync` in various ways: + +- You can specify which order to operate on, which gives you more control for one-off operations. +- It lets you move order data between datastores irrespective of what the current order storage is in WC settings. In contrast, `wp wc cot sync` will only sync data from the current datastore to the other. +- In addition to letting you migrate full orders, it lets you choose which bits of data (order fields or metadata) to migrate. + +#### Example 1 - Migrate a full order from HPOS to posts + +```plaintext +$ wp wc hpos backfill 99709 +Success: Order 99709 backfilled from hpos to posts. +``` + +#### Example 2 - Migrate metadata + +Continuing with example 2 from the previous section, we can see how to migrate just one key of metadata from posts to HPOS. + +```plaintext +$ wp wc hpos backfill 100126 --from=posts --to=hpos --meta_keys=post_only_meta +Success: Order 100126 backfilled from posts to hpos. +``` + +If you now run `wp wc hpos diff` on this order, you can see that the bit of metadata is no longer listed as a difference. + +```plaintext +$ wp wc hpos diff 100126 +Warning: Differences found for order 100126: ++--------------------+---------------------------+---------------------------+ +| property | hpos | post | ++--------------------+---------------------------+---------------------------+ +| status | completed | pending | +| total | 567.25 | 267.25 | +| date_modified | 2024-04-04T15:32:27+00:00 | 2024-04-04T19:00:26+00:00 | +| billing_first_name | Hans | Jans | ++--------------------+---------------------------+---------------------------+ +``` + +#### Example 3 + +Also following the previous example, we can now reconcile all the data as we see fit. For example, we can migrate the status from posts to HPOS and the other bits of info in the other direction. +In the end, the orders will be identical, as can be confirmed through `wp wc hpos diff`. + +1. Sync order status from posts to HPOS. This means the order will become "pending" in both datastores. + + ```plaintext + $ wp wc hpos backfill 100126 --from=posts --to=hpos --props=status + Success: Order 100126 backfilled from posts to hpos. + ``` + +2. Sync the other properties from HPOS to posts. + + ```plaintext + $ wp wc hpos backfill 100126 --from=hpos --to=posts --props=total,date_modified,billing_first_name + Success: Order 100126 backfilled from hpos to posts. + ``` + +3. Are we all sync'ed yet? + + ```plaintext + $ wp wc hpos diff 100126 + Success: No differences found. + ``` + +### `wc hpos cleanup` + +The cleanup command can be used to remove order data from legacy tables when HPOS is enabled and compatibility mode is disabled. + +Given this is a destructive operation, the tool won't do anything by default. You'll have to specify an order ID, a range of order IDs or `all` to operate on all orders. + +The tool will also verify orders before removal, stopping if the post version seems more recent than the HPOS one. This allows closer inspection of those differences (for example, with `wp wc hpos diff`) and reconciling the data (with [`wp wc hpos backfill`](#wc-hpos-backfill)) before the deletion is executed. + +**Note:** This command won't remove placeholder records (posts with type `shop_order_placehold`) from the posts table. We're working on allowing this in the near future, but for now leave placeholders so that datastores can be switched if necessary. Metadata is removed, which is where most data is stored in the legacy order storage, so the remaining placeholder post is very lightweight. + +#### Example 1 - Error during cleanup + +Cleaning up of an order that seems more recent on the posts datastore is prevented by default. + +```plaintext +$ wp wc hpos cleanup 100126 +Starting cleanup for 1 order... +Warning: An error occurred while cleaning up order 100126: Data in posts table appears to be more recent than in HPOS tables. +``` + +You can investigate the differences with `wp wc hpos diff`: + +```plaintext +$ wp wc hpos diff 100126 +Warning: Differences found for order 100126: ++---------------+---------------------------+---------------------------+ +| property | hpos | post | ++---------------+---------------------------+---------------------------+ +| date_modified | 2024-04-05T15:19:56+00:00 | 2024-04-05T16:39:26+00:00 | ++---------------+---------------------------+---------------------------+ +``` + +If reconciling is not necessary, the `--force` flag can be used to skip the verification checks: + +```plaintext +$ wp wc hpos cleanup 100126 --force +Starting cleanup for 1 order... +HPOS cleanup 100% [=====================================================================================================================] 0:00 / 0:00 +Success: Cleanup completed for 1 order. +``` + +#### Example 2 - Cleaning up a range of order IDs + +```plaintext +$ wp wc hpos cleanup 90000-100000 +Starting cleanup for 865 orders... +HPOS cleanup 100% [=====================================================================================================================] 0:01 / 0:12 +Success: Cleanup completed for 865 orders. +``` + +#### Example 3 -Cleaning up all orders + +```plaintext +$ wp wc hpos cleanup all +Starting cleanup for 999 orders... +HPOS cleanup 100% [=====================================================================================================================] 0:01 / 0:05 +Success: Cleanup completed for 999 orders. +``` + diff --git a/docs/high-performance-order-storage/recipe-book.md b/docs/high-performance-order-storage/recipe-book.md new file mode 100644 index 00000000000..51322142d22 --- /dev/null +++ b/docs/high-performance-order-storage/recipe-book.md @@ -0,0 +1,182 @@ +--- +post_title: HPOS extension recipe book +tags: how-to +--- +## What is High-Performance Order Storage (HPOS)? + +Up until recently, WooCommerce stored order-related data in the post and postmeta tables in the database as a custom WordPress post type, which allowed everyone working in the ecosystem to take advantage of extensive APIs provided by the WordPress core in managing orders as custom post types. + +However, in early 2022, [we announced the plans to migrate to dedicated tables for orders](https://developer.woocommerce.com/2022/01/17/the-plan-for-the-woocommerce-custom-order-table/). Orders in their own tables will allow the shops to scale more easily, make the data storage simpler and increase reliability. For further details, please check out our [deep dive on the database structure on our dev blog](https://developer.woocommerce.com/2022/09/15/high-performance-order-storage-database-schema/). + +Generally, WooCommerce has tried to be fully backward compatible with the older versions, but, as a result of this project, extension developers will be required to make some changes to their plugins to take advantage of the HPOS. This is because the underlying data structure has changed fundamentally. + +More specifically, instead of using WordPress-provided APIs to access order data, developers will need to use WooCommerce-specific APIs. We [introduced these APIs in WooCommerce version 3.0](https://developer.woocommerce.com/2017/04/04/say-hello-to-woocommerce-3-0-bionic-butterfly/) to make the transition to HPOS easier. + +In this guide, we will focus on the changes required to make an extension, or any snippet of custom code, compatible with HPOS. + +For details on how to take enable or disable HPOS, as well as details on how orders are synced between datastores please refer to the [HPOS documentation](https://woocommerce.com/document/high-performance-order-storage/). + +## Backward compatibility + +To make the transition easier for shops and developers alike, we have tried to be as backward compatible as possible. One of the major compatibility issues with this project is that since the underlying data structure was `wp_posts` and `wp_postmeta` tables, circumventing the WC-specific CRUD classes and accessing the data directly using WordPress APIs worked fine. + +Now that this has changed, directly reading from these WordPress tables may mean reading an outdated order, and directly writing to these tables may mean updating an order that will not be read. Since this is a pretty significant issue, we have added a few mitigations for the transitory period. + +### Switching data source + +Additionally, you can switch from using HPOS to posts tables manually if you see an issue with the new tables. To switch back, change the "Order data storage" setting in the WC > Settings > Advanced > Features. + +## Supporting High-Performance Order Storage in your extension + +While the backward compatibility policies make it easier for merchants to use the project, for extension developers this means that you have to support both Posts and HPOS for a while. + +To help with this, we have provided a few guidelines for extension developers to follow. + +**Note:** We recommend you use the development version of WooCommerce while working on your extension, in order to get all of the latest HPOS fixes and APIs. Refer to our [development guide](https://github.com/woocommerce/woocommerce/blob/trunk/DEVELOPMENT.md) to understand how the WooCommerce repo is structured and how to build the plugin from source. + +### Detecting whether HPOS tables are being used in the store + +While the WooCommerce CRUD API will let you support both posts and custom tables without additional effort most of the time, in some cases (like when you are writing a SQL query for better performance) you would want to know whether the store is using HPOS tables or not. In this case, you can use the following pattern: + +```php +use Automattic\WooCommerce\Utilities\OrderUtil; + +if ( OrderUtil::custom_orders_table_usage_is_enabled() ) { + // HPOS usage is enabled. +} else { + // Traditional CPT-based orders are in use. +} +``` + +### Auditing the code base for direct DB access usage + +To support HPOS tables, a good place to start is to audit your code base for direct DB access and usage of WordPress APIs that shouldn't be used to work with orders anymore. You can search using the following regexp to perform this audit: + +```regexp +wpdb|get_post|get_post_field|get_post_status|get_post_type|get_post_type_object|get_posts|metadata_exists|get_post_meta|get_metadata|get_metadata_raw|get_metadata_default|get_metadata_by_mid|wp_insert_post|add_metadata|add_post_meta|wp_update_post|update_post_meta|update_metadata|update_metadata_by_mid|delete_metadata|delete_post_meta|delete_metadata_by_mid|delete_post_meta_by_key|wp_delete_post|wp_trash_post|wp_untrash_post|wp_transition_post_status|clean_post_cache|update_post_caches|update_postmeta_cache|post_exists|wp_count_post|shop_order +``` + +Please note that you will find lots of false positives, but this regular expression is quite thorough and should catch most of the true positives. + +Search for the above regular expression in your source code, and: + +1. Go through the matches one by one and check whether the occurrence relates to an order. Most of the matches will probably be false positives i.e. they won't be related to orders. +2. If you see one of the matches are directly accessing or modifying order data, you will need to change it to use WooCommerce's CRUD API instead. + +### APIs for getting/setting posts and postmeta + +Any code getting posts directly can be converted to a `wc_get_order` call instead: + +```php +// Instead of +$post = get_post( $post_id ); // returns WP_Post object. +// use +$order = wc_get_order( $post_id ); // returns WC_Order object. +``` + +For interacting with metadata, use the `update_`/`add_`/`delete_metadata` methods on the order object, followed by a `save` call. WooCommerce will take care of figuring out which tables are active, and saving data in appropriate locations. + +```php +// Instead of following update/add/delete methods, use: +update_post_meta( $post_id, $meta_key_1, $meta_value_1 ); +add_post_meta( $post_id, $meta_key_2, $meta_value_2 ); +delete_post_meta( $post_id, $meta_key_3, $meta_value_3 ); + +// use +$order = wc_get_order( $post_id ); +$order->update_meta_data( $meta_key_1, $meta_value_1 ); +$order->add_meta_data( $meta_key_2, $meta_value_2 ); +$order->delete_meta_data( $meta_key_3, $meta_value_3 ); +$order->save(); +``` + +💡 Calling the `save()` method is a relatively expensive operation, so you may wish to avoid calling it more times than necessary (for example, if you know it will be called later in the same flow, you may wish to avoid additional earlier calls when operating on the same object). + +When getting exact type of an order, or checking if given ID is an order, you can use methods from `OrderUtil` class. + +```php +// Pattern to check when an ID is an order +'shop_order' === get_post_type( $post_id ); // or +in_array( get_post_type( $post_type ), wc_get_order_types() ); + +// replace with: +use Automattic\WooCommerce\Utilities\OrderUtil; +'shop_order' === OrderUtil::get_order_type( $post_id ); // or +OrderUtil::is_order( $post_id, wc_get_order_types() ); +``` + +### Audit for order administration screen functions + +As WC can't use the WordPress-provided post list and post edit screens, we have also added new screens for order administration. These screens are very similar to the one you see in the WooCommerce admin currently (except for the fact that they are using HPOS tables). You can use the following regular expression to perform this audit: + +```regexp +post_updated_messages|do_meta_boxes|enter_title_here|edit_form_before_permalink|edit_form_after_title|edit_form_after_editor|submitpage_box|submitpost_box|edit_form_advanced|dbx_post_sidebar|manage_shop_order_posts_columns|manage_shop_order_posts_custom_column +``` + +You will see a lot of false positives here as well. However, if you do encounter a usage where these methods are called for the order screen then to upgrade them to HPOS, the following changes have to be done: + +Instead of a `$post` object of the `WP_Post` class, you will need to use an `$order` object of the `WC_Order` class. If it’s a filter or an action, then we will implement a similar filter in the new WooCommerce screen as well and instead of passing the post object, it will accept a WC_Order object instead. + +The following snippet shows a way to add meta boxes to the legacy order editor screen when legacy orders are in effect, and to the new HPOS-powered editor screen otherwise: + +```php +use Automattic\WooCommerce\Internal\DataStores\Orders\CustomOrdersTableController; + +add_action( 'add_meta_boxes', 'add_xyz_metabox' ); + +function add_xyz_metabox() { + $screen = class_exists( '\Automattic\WooCommerce\Internal\DataStores\Orders\CustomOrdersTableController' ) && wc_get_container()->get( CustomOrdersTableController::class )->custom_orders_table_usage_is_enabled() + ? wc_get_page_screen_id( 'shop-order' ) + : 'shop_order'; + + add_meta_box( + 'xyz', + 'Custom Meta Box', + 'render_xyz_metabox', + $screen, + 'side', + 'high' + ); +} +``` + +The above will also change the parameter passed to the metabox to order. So in your metaboxes, you would have to account for both a post or order object that may be passed. We recommend fetching the order object and working with it completely instead of the passed parameter. + +```php +function render_xyz_metabox( $post_or_order_object ) { + $order = ( $post_or_order_object instanceof WP_Post ) ? wc_get_order( $post_or_order_object->ID ) : $post_or_order_object; + + // ... rest of the code. $post_or_order_object should not be used directly below this point. +} +``` + +### Declaring extension (in)compatibility + +Once you examined the extension's code, you can declare whether it's compatible with HPOS or not. We've prepared an API to make this easy. To **declare your extension compatible**, place the following code into your **main plugin file**: + +```php +add_action( 'before_woocommerce_init', function() { + if ( class_exists( \Automattic\WooCommerce\Utilities\FeaturesUtil::class ) ) { + \Automattic\WooCommerce\Utilities\FeaturesUtil::declare_compatibility( 'custom_order_tables', __FILE__, true ); + } +} ); +``` + +If you know your code doesn't support HPOS, you should declare **incompatibility** in the following way. Place the following code into your **main plugin file**: + +```php +add_action( 'before_woocommerce_init', function() { + if ( class_exists( \Automattic\WooCommerce\Utilities\FeaturesUtil::class ) ) { + \Automattic\WooCommerce\Utilities\FeaturesUtil::declare_compatibility( 'custom_order_tables', __FILE__, false ); + } +} ); +``` + +If you prefer to include the compatibility declaration outside of your main plugin file, please pass 'my-plugin-slug/my-plugin.php' instead of the `__FILE__` parameter in the snippets above. + +To prevent problems, WooCommerce will warn users if they try to enable HPOS while any of the incompatible plugins are active. It will also display a warning in the Plugins screen to make sure people would know if extension is incompatible. +As many WordPress extensions aren't WooCommerce related, WC will only display this information for extensions that declare `WC tested up to` in the header of the main plugin file. + +### New order querying APIs + +HPOS, through `WC_Order_Query`, introduces new query types that allow for more complex order queries involving dates, metadata and order fields. Head over to [HPOS: new order querying APIs](https://github.com/woocommerce/woocommerce/wiki/HPOS:-new-order-querying-APIs) for details and examples. diff --git a/docs/high-performance-order-storage/wc-order-query-improvements.md b/docs/high-performance-order-storage/wc-order-query-improvements.md new file mode 100644 index 00000000000..b07a97e664f --- /dev/null +++ b/docs/high-performance-order-storage/wc-order-query-improvements.md @@ -0,0 +1,181 @@ +--- +post_title: HPOS order querying APIs +tags: reference +--- + +With the introduction of HPOS, we’ve enhanced the querying functionality in WC. Now, in addition to the well-known [existing APIs](https://github.com/woocommerce/woocommerce/wiki/wc_get_orders-and-WC_Order_Query), we’re adding a few features that would make it easier to create complex queries on orders or their properties, including the ability to query custom order metadata. + +All the new query types are implemented as additional query arguments that can be passed to `wc_get_orders()` and are heavily inspired by similar functionality in WordPress’ own `WP_Query`. As regular query arguments, they can be combined with other arguments to produce complex queries that, until now, would have required writing custom code and SQL. + +## The new query types + +### Metadata queries (`meta_query`) + +With the introduction of HPOS, order fields that were previously stored as post metadata have been moved to their own tables, but the remaining metadata (custom, or from other extensions) can now be queried through the `meta_query `argument. + +At its core, `meta_query` is an array that can contain one or more arrays with keys: +`key` (the meta key name), +`value` (the meta value) +`compare` (optional) an operator to use for comparison purposes such as LIKE, RLIKE, NOT BETWEEN, BETWEEN, etc. +`type` to cast the meta value to a specific SQL type in the query + +The different arrays can also be combined using `relation` (which admits 'AND' or 'OR' values) to produce more complex queries. The syntax for this new argument is exactly the same as for WP_Query’s `meta_query`. As such, you can refer to the [`meta_query` docs](https://developer.wordpress.org/reference/classes/wp_query/#custom-field-post-meta-parameters) for more details. + +```php +// Example: obtain all orders which have metadata with the "color" key (any value) and have metadata +// with key "size" containing "small" (so it’d match "extra-small" as well as "small", for example). +$orders = wc_get_orders( + array( + 'meta_query' => array( + array( + 'key' => 'color', + ), + array( + 'key' => 'size', + 'value' => 'small', + 'compare' => 'LIKE' + ), + ), + ) +); +``` + +### Order field queries (`field_query`) + +This query type has a syntax similar to that of meta queries (`meta_query`) but instead of `key` you’d use `field` inside the different clauses. Here, `field` refers to any order property (such as `billing_first_name`, `total` or `order_key`, etc.) which are also accessible as top-level keys in the query arguments as usual. The difference between directly querying those properties and using a `field_query` is that you can create more complex queries by implementing comparison operators and combining or nesting. + +```php +// Example. For a simple query, you’d be better off by using the order properties directly, even though there's a `field_query` equivalent. +$orders = wc_get_orders( + array( + 'billing_first_name' => 'Lauren', + 'order_key' => 'my_order_key', + ) +); + +$orders = wc_get_orders( + array( + 'field_query' => array( + array( + 'field' => 'billing_first_name', + 'value' => 'Lauren' + ), + array( + 'field' => 'order_key', + 'value' => 'my_order_key', + ) + ) + ) +); +``` + +The true power of `field_query` is revealed when you want to perform more interesting queries, that were not possible before... + +```php +// Example. Obtain all orders where either the total or shipping total is less than 5.0. + +$orders = wc_get_orders( + array( + 'field_query' => array( + 'relation' => 'OR', + array( + 'field' => 'total', + 'value' => '5.0', + 'compare' => '<', + ), + array( + 'field' => 'shipping_total', + 'value' => '5.0', + 'compare' => '<', + ), + ) + ) +); +``` + +### Date queries (`date_query`) + +Date queries allow you to fetch orders by querying their associated dates (`date_completed`, `date_created`, `date_updated`, `date_paid`) in various ways. +The syntax for `date_query` is fully compatible with that of `date_query` in `WP_Query`. As such, a good source of examples and details is [the `meta_query` docs](https://developer.wordpress.org/reference/classes/wp_query/#date-parameters) in the WP codex. + +```php +// Example. Obtain all orders paid in the last month that were created before noon (on any date). + +$orders = wc_get_orders( + array( + 'date_query' => array( + 'relation' => 'AND', + array( + 'column' => 'date_created_gmt', + 'hour' => 12, + 'compare' => '<' + ), + array( + 'column' => 'date_paid_gmt', + 'after' => '1 month ago', + ), + ), + ) +); +``` + +## Advanced Examples + +```php +// Obtain orders either "on-hold" or "pending" that have metadata `weight` >= 50 and metadata `color` or `size` is set. + +$query_args = array( + 'status' => array( 'pending', 'on-hold' ), + 'meta_query' => array( + array( + 'key' => 'weight', + 'value' => '50', + 'compare' => '>=', + ), + array( + 'relation' => 'OR', + array( + 'key' => 'color', + 'compare' => 'EXISTS', + ), + array( + 'key' => 'size', + 'compare' => 'EXISTS', + ) + ), + ) +); + +$orders = wc_get_orders( $query_args ); +``` + +```php +// Obtain orders where the first name in the billing details contains "laur" (so it’d both match "lauren" and "laura", for example), and where the order’s total is less than 10.0 and the total discount is >= 5.0. + +$orders = wc_get_orders( + array( + 'field_query' => array( + array( + 'field' => 'billing_first_name', + 'value' => 'laur', + 'compare' => 'LIKE', + ), + array( + 'relation' => 'AND', + array( + 'field' => 'total', + 'value' => '10.0', + 'compare' => '<', + 'type' => 'NUMERIC', + ), + array( + 'field' => 'discount_total', + 'value' => '5.0', + 'compare' => '>=', + 'type' => 'NUMERIC', + ) + ) + ), + ) +); +```