__('Prepare To Print Date', 'studiou-wc-ord-print-statuses'), 'in_print_date' => __('In Print Date', 'studiou-wc-ord-print-statuses'), 'external_ref_ord_date' => __('External Order Date', 'studiou-wc-ord-print-statuses'), 'external_ref_ord_delivered' => __('External Order Delivered', 'studiou-wc-ord-print-statuses'), ); } /** * Meta key → translatable label for plain text fields. */ private static function text_fields() { return array( 'external_ref_ord_no' => __('External Order Number', 'studiou-wc-ord-print-statuses'), ); } public function __construct() { add_action('woocommerce_admin_order_data_after_order_details', array($this, 'display_custom_order_fields')); add_action('woocommerce_process_shop_order_meta', array($this, 'save_custom_order_fields')); } public function display_custom_order_fields($order) { ?>

$label) { woocommerce_wp_text_input(array( 'id' => $key, 'label' => $label, 'type' => 'datetime-local', 'value' => $this->mysql_to_datetime_local($order->get_meta($key)), 'wrapper_class' => 'form-field-wide', )); } foreach (self::text_fields() as $key => $label) { woocommerce_wp_text_input(array( 'id' => $key, 'label' => $label, 'value' => $order->get_meta($key), 'wrapper_class' => 'form-field-wide', )); } ?>
update_meta_data($field, $this->datetime_local_to_mysql($_POST[$field])); } } foreach (array_keys(self::text_fields()) as $field) { if (isset($_POST[$field])) { $order->update_meta_data($field, sanitize_text_field(wp_unslash($_POST[$field]))); } } $order->save(); } /** * Convert a MySQL datetime string (as written by current_time('mysql')) to * the HTML5 `datetime-local` format. Done as pure string reformatting so * the value's timezone is preserved — current_time('mysql') already uses * WP local time, and the browser interprets datetime-local as local time. * Using strtotime()/date() here would re-interpret the value in the server * timezone and drift on UTC-server / non-UTC-site configurations. */ private function mysql_to_datetime_local($value) { $value = trim((string) $value); if ($value === '') { return ''; } // Accepts 'YYYY-MM-DD HH:MM[:SS]' or 'YYYY-MM-DDTHH:MM[:SS]'. if (preg_match('/^(\d{4}-\d{2}-\d{2})[ T](\d{2}:\d{2})/', $value, $m)) { return $m[1] . 'T' . $m[2]; } return ''; } /** * Convert an HTML5 datetime-local form value to a MySQL datetime string. * No timezone conversion — the form value and the stored value are both * treated as WP local time (matching current_time('mysql')). * * Browsers prevent out-of-range values on `` * but a power user editing via DevTools or a script POSTing directly to * the order edit URL could submit `2026-13-45T25:99`. The regex matches * structurally; checkdate() + minute/hour bounds catch the semantics. */ private function datetime_local_to_mysql($value) { $value = sanitize_text_field(wp_unslash($value)); if ($value === '') { return ''; } if (!preg_match('/^(\d{4})-(\d{2})-(\d{2})T(\d{2}):(\d{2})(?::(\d{2}))?$/', $value, $m)) { return ''; } list(, $year, $month, $day, $hour, $minute) = $m; $second = isset($m[6]) ? $m[6] : '00'; if (!checkdate((int) $month, (int) $day, (int) $year)) { return ''; } if ((int) $hour > 23 || (int) $minute > 59 || (int) $second > 59) { return ''; } return $year . '-' . $month . '-' . $day . ' ' . $hour . ':' . $minute . ':' . $second; } }