# CLAUDE.md This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository. ## Project Overview This is a WordPress plugin that extends WooCommerce functionality to manage print orders sent to third-party providers. It adds custom order statuses ("Prepare to Printing" and "In Printing"), bulk actions, CSV export/import capabilities, and tracking fields specific to the printing workflow. **Plugin Details:** - Text Domain: `studiou-wc-ord-print-statuses` - Current Version: 1.5.1 - Requires: WordPress 5.8+, WooCommerce 3.0+, PHP 7.2+ - Tested with: WordPress 6.9.4, WooCommerce 10.7.0 (HPOS enabled) ## Documentation User-facing and historical documentation lives at the project root and under `docs/`. Consult these alongside this file before making changes: - [`README.md`](README.md) — Primary, up-to-date plugin documentation (features, install, CSV formats, architecture, changelog). - [`docs/readme.md`](docs/readme.md) — Previous user-facing readme; retained for history. - [`docs/instructions.txt`](docs/instructions.txt) — Original feature specification used to scaffold the plugin. Useful when verifying intended behaviour against current implementation. - [`docs/translations.txt`](docs/translations.txt) — EN → CS translation reference for the bundled `cs_CZ` locale. - [`docs/revise-1.3.2.md`](docs/revise-1.3.2.md) — Analytical review of v1.3.2. Every finding listed there is resolved in 1.4.0 (see README changelog). - [`docs/revise-1.4.0.md`](docs/revise-1.4.0.md) — Analytical review of v1.4.0. All in-scope findings are resolved in 1.4.1; the doc carries per-item status annotations. - [`docs/revise-1.4.1.md`](docs/revise-1.4.1.md) — Fresh-eye analytical review of v1.4.1. All in-scope findings are resolved in 1.4.2; per-item status annotations inside. - [`docs/revise-1.4.2.md`](docs/revise-1.4.2.md) — Fresh-eye analytical review of v1.4.2. All in-scope findings are resolved in 1.4.3; per-item status annotations inside. - [`docs/revise-1.4.3.md`](docs/revise-1.4.3.md) — Fresh-eye analytical review of v1.4.3. All in-scope findings are resolved in 1.4.4; per-item status annotations inside. - [`docs/revise-1.4.4.md`](docs/revise-1.4.4.md) — Fresh-eye analytical review of v1.4.4. Mediums and lows are addressed in 1.4.5; per-item status annotations inside. - [`docs/order-done-plan-01.md`](docs/order-done-plan-01.md) — Plan for auto "Done Print" on the HPOS bulk "Change status to Completed" action (implemented in 1.5.1). Supersedes [`docs/order-done-plan-00.md`](docs/order-done-plan-00.md), kept for history. When adding new documentation files, place them in `docs/` and add a one-line pointer here. ## Recent Changes ### Version 1.5.1 (2026-06-15) **New feature:** auto "Done Print" on the HPOS Orders-list **Change status to Completed** bulk action. See `README.md` §"Per-order-item print status" and `docs/order-done-plan-01.md`. - **New method:** `Order_Item_Status_Manager::bulk_complete_advance_items()`, hooked to `handle_bulk_actions-woocommerce_page_wc-orders` at **priority 9** (before WooCommerce's own priority-10 `mark_completed` handler on the same filter). For each selected non-terminal order it runs `advance_remaining_to_done()` — advancing every `pending-print`/`in-print` product item to `done-print` — *before* WC flips the status, so the completion guard passes instead of reverting. - **Scope:** fires only for the `mark_completed` action on the HPOS Orders screen. Single-order completion (order-edit dropdown), programmatic/REST/cron changes, the protocol imports, and every other bulk action are unchanged — the standard completion guard still applies. The legacy CPT `edit-shop_order` screen is intentionally not hooked. - **Terminal-skip:** orders already in `Studiou_DB_Manager::TERMINAL_STATUSES` (`completed`/`cancelled`/`refunded`/`failed`) are not advanced — already-completed orders are left untouched (no silent item mutation), and terminal orders with unresolved items stay blocked by the guard. Residual edge (unchanged from 1.5.0): a terminal order whose items are already all resolved is still completed by WC core, since the guard never blocks on terminal status. - **Resilience:** each order is processed in a `try/catch`; a failing order is logged and skipped so the batch (and WC's own priority-10 completion pass) is not aborted. A per-order activity note records the advance; one batch success notice summarises totals; a warning notice reports any per-order failures. - **Guard fallback (belt-and-suspenders):** `on_order_status_changed()` carries a `→ completed` fallback for the same bulk entry point, gated by `is_bulk_complete_request()` (`page=wc-orders` + resolved bulk action `mark_completed`). If the priority-9 pre-advance ever fails to run before WC's status flip, the guard advances remaining items on the same `$order` instance instead of reverting — priority-independent, and closes the two-instance object-cache concern. Orders whose *previous* status (`$from`) was terminal are still reverted, preserving the terminal-skip. No new translatable strings (reuses the bulk advance note). ### Version 1.5.0 (2026-05-12) **New feature:** per-order-item print status. See `README.md` §"Per-order-item print status" and `docs/feature-order-item-status-analysis.md` for the full design rationale. - **New manager:** `Order_Item_Status_Manager` (`includes/class-order-item-status-manager.php`). Renders the per-item Print Status column on the order edit screen, persists POST changes, and runs the combined propagation + completion guard on `woocommerce_order_status_changed` at priority 5. - **Storage:** `_print_status` meta key on each `WC_Order_Item_Product`, in `{$wpdb->prefix}woocommerce_order_itemmeta`. Lazy default = `pending-print` (no migration needed). - **Coupling:** order → `wc-in-print` propagates `pending-print` items to `in-print`; order → `wc-completed` is gated by every product item being in `done-print` or `skip-print`. All other transitions leave items alone. - **`Studiou_DB_Manager::import_delivered_protocol()`** now calls `advance_remaining_to_done()` before the order-status flip so the completion guard is satisfied. Surfaces the total items advanced in the import summary. - **`Studiou_DB_Manager::get_orders_for_printing()`** adds `prod_print_status` column via `LEFT JOIN` on `woocommerce_order_itemmeta` filtered to `_print_status`, with `COALESCE(..., 'pending-print')` for the lazy default. - **CSV export** now includes the new column. Print-shop tooling that consumes the CSV needs to either consume or ignore `prod_print_status`. - **Bulk action behaviour change:** "Set Status to In Printing" now propagates items via the listener (no explicit code in Bulk_Actions_Manager). "Set Status to Prepare to Printing" remains order-only. ### Version 1.4.5 (2026-05-12) Defensive-hardening release resolving the findings in `docs/revise-1.4.4.md`. No critical or high findings; this round closes mediums and lows. - **`normalise_csv_date()` fast path** now runs `checkdate()` + hour/minute/second bounds before accepting the regex match. Bogus inputs like `"2026-13-45T25:99"` no longer slip through to be stored verbatim. - **`emit_import_summary()`** adds an "info: no actionable rows" notice when both `$updated` and `$errors` are zero. Closes the silent-import path when every CSV row had an empty `order_no` after dedup. - **`csv_escape()`** trims leading whitespace before the first-char check. `" =CMD"`-style bypasses are defused. - **`parse_csv()`** rejects CSVs with normalised header collisions (e.g., `"Order No"` and `"order_no"` both → `order_no`). Avoids silent `array_combine` overwrite. - **`bulk_set_print_status` note dispatch** lifted to an explicit `array(slug => note)` map. Future-contributor trap closed. Deferred (low-impact): main plugin instance lifecycle, `woocommerce_missing_notice` mid-request edge case, theoretical MySQL collation mismatch on the slug join (now mentioned in README "Known limitations"). ### Version 1.4.4 (2026-05-12) Bug-fix release. Notable code changes resolving findings in `docs/revise-1.4.3.md`: - **`Studiou_DB_Manager::normalise_csv_date()`** rewritten — drops `strtotime` entirely. Fast path: regex reformat for ISO-like inputs (no timezone shift). Fallback: `DateTimeImmutable($raw, wp_timezone())->setTimezone(wp_timezone())`. Eliminates the WP-UTC-default + wp_date timezone double-handling that caused a Prague-shifted 14:30 → 16:30 drift in 1.4.2/1.4.3. **Zero `strtotime` calls in `includes/` now** — the recurring bug pattern is structurally eliminated. - **HPOS table pre-check.** `get_orders_for_printing()` now bails with an admin notice when `{$wpdb->prefix}wc_orders` is missing (HPOS disabled), parallel to the existing check on `wc_order_product_lookup`. - **Bulk-action order notes.** `bulk_set_print_status` passes "Marked Prepare to Printing by bulk action." / "Marked In Printing by bulk action." as the `update_status` note. Audit trail now consistent across bulk and import paths. - **Column rendering perf.** `Custom_Columns_Manager` uses the WC_Order object directly under HPOS (no `wc_get_order` call) and memoises by ID per request under legacy CPT. Saves ~150 lookups per 50-orders page. - **Import summary suppression.** `Import_Manager::emit_import_summary()` skips the "%d orders updated successfully" notice when no rows changed. - **`call_user_func` → direct callable** in `run_import`. - **`before_woocommerce_init` callback** converted from anonymous to named (`studiou_wc_ord_print_statuses_declare_hpos_compat`) so it can be `remove_action`-ed. ### Version 1.4.3 (2026-05-12) Bug-fix release resolving the fresh-eye findings in `docs/revise-1.4.2.md`. Notable code changes: - **Symmetric terminal-status guard in `import_delivered_protocol`.** Previously absent — `cancelled`/`refunded`/`failed` orders in the delivered CSV were silently reactivated to `completed`. Now skipped with a per-row error. - **Import cap aligned with bulk-action cap.** `add_submenu_page` and the handler check both use `edit_shop_orders` (was `manage_woocommerce`) so Shop Managers can run protocol imports. - **CSV formula-injection defence.** `Studiou_DB_Manager::csv_escape()` prefixes any cell value beginning with `=`, `+`, `-`, `@`, `\t`, or `\r` with a single quote. - **`table_exists()` uses `$wpdb->esc_like($table)`.** `SHOW TABLES LIKE` interprets `_` and `%` as wildcards; conventional WP prefixes (e.g., `wp_test_`) contain `_`. Mitigated previously by post-query equality check; now mitigated correctly at the query layer. - **Datetime form values are range-validated.** `Order_Fields_Manager::datetime_local_to_mysql()` runs `checkdate()` plus hour/minute/second bounds. Direct-POST or DevTools-edited bogus values are rejected. - **`qty` normalised in PHP.** `SUM(DECIMAL(8,4))` returns `"2.0000"` — the post-processing step formats whole numbers as integers and trims trailing zeros from decimals. - **Order notes on import status changes.** Both import paths now pass an explanatory note to `update_status`. - **Per-row logging symmetry.** Import handlers now log per-row to `UtilsLog::log`, matching the bulk-action path. - **Import summary error dedup.** `emit_import_summary` shows unique error strings; the count is the total. - **`UtilsLog::render_notices` uses `esc_html`** instead of `wp_kses_post` — tighter escape, all current call sites pass plain text. - **Removed `set_order_processing_status` + its `woocommerce_payment_complete` hook.** Dead code on modern WC — WC sets the status itself before firing the action; our handler was a no-op or skip in every path. Related translation entry removed. ### Version 1.4.2 (2026-05-12) Bug-fix release addressing the fresh-eye findings in `docs/revise-1.4.1.md`. Notable code changes: - **`Custom_Columns_Manager::format_meta_date()`** — switched to `DateTimeImmutable(..., wp_timezone())` + `wp_date()`. The "To Print Date" / "In Print Date" columns no longer drift by the WP-vs-server-timezone offset. - **Export SQL slug join** — `Studiou_DB_Manager::get_orders_for_printing()` now joins `product_variations_name.slug = product_variations_attr.meta_value` (WC stores the slug in `attribute_pa_format` postmeta, not the term name). Display still uses `MIN(terms.name)` to surface the human-readable label. - **Export SQL `SUM(qty)`** — was `MIN(qty)`. A single order can have multiple lookup rows for the same (product, variation) when the customer added it twice without cart-merge; `MIN()` silently undercounted. - **`LEFT JOIN product_variations`** — simple (non-variable) products were silently excluded by the previous inner join. They now appear with blank `prod_var` / `prod_var_type`. - **Image URL via WP API** — drops `wp_posts.guid` (codex warns it's not necessarily a URL). Selects `_thumbnail_id`, resolves via `wp_get_attachment_url()`. Offloaded-media-friendly. - **`normalise_csv_date()` uses `wp_date()`** — matches `current_time('mysql')` elsewhere instead of `date()`'s server-timezone interpretation. - **`wc_order_product_lookup` pre-check** — bails with a clear admin notice if WC Analytics is disabled and the lookup table is missing. - **`SET SESSION group_concat_max_len`** uses `GREATEST(@@SESSION.group_concat_max_len, 65535)` — never lowers a higher pre-existing value. - **Server-side `.csv` extension check** on protocol uploads (defense-in-depth). - **`Order_Status_Manager::add_custom_order_statuses_to_list`** falls back to appending the custom statuses at the end of the dropdown if `wc-processing` is absent. - **`parse_csv()`** drops `@fopen` suppression; opens explicitly and logs on failure. - **Doc:** README adds a "Known limitations" section and clarifies what `order_no` means in CSV protocols. CLAUDE.md description of the `attribute_pa_format` join corrected (slug, not name — see below). ### Version 1.4.1 (2026-05-12) Bug-fix release resolving findings from `docs/revise-1.4.0.md`. Key code changes: - **SQL strict-mode safety** in `Studiou_DB_Manager::get_orders_for_printing()` — non-aggregated SELECT columns wrapped in `MIN()`; `SET SESSION group_concat_max_len = 65535` before the query. - **CSV BOM handling** in `parse_csv()` — strips a leading UTF-8 BOM (which our own export writes); `normalise_header()` also defensively strips one. - **Datetime round-trip** in `Order_Fields_Manager` — switched to pure string reformatting (`YYYY-MM-DD HH:MM:SS` ↔ `YYYY-MM-DDTHH:MM`). No `strtotime()`/`date()` so server-vs-WP-timezone drift is eliminated. - **Capability check** added to `Order_Fields_Manager::save_custom_order_fields()`. - **Single order note** in `set_order_processing_status()` — the second `add_order_note()` call was removed. - **Bulk action UX** — `notify_moved_count()` helper suppresses notices when count is zero. - **`output_csv()` safety** — fails loudly via `wp_die()` if `headers_sent()`, logging the source file/line. - **PHP 8.4 compatibility** — `fputcsv`/`fgetcsv` now pass an explicit empty-string escape character. - **Import dispatch map** — `Import_Manager::protocol_dispatch()` centralises the protocol → handler mapping; `run_import()` uses explicit `return $this->redirect_back()` on every non-happy branch. - **Dedup case-insensitive** — `dedupe_by_order_no()` lowercases keys. - **`normalise_csv_date()`** returns `''` and logs on parse failure (was returning the raw unparseable string). - **Notice TTL** bumped 60 s → 300 s. Deferred (not addressed in 1.4.1): live HPOS search-filter verification, `Network:` header, `uninstall.php`, block-based order edit screen. ### Version 1.4.0 (2026-05-12) Comprehensive fix-up release addressing every finding in `docs/revise-1.3.2.md`: - **Search by External Order Number** is now functional. Reworked `Order_Search_Manager` uses `woocommerce_shop_order_search_fields` (legacy) and `woocommerce_order_table_search_query_meta_keys` (HPOS) — the standard "Search orders" input now matches `external_ref_ord_no`. Removed all broken hooks (`parse_query` with CPT-only guard, columns-filter-as-search-fields-filter, search input rendered inside table cells). - **HPOS metadata stamping** fixed in `Order_Status_Manager::handle_status_transitions()` — now uses `wc_get_order() + update_meta_data() + save()` instead of `update_post_meta()`. - **`set_order_processing_status()`** now treats `to-print`, `in-print`, and `completed` as protected; late `woocommerce_payment_complete` no longer demotes print-state orders. - **Bulk export atomicity:** CSV is built fully in memory before any status change. UTF-8 BOM prepended; `Content-Type: text/csv; charset=utf-8`; `nocache_headers()`. - **Export SQL** now uses `$wpdb->prepare()` with `%d` placeholders (replaces the `intval()`-only defense). `GROUP_CONCAT(DISTINCT t.name)` + `GROUP BY orders.id, product_id, variation_id` collapses multi-category products to one row per line item. - **CSV import** deduplicates by `order_no`; headers are normalised (lowercased, internal whitespace → `_`), so both spec casing (`Order No`) and the documented lowercase form (`order_no`) work; orders in terminal statuses are skipped with a per-row error. - **File-upload validation:** `$_FILES[...]['error']`, `is_uploaded_file()`, and non-empty header row are now checked server-side. - **Cap checks:** bulk-action handler guards with `current_user_can('edit_shop_orders')` before dispatch. - **UtilsLog:** `log()` guard relaxed to `defined('WP_DEBUG') && WP_DEBUG`; `message()` implemented as per-user transient → `admin_notices` hook. Bulk actions and imports now produce visible feedback. - **Order_Fields_Manager:** added `external_ref_ord_date` field to the Print Information panel; datetime values round-trip through `strtotime()` → MySQL datetime. - **Custom_Columns_Manager:** column-content callback parameter renamed to `$order_or_id` to reflect HPOS contract; passes through `wc_get_order()`. - **Bootstrap:** `STUDIOU_WC_OPS_VERSION` / `STUDIOU_WC_OPS_FILE` constants defined. Text domain loader pinned to `plugins_loaded` priority 5; plugin init at default priority 10 — guarantees translations are loaded before any manager instantiates. - **Translations:** `docs/translations.txt` extended with previously-missing strings. The `.mo` file must be rebuilt by hand (e.g. `wp i18n make-mo languages/` or Poedit); 1.4.0 ships only the source updates. - **Docs:** README architecture, install, usage, and changelog updated. Misleading "SQL prepare via intval" claim in CLAUDE.md removed. ### Version 1.3.2 (2025-10-18) **Bug Fix: Empty prod_var_type column in CSV export** Fixed an issue where the `prod_var_type` column was always empty in the "Prepare to Printing Export" CSV file. **Root Cause:** The SQL query in `Studiou_DB_Manager::get_orders_for_printing()` was incorrectly using the `wc_product_attributes_lookup` table to retrieve product variation attributes. This table stores product-level attribute data (for parent products), not the specific attribute values for individual variations. **Solution:** Modified the query (in `includes/class-db-manager.php`) to: 1. Join with `wp_postmeta` table instead of `wc_product_attributes_lookup` 2. Look for meta_key = 'attribute_pa_format' which stores the variation's format attribute value 3. Join `wp_terms.slug = postmeta.meta_value` (WC stores the slug in `attribute_pa_format`, not the term name). Display uses `MIN(wp_terms.name)` to surface the human-readable label. *(Originally written as "name" — corrected to "slug" in 1.4.2 when the live data confirmed the slug shape; see `docs/revise-1.4.1.md` §2.2.)* **HPOS Compatibility Declaration** Added explicit HPOS (High-Performance Order Storage) compatibility declaration to prevent WooCommerce warnings. **Solution:** Added `before_woocommerce_init` hook in the main plugin file to declare compatibility with WooCommerce custom order tables using `FeaturesUtil::declare_compatibility()`. **Files Changed:** - `includes/class-db-manager.php` - Updated LEFT JOIN logic for product_variations_attr and product_variations_name tables - `studiou-wc-ord-print-statuses.php` - Added HPOS compatibility declaration **Impact:** - The `prod_var_type` column in exported CSV files now correctly displays the Format attribute value (e.g., "A4", "A5") for each product variation - Plugin no longer shows HPOS incompatibility warnings in WooCommerce **Tested On:** - WordPress 6.8.3 - WooCommerce 10.2.2 (with HPOS enabled) ## Architecture ### Manager-Based Pattern The plugin uses a manager-based architecture where the main plugin class (`Studiou_WC_Ord_Print_Statuses`) initializes specialized manager classes that handle distinct concerns: 1. **Order_Status_Manager** (`includes/class-order-status-manager.php`) - Registers custom order statuses: `wc-to-print` and `wc-in-print` - Handles status transitions and automatically updates metadata timestamps - Hooks into WooCommerce payment completion to set orders to "processing" 2. **Order_Fields_Manager** (`includes/class-order-fields-manager.php`) - Adds "Print Information" section to order edit pages - Manages fields: `to_print_date`, `in_print_date`, `external_ref_ord_no`, `external_ref_ord_date`, `external_ref_ord_delivered` - Datetime fields round-trip via `strtotime()`/MySQL datetime so admin edits stay consistent with values written by imports. 3. **Bulk_Actions_Manager** (`includes/class-bulk-actions-manager.php`) - Registers bulk actions: "Prepare to Printing Export", "Set Status to Prepare to Printing", "Set Status to In Printing" - Generates CSV exports with complex product data (categories, variations, images) 4. **Custom_Columns_Manager** (`includes/class-custom-columns-manager.php`) - Adds custom columns to WooCommerce orders list: "To Print Date", "In Print Date", "External Order Number" 5. **Import_Manager** (`includes/class-import-manager.php`) - Provides admin UI under WooCommerce menu for importing CSV protocols - Handles InPrint Protocol (sets orders to "in-print" with external references) - Handles Delivered Protocol (sets orders to "completed" with delivery timestamp) 6. **Order_Search_Manager** (`includes/class-order-search-manager.php`) - Extends WooCommerce order search to include the `external_ref_ord_no` meta key. - Hooks `woocommerce_shop_order_search_fields` (legacy CPT) and `woocommerce_order_table_search_query_meta_keys` (HPOS) with the same callback. No separate UI input — the standard "Search orders" box handles it. 7. **Studiou_DB_Manager** (`includes/class-db-manager.php`) - Centralized database operations using static methods - Contains complex SQL for export query joining orders, products, variations, categories, and images - Handles CSV parsing and protocol imports 8. **Order_Item_Status_Manager** (`includes/class-order-item-status-manager.php`) - New in 1.5.0. Owns the per-line-item print status (`_print_status` meta on `WC_Order_Item_Product`). - Four status constants: `STATUS_PENDING` / `STATUS_IN` / `STATUS_DONE` / `STATUS_SKIP`. - Renders the "Print Status" column on the order edit screen items table. - Persists POST changes via `woocommerce_process_shop_order_meta`. - Combined propagation + completion guard listener on `woocommerce_order_status_changed` (priority 5, with static recursion-guard flag). - Order → `wc-in-print`: `pending-print` items advance to `in-print`. - Order → `wc-completed`: gated; blocked if any product item is still `pending-print` or `in-print`. - All other order transitions: no automatic item change. - Exposes `advance_pending_to_in()` and `advance_remaining_to_done()` for protocol imports. - `bulk_complete_advance_items()` (new in 1.5.1) — hooked to `handle_bulk_actions-woocommerce_page_wc-orders` at **priority 9**, before WooCommerce's own priority-10 `mark_completed` handler. Scoped to that one bulk action on the HPOS Orders screen: for each selected non-terminal order it pre-advances remaining items to `done-print` so the completion guard passes. Terminal/already-completed orders are skipped; each order runs in a `try/catch` so one failure doesn't abort the batch. - `is_bulk_complete_request()` (new in 1.5.1) — detects the HPOS `mark_completed` bulk-action request (mirrors `WP_List_Table::current_action()`). Used by the completion guard's belt-and-suspenders fallback: if the priority-9 pre-advance fails to win the ordering race, the guard advances remaining items on the same `$order` it just completed instead of reverting (still reverting orders whose `$from` was terminal). ### Key Database Operations The export query in `Studiou_DB_Manager::get_orders_for_printing()` performs a complex join across: - `wp_wc_orders` (order data) - `wp_wc_order_product_lookup` (order products — depends on WC Analytics being enabled; the method pre-checks this table) - `wp_posts` (products and variations — variations via `LEFT JOIN` so simple products are included) - `wp_postmeta` (product variation attributes, specifically `attribute_pa_format` whose value is the term **slug**) - `wp_terms` and `wp_term_taxonomy` (product categories and variation attribute values) - `wp_postmeta` (product `_thumbnail_id` — image URL resolved in PHP via `wp_get_attachment_url()`, not from `wp_posts.guid`) The query extracts: order number, product category, product name, variation name, variation type, image URL, **summed** quantity (per `(order, product, variation)` group), and customer email. Non-aggregated columns are wrapped in `MIN()` (or `SUM()` for qty) to remain compliant with `ONLY_FULL_GROUP_BY`. `group_concat_max_len` is raised via `SET SESSION ... = GREATEST(@@SESSION..., 65535)` before the query. ### Order Metadata Keys - `to_print_date` - Timestamp when order moved to "Prepare to Printing" status - `in_print_date` - Timestamp when order moved to "In Printing" status - `external_ref_ord_no` - External order number from print provider - `external_ref_ord_date` - Date from external print provider - `external_ref_ord_delivered` - Delivery timestamp ## Development Commands ### Logging & user notices The plugin includes a logging + notice utility (`includes/utils-log.php`): - `UtilsLog::log($message)` - Writes to the WordPress error log when `WP_DEBUG` is truthy (guarded as `defined('WP_DEBUG') && WP_DEBUG`). Check `wp-content/debug.log` when `WP_DEBUG_LOG` is on. - `UtilsLog::message($message, $type)` - Queues a dismissible WordPress admin notice (`info` / `success` / `warning` / `error`) for the current user via a 60-second transient + `admin_notices` hook. Survives the redirect that follows bulk actions and imports. ### WordPress/WooCommerce Hooks All managers register their hooks in constructors. Common patterns: - `add_action('init', ...)` - Register post statuses - `add_filter('wc_order_statuses', ...)` - Modify WooCommerce status lists - `add_filter('bulk_actions-woocommerce_page_wc-orders', ...)` - Add bulk actions - `add_action('woocommerce_admin_order_data_after_order_details', ...)` - Display custom fields ### Testing When testing status changes: 1. Enable `WP_DEBUG` in `wp-config.php` to see log output 2. Check that metadata is properly set when statuses change 3. Test bulk actions with multiple orders to verify CSV generation 4. Test import functionality with properly formatted CSV files ### CSV Formats **Export Format (Prepare to Printing):** - Headers: `order_no`, `prod_cat`, `prod_name`, `prod_var`, `prod_var_type`, `prod_img_url`, `qty`, `email` **InPrint Protocol Import Format:** - Required headers: `order_no`, `externalorder`, `externalorderdate` **Delivered Protocol Import Format:** - Required header: `order_no` ## Localization The plugin supports internationalization: - Text domain: `studiou-wc-ord-print-statuses` - Translation files in `languages/` directory - Czech localization (cs_CZ) already implemented - Use WordPress `__()`, `_e()`, `_n_noop()`, `_x()` functions for all user-facing strings ## Important Considerations 1. **HPOS Compatibility**: The plugin is fully compatible with WooCommerce High-Performance Order Storage (HPOS) - Uses `woocommerce_page_wc-orders` hooks for HPOS compatibility - Uses `{$wpdb->prefix}wc_orders` table (HPOS custom table) in SQL queries - Uses `wc_get_order()` which works with both HPOS and legacy systems - Declares compatibility via `FeaturesUtil::declare_compatibility('custom_order_tables', ...)` 2. **Status Slug Naming**: Custom statuses use `wc-` prefix internally but display without prefix (`wc-to-print` vs "Prepare to Printing") 3. **CSV Output**: The `prepare_to_printing_export` bulk action terminates with `exit` after outputting CSV headers, preventing further PHP execution 4. **Database Queries**: The export query in `Studiou_DB_Manager::get_orders_for_printing()` uses `$wpdb->prepare()` with `%d` placeholders for the order ID list. When adding new SQL, prefer `$wpdb->prepare()` over inline coercion. Order IDs are also pre-filtered with `array_map('intval', …)` as defense in depth. 5. **Admin Menu**: Import functionality is added as a submenu under WooCommerce admin menu, requires `manage_woocommerce` capability