This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
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:
studiou-wc-ord-print-statusesUser-facing and historical documentation lives at the project root and under docs/. Consult these alongside this file before making changes:
README.md — Primary, up-to-date plugin documentation (features, install, CSV formats, architecture, changelog).docs/readme.md — Previous user-facing readme; retained for history.docs/instructions.txt — Original feature specification used to scaffold the plugin. Useful when verifying intended behaviour against current implementation.docs/translations.txt — EN → CS translation reference for the bundled cs_CZ locale.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 — 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 — Fresh-eye analytical review of v1.4.1. All in-scope findings are resolved in 1.4.2; per-item status annotations inside.When adding new documentation files, place them in docs/ and add a one-line pointer here.
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.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.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.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..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.order_no means in CSV protocols. CLAUDE.md description of the attribute_pa_format join corrected (slug, not name — see below).Bug-fix release resolving findings from docs/revise-1.4.0.md. Key code changes:
Studiou_DB_Manager::get_orders_for_printing() — non-aggregated SELECT columns wrapped in MIN(); SET SESSION group_concat_max_len = 65535 before the query.parse_csv() — strips a leading UTF-8 BOM (which our own export writes); normalise_header() also defensively strips one.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.Order_Fields_Manager::save_custom_order_fields().set_order_processing_status() — the second add_order_note() call was removed.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.fputcsv/fgetcsv now pass an explicit empty-string escape character.Import_Manager::protocol_dispatch() centralises the protocol → handler mapping; run_import() uses explicit return $this->redirect_back() on every non-happy branch.dedupe_by_order_no() lowercases keys.normalise_csv_date() returns '' and logs on parse failure (was returning the raw unparseable string).Deferred (not addressed in 1.4.1): live HPOS search-filter verification, Network: header, uninstall.php, block-based order edit screen.
Comprehensive fix-up release addressing every finding in docs/revise-1.3.2.md:
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).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.Content-Type: text/csv; charset=utf-8; nocache_headers().$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.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.$_FILES[...]['error'], is_uploaded_file(), and non-empty header row are now checked server-side.current_user_can('edit_shop_orders') before dispatch.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.external_ref_ord_date field to the Print Information panel; datetime values round-trip through strtotime() → MySQL datetime.$order_or_id to reflect HPOS contract; passes through wc_get_order().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.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.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:
wp_postmeta table instead of wc_product_attributes_lookupwp_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 tablesstudiou-wc-ord-print-statuses.php - Added HPOS compatibility declarationImpact:
prod_var_type column in exported CSV files now correctly displays the Format attribute value (e.g., "A4", "A5") for each product variationTested On:
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:
Order_Status_Manager (includes/class-order-status-manager.php)
wc-to-print and wc-in-printOrder_Fields_Manager (includes/class-order-fields-manager.php)
to_print_date, in_print_date, external_ref_ord_no, external_ref_ord_date, external_ref_ord_deliveredstrtotime()/MySQL datetime so admin edits stay consistent with values written by imports.Bulk_Actions_Manager (includes/class-bulk-actions-manager.php)
Custom_Columns_Manager (includes/class-custom-columns-manager.php)
Import_Manager (includes/class-import-manager.php)
Order_Search_Manager (includes/class-order-search-manager.php)
external_ref_ord_no meta key.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.Studiou_DB_Manager (includes/class-db-manager.php)
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.
to_print_date - Timestamp when order moved to "Prepare to Printing" statusin_print_date - Timestamp when order moved to "In Printing" statusexternal_ref_ord_no - External order number from print providerexternal_ref_ord_date - Date from external print providerexternal_ref_ord_delivered - Delivery timestampThe 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.All managers register their hooks in constructors. Common patterns:
add_action('init', ...) - Register post statusesadd_filter('wc_order_statuses', ...) - Modify WooCommerce status listsadd_filter('bulk_actions-woocommerce_page_wc-orders', ...) - Add bulk actionsadd_action('woocommerce_admin_order_data_after_order_details', ...) - Display custom fieldsWhen testing status changes:
WP_DEBUG in wp-config.php to see log outputExport Format (Prepare to Printing):
order_no, prod_cat, prod_name, prod_var, prod_var_type, prod_img_url, qty, emailInPrint Protocol Import Format:
order_no, externalorder, externalorderdateDelivered Protocol Import Format:
order_noThe plugin supports internationalization:
studiou-wc-ord-print-statuseslanguages/ directory__(), _e(), _n_noop(), _x() functions for all user-facing stringsHPOS Compatibility: The plugin is fully compatible with WooCommerce High-Performance Order Storage (HPOS)
woocommerce_page_wc-orders hooks for HPOS compatibility{$wpdb->prefix}wc_orders table (HPOS custom table) in SQL querieswc_get_order() which works with both HPOS and legacy systemsFeaturesUtil::declare_compatibility('custom_order_tables', ...)Status Slug Naming: Custom statuses use wc- prefix internally but display without prefix (wc-to-print vs "Prepare to Printing")
CSV Output: The prepare_to_printing_export bulk action terminates with exit after outputting CSV headers, preventing further PHP execution
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.
Admin Menu: Import functionality is added as a submenu under WooCommerce admin menu, requires manage_woocommerce capability