<?php
namespace Automattic\WCShipping\Shipment;
use WC_Order;
class ShipmentFromLabelGenerator {
/*
* @var array
*/
private $orderItemsMap;
/*
* Create a new ShipmentFromLabelGenerator instance.
*
* @param WC_Order $order The order object.
*/
public function __construct( WC_Order $order ) {
$this->orderItemsMap = $this->build_order_items_map( $order );
}
/*
* Generate shipments from labels.
*
* @param array|null $labels Array of label metadata.
* @return array An array with two elements: shipments array and a fallback flag.
*/
public function generate_shipments( ?array $labels = null ) {
if ( null === $labels ) {
return array(
'shipments' => array(),
'autogenerated_from_labels' => array(),
);
}
$shipments = array();
$autogenerated_from_labels = array();
foreach ( $labels as $index => $label ) {
$result = $this->generate_shipment_from_label( $label );
$shipments[] = $result['items'];
if ( $result['shipment_uses_autogenerated_items'] ) {
$autogenerated_from_labels[] = $index;
}
}
return array(
'shipments' => $shipments,
'autogenerated_from_labels' => $autogenerated_from_labels,
);
}
/*
* Generate shipment items based on a label.
*
* If any product mapping fails or if no items are generated,
* fall back to including all order items with a fallback flag.
*
* @param array $label The label data, expected to contain a 'product_ids' key.
* @return array An array with the keys 'items' and 'shipment_uses_autogenerated_items'.
*/
private function generate_shipment_from_label( array $label ) {
$product_id_quantities = $this->count_product_id_quantities( $label['product_ids'] );
$shipmentItems = array();
$mappingSuccessful = true;
// Process each unique product id found in the label.
foreach ( $product_id_quantities as $pid => $quantity ) {
// Check if we can find this product in the order.
if ( ! isset( $this->orderItemsMap[ $pid ] ) ) {
$mappingSuccessful = false;
continue;
}
$baseItem = $this->orderItemsMap[ $pid ];
$baseItem['quantity'] = $quantity;
$baseItem['subItems'] = array();
// If there is more than one occurrence, add subItems.
if ( $quantity > 1 ) {
for ( $i = 1; $i < $quantity; $i++ ) {
$subItem = $baseItem;
$subItem['id'] = $pid . '-sub-' . ( $i - 1 );
$subItem['quantity'] = 1;
$subItem['parentId'] = $baseItem['id'];
$subItem['subItems'] = array();
$baseItem['subItems'][] = $subItem;
}
}
$shipmentItems[] = $baseItem;
}
// Fallback: if mapping is not completely successful or no shipment items were created.
if ( ! $mappingSuccessful || empty( $shipmentItems ) ) {
$fallbackItems = array();
foreach ( $this->orderItemsMap as $item ) {
$fallbackItems[] = $item;
}
return array(
'items' => $fallbackItems,
'shipment_uses_autogenerated_items' => true,
);
}
return array(
'items' => $shipmentItems,
'shipment_uses_autogenerated_items' => false,
);
}
/**
* Group product IDs by occurrence in the label.
*
* Takes a list of product IDs from a label and groups them by product ID,
* tracking the original indices of each occurrence.
*
* @param array $productIds List of product IDs from the label.
* @return array An associative array where keys are product IDs and values are arrays of indices where each product appears.
*/
private function count_product_id_quantities( array $productIds ) {
$product_id_quantities = array_fill_keys( $productIds, 0 );
foreach ( $productIds as $pid ) {
++$product_id_quantities[ $pid ];
}
return $product_id_quantities;
}
/*
* Build a lookup map of order items by product ID.
*
* @param WC_Order $order The order object.
* @return array Array with product IDs as keys and order item data as values.
*/
private function build_order_items_map( WC_Order $order ) {
$map = array();
foreach ( $order->get_items() as $item ) {
$pid = $item['product_id'];
$map[ $pid ] = $item->get_data();
}
return $map;
}
}