Skip to content

Add object forms for WooCommerce order edit screens - #35

Merged
ahegyes merged 9 commits into
trunkfrom
feat/order-meta-box
Oct 20, 2026
Merged

ahegyes merged 9 commits into
trunkfrom
feat/order-meta-box

Conversation

@ahegyes

@ahegyes ahegyes commented Oct 20, 2026 •

Copy link
Copy Markdown
Owner

Summary

Object forms could show on post, profile and term screens, but not where WooCommerce edits orders. WooCommerce draws the order screen itself when orders live in its order tables and through post.php when they live in the posts table, with different screen ids and objects, and orders in its tables have no metadata API type to store into.

  • OrderMetaBox hosts an ObjectForm in a meta box on the edit screens of the given order types (shop_order by default), in both storages, with the same stacked rows as PostMetaBox. It matches the screen ids wc_get_page_screen_id() gives for its types, so it also follows custom order types and the screen of a user without WooCommerce's menu.
  • The form saves on woocommerce_process_shop_order_meta at 35, which WooCommerce fires only from its order edit form after its own nonce check, and before it saves the order's data at 40 and its actions at 50, so a status change and the emails it sends see the form's values.
  • OrderMetaStore keeps the values as order meta through WooCommerce's order CRUD: it reads only the keys the order has, with the edit context, and writes one save's values in one order save, so WooCommerce's order hooks and caches see them.
  • The object capability is edit_post, which WooCommerce maps to the order type's capabilities in both storages.
  • WooCommerce never refreshes its order nonces, so an order screen left open past the nonce lifetime saves neither WooCommerce's boxes nor the form; the box adds no refresh of its own.

AGENTS.md records the file map, and that an object form field's validate also does not run for order CRUD writes or a Custom Fields box on order screens.

Affected packages

  • wp-framework-bootstrap
  • wp-framework-shared
  • wp-framework-core
  • wp-framework-settings
  • wp-framework-woocommerce

Checklist

  • Tests added or updated:
    • Integration: tests/Integration/WooCommerce/OrderMetaBoxTest.php (24 rows) drives WooCommerce's order edit and new-order screens with orders in its order tables, and post.php with orders in the posts table, each request in its own PHP process, with forms posted back as a browser serializes them; OrderMetaStoreTest.php (9) runs in both storages.
  • composer quality-check passes locally (Unit 204, Integration 970), plus composer test:integration:multisite (29) and PHP 7.4 php -l over packages/bootstrap and the woocommerce uninstall files
  • Public API changes are documented in the affected package's README.md (the package READMEs stay pointer-form; AGENTS.md carries the file map)
  • Breaking changes are explicitly justified below (none: every class is new)

Object forms on order screens need a store over WooCommerce's order CRUD, because orders in WooCommerce's order tables have no metadata API type. It must read only the keys an order has, so a stored empty string or empty list never reads as missing, and write a form's values in one order save, so WooCommerce's order hooks and caches see them.

OrderMetaStoreTest runs each row with orders in WooCommerce's order tables and in the posts table:
- only the keys the order has come back, with a stored empty string and empty list among them;
- text keeps its backslashes;
- a stored empty string and empty list read back as stored;
- one write saves the order once, and woocommerce_update_order sees the new values.

OrderFixturesTrait moves the test process to the posts table with a pre_option filter, because update_option() refuses the switch while orders are out of sync.

Nothing was outdated, so this branch has no package update: composer outdated --direct and npm outdated listed nothing, composer update and npm update left both locks unchanged, and the configs workflows already pin configs trunk 6302f1f.

Assisted-by: Claude Code:claude-opus-5-5
Orders in WooCommerce's order tables have no metadata API type, so an object form on an order screen had no store for its values.

OrderMetaStore reads and writes through WooCommerce's order CRUD, so it works wherever WooCommerce stores orders:
- it reads a key only when the order has it, with the edit context, so a stored empty value is returned and WooCommerce's view filters do not change what the form shows;
- it writes every value of one save to the order and saves the order once, so WooCommerce's order hooks and caches see the change.

The test helper now reindexes the meta values it reads, because WooCommerce keeps their array positions, and a row pins the edit-context read.

Assisted-by: Claude Code:claude-opus-5-5
Object forms could show on post, profile and term screens, but not on the screens that edit WooCommerce orders. WooCommerce draws those through its own page when orders live in its order tables, and through post.php when they live in the posts table, and passes add_meta_boxes the screen id and a WC_Order on the first.

