PluginProbe
PostNL for WooCommerce / 4.0.0
PostNL for WooCommerce v4.0.0
5.9.12 5.9.11 5.9.10 5.9.9 5.9.8 5.9.7 5.9.6 trunk 2.5.0 2.5.1 2.5.2 2.5.3 2.5.4 2.5.5 3.1.4 3.1.5 3.1.6 3.1.7 4.0.0 4.0.1 4.0.2 4.3.2 4.3.3 4.4.0 4.4.1 All 72 releases
woo-postnl / includes / admin / class-wcpn-export.php

class-wcpn-export.php in PostNL for WooCommerce 4.0.0, at includes/admin/class-wcpn-export.php

1,549 lines 52.3 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2
3 use MyParcelNL\Sdk\src\Adapter\DeliveryOptions\AbstractDeliveryOptionsAdapter;
4 use MyParcelNL\Sdk\src\Exception\ApiException;
5 use MyParcelNL\Sdk\src\Exception\MissingFieldException;
6 use MyParcelNL\Sdk\src\Helper\MyParcelCollection;
7 use MyParcelNL\Sdk\src\Model\Consignment\AbstractConsignment;
8 use MyParcelNL\Sdk\src\Model\Consignment\PostNLConsignment;
9 use MyParcelNL\Sdk\src\Support\Arr;
10 use MyParcelNL\Sdk\src\Support\Str;
11 use WPO\WC\PostNL\Compatibility\Order as WCX_Order;
12 use WPO\WC\PostNL\Compatibility\WC_Core as WCX;
13 use WPO\WC\PostNL\Compatibility\WCPN_ChannelEngine_Compatibility as ChannelEngine;
14
15 if (! defined("ABSPATH")) {
16 exit;
17 } // Exit if accessed directly
18
19 if (class_exists("WCPN_Export")) {
20 return new WCPN_Export();
21 }
22
23 class WCPN_Export
24 {
25 /**
26 * @param WC_Order $order
27 * @param string $shippingMethodId
28 *
29 * @return int|null
30 * @throws Exception
31 */
32 public function getOrderShippingClass(WC_Order $order, string $shippingMethodId = ''): ?int
33 {
34 if (empty($shippingMethodId)) {
35 $orderShippingMethods = $order->get_items('shipping');
36
37 if (! empty($orderShippingMethods)) {
38 // we're taking the first (we're not handling multiple shipping methods as of yet)
39 $orderShippingMethod = array_shift($orderShippingMethods);
40 $shippingMethodId = $orderShippingMethod['method_id'];
41 } else {
42 return null;
43 }
44 }
45
46 $shippingMethod = $this->getShippingMethod($shippingMethodId);
47
48 if (empty($shippingMethod)) {
49 return null;
50 }
51
52 // get shipping classes from order
53 $foundShippingClasses = $this->find_order_shipping_classes($order);
54
55 $highest_class = $this->getShippingClass($shippingMethod, $foundShippingClasses);
56
57 return $highest_class;
58 }
59
60 public const EXPORT = "wcpn_export";
61 public const ADD_SHIPMENTS = "add_shipments";
62 public const ADD_RETURN = "add_return";
63 public const GET_LABELS = "get_labels";
64
65 public const MODAL_DIALOG = "modal_dialog";
66 /**
67 * Maximum characters length of item description.
68 */
69 public const ITEM_DESCRIPTION_MAX_LENGTH = 50;
70
71 public const ORDER_DESCRIPTION_MAX_LENGTH = 45;
72 public const DEFAULT_POSITIONS = [2, 4, 1, 3];
73
74 public const SUFFIX_CHECK_REG = "~^([a-z]{1}\d{1,3}|-\d{1,4}\d{2}\w{1,2}|[a-z]{1}[a-z\s]{0,3})(?:\W|$)~i";
75
76 /**
77 * Shipping methods that can never have delivery options.
78 */
79 public const DISALLOWED_SHIPPING_METHODS = [
80 WCPN_Shipping_Methods::LOCAL_PICKUP,
81 ];
82 public $order_id;
83 public $success;
84 public $errors;
85
86 public $postNLCollection;
87
88 private $prefix_message;
89
90 public function __construct()
91 {
92 $this->success = [];
93 $this->errors = [];
94
95 require_once("class-wcpn-rest.php");
96 require_once("class-wcpn-api.php");
97
98 add_action("admin_notices", [$this, "admin_notices"]);
99
100 add_action("wp_ajax_" . self::EXPORT, [$this, "export"]);
101 }
102
103 /**
104 * @param int $orderId
105 *
106 * @throws ApiException
107 * @throws ErrorException
108 * @throws MissingFieldException
109 */
110 public function exportByOrderId(int $orderId): void
111 {
112 if ($orderId) {
113 $export = new self();
114 $export->addShipments([(string) $orderId], 0, false);
115 }
116 }
117
118 /**
119 * Get the value of a shipment option. Check if it was set manually, through the delivery options for example,
120 * if not get the value of the default export setting for given settingName.
121 *
122 * @param mixed $option Chosen value.
123 * @param string $settingName Name of the setting to fall back to if there is no chosen value.
124 *
125 * @return mixed
126 */
127 public static function getChosenOrDefaultShipmentOption($option, string $settingName)
128 {
129 return $option ?? WCPOST()->setting_collection->getByName($settingName);
130 }
131
132 /**
133 * @param $item
134 * @param $order
135 *
136 * @return mixed|string
137 */
138 public static function get_item_display_name($item, $order)
139 {
140 // set base name
141 $name = $item['name'];
142
143 // add variation name if available
144 $product = $order->get_product_from_item($item);
145 if ($product && isset($item['variation_id']) && $item['variation_id'] > 0 && method_exists($product, 'get_variation_attributes')) {
146 $name .= woocommerce_get_formatted_variation($product->get_variation_attributes());
147 }
148
149 return $name;
150 }
151
152 public function admin_notices()
153 {
154 // only do this when the user that initiated this
155 if (isset($_GET["postnl_done"])) {
156 $action_return = get_option("wcpostnl_admin_notices");
157 $print_queue = get_option("wcpostnl_print_queue", []);
158 $error_notice = get_option("wcpostnl_admin_error_notices");
159
160 if (! empty($action_return)) {
161 foreach ($action_return as $type => $message) {
162 if (! in_array($type, ["success", "error"])) {
163 continue;
164 }
165
166 if ($type === "success" && ! empty($print_queue)) {
167 $print_queue_store = sprintf(
168 '<input type="hidden" value=\'%s\' class="wcpn__print-queue">',
169 json_encode(
170 [
171 "shipment_ids" => $print_queue["shipment_ids"],
172 "order_ids" => $print_queue["order_ids"],
173 "offset" => $print_queue["offset"],
174 ]
175 )
176 );
177
178 // Empty queue
179 delete_option("wcpostnl_print_queue");
180 }
181
182 printf(
183 '<div class="wcpn__notice is-dismissible notice notice-%s"><p>%s</p>%s</div>',
184 $type,
185 $message,
186 $print_queue_store ?? ""
187 );
188 }
189 // destroy after reading
190 delete_option("wcpostnl_admin_notices");
191 wp_cache_delete("wcpostnl_admin_notices", "options");
192 }
193 }
194
195 if (! empty($error_notice)) {
196 printf(
197 '<div class="wcpn__notice is-dismissible notice notice-error"><p>%s</p>%s</div>',
198 $error_notice,
199 $print_queue_store ?? ""
200 );
201 // destroy after reading
202 delete_option("wcpostnl_admin_error_notices");
203 wp_cache_delete("wcpostnl_admin_error_notices", "options");
204 }
205
206 if (isset($_GET["postnl"])) {
207 switch ($_GET["postnl"]) {
208 case "no_consignments":
209 $message = __(
210 "You have to export the orders to PostNL before you can print the labels!",
211 "woocommerce-postnl"
212 );
213 printf('<div class="wcpn__notice is-dismissible notice notice-error"><p>%s</p></div>', $message);
214 break;
215 default:
216 break;
217 }
218 }
219 }
220
221 /**
222 * Export selected orders.
223 *
224 * @access public
225 * @return void
226 * @throws ApiException
227 * @throws MissingFieldException
228 * @throws Exception
229 */
230 public function export()
231 {
232 // Check the nonce
233 if (! check_ajax_referer(WCPOST::NONCE_ACTION, "_wpnonce", false)) {
234 die("Ajax security check failed. Did you pass a valid nonce in \$_REQUEST['_wpnonce']?");
235 }
236
237 if (! is_user_logged_in()) {
238 wp_die(__("You do not have sufficient permissions to access this page.", "woocommerce-postnl"));
239 }
240
241 $return = [];
242
243 // Check the user privileges (maybe use order ids for filter?)
244 if (apply_filters(
245 "wc_postnl_check_privs",
246 ! current_user_can("manage_woocommerce_orders") && ! current_user_can("edit_shop_orders")
247 )) {
248 $return["error"] = __(
249 "You do not have sufficient permissions to access this page.",
250 "woocommerce-postnl"
251 );
252 echo json_encode($return);
253 die();
254 }
255
256 $dialog = $_REQUEST["dialog"] ?? null;
257 $print = $_REQUEST["print"] ?? null;
258 $offset = $_REQUEST["offset"] ?? 0;
259 $request = $_REQUEST["request"];
260
261 /**
262 * @var $order_ids
263 */
264 $order_ids = $this->sanitize_posted_array($_REQUEST["order_ids"] ?? []);
265 $shipment_ids = $this->sanitize_posted_array($_REQUEST["shipment_ids"] ?? []);
266
267 if (empty($shipment_ids) && empty($order_ids)) {
268 $this->errors[] = __("You have not selected any orders!", "woocommerce-postnl");
269 } else {
270 try {
271 switch ($request) {
272 // Creating consignments.
273 case self::ADD_SHIPMENTS:
274 $this->addShipments($order_ids, $offset, $print);
275 break;
276
277 // Creating a return shipment.
278 case self::ADD_RETURN:
279 if (empty($order_ids)) {
280 $this->errors[] = __("You have not selected any orders!", "woocommerce-postnl");
281 break;
282 }
283
284 $return = $this->addReturn($order_ids);
285 break;
286
287 // Downloading labels.
288 case self::GET_LABELS:
289 $return = $this->printLabels($order_ids, $shipment_ids, $offset);
290 break;
291
292 case self::MODAL_DIALOG:
293 $order_ids = $this->filterOrderDestinations($order_ids);
294 $this->modal_dialog($order_ids, $dialog);
295 break;
296 }
297 } catch (Exception $e) {
298 $errorMessage = $e->getMessage();
299 $this->errors[] = "$request: {$errorMessage}";
300 add_option("wcpostnl_admin_error_notices", $errorMessage);
301 }
302 }
303
304 // display errors directly if PDF requested or modal
305 if (in_array($request, [self::ADD_RETURN, self::GET_LABELS, self::MODAL_DIALOG]) && ! empty($this->errors)) {
306 echo $this->parse_errors($this->errors);
307 die();
308 }
309
310 // format errors for html output
311 if (! empty($this->errors)) {
312 $return["error"] = $this->parse_errors($this->errors);
313 }
314
315 // if we're directed here from modal, show proper result page
316 if (isset($_REQUEST["modal"])) {
317 $this->modal_success_page($request, $return);
318 } else {
319 // return JSON response
320 echo json_encode($return);
321 die();
322 }
323 }
324
325 /**
326 * @param string|array $array
327 *
328 * @return array
329 */
330 public function sanitize_posted_array($array): array
331 {
332 if (is_array($array)) {
333 return $array;
334 }
335
336 // check for JSON
337 if (is_string($array) && strpos($array, "[") !== false) {
338 $array = json_decode(stripslashes($array));
339 }
340
341 return (array) $array;
342 }
343
344 /**
345 * @param $order_ids
346 * @param $process
347 *
348 * @return array
349 * @throws ApiException
350 * @throws MissingFieldException
351 * @throws ErrorException
352 * @throws Exception
353 */
354 public function add_shipments(array $order_ids, bool $process)
355 {
356 $return = [];
357 $collection = new MyParcelCollection();
358 $processDirectly = WCPOST()->setting_collection->isEnabled(WCPOST_Settings::SETTING_PROCESS_DIRECTLY) || $process === true;
359
360 WCPN_Log::add("*** Creating shipments started ***");
361
362 /**
363 * Loop over the order ids and create consignments for each order.
364 */
365 foreach ($order_ids as $order_id) {
366 $order = WCX::get_order($order_id);
367 $consignment = (new WCPN_Export_Consignments($order))->getConsignment();
368
369 $extraOptions = WCX_Order::get_meta($order, WCPOST_Admin::META_SHIPMENT_OPTIONS_EXTRA);
370 $colloAmount = $extraOptions["collo_amount"] ?? 1;
371
372 if ($colloAmount > 1) {
373 $collection->addMultiCollo($consignment, $colloAmount);
374 } else {
375 $collection->addConsignment($consignment);
376 }
377
378 WCPN_Log::add("Shipment data for order {$order_id}.");
379 }
380
381 $collection = $collection->createConcepts();
382
383 if ($processDirectly) {
384 $collection->setLinkOfLabels();
385 }
386
387 foreach ($order_ids as $order_id) {
388 $order = WCX::get_order($order_id);
389 $consignmentIds = ($collection->getConsignmentsByReferenceIdGroup($order_id))->getConsignmentIds();
390
391 foreach ($consignmentIds as $consignmentId) {
392 $shipment["shipment_id"] = $consignmentId;
393
394 $this->saveShipmentData($order, $shipment);
395 $this->updateOrderStatus($order);
396
397 $this->success[$order_id] = $consignmentId;
398 }
399
400 if ($processDirectly) {
401 $this->getShipmentData($consignmentIds, $order);
402 }
403
404 WCX_Order::update_meta_data(
405 $order,
406 WCPOST_Admin::META_LAST_SHIPMENT_IDS,
407 $consignmentIds
408 );
409 }
410
411 if (! empty($this->success)) {
412 $return["success"] = sprintf(
413 __("%s shipments successfully prepare for PostNL", "woocommerce-postnl"),
414 count($collection->getConsignmentIds())
415 );
416 $return["success_ids"] = $collection->getConsignmentIds();
417
418 WCPN_Log::add($return["success"]);
419 WCPN_Log::add("ids: " . implode(", ", $return["success_ids"]));
420 }
421
422 return $return;
423 }
424
425 /**
426 * @param array $order_ids
427 *
428 * @return array
429 * @throws Exception
430 */
431 public function addReturn(array $order_ids)
432 {
433 $return = [];
434
435 WCPN_Log::add("*** Creating return shipments started ***");
436
437 foreach ($order_ids as $order_id) {
438 try {
439 $return_shipments = [$this->prepareReturnShipmentData($order_id)];
440 WCPN_Log::add("Return shipment data for order {$order_id}:", print_r($return_shipments, true));
441
442 $api = $this->init_api();
443 $response = $api->add_shipments($return_shipments, "return");
444
445 WCPN_Log::add("API response (order {$order_id}):\n" . print_r($response, true));
446
447 $ids = Arr::get($response, "body.data.ids");
448
449 if ($ids && ! empty($ids)) {
450 $order = WCX::get_order($order_id);
451 $ids = array_shift($response["body"]["data"]["ids"]);
452 $shipment_id = $ids["id"];
453 $this->success[$order_id] = $shipment_id;
454
455 $shipment = [
456 "shipment_id" => $shipment_id,
457 ];
458
459 // save shipment data in order meta
460 $this->saveShipmentData($order, $shipment);
461 } else {
462 WCPN_Log::add("\$response\[\"body.data.ids\"] empty or not found.", print_r($response, true));
463 throw new Exception("\$response\[\"body.data.ids\"] empty or not found.");
464 }
465 } catch (Exception $e) {
466 $errorMessage = $e->getMessage();
467 $this->errors[$order_id] = $errorMessage;
468 add_option('wcpostnl_admin_error_notices', $errorMessage);
469 }
470 }
471
472 return $return;
473 }
474
475 /**
476 * @param array $shipment_ids
477 * @param array $order_ids
478 * @param int $offset
479 * @param string|null $displayOverride - Overrides display setting.
480 *
481 * @return array
482 * @throws Exception
483 */
484 public function downloadOrGetUrlOfLabels(
485 array $shipment_ids,
486 array $order_ids = [],
487 int $offset = 0,
488 string $displayOverride = null
489 ) {
490 $return = [];
491
492 WCPN_Log::add("*** downloadOrGetUrlOfLabels() ***");
493 WCPN_Log::add("Shipment IDs: " . implode(", ", $shipment_ids));
494
495 try {
496 $api = $this->init_api();
497
498 // positions are defined on landscape, but paper is filled portrait-wise
499 $positions = array_slice(self::DEFAULT_POSITIONS, $offset % 4);
500
501 $displaySetting = WCPOST()->setting_collection->getByName(WCPOST_Settings::SETTING_DOWNLOAD_DISPLAY);
502 $display = ($displayOverride ?? $displaySetting) === "display";
503 $api->getShipmentLabels($shipment_ids, $order_ids, $positions, $display);
504 } catch (Exception $e) {
505 throw new Exception($e->getMessage());
506 add_option('wcpostnl_admin_error_notice', $e->getMessage());
507 }
508
509 return $return;
510 }
511
512 /**
513 * @param array $order_ids
514 * @param int $offset
515 * @param string|null $display
516 *
517 * @return array
518 * @throws Exception
519 */
520 public function getOrderLabels(array $order_ids, int $offset = 0, string $display = null)
521 {
522 $shipment_ids = $this->getShipmentIds($order_ids, ["only_last" => true]);
523
524 if (empty($shipment_ids)) {
525 WCPN_Log::add(" *** Failed label request(not exported yet) ***");
526
527 throw new Exception(__(
528 "The selected orders have not been exported to PostNL yet! ",
529 "woocommerce-postnl"
530 ));
531 }
532
533 return $this->downloadOrGetUrlOfLabels(
534 $shipment_ids,
535 $order_ids,
536 $offset,
537 $display
538 );
539 }
540
541 /**
542 * @param $order_ids
543 */
544 public function modal_dialog($order_ids, $dialog): void
545 {
546 // check for JSON
547 if (is_string($order_ids) && strpos($order_ids, "[") !== false) {
548 $order_ids = json_decode(stripslashes($order_ids));
549 }
550
551 // cast as array for single exports
552 $order_ids = (array) $order_ids;
553 require("views/html-bulk-options-form.php");
554 die();
555 }
556
557 /**
558 * @param $request
559 * @param $result
560 */
561 public function modal_success_page($request, $result)
562 {
563 require("views/html-modal-result-page.php");
564 die();
565 }
566
567 /**
568 * @return WCPN_API
569 * @throws Exception
570 */
571 public function init_api()
572 {
573 $key = $this->getSetting(WCPOST_Settings::SETTING_API_KEY);
574
575 if (! ($key)) {
576 throw new ErrorException(__("No API key found in PostNL settings", "woocommerce-postnl"));
577 }
578
579 return new WCPN_API($key);
580 }
581
582 /**
583 * TODO: There are no options being passed right now but these will be necessary for NL.
584 *
585 * @param $order_id
586 * @param $options
587 *
588 * @return array
589 * @throws Exception
590 */
591 public function prepareReturnShipmentData($order_id, $options = [])
592 {
593 $order = WCX::get_order($order_id);
594
595 $shipping_name =
596 method_exists($order, "get_formatted_shipping_full_name") ? $order->get_formatted_shipping_full_name()
597 : trim($order->get_shipping_first_name() . " " . $order->get_shipping_last_name());
598
599 // set name & email
600 $return_shipment_data = [
601 "name" => $shipping_name,
602 "email" => WCX_Order::get_prop($order, "billing_email"),
603 "carrier" => PostNLConsignment::CARRIER_ID, // default to PostNL for now
604 ];
605
606 if (! Arr::get($return_shipment_data, "email")) {
607 throw new Exception(__("No e-mail address found in order.", "woocommerce-postnl"));
608 }
609
610 // add options if available
611 if (! empty($options)) {
612 // convert insurance option
613 if (! isset($options["insurance"]) && isset($options["insured_amount"])) {
614 if ($options["insured_amount"] > 0) {
615 $options["insurance"] = [
616 "amount" => (int) $options["insured_amount"] * 100,
617 "currency" => "EUR",
618 ];
619 }
620 unset($options["insured_amount"]);
621 unset($options["insured"]);
622 }
623 // PREVENT ILLEGAL SETTINGS
624 // convert numeric strings to int
625 $int_options = ["package_type", "delivery_type", "signature", "return "];
626 foreach ($options as $key => &$value) {
627 if (in_array($key, $int_options)) {
628 $value = (int) $value;
629 }
630 }
631 // remove frontend insurance option values
632 if (isset($options["insured_amount"])) {
633 unset($options["insured_amount"]);
634 }
635 if (isset($options["insured"])) {
636 unset($options["insured"]);
637 }
638 $return_shipment_data["options"] = $options;
639 }
640
641 // get parent
642 $shipment_ids = $this->getShipmentIds(
643 (array) $order_id,
644 [
645 "exclude_concepts" => true,
646 "only_last" => true,
647 ]
648 );
649
650 if (! empty($shipment_ids)) {
651 $return_shipment_data["parent"] = (int) array_pop($shipment_ids);
652 }
653
654 return $return_shipment_data;
655 }
656
657 /**
658 * @param WC_Order $order
659 *
660 * @return mixed|void
661 * @throws Exception
662 */
663 public static function getRecipientFromOrder(WC_Order $order)
664 {
665 $isUsingPostNLFields = WCX_Order::has_meta($order, "_billing_street_name")
666 && WCX_Order::has_meta($order, "_billing_house_number");
667
668 $shipping_name =
669 method_exists($order, "get_formatted_shipping_full_name") ? $order->get_formatted_shipping_full_name()
670 : trim($order->get_shipping_first_name() . " " . $order->get_shipping_last_name());
671
672
673 $connectPhone = WCPOST()->setting_collection->isEnabled(WCPOST_Settings::SETTING_CONNECT_PHONE);
674
675 $address = [
676 "cc" => (string) WCX_Order::get_prop($order, "shipping_country"),
677 "city" => (string) WCX_Order::get_prop($order, "shipping_city"),
678 "person" => $shipping_name,
679 "company" => (string) WCX_Order::get_prop($order, "shipping_company"),
680 "email" => WCX_Order::get_prop($order, "billing_email") ?? "",
681 "phone" => $connectPhone ? WCX_Order::get_prop($order, "billing_phone") : "",
682 "street_additional_info" => WCX_Order::get_prop($order, "shipping_address_2"),
683 ];
684
685 $shipping_country = WCX_Order::get_prop($order, "shipping_country");
686 if ($shipping_country === "NL") {
687 // use billing address if old "pakjegemak" (1.5.6 and older)
688 $pgAddress = WCX_Order::get_meta($order, WCPOST_Admin::META_PGADDRESS);
689
690 if ($pgAddress) {
691 $billing_name = method_exists($order, "get_formatted_billing_full_name")
692 ? $order->get_formatted_billing_full_name()
693 : trim(
694 $order->get_billing_first_name() . " " . $order->get_billing_last_name()
695 );
696 $address_intl = [
697 "city" => (string) WCX_Order::get_prop($order, "billing_city"),
698 "person" => $billing_name,
699 "company" => (string) WCX_Order::get_prop($order, "billing_company"),
700 "postal_code" => (string) WCX_Order::get_prop($order, "billing_postcode"),
701 ];
702
703 if ($isUsingPostNLFields) {
704 $address_intl["street"] = (string) WCX_Order::get_meta($order, "_billing_street_name");
705 $address_intl["number"] = (string) WCX_Order::get_meta($order, "_billing_house_number");
706 $address_intl["number_suffix"] =
707 (string) WCX_Order::get_meta($order, "_billing_house_number_suffix");
708 } else {
709 // Split the address line 1 into three parts
710 preg_match(
711 WCPN_NL_Postcode_Fields::SPLIT_STREET_REGEX,
712 WCX_Order::get_prop($order, "billing_address_1"),
713 $address_parts
714 );
715 $address_intl["street"] = (string) $address_parts["street"];
716 $address_intl["number"] = (string) $address_parts["number"];
717 $address_intl["number_suffix"] =
718 array_key_exists("number_suffix", $address_parts) // optional
719 ? (string) $address_parts["number_suffix"] : "";
720 $address_intl["street_additional_info"] = WCX_Order::get_prop($order, "billing_address_2");
721 }
722 } else {
723 $address_intl = [
724 "postal_code" => (string) WCX_Order::get_prop($order, "shipping_postcode"),
725 ];
726 // If not using old fields
727 if ($isUsingPostNLFields) {
728 $address_intl["street"] = (string) WCX_Order::get_meta($order, "_shipping_street_name");
729 $address_intl["number"] = (string) WCX_Order::get_meta($order, "_shipping_house_number");
730 $address_intl["number_suffix"] =
731 (string) WCX_Order::get_meta($order, "_shipping_house_number_suffix");
732 } else {
733 // Split the address line 1 into three parts
734 preg_match(
735 WCPN_NL_Postcode_Fields::SPLIT_STREET_REGEX,
736 WCX_Order::get_prop($order, "shipping_address_1"),
737 $address_parts
738 );
739
740 $address_intl["street"] = (string) $address_parts["street"];
741 $address_intl["number"] = (string) $address_parts["number"];
742 $address_intl["number_suffix"] = (string) $address_parts["extension"] ?: "";
743
744 if (! $address_intl["number_suffix"]) {
745 if (preg_match(self::SUFFIX_CHECK_REG, $address["street_additional_info"])) {
746 $address_intl["number_suffix"] = $address["street_additional_info"];
747 $address["street_additional_info"] = "";
748 }
749 }
750 }
751 }
752 } else {
753 $address_intl = [
754 "postal_code" => (string) WCX_Order::get_prop($order, "shipping_postcode"),
755 "street" => (string) WCX_Order::get_prop($order, "shipping_address_1"),
756 "street_additional_info" => (string) WCX_Order::get_prop($order, "shipping_address_2"),
757 "region" => (string) WCX_Order::get_prop($order, "shipping_state"),
758 ];
759 }
760
761 $address = array_merge($address, $address_intl);
762
763 return apply_filters("wc_postnl_recipient", $address, $order);
764 }
765
766 /**
767 * @param int $order_id
768 * @param array $track_traces
769 *
770 * @internal param $shipment_ids
771 */
772 public static function addTrackTraceNoteToOrder(int $order_id, array $track_traces): void
773 {
774 if (! WCPOST()->setting_collection->isEnabled(WCPOST_Settings::SETTING_BARCODE_IN_NOTE)) {
775 return;
776 }
777
778 $prefix_message = WCPOST()->setting_collection->getByName(WCPOST_Settings::SETTING_BARCODE_IN_NOTE_TITLE);
779
780 // Select the barcode text of the PostNL settings
781 $prefix_message = $prefix_message ? $prefix_message . " " : "";
782
783 $order = WCX::get_order($order_id);
784 $order->add_order_note($prefix_message . implode(", ", $track_traces));
785 }
786
787 /**
788 * @param string $name
789 *
790 * @return mixed
791 */
792 private function getSetting(string $name)
793 {
794 return WCPOST()->setting_collection->getByName($name);
795 }
796
797 /**
798 * @param array $order_ids
799 * @param array $args
800 *
801 * @return array
802 */
803 public function getShipmentIds(array $order_ids, array $args): array
804 {
805 $shipment_ids = [];
806
807 foreach ($order_ids as $order_id) {
808 $order = WCX::get_order($order_id);
809 $order_shipments = WCX_Order::get_meta($order, WCPOST_Admin::META_SHIPMENTS);
810
811 if (empty($order_shipments)) {
812 continue;
813 }
814
815 $order_shipment_ids = [];
816 // exclude concepts or only concepts
817 foreach ($order_shipments as $shipment_id => $shipment) {
818 if (isset($args["exclude_concepts"]) && empty($shipment["track_trace"])) {
819 continue;
820 }
821 if (isset($args["only_concepts"]) && ! empty($shipment["track_trace"])) {
822 continue;
823 }
824
825 $order_shipment_ids[] = $shipment_id;
826 }
827
828 if (isset($args["only_last"])) {
829 $last_shipment_ids = WCX_Order::get_meta($order, WCPOST_Admin::META_LAST_SHIPMENT_IDS);
830
831 if (! empty($last_shipment_ids) && is_array($last_shipment_ids)) {
832 foreach ($order_shipment_ids as $order_shipment_id) {
833 if (in_array($order_shipment_id, $last_shipment_ids)) {
834 $shipment_ids[] = $order_shipment_id;
835 }
836 }
837 } else {
838 $shipment_ids[] = array_pop($order_shipment_ids);
839 }
840 } else {
841 $shipment_ids[] = array_merge($shipment_ids, $order_shipment_ids);
842 }
843 }
844
845 return $shipment_ids;
846 }
847
848 /**
849 * @param WC_Order $order
850 * @param array $shipment
851 *
852 * @return void
853 * @throws Exception
854 */
855 public function saveShipmentData(WC_Order $order, array $shipment): void
856 {
857 if (empty($shipment)) {
858 throw new Exception("save_shipment_data requires a valid \$shipment.");
859 }
860
861 $old_shipments = [];
862 $new_shipments = [];
863 $new_shipments[$shipment["shipment_id"]] = $shipment;
864
865 if (WCX_Order::has_meta($order, WCPOST_Admin::META_SHIPMENTS)) {
866 $old_shipments = WCX_Order::get_meta($order, WCPOST_Admin::META_SHIPMENTS);
867 }
868
869 $new_shipments = array_replace_recursive($old_shipments, $new_shipments);
870
871 WCX_Order::update_meta_data($order, WCPOST_Admin::META_SHIPMENTS, $new_shipments);
872 }
873
874 /**
875 * Determine appropriate package type for this order.
876 *
877 * @param WC_Order $order
878 * @param AbstractDeliveryOptionsAdapter|null $deliveryOptions
879 *
880 * @return string
881 * @throws Exception
882 */
883 public function getPackageTypeFromOrder(
884 WC_Order $order,
885 AbstractDeliveryOptionsAdapter $deliveryOptions = null
886 ): string {
887 $packageTypeFromDeliveryOptions = $deliveryOptions
888 ? $deliveryOptions->getPackageType()
889 : null;
890
891 if ($packageTypeFromDeliveryOptions) {
892 return $packageTypeFromDeliveryOptions;
893 }
894
895 // Get pre 4.0.0 package type if it exists.
896 if (WCX_Order::has_meta($order, WCPOST_Admin::META_SHIPMENT_OPTIONS_LT_4_0_0)) {
897 $shipmentOptions = WCX_Order::get_meta($order, WCPOST_Admin::META_SHIPMENT_OPTIONS_LT_4_0_0);
898 return self::getPackageTypeAsString($shipmentOptions['package_type']);
899 }
900
901 $packageType = AbstractConsignment::DEFAULT_PACKAGE_TYPE_NAME;
902
903 // get shipping methods from order
904 $orderShippingMethods = $order->get_items('shipping');
905
906 if (! empty($orderShippingMethods)) {
907 // we're taking the first (we're not handling multiple shipping methods as of yet)
908 $orderShippingMethod = array_shift($orderShippingMethods);
909 $orderShippingMethod = $orderShippingMethod['method_id'];
910
911 $orderShippingClass = WCX_Order::get_meta($order, WCPOST_Admin::META_HIGHEST_SHIPPING_CLASS);
912 if (empty($orderShippingClass)) {
913 $orderShippingClass = $this->getOrderShippingClass($order, $orderShippingMethod);
914 }
915
916 $packageType = self::getPackageTypeFromShippingMethod(
917 $orderShippingMethod,
918 $orderShippingClass
919 );
920 }
921
922 return $packageType;
923 }
924
925 /**
926 * @param $shippingMethod
927 * @param $shippingClass
928 *
929 * @return string
930 */
931 public static function getPackageTypeFromShippingMethod($shippingMethod, $shippingClass): string
932 {
933 $packageType = AbstractConsignment::PACKAGE_TYPE_PACKAGE_NAME;
934 $shippingMethodIdClass = "";
935
936 if (Str::startsWith($shippingMethod, 'table_rate:') && class_exists('WC_Table_Rate_Shipping')) {
937 // Automattic / WooCommerce table rate
938 // use full method = method_id:instance_id:rate_id
939 $shippingMethodId = $shippingMethod;
940 } else {
941 // non table rates
942 if (Str::contains($shippingMethodIdClass, ':')) {
943 // means we have method_id:instance_id
944 $shippingMethod = explode(':', $shippingMethod);
945 $shippingMethodId = $shippingMethod[0];
946 } else {
947 $shippingMethodId = $shippingMethod;
948 }
949
950 // add class if we have one
951 if (! empty($shippingClass)) {
952 $shippingMethodIdClass = "{$shippingMethodId}:{$shippingClass}";
953 }
954 }
955
956 $packageTypes = WCPOST()->setting_collection->getByName(WCPOST_Settings::SETTING_SHIPPING_METHODS_PACKAGE_TYPES);
957 foreach ($packageTypes as $packageTypeKey => $packageTypeShippingMethods) {
958 if (self::isActiveMethod(
959 $shippingMethodId,
960 $packageTypeShippingMethods,
961 $shippingMethodIdClass,
962 $shippingClass
963 )) {
964 $packageType = $packageTypeKey;
965 break;
966 }
967 }
968
969 return self::getPackageTypeAsString($packageType);
970 }
971
972 /**
973 * @param string|null $packageType
974 *
975 * @return string
976 */
977 public static function getPackageTypeHuman(?string $packageType): string
978 {
979 if ($packageType) {
980 $packageType = WCPN_Data::getPackageTypeHuman($packageType);
981 }
982
983 return $packageType ?? __("Unknown", "woocommerce-postnl");
984 }
985
986 /**
987 * Will convert any package type to a valid string package type.
988 *
989 * @param mixed $packageType
990 *
991 * @return string
992 */
993 public static function getPackageTypeAsString($packageType): string
994 {
995 if (is_numeric($packageType)) {
996 $packageType = WCPN_Data::getPackageTypeName($packageType);
997 }
998
999 if (! is_string($packageType) || ! in_array($packageType, WCPN_Data::getPackageTypes())) {
1000 // Log data when this occurs but don't actually throw an exception.
1001 $type = gettype($packageType);
1002 WCPN_Log::add(new Exception("Tried to convert invalid value to package type: $packageType ($type)"));
1003
1004 $packageType = null;
1005 }
1006
1007 return $packageType ?? AbstractConsignment::DEFAULT_PACKAGE_TYPE_NAME;
1008 }
1009
1010 /**
1011 * @param $errors
1012 *
1013 * @return mixed|string
1014 */
1015 public function parse_errors($errors)
1016 {
1017 $parsed_errors = [];
1018
1019 foreach ($errors as $key => $error) {
1020 // check if we have an order_id
1021 if ($key > 10) {
1022 $parsed_errors[] = sprintf(
1023 "<strong>%s %s:</strong> %s",
1024 __("Order", "woocommerce-postnl"),
1025 $key,
1026 $error
1027 );
1028 } else {
1029 $parsed_errors[] = $error;
1030 }
1031 }
1032
1033 if (count($parsed_errors) == 1) {
1034 $html = array_shift($parsed_errors);
1035 } else {
1036 foreach ($parsed_errors as &$parsed_error) {
1037 $parsed_error = "<li>{$parsed_error}</li>";
1038 }
1039 $html = sprintf("<ul>%s</ul>", implode("\n", $parsed_errors));
1040 }
1041
1042 return $html;
1043 }
1044
1045 public function getShipmentStatusName($status_code)
1046 {
1047 $shipment_statuses = [
1048 1 => __("pending - concept", "woocommerce-postnl"),
1049 2 => __("pending - registered", "woocommerce-postnl"),
1050 3 => __("enroute - handed to carrier", "woocommerce-postnl"),
1051 4 => __("enroute - sorting", "woocommerce-postnl"),
1052 5 => __("enroute - distribution", "woocommerce-postnl"),
1053 6 => __("enroute - customs", "woocommerce-postnl"),
1054 7 => __("delivered - at recipient", "woocommerce-postnl"),
1055 8 => __("delivered - ready for pickup", "woocommerce-postnl"),
1056 9 => __("delivered - package picked up", "woocommerce-postnl"),
1057 30 => __("inactive - concept", "woocommerce-postnl"),
1058 31 => __("inactive - registered", "woocommerce-postnl"),
1059 32 => __("inactive - enroute - handed to carrier", "woocommerce-postnl"),
1060 33 => __("inactive - enroute - sorting", "woocommerce-postnl"),
1061 34 => __("inactive - enroute - distribution", "woocommerce-postnl"),
1062 35 => __("inactive - enroute - customs", "woocommerce-postnl"),
1063 36 => __("inactive - delivered - at recipient", "woocommerce-postnl"),
1064 37 => __("inactive - delivered - ready for pickup", "woocommerce-postnl"),
1065 38 => __("inactive - delivered - package picked up", "woocommerce-postnl"),
1066 99 => __("inactive - unknown", "woocommerce-postnl"),
1067 ];
1068
1069 if (isset($shipment_statuses[$status_code])) {
1070 return $shipment_statuses[$status_code];
1071 } else {
1072 return __("Unknown status", "woocommerce-postnl");
1073 }
1074 }
1075
1076 /**
1077 * Retrieves, updates and returns shipment data for given id.
1078 *
1079 * @param array $ids
1080 * @param WC_Order $order
1081 *
1082 * @return array
1083 * @throws Exception
1084 */
1085 public function getShipmentData(array $ids, WC_Order $order): array
1086 {
1087 $data = [];
1088 $api = $this->init_api();
1089 $response = $api->get_shipments($ids);
1090
1091 $shipments = Arr::get($response, "body.data.shipments");
1092
1093 if (! $shipments) {
1094 return [];
1095 }
1096
1097 foreach ($shipments as $shipment) {
1098 if (! isset($shipment["id"])) {
1099 return [];
1100 }
1101
1102 // if shipment id matches and status is not concept, get track trace barcode and status name
1103 $status = $this->getShipmentStatusName($shipment["status"]);
1104 $track_trace = $shipment["barcode"];
1105 $shipment_id = $shipment["id"];
1106 $shipment_data = compact("shipment_id", "status", "track_trace", "shipment");
1107 $this->saveShipmentData($order, $shipment_data);
1108
1109 ChannelEngine::updateMetaOnExport($order, $track_trace);
1110
1111 $data[$shipment_id] = $shipment_data;
1112 }
1113
1114 return $data;
1115 }
1116
1117 /**
1118 * @param $item
1119 * @param $order
1120 *
1121 * @return float
1122 */
1123 public static function getItemWeightKg($item, WC_Order $order): float
1124 {
1125 $product = $order->get_product_from_item($item);
1126
1127 if (empty($product)) {
1128 return 0;
1129 }
1130
1131 $weight = (float) $product->get_weight();
1132 $productWeight = WCPN_Export::calculatedKiloWeight($weight);
1133
1134 return $productWeight * (int) $item["qty"];
1135 }
1136
1137 /**
1138 * @param float $weight
1139 *
1140 * @return float
1141 */
1142 public static function calculatedKiloWeight(float $weight): float
1143 {
1144 $weightUnit = get_option("woocommerce_weight_unit");
1145 switch ($weightUnit) {
1146 case "g":
1147 $calculatedWeight = $weight / 1000;
1148 break;
1149 case "lbs":
1150 $calculatedWeight = $weight * 0.45359237;
1151 break;
1152 case "oz":
1153 $calculatedWeight = $weight * 0.0283495231;
1154 break;
1155 default:
1156 $calculatedWeight = $weight;
1157 break;
1158 }
1159
1160 return $calculatedWeight;
1161 }
1162
1163 /**
1164 * @param $chosenMethod
1165 *
1166 * @throws \Exception
1167 */
1168 public static function getShippingMethod(string $chosenMethod)
1169 {
1170 if (version_compare(WOOCOMMERCE_VERSION, "2.6", "<") || $chosenMethod ===
1171 WCPN_Shipping_Methods::LEGACY_FLAT_RATE) {
1172 return self::getLegacyShippingMethod($chosenMethod);
1173 }
1174
1175 [$methodSlug, $methodInstance] = WCPN_Checkout::splitShippingMethodString($chosenMethod);
1176
1177 $isDisallowedShippingMethod = in_array($methodSlug, self::DISALLOWED_SHIPPING_METHODS);
1178 $isManualOrder = empty($methodInstance);
1179
1180 if ($isDisallowedShippingMethod || $isManualOrder) {
1181 return null;
1182 }
1183
1184 return WC_Shipping_Zones::get_shipping_method($methodInstance) ?? null;
1185 }
1186
1187 /**
1188 * @param string $chosen_method
1189 *
1190 * @return null|WC_Shipping_Method
1191 */
1192 private static function getLegacyShippingMethod(string $chosen_method): ?WC_Shipping_Method
1193 {
1194 // only for flat rate or legacy flat rate
1195 if (! in_array(
1196 $chosen_method,
1197 [
1198 WCPN_Shipping_Methods::FLAT_RATE,
1199 WCPN_Shipping_Methods::LEGACY_FLAT_RATE,
1200 ]
1201 )) {
1202 return null;
1203 }
1204
1205 $shipping_methods = WC()->shipping()->load_shipping_methods();
1206
1207 if (! isset($shipping_methods[$chosen_method])) {
1208 return null;
1209 }
1210
1211 return $shipping_methods[$chosen_method];
1212 }
1213
1214 /**
1215 * @param $shipping_method
1216 * @param $found_shipping_classes
1217 *
1218 * @return int|null
1219 */
1220 public function getShippingClass($shipping_method, $found_shipping_classes): ?int
1221 {
1222 // get most expensive class
1223 // adapted from $shipping_method->calculate_shipping()
1224 $highest_class_cost = 0;
1225 $highest_class = null;
1226 foreach ($found_shipping_classes as $shipping_class => $products) {
1227 // Also handles BW compatibility when slugs were used instead of ids
1228 $shipping_class_term = get_term_by("slug", $shipping_class, "product_shipping_class");
1229 $shipping_class_term_id = "";
1230
1231 if ($shipping_class_term != null) {
1232 $shipping_class_term_id = $shipping_class_term->term_id;
1233 }
1234
1235 $class_cost_string = $shipping_class_term && $shipping_class_term_id ? $shipping_method->get_option(
1236 "class_cost_" . $shipping_class_term_id,
1237 $shipping_method->get_option("class_cost_" . $shipping_class, "")
1238 ) : $shipping_method->get_option("no_class_cost", "");
1239
1240 if ($class_cost_string === "") {
1241 continue;
1242 }
1243
1244 $class_cost = $this->wc_flat_rate_evaluate_cost(
1245 $class_cost_string,
1246 [
1247 "qty" => array_sum(wp_list_pluck($products, "quantity")),
1248 "cost" => array_sum(wp_list_pluck($products, "line_total")),
1249 ],
1250 $shipping_method
1251 );
1252 if ($class_cost > $highest_class_cost && ! empty($shipping_class_term_id)) {
1253 $highest_class_cost = $class_cost;
1254 $highest_class = $shipping_class_term->term_id;
1255 }
1256 }
1257
1258 return $highest_class;
1259 }
1260
1261 /**
1262 * Adapted from WC_Shipping_Flat_Rate - Protected method
1263 * Evaluate a cost from a sum/string.
1264 *
1265 * @param string $sum
1266 * @param array $args
1267 * @param $flat_rate_method
1268 *
1269 * @return string
1270 */
1271 public function wc_flat_rate_evaluate_cost(string $sum, array $args, $flat_rate_method)
1272 {
1273 if (version_compare(WOOCOMMERCE_VERSION, "2.6", ">=")) {
1274 include_once(WC()->plugin_path() . "/includes/libraries/class-wc-eval-math.php");
1275 } else {
1276 include_once(WC()->plugin_path() . "/includes/shipping/flat-rate/includes/class-wc-eval-math.php");
1277 }
1278
1279 // Allow 3rd parties to process shipping cost arguments
1280 $args = apply_filters("woocommerce_evaluate_shipping_cost_args", $args, $sum, $flat_rate_method);
1281 $locale = localeconv();
1282 $decimals = [
1283 wc_get_price_decimal_separator(),
1284 $locale["decimal_point"],
1285 $locale["mon_decimal_point"],
1286 ",",
1287 ];
1288 $this->fee_cost = $args["cost"];
1289
1290 // Expand shortcodes
1291 add_shortcode("fee", [$this, "wc_flat_rate_fee"]);
1292
1293 $sum = do_shortcode(
1294 str_replace(
1295 ["[qty]", "[cost]"],
1296 [$args["qty"], $args["cost"]],
1297 $sum
1298 )
1299 );
1300
1301 remove_shortcode("fee");
1302
1303 // Remove whitespace from string
1304 $sum = preg_replace("/\s+/", "", $sum);
1305
1306 // Remove locale from string
1307 $sum = str_replace($decimals, ".", $sum);
1308
1309 // Trim invalid start/end characters
1310 $sum = rtrim(ltrim($sum, "\t\n\r\0\x0B+*/"), "\t\n\r\0\x0B+-*/");
1311
1312 // Do the math
1313 return $sum ? WC_Eval_Math::evaluate($sum) : 0;
1314 }
1315
1316 /**
1317 * Adapted from WC_Shipping_Flat_Rate - Protected method
1318 * Work out fee (shortcode).
1319 *
1320 * @param array $atts
1321 *
1322 * @return string
1323 */
1324 public function wc_flat_rate_fee($atts)
1325 {
1326 $atts = shortcode_atts(
1327 [
1328 "percent" => "",
1329 "min_fee" => "",
1330 "max_fee" => "",
1331 ],
1332 $atts
1333 );
1334
1335 $calculated_fee = 0;
1336
1337 if ($atts["percent"]) {
1338 $calculated_fee = $this->fee_cost * (floatval($atts["percent"]) / 100);
1339 }
1340
1341 if ($atts["min_fee"] && $calculated_fee < $atts["min_fee"]) {
1342 $calculated_fee = $atts["min_fee"];
1343 }
1344
1345 if ($atts["max_fee"] && $calculated_fee > $atts["max_fee"]) {
1346 $calculated_fee = $atts["max_fee"];
1347 }
1348
1349 return $calculated_fee;
1350 }
1351
1352 /**
1353 * Filter out orders shipping to country codes that are not in the allowed list.
1354 *
1355 * @param $order_ids
1356 *
1357 * @return mixed
1358 * @throws Exception
1359 */
1360 public function filterOrderDestinations(array $order_ids): array
1361 {
1362 foreach ($order_ids as $key => $order_id) {
1363 $order = WCX::get_order($order_id);
1364 $shipping_country = WCX_Order::get_prop($order, "shipping_country");
1365
1366 if (! WCPN_Country_Codes::isAllowedDestination($shipping_country)) {
1367 unset($order_ids[$key]);
1368 }
1369 }
1370
1371 return $order_ids;
1372 }
1373
1374 /**
1375 * @param $shipment_id
1376 *
1377 * @return mixed
1378 * @throws ErrorException
1379 * @throws Exception
1380 */
1381 private function getShipmentBarcodeFromApi($shipment_id)
1382 {
1383 $api = $this->init_api();
1384 $response = $api->get_shipments($shipment_id);
1385
1386 if (! isset($response["body"]["data"]["shipments"][0]["barcode"])) {
1387 throw new ErrorException("No PostNL barcode found for shipment id; " . $shipment_id);
1388 }
1389
1390 return $response["body"]["data"]["shipments"][0]["barcode"];
1391 }
1392
1393 /**
1394 * @param $shipping_method_id
1395 * @param $package_type_shipping_methods
1396 * @param $shipping_method_id_class
1397 * @param $shipping_class
1398 *
1399 * @return bool
1400 */
1401 private static function isActiveMethod(
1402 $shipping_method_id,
1403 $package_type_shipping_methods,
1404 $shipping_method_id_class,
1405 $shipping_class
1406 ) {
1407 //support WooCommerce flat rate
1408 // check if we have a match with the predefined methods
1409 if (in_array($shipping_method_id, $package_type_shipping_methods)) {
1410 return true;
1411 }
1412
1413 if (in_array($shipping_method_id_class, $package_type_shipping_methods)) {
1414 return true;
1415 }
1416
1417 // fallback to bare method (without class) (if bare method also defined in settings)
1418 if (! empty($shipping_method_id_class)
1419 && in_array(
1420 $shipping_method_id_class,
1421 $package_type_shipping_methods
1422 )) {
1423 return true;
1424 }
1425
1426 // support WooCommerce Table Rate Shipping by WooCommerce
1427 if (! empty($shipping_class) && in_array($shipping_class, $package_type_shipping_methods)) {
1428 return true;
1429 }
1430
1431 // support WooCommerce Table Rate Shipping by Bolder Elements
1432 $newShippingClass = str_replace(":", "_", $shipping_class);
1433 if (! empty($shipping_class) && in_array($newShippingClass, $package_type_shipping_methods)) {
1434 return true;
1435 }
1436
1437 return false;
1438 }
1439
1440 /**
1441 * Update the status of given order based on the automatic order status settings.
1442 *
1443 * @param WC_Order $order
1444 */
1445 private function updateOrderStatus(WC_Order $order): void
1446 {
1447 if (WCPOST()->setting_collection->isEnabled(WCPOST_Settings::SETTING_ORDER_STATUS_AUTOMATION)) {
1448 $order->update_status(
1449 $this->getSetting(WCPOST_Settings::SETTING_AUTOMATIC_ORDER_STATUS),
1450 __("PostNL shipment created:", "woocommerce-postnl")
1451 );
1452 }
1453 }
1454
1455 /**
1456 * @param array $order_ids
1457 * @param array $shipment_ids
1458 * @param int $offset
1459 *
1460 * @return array
1461 * @throws Exception
1462 */
1463 private function printLabels(array $order_ids, array $shipment_ids, int $offset)
1464 {
1465 if (! empty($shipment_ids)) {
1466 $return = $this->downloadOrGetUrlOfLabels(
1467 $shipment_ids,
1468 $order_ids,
1469 $offset
1470 );
1471 } else {
1472 $order_ids = $this->filterOrderDestinations($order_ids);
1473 $return = $this->getOrderLabels($order_ids, $offset);
1474 }
1475
1476 return $return;
1477 }
1478
1479 /**
1480 * @param $order_ids
1481 * @param $offset
1482 * @param $print
1483 *
1484 * @return array|void
1485 * @throws ApiException
1486 * @throws ErrorException
1487 * @throws MissingFieldException
1488 * @throws Exception
1489 */
1490 private function addShipments($order_ids, $offset, $print)
1491 {
1492 $order_ids = $this->filterOrderDestinations($order_ids);
1493
1494 if (empty($order_ids)) {
1495 $this->errors[] =
1496 __(
1497 "The order(s) you have selected have invalid shipping countries.",
1498 "woocommerce-postnl"
1499 );
1500
1501 return;
1502 }
1503
1504 // if we're going to print directly, we need to process the orders first, regardless of the settings
1505 $process = $print === "yes";
1506 $return = $this->add_shipments($order_ids, $process);
1507
1508 // When adding shipments, store $return for use in admin_notice
1509 // This way we can refresh the page (JS) to show all new buttons
1510 if ($print === "no" || $print === "after_reload") {
1511 update_option("wcpostnl_admin_notices", $return);
1512 if ($print === "after_reload") {
1513 $print_queue = [
1514 "order_ids" => $order_ids,
1515 "shipment_ids" => $return["success_ids"],
1516 "offset" => isset($offset) && is_numeric($offset) ? $offset % 4 : 0,
1517 ];
1518 update_option("wcpostnl_print_queue", $print_queue);
1519 }
1520 }
1521
1522 return $return;
1523 }
1524
1525 /**
1526 * Save created track & trace information as meta data to the corresponding order(s).
1527 *
1528 * @param MyParcelCollection $collection
1529 * @param array $order_ids
1530 */
1531 public static function saveTrackTracesToOrders(MyParcelCollection $collection, array $order_ids): void
1532 {
1533 foreach ($order_ids as $order_id) {
1534 $trackTraces = [];
1535
1536 foreach ($collection->getConsignmentsByReferenceId($order_id) as $consignment) {
1537 /**
1538 * @var AbstractConsignment $consignment
1539 */
1540 array_push($trackTraces, $consignment->getBarcode());
1541 }
1542
1543 WCPN_Export::addTrackTraceNoteToOrder($order_id, $trackTraces);
1544 }
1545 }
1546 }
1547
1548 return new WCPN_Export();
1549