PluginProbe
PostNL for WooCommerce / 4.4.0
PostNL for WooCommerce v4.4.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.4.0, at includes/admin/class-wcpn-export.php

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