|
@@ -1,249 +1,279 @@
|
|
|
<?php
|
|
<?php
|
|
|
/**
|
|
/**
|
|
|
- * Database operations manager for the Studiou WC Order Print Statuses plugin.
|
|
|
|
|
- *
|
|
|
|
|
- * This class centralizes database queries for better maintainability.
|
|
|
|
|
|
|
+ * Database operations and CSV-protocol processing for the
|
|
|
|
|
+ * Studiou WC Order Print Statuses plugin.
|
|
|
*/
|
|
*/
|
|
|
|
|
|
|
|
defined('ABSPATH') || exit;
|
|
defined('ABSPATH') || exit;
|
|
|
|
|
|
|
|
class Studiou_DB_Manager {
|
|
class Studiou_DB_Manager {
|
|
|
/**
|
|
/**
|
|
|
- * Get orders data for printing export
|
|
|
|
|
|
|
+ * Order statuses that block automated print-pipeline transitions.
|
|
|
|
|
+ * Cancelled / refunded orders should not be silently reactivated by a bulk
|
|
|
|
|
+ * action; completed orders are already finished.
|
|
|
|
|
+ */
|
|
|
|
|
+ const TERMINAL_STATUSES = array('cancelled', 'refunded', 'failed', 'completed');
|
|
|
|
|
+
|
|
|
|
|
+ /**
|
|
|
|
|
+ * Return one row per (order × line-item) for the print-shop export.
|
|
|
|
|
+ * Multiple product categories are concatenated into prod_cat instead of
|
|
|
|
|
+ * multiplying the row.
|
|
|
*
|
|
*
|
|
|
- * @param array $order_ids Array of order IDs
|
|
|
|
|
- * @return array Array of order data
|
|
|
|
|
|
|
+ * @param int[] $order_ids
|
|
|
|
|
+ * @return array
|
|
|
*/
|
|
*/
|
|
|
public static function get_orders_for_printing($order_ids) {
|
|
public static function get_orders_for_printing($order_ids) {
|
|
|
global $wpdb;
|
|
global $wpdb;
|
|
|
-
|
|
|
|
|
- $query = "
|
|
|
|
|
|
|
+
|
|
|
|
|
+ $order_ids = array_filter(array_map('intval', (array) $order_ids));
|
|
|
|
|
+ if (empty($order_ids)) {
|
|
|
|
|
+ return array();
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ $placeholders = implode(',', array_fill(0, count($order_ids), '%d'));
|
|
|
|
|
+
|
|
|
|
|
+ $sql = "
|
|
|
SELECT
|
|
SELECT
|
|
|
`orders`.`id` AS `order_no`,
|
|
`orders`.`id` AS `order_no`,
|
|
|
- `t`.`name` AS `prod_cat`,
|
|
|
|
|
|
|
+ GROUP_CONCAT(DISTINCT `t`.`name` ORDER BY `t`.`name` SEPARATOR ', ') AS `prod_cat`,
|
|
|
`products`.`post_title` AS `prod_name`,
|
|
`products`.`post_title` AS `prod_name`,
|
|
|
`product_variations`.`post_title` AS `prod_var`,
|
|
`product_variations`.`post_title` AS `prod_var`,
|
|
|
`product_variations_name`.`name` AS `prod_var_type`,
|
|
`product_variations_name`.`name` AS `prod_var_type`,
|
|
|
`images`.`guid` AS `prod_img_url`,
|
|
`images`.`guid` AS `prod_img_url`,
|
|
|
`order_products`.`product_qty` AS `qty`,
|
|
`order_products`.`product_qty` AS `qty`,
|
|
|
`orders`.`billing_email` AS `email`
|
|
`orders`.`billing_email` AS `email`
|
|
|
- FROM (
|
|
|
|
|
- (
|
|
|
|
|
- (
|
|
|
|
|
- (
|
|
|
|
|
- (
|
|
|
|
|
- (
|
|
|
|
|
- (
|
|
|
|
|
- `{$wpdb->prefix}wc_orders` `orders`
|
|
|
|
|
- JOIN `{$wpdb->prefix}wc_order_product_lookup` `order_products`
|
|
|
|
|
- ON
|
|
|
|
|
- (
|
|
|
|
|
- `orders`.`id` = `order_products`.`order_id`
|
|
|
|
|
- )
|
|
|
|
|
- )
|
|
|
|
|
- JOIN `{$wpdb->posts}` `products`
|
|
|
|
|
- ON
|
|
|
|
|
- (
|
|
|
|
|
- `products`.`ID` = `order_products`.`product_id`
|
|
|
|
|
- )
|
|
|
|
|
- )
|
|
|
|
|
- JOIN `{$wpdb->posts}` `product_variations`
|
|
|
|
|
- ON
|
|
|
|
|
- (
|
|
|
|
|
- `product_variations`.`ID` = `order_products`.`variation_id`
|
|
|
|
|
- )
|
|
|
|
|
- LEFT JOIN `{$wpdb->postmeta}` `product_variations_attr`
|
|
|
|
|
- ON
|
|
|
|
|
- (
|
|
|
|
|
- `product_variations_attr`.`post_id` = `product_variations`.`ID`
|
|
|
|
|
- AND `product_variations_attr`.`meta_key` = 'attribute_pa_format'
|
|
|
|
|
- )
|
|
|
|
|
- LEFT JOIN `{$wpdb->terms}` `product_variations_name`
|
|
|
|
|
- ON
|
|
|
|
|
- (
|
|
|
|
|
- `product_variations_name`.`name` = `product_variations_attr`.`meta_value`
|
|
|
|
|
- )
|
|
|
|
|
- )
|
|
|
|
|
- LEFT JOIN `{$wpdb->postmeta}` `product_meta`
|
|
|
|
|
- ON
|
|
|
|
|
- (
|
|
|
|
|
- `product_meta`.`post_id` = `products`.`ID` AND `product_meta`.`meta_key` = '_thumbnail_id'
|
|
|
|
|
- )
|
|
|
|
|
- )
|
|
|
|
|
- LEFT JOIN `{$wpdb->posts}` `images`
|
|
|
|
|
- ON
|
|
|
|
|
- (
|
|
|
|
|
- `images`.`ID` = `product_meta`.`meta_value` AND `images`.`post_type` = 'attachment'
|
|
|
|
|
- )
|
|
|
|
|
- JOIN `{$wpdb->term_relationships}` `tr`
|
|
|
|
|
- ON
|
|
|
|
|
- (`products`.`ID` = `tr`.`object_id`)
|
|
|
|
|
- )
|
|
|
|
|
- JOIN `{$wpdb->term_taxonomy}` `tt`
|
|
|
|
|
- ON
|
|
|
|
|
- (
|
|
|
|
|
- `tr`.`term_taxonomy_id` = `tt`.`term_taxonomy_id`
|
|
|
|
|
- )
|
|
|
|
|
- )
|
|
|
|
|
- JOIN `{$wpdb->terms}` `t`
|
|
|
|
|
- ON
|
|
|
|
|
- (`tt`.`term_id` = `t`.`term_id`)
|
|
|
|
|
- )
|
|
|
|
|
- WHERE
|
|
|
|
|
- `orders`.`id` IN (" . implode(',', array_map('intval', $order_ids)) . ")
|
|
|
|
|
|
|
+ FROM `{$wpdb->prefix}wc_orders` `orders`
|
|
|
|
|
+ JOIN `{$wpdb->prefix}wc_order_product_lookup` `order_products`
|
|
|
|
|
+ ON `orders`.`id` = `order_products`.`order_id`
|
|
|
|
|
+ JOIN `{$wpdb->posts}` `products`
|
|
|
|
|
+ ON `products`.`ID` = `order_products`.`product_id`
|
|
|
|
|
+ JOIN `{$wpdb->posts}` `product_variations`
|
|
|
|
|
+ ON `product_variations`.`ID` = `order_products`.`variation_id`
|
|
|
|
|
+ LEFT JOIN `{$wpdb->postmeta}` `product_variations_attr`
|
|
|
|
|
+ ON `product_variations_attr`.`post_id` = `product_variations`.`ID`
|
|
|
|
|
+ AND `product_variations_attr`.`meta_key` = 'attribute_pa_format'
|
|
|
|
|
+ LEFT JOIN `{$wpdb->terms}` `product_variations_name`
|
|
|
|
|
+ ON `product_variations_name`.`name` = `product_variations_attr`.`meta_value`
|
|
|
|
|
+ LEFT JOIN `{$wpdb->postmeta}` `product_meta`
|
|
|
|
|
+ ON `product_meta`.`post_id` = `products`.`ID`
|
|
|
|
|
+ AND `product_meta`.`meta_key` = '_thumbnail_id'
|
|
|
|
|
+ LEFT JOIN `{$wpdb->posts}` `images`
|
|
|
|
|
+ ON `images`.`ID` = `product_meta`.`meta_value`
|
|
|
|
|
+ AND `images`.`post_type` = 'attachment'
|
|
|
|
|
+ LEFT JOIN `{$wpdb->term_relationships}` `tr`
|
|
|
|
|
+ ON `tr`.`object_id` = `products`.`ID`
|
|
|
|
|
+ LEFT JOIN `{$wpdb->term_taxonomy}` `tt`
|
|
|
|
|
+ ON `tt`.`term_taxonomy_id` = `tr`.`term_taxonomy_id`
|
|
|
AND `tt`.`taxonomy` = 'product_cat'
|
|
AND `tt`.`taxonomy` = 'product_cat'
|
|
|
- ORDER BY
|
|
|
|
|
- `orders`.`id`
|
|
|
|
|
|
|
+ LEFT JOIN `{$wpdb->terms}` `t`
|
|
|
|
|
+ ON `t`.`term_id` = `tt`.`term_id`
|
|
|
|
|
+ WHERE `orders`.`id` IN ($placeholders)
|
|
|
|
|
+ GROUP BY
|
|
|
|
|
+ `orders`.`id`,
|
|
|
|
|
+ `order_products`.`product_id`,
|
|
|
|
|
+ `order_products`.`variation_id`
|
|
|
|
|
+ ORDER BY `orders`.`id`
|
|
|
";
|
|
";
|
|
|
-
|
|
|
|
|
- $results = $wpdb->get_results($query);
|
|
|
|
|
-
|
|
|
|
|
- // Error handling for database query failures
|
|
|
|
|
|
|
+
|
|
|
|
|
+ $prepared = $wpdb->prepare($sql, $order_ids);
|
|
|
|
|
+ $results = $wpdb->get_results($prepared);
|
|
|
|
|
+
|
|
|
if ($results === false) {
|
|
if ($results === false) {
|
|
|
- UtilsLog::log('SQL Error: ' . $wpdb->last_error);
|
|
|
|
|
|
|
+ UtilsLog::log('SQL error in get_orders_for_printing: ' . $wpdb->last_error);
|
|
|
return array();
|
|
return array();
|
|
|
}
|
|
}
|
|
|
-
|
|
|
|
|
return $results;
|
|
return $results;
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
/**
|
|
|
- * Set order status to 'to-print'
|
|
|
|
|
- *
|
|
|
|
|
- * @param array $order_ids Array of order IDs
|
|
|
|
|
- * @return int Number of orders updated
|
|
|
|
|
|
|
+ * Bulk-set orders to "to-print", skipping orders in terminal states.
|
|
|
|
|
+ *
|
|
|
|
|
+ * @param int[] $order_ids
|
|
|
|
|
+ * @return int Number of orders actually updated.
|
|
|
*/
|
|
*/
|
|
|
public static function set_orders_to_prepare_printing($order_ids) {
|
|
public static function set_orders_to_prepare_printing($order_ids) {
|
|
|
- $updated_count = 0;
|
|
|
|
|
-
|
|
|
|
|
- foreach ($order_ids as $order_id) {
|
|
|
|
|
- UtilsLog::log('Setting status Order #' . $order_id . ' to "to-print"');
|
|
|
|
|
- $order = wc_get_order($order_id);
|
|
|
|
|
- if ($order) {
|
|
|
|
|
- $order->update_status('to-print');
|
|
|
|
|
- $order->update_meta_data('to_print_date', current_time('mysql'));
|
|
|
|
|
- $order->save();
|
|
|
|
|
- $updated_count++;
|
|
|
|
|
- UtilsLog::log('Order #' . $order_id . ' marked as "to-print"');
|
|
|
|
|
- UtilsLog::message('Order #' . $order_id . ' marked as "to-print"' );
|
|
|
|
|
- } else {
|
|
|
|
|
- UtilsLog::log('Order #' . $order_id . ' not found');
|
|
|
|
|
- }
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- return $updated_count;
|
|
|
|
|
|
|
+ return self::bulk_set_print_status($order_ids, 'to-print', 'to_print_date');
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
/**
|
|
|
- * Set order status to 'in-print'
|
|
|
|
|
- *
|
|
|
|
|
- * @param array $order_ids Array of order IDs
|
|
|
|
|
- * @return int Number of orders updated
|
|
|
|
|
|
|
+ * Bulk-set orders to "in-print", skipping orders in terminal states.
|
|
|
|
|
+ *
|
|
|
|
|
+ * @param int[] $order_ids
|
|
|
|
|
+ * @return int Number of orders actually updated.
|
|
|
*/
|
|
*/
|
|
|
public static function set_orders_to_in_printing($order_ids) {
|
|
public static function set_orders_to_in_printing($order_ids) {
|
|
|
|
|
+ return self::bulk_set_print_status($order_ids, 'in-print', 'in_print_date');
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ private static function bulk_set_print_status($order_ids, $status_slug, $meta_key) {
|
|
|
$updated_count = 0;
|
|
$updated_count = 0;
|
|
|
-
|
|
|
|
|
- foreach ($order_ids as $order_id) {
|
|
|
|
|
- $order = wc_get_order($order_id);
|
|
|
|
|
- if ($order) {
|
|
|
|
|
- $order->update_status('in-print');
|
|
|
|
|
- $order->update_meta_data('in_print_date', current_time('mysql'));
|
|
|
|
|
- $order->save();
|
|
|
|
|
- $updated_count++;
|
|
|
|
|
- UtilsLog::message('Order #' . $order_id . ' marked as "in-print"' );
|
|
|
|
|
|
|
+ $skipped = 0;
|
|
|
|
|
+
|
|
|
|
|
+ foreach ((array) $order_ids as $order_id) {
|
|
|
|
|
+ $order_id = (int) $order_id;
|
|
|
|
|
+ $order = wc_get_order($order_id);
|
|
|
|
|
+ if (!$order) {
|
|
|
|
|
+ UtilsLog::log('Order #' . $order_id . ' not found');
|
|
|
|
|
+ continue;
|
|
|
|
|
+ }
|
|
|
|
|
+ if ($order->has_status(self::TERMINAL_STATUSES)) {
|
|
|
|
|
+ UtilsLog::log('Order #' . $order_id . ' skipped (terminal status: ' . $order->get_status() . ')');
|
|
|
|
|
+ $skipped++;
|
|
|
|
|
+ continue;
|
|
|
}
|
|
}
|
|
|
|
|
+ $order->update_status($status_slug);
|
|
|
|
|
+ $order->update_meta_data($meta_key, current_time('mysql'));
|
|
|
|
|
+ $order->save();
|
|
|
|
|
+ $updated_count++;
|
|
|
|
|
+ UtilsLog::log('Order #' . $order_id . ' marked as "' . $status_slug . '"');
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ if ($skipped > 0) {
|
|
|
|
|
+ UtilsLog::message(
|
|
|
|
|
+ sprintf(
|
|
|
|
|
+ _n(
|
|
|
|
|
+ '%d order skipped because its status is final (cancelled, refunded, failed, or completed).',
|
|
|
|
|
+ '%d orders skipped because their status is final (cancelled, refunded, failed, or completed).',
|
|
|
|
|
+ $skipped,
|
|
|
|
|
+ 'studiou-wc-ord-print-statuses'
|
|
|
|
|
+ ),
|
|
|
|
|
+ $skipped
|
|
|
|
|
+ ),
|
|
|
|
|
+ 'warning'
|
|
|
|
|
+ );
|
|
|
}
|
|
}
|
|
|
-
|
|
|
|
|
return $updated_count;
|
|
return $updated_count;
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
/**
|
|
|
- * Import inprint protocol data
|
|
|
|
|
- *
|
|
|
|
|
- * @param array $csv_data Parsed CSV data
|
|
|
|
|
- * @return array Array with updated count and errors
|
|
|
|
|
|
|
+ * Import an InPrint protocol CSV.
|
|
|
|
|
+ *
|
|
|
|
|
+ * @param array $csv_data Rows produced by parse_csv().
|
|
|
|
|
+ * @return array{updated_count:int,errors:string[]}
|
|
|
*/
|
|
*/
|
|
|
public static function import_inprint_protocol($csv_data) {
|
|
public static function import_inprint_protocol($csv_data) {
|
|
|
$updated_count = 0;
|
|
$updated_count = 0;
|
|
|
- $errors = array();
|
|
|
|
|
|
|
+ $errors = array();
|
|
|
|
|
|
|
|
- foreach ($csv_data as $row) {
|
|
|
|
|
|
|
+ foreach (self::dedupe_by_order_no($csv_data) as $row) {
|
|
|
if (!isset($row['order_no']) || !isset($row['externalorder']) || !isset($row['externalorderdate'])) {
|
|
if (!isset($row['order_no']) || !isset($row['externalorder']) || !isset($row['externalorderdate'])) {
|
|
|
$errors[] = __('Invalid row format in CSV.', 'studiou-wc-ord-print-statuses');
|
|
$errors[] = __('Invalid row format in CSV.', 'studiou-wc-ord-print-statuses');
|
|
|
continue;
|
|
continue;
|
|
|
}
|
|
}
|
|
|
-
|
|
|
|
|
$order = wc_get_order($row['order_no']);
|
|
$order = wc_get_order($row['order_no']);
|
|
|
if (!$order) {
|
|
if (!$order) {
|
|
|
$errors[] = sprintf(__('Order %s not found.', 'studiou-wc-ord-print-statuses'), $row['order_no']);
|
|
$errors[] = sprintf(__('Order %s not found.', 'studiou-wc-ord-print-statuses'), $row['order_no']);
|
|
|
continue;
|
|
continue;
|
|
|
}
|
|
}
|
|
|
-
|
|
|
|
|
|
|
+ if ($order->has_status(self::TERMINAL_STATUSES)) {
|
|
|
|
|
+ $errors[] = sprintf(
|
|
|
|
|
+ __('Order %s is in a final state and was not updated.', 'studiou-wc-ord-print-statuses'),
|
|
|
|
|
+ $row['order_no']
|
|
|
|
|
+ );
|
|
|
|
|
+ continue;
|
|
|
|
|
+ }
|
|
|
$order->update_status('in-print');
|
|
$order->update_status('in-print');
|
|
|
$order->update_meta_data('in_print_date', current_time('mysql'));
|
|
$order->update_meta_data('in_print_date', current_time('mysql'));
|
|
|
- $order->update_meta_data('external_ref_ord_no', $row['externalorder']);
|
|
|
|
|
- $order->update_meta_data('external_ref_ord_date', $row['externalorderdate']);
|
|
|
|
|
|
|
+ $order->update_meta_data('external_ref_ord_no', sanitize_text_field($row['externalorder']));
|
|
|
|
|
+ $order->update_meta_data('external_ref_ord_date', self::normalise_csv_date($row['externalorderdate']));
|
|
|
$order->save();
|
|
$order->save();
|
|
|
-
|
|
|
|
|
$updated_count++;
|
|
$updated_count++;
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
- return array(
|
|
|
|
|
- 'updated_count' => $updated_count,
|
|
|
|
|
- 'errors' => $errors
|
|
|
|
|
- );
|
|
|
|
|
|
|
+ return array('updated_count' => $updated_count, 'errors' => $errors);
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
/**
|
|
|
- * Import delivered protocol data
|
|
|
|
|
- *
|
|
|
|
|
- * @param array $csv_data Parsed CSV data
|
|
|
|
|
- * @return array Array with updated count and errors
|
|
|
|
|
|
|
+ * Import a Delivered protocol CSV.
|
|
|
|
|
+ *
|
|
|
|
|
+ * @param array $csv_data Rows produced by parse_csv().
|
|
|
|
|
+ * @return array{updated_count:int,errors:string[]}
|
|
|
*/
|
|
*/
|
|
|
public static function import_delivered_protocol($csv_data) {
|
|
public static function import_delivered_protocol($csv_data) {
|
|
|
$updated_count = 0;
|
|
$updated_count = 0;
|
|
|
- $errors = array();
|
|
|
|
|
|
|
+ $errors = array();
|
|
|
|
|
|
|
|
- foreach ($csv_data as $row) {
|
|
|
|
|
|
|
+ foreach (self::dedupe_by_order_no($csv_data) as $row) {
|
|
|
if (!isset($row['order_no'])) {
|
|
if (!isset($row['order_no'])) {
|
|
|
$errors[] = __('Invalid row format in CSV.', 'studiou-wc-ord-print-statuses');
|
|
$errors[] = __('Invalid row format in CSV.', 'studiou-wc-ord-print-statuses');
|
|
|
continue;
|
|
continue;
|
|
|
}
|
|
}
|
|
|
-
|
|
|
|
|
$order = wc_get_order($row['order_no']);
|
|
$order = wc_get_order($row['order_no']);
|
|
|
if (!$order) {
|
|
if (!$order) {
|
|
|
$errors[] = sprintf(__('Order %s not found.', 'studiou-wc-ord-print-statuses'), $row['order_no']);
|
|
$errors[] = sprintf(__('Order %s not found.', 'studiou-wc-ord-print-statuses'), $row['order_no']);
|
|
|
continue;
|
|
continue;
|
|
|
}
|
|
}
|
|
|
-
|
|
|
|
|
$order->update_status('completed');
|
|
$order->update_status('completed');
|
|
|
$order->update_meta_data('external_ref_ord_delivered', current_time('mysql'));
|
|
$order->update_meta_data('external_ref_ord_delivered', current_time('mysql'));
|
|
|
$order->save();
|
|
$order->save();
|
|
|
-
|
|
|
|
|
$updated_count++;
|
|
$updated_count++;
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
- return array(
|
|
|
|
|
- 'updated_count' => $updated_count,
|
|
|
|
|
- 'errors' => $errors
|
|
|
|
|
- );
|
|
|
|
|
|
|
+ return array('updated_count' => $updated_count, 'errors' => $errors);
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
/**
|
|
|
- * Parse CSV file
|
|
|
|
|
- *
|
|
|
|
|
- * @param string $file_path Path to CSV file
|
|
|
|
|
- * @return array|false Parsed CSV data or false on error
|
|
|
|
|
|
|
+ * Parse a CSV file into an array of associative rows.
|
|
|
|
|
+ * Headers are normalised (lowercased, whitespace stripped) so files prepared
|
|
|
|
|
+ * to either the legacy spec casing ("Order No") or the documented lowercase
|
|
|
|
|
+ * form ("order_no") both work.
|
|
|
|
|
+ *
|
|
|
|
|
+ * @param string $file_path
|
|
|
|
|
+ * @return array Empty array on failure.
|
|
|
*/
|
|
*/
|
|
|
public static function parse_csv($file_path) {
|
|
public static function parse_csv($file_path) {
|
|
|
$csv_data = array();
|
|
$csv_data = array();
|
|
|
- if (($handle = fopen($file_path, "r")) !== FALSE) {
|
|
|
|
|
- $headers = fgetcsv($handle, 1000, ",");
|
|
|
|
|
- while (($data = fgetcsv($handle, 1000, ",")) !== FALSE) {
|
|
|
|
|
|
|
+ $handle = @fopen($file_path, 'r');
|
|
|
|
|
+ if ($handle === false) {
|
|
|
|
|
+ UtilsLog::log('parse_csv: failed to open ' . $file_path);
|
|
|
|
|
+ return $csv_data;
|
|
|
|
|
+ }
|
|
|
|
|
+ try {
|
|
|
|
|
+ $headers = fgetcsv($handle, 0, ',');
|
|
|
|
|
+ if (!is_array($headers) || empty(array_filter($headers))) {
|
|
|
|
|
+ UtilsLog::log('parse_csv: header row missing or empty');
|
|
|
|
|
+ return $csv_data;
|
|
|
|
|
+ }
|
|
|
|
|
+ $headers = array_map(array(__CLASS__, 'normalise_header'), $headers);
|
|
|
|
|
+ while (($data = fgetcsv($handle, 0, ',')) !== false) {
|
|
|
|
|
+ if (count($data) !== count($headers)) {
|
|
|
|
|
+ continue;
|
|
|
|
|
+ }
|
|
|
$csv_data[] = array_combine($headers, $data);
|
|
$csv_data[] = array_combine($headers, $data);
|
|
|
}
|
|
}
|
|
|
|
|
+ } finally {
|
|
|
fclose($handle);
|
|
fclose($handle);
|
|
|
}
|
|
}
|
|
|
return $csv_data;
|
|
return $csv_data;
|
|
|
}
|
|
}
|
|
|
-}
|
|
|
|
|
|
|
+
|
|
|
|
|
+ private static function normalise_header($header) {
|
|
|
|
|
+ $header = strtolower(trim((string) $header));
|
|
|
|
|
+ // Map "order no" → "order_no" by collapsing internal whitespace to underscore.
|
|
|
|
|
+ $header = preg_replace('/\s+/', '_', $header);
|
|
|
|
|
+ return $header;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ private static function dedupe_by_order_no($rows) {
|
|
|
|
|
+ $seen = array();
|
|
|
|
|
+ $unique = array();
|
|
|
|
|
+ foreach ($rows as $row) {
|
|
|
|
|
+ $key = isset($row['order_no']) ? trim((string) $row['order_no']) : '';
|
|
|
|
|
+ if ($key === '' || isset($seen[$key])) {
|
|
|
|
|
+ continue;
|
|
|
|
|
+ }
|
|
|
|
|
+ $seen[$key] = true;
|
|
|
|
|
+ $unique[] = $row;
|
|
|
|
|
+ }
|
|
|
|
|
+ return $unique;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ private static function normalise_csv_date($raw) {
|
|
|
|
|
+ $raw = sanitize_text_field((string) $raw);
|
|
|
|
|
+ if ($raw === '') {
|
|
|
|
|
+ return '';
|
|
|
|
|
+ }
|
|
|
|
|
+ $timestamp = strtotime($raw);
|
|
|
|
|
+ return $timestamp ? date('Y-m-d H:i:s', $timestamp) : $raw;
|
|
|
|
|
+ }
|
|
|
|
|
+}
|