OrderMetaBoxTest drives WooCommerce's order edit screen, admin.php?page=wc-orders, with orders in the order tables:
- a box serves one form, is added on add_meta_boxes and saves at 35 on woocommerce_process_shop_order_meta, before WooCommerce's order data save at 40;
- the definition is evaluated once on the edit screen and never on the dashboard;
- the box shows, titled and with its nonce, only for orders of its types, and in its context;
- a shop manager without the form's capability gets no box;
- a single control is labelled by its row and a group by the row's visible legend;
- controls carry the stored value, a stored empty value or the default;
- per-object fields receive the order id, and a contributed field renders.

The request fixture gains the order host and a switch that keeps one request's orders in the posts table.

Assisted-by: Claude Code:claude-opus-5-5
An object form had no host on WooCommerce's order screens, whose ids and objects depend on where WooCommerce stores orders.

OrderMetaBox takes the order types whose edit screens show it, a context and a priority:
- on add_meta_boxes it adds the box when the screen id is one that wc_get_page_screen_id() gives for its types and the form shows for the order, reading the order id from the WC_Order or WP_Post WooCommerce passes;
- the object capability is edit_post, which WooCommerce maps to the order type's capabilities in both storages;
- the default store is OrderMetaStore;
- rows stack as PostMetaBox's do, with a group named by a visible legend.

The hook row now covers add_meta_boxes only; the save hook gets its row with saving.

Assisted-by: Claude Code:claude-opus-5-5
The order meta box rendered the form but nothing stored it when WooCommerce saved the order.

OrderMetaBoxTest adds rows through WooCommerce's order edit and new-order screens:
- the box saves at 35 on woocommerce_process_shop_order_meta;
- Update stores accepted values as the order's meta: text unslashed, an unchecked checkbox as no, a cleared list as an empty list, and an unposted field keeps its value;
- a worded and a silent rejection keep the value and show once, as text, on the edit screen WooCommerce redirects to, and not after;
- a status change in the same save sees the stored value, because WooCommerce changes the status at 40;
- the new-order screen renders defaults and its first save stores them;
- a nonce minted for another order or none, a user without the form's capability, and an order saved in code store nothing.

The three guard rows pass until saving exists. The request fixture records what woocommerce_order_status_changed sees under a meta key. A spacing error from the rendering tests is fixed.

Assisted-by: Claude Code:claude-opus-5-5
The order meta box rendered the form, but nothing stored it.

OrderMetaBox now calls the form's save() on woocommerce_process_shop_order_meta at 35, which WooCommerce fires only from its order edit form, in both storages, after its own nonce check. The form stores before WooCommerce saves the order's data at 40 and its actions at 50, so a status change and the emails it sends see the form's values.

The save does not check the order type: WooCommerce fires the hook for every order type, but only the box's own screens print the form's nonce, which is bound to the order.

Assisted-by: Claude Code:claude-opus-5-5
WooCommerce edits orders stored in the posts table on post.php, passes add_meta_boxes the post type and a WP_Post, and reaches woocommerce_process_shop_order_meta through save_post. The rows so far ran only with orders in WooCommerce's order tables.

Three rows switch both the test process and the request to the posts table:
- the box shows on post.php with its nonce and the stored value;
- Update stores the accepted values;
- a rejection keeps the value and shows on the screen WordPress redirects to.

They pass on arrival, because the box already reads its screen ids from wc_get_page_screen_id() and its order id from either object. Each fails when the box drops the WP_Post order id or matches only the order-table screen id.

Assisted-by: Claude Code:claude-opus-5-5
The file map did not list the order classes, and limitation 6 named only core's Custom Fields box as a write that skips an object form field's validate.

The file map lists Orders/ with OrderMetaBox and OrderMetaStore. Limitation 6 adds order CRUD writes and WooCommerce's Custom Fields box on the order tables' edit screen, and says that core's box covers orders stored as posts, whose post type supports custom-fields.

Assisted-by: Claude Code:claude-opus-5-5
The add_meta_box() docblock said the hook passes a WC_Order or a WP_Post, but add_meta_boxes passes other objects on other screens, such as a comment, which the box ignores.

The parameter now says that the object is a WC_Order or a WP_Post on an order screen.

Assisted-by: Claude Code:claude-opus-5-5
@ahegyes ahegyes changed the title Feat/order meta box Add object forms for WooCommerce order edit screens Oct 20, 2026
@ahegyes
ahegyes merged commit 634c60a into trunk Oct 20, 2026
16 checks passed
@ahegyes
ahegyes deleted the feat/order-meta-box branch October 20, 2026 12:22
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant