CLAUDE.md 18 KB

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.4.2
  • 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 — 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.

Recent Changes

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 joinStudiou_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:SSYYYY-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 UXnotify_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 compatibilityfputcsv/fgetcsv now pass an explicit empty-string escape character.
  • Import dispatch mapImport_Manager::protocol_dispatch() centralises the protocol → handler mapping; run_import() uses explicit return $this->redirect_back() on every non-happy branch.
  • Dedup case-insensitivededupe_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

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