PluginProbe
PostNL for WooCommerce / 4.0.1
PostNL for WooCommerce v4.0.1
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-wcpost-admin.php

class-wcpost-admin.php in PostNL for WooCommerce 4.0.1, at includes/admin/class-wcpost-admin.php

1,055 lines 35.2 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 as DeliveryOptions;
4 use MyParcelNL\Sdk\src\Factory\DeliveryOptionsAdapterFactory;
5 use MyParcelNL\Sdk\src\Model\Consignment\AbstractConsignment;
6 use WPO\WC\PostNL\Compatibility\Order as WCX_Order;
7 use WPO\WC\PostNL\Compatibility\Product as WCX_Product;
8 use WPO\WC\PostNL\Compatibility\WC_Core as WCX;
9 use WPO\WC\PostNL\Entity\SettingsFieldArguments;
10
11 if (! defined('ABSPATH')) {
12 exit;
13 } // Exit if accessed directly
14
15 if (class_exists('WCPOST_Admin')) {
16 return new WCPOST_Admin();
17 }
18
19 /**
20 * Admin options, buttons & data
21 */
22 class WCPOST_Admin
23 {
24 public const META_CONSIGNMENTS = "_postnl_consignments";
25 public const META_CONSIGNMENT_ID = "_postnl_consignment_id";
26 public const META_DELIVERY_OPTIONS = "_postnl_delivery_options";
27 public const META_HIGHEST_SHIPPING_CLASS = "_postnl_highest_shipping_class";
28 public const META_LAST_SHIPMENT_IDS = "_postnl_last_shipment_ids";
29 public const META_ORDER_VERSION = "_postnl_order_version";
30 public const META_ORDER_WEIGHT = "_postnl_order_weight";
31 public const META_PGADDRESS = "_postnl_pgaddress";
32 public const META_SHIPMENTS = "_postnl_shipments";
33 public const META_SHIPMENT_OPTIONS_EXTRA = "_postnl_shipment_options_extra";
34 public const META_TRACK_TRACE = "_postnl_tracktrace";
35 public const META_HS_CODE = "_postnl_hs_code";
36 public const META_HS_CODE_VARIATION = "_postnl_hs_code_variation";
37 public const META_COUNTRY_OF_ORIGIN = "_postnl_country_of_origin";
38 public const META_ORDER_TOTAL = "_order_version";
39
40 /**
41 * Legacy meta keys.
42 */
43 public const META_SHIPMENT_OPTIONS_LT_4_0_0 = "_postnl_shipment_options";
44
45 // Ids referring to shipment statuses.
46 public const ORDER_STATUS_DELIVERED_AT_RECIPIENT = 7;
47 public const ORDER_STATUS_DELIVERED_READY_FOR_PICKUP = 8;
48 public const ORDER_STATUS_DELIVERED_PACKAGE_PICKED_UP = 9;
49
50 public const SHIPMENT_OPTIONS_FORM_NAME = "postnl_options";
51
52 public const BULK_ACTION_EXPORT = "wcpn_export";
53 public const BULK_ACTION_PRINT = "wcpn_print";
54 public const BULK_ACTION_EXPORT_PRINT = "wcpn_export_print";
55
56 public function __construct()
57 {
58 if (is_wp_version_compatible("4.7.0")) {
59 add_action("bulk_actions-edit-shop_order", [$this, "addBulkActions"], 100);
60 } else {
61 add_action("admin_footer", [$this, "bulk_actions"]);
62 }
63
64 add_action("admin_footer", [$this, "renderOffsetDialog"]);
65 add_action("admin_footer", [$this, "renderShipmentOptionsForm"]);
66
67 /**
68 * Orders page
69 * --
70 * showPostNLSettings is on the woocommerce_admin_order_actions_end hook because there is no hook to put it
71 * in the shipping address column... It is put in the right place after loading using JavaScript.
72 *
73 * @see wcpn-admin.js -> runTriggers()
74 */
75 add_action("woocommerce_admin_order_actions_end", [$this, "showPostNLSettings"], 9999);
76 add_action("woocommerce_admin_order_actions_end", [$this, "showOrderActions"], 20);
77
78 /*
79 * Single order page
80 */
81 add_action("add_meta_boxes_shop_order", [$this, "add_order_meta_box"]);
82 add_action("woocommerce_admin_order_data_after_shipping_address", [$this, "single_order_shipment_options"]);
83
84 add_action("wp_ajax_wcpn_save_shipment_options", [$this, "save_shipment_options_ajax"]);
85 add_action("wp_ajax_wcpn_get_shipment_summary_status", [$this, "order_list_ajax_get_shipment_summary"]);
86 add_action("wp_ajax_wcpn_get_shipment_options", [$this, "ajaxGetShipmentOptions"]);
87
88 // HS code in product shipping options tab
89 add_action("woocommerce_product_options_shipping", [$this, "productHsCodeField"]);
90 add_action("woocommerce_process_product_meta", [$this, "productHsCodeFieldSave"]);
91
92 // Country of Origin in product shipping options tab
93 add_action("woocommerce_product_options_shipping", [$this, "productCountryOfOriginField"]);
94 add_action("woocommerce_process_product_meta", [$this, "productCountryOfOriginFieldSave"]);
95
96 // Add barcode in order grid
97 add_filter("manage_edit-shop_order_columns", [$this, "barcode_add_new_order_admin_list_column"], 10, 1);
98 add_action("manage_shop_order_posts_custom_column", [$this, "addBarcodeToOrderColumn"], 10, 2);
99
100 // Auto export when order is paid
101 // add_action('woocommerce_payment_complete', [$this, 'automaticExportOrder'], 1000);
102
103 add_action("init", [$this, "registerDeliveredPostStatus"], 10, 1);
104 add_filter("wc_order_statuses", [$this, "displayDeliveredPostStatus"], 10, 2);
105
106 add_action('woocommerce_product_after_variable_attributes', array($this, 'variation_hs_code_field'), 10, 3);
107 add_action('woocommerce_save_product_variation', array($this, 'save_variation_hs_code_field'), 10, 2);
108 add_filter('woocommerce_available_variation', array($this, 'load_variation_hs_code_field'), 10, 1);
109 }
110
111 /**
112 * @param \MyParcelNL\Sdk\src\Adapter\DeliveryOptions\AbstractDeliveryOptionsAdapter $deliveryOptions
113 */
114 public static function renderPickupLocation(DeliveryOptions $deliveryOptions): void
115 {
116 if (! $deliveryOptions->isPickup()) {
117 return;
118 }
119
120 $pickup = $deliveryOptions->getPickupLocation();
121
122 printf(
123 "<div class=\"pickup-location\"><strong>%s:</strong><br /> %s<br />%s %s<br />%s %s</div>",
124 __("Pickup location", "woocommerce-postnl"),
125 $pickup->getLocationName(),
126 $pickup->getStreet(),
127 $pickup->getNumber(),
128 $pickup->getPostalCode(),
129 $pickup->getCity()
130 );
131
132 echo "<hr>";
133 }
134
135 /**
136 * @param $loop
137 * @param $variationData
138 * @param $variation
139 */
140 public function variation_hs_code_field($loop, $variationData, $variation)
141 {
142 woocommerce_wp_text_input(
143 array(
144 'id' => self::META_HS_CODE_VARIATION . "[{$loop}]",
145 'name' => self::META_HS_CODE_VARIATION . "[{$loop}]",
146 'value' => get_post_meta($variation->ID, self::META_HS_CODE_VARIATION, true),
147 'label' => __('HS Code', 'woocommerce'),
148 'desc_tip' => true,
149 'description' => __('This HS Code overwrites the parents HS Code.', 'woocommerce'),
150 'wrapper_class' => 'form-row form-row-full',
151 )
152 );
153 }
154
155 /**
156 * @param $variationId
157 * @param $loop
158 */
159 public function save_variation_hs_code_field($variationId, $loop)
160 {
161 $hsCodeValue = $_POST[self::META_HS_CODE_VARIATION][$loop];
162
163 if (! empty($hsCodeValue)) {
164 update_post_meta($variationId, self::META_HS_CODE_VARIATION, esc_attr($hsCodeValue));
165 }
166 }
167
168 /**
169 * @param $variation
170 *
171 * @return mixed
172 */
173 public function load_variation_hs_code_field($variation)
174 {
175 $variation[self::META_HS_CODE_VARIATION] = get_post_meta($variation['variation_id'], self::META_HS_CODE_VARIATION, true);
176
177 return $variation;
178 }
179
180 /**
181 * Add delivered post type to order statuses list
182 */
183 public function registerDeliveredPostStatus(): void
184 {
185 register_post_status('wc-custom-delivered',
186 [
187 'label' => 'Delivered',
188 'public' => true,
189 'exclude_from_search' => false,
190 'show_in_admin_all_list' => true,
191 'show_in_admin_status_list' => true,
192 'label_count' => _n_noop('Delivered (%s)', 'Delivered (%s)'),
193 ]
194 );
195 }
196
197 /**
198 * @param array $order_statuses
199 *
200 * @return array
201 */
202 public function displayDeliveredPostStatus(array $order_statuses): array
203 {
204 $new_order_statuses = [];
205
206 foreach ($order_statuses as $key => $status) {
207 $new_order_statuses[$key] = $status;
208
209 if ('wc-processing' === $key) {
210 $new_order_statuses['wc-custom-delivered'] = 'Delivered';
211 }
212 }
213
214 return $new_order_statuses;
215 }
216
217 /**
218 * @param $orderId
219 *
220 * @throws ErrorException
221 * @throws \MyParcelNL\Sdk\src\Exception\ApiException
222 * @throws \use MyParcelNL\Sdk\src\Exception\MissingFieldException
223 */
224 public function automaticExportOrder($orderId): void
225 {
226 (new WCPN_Export())->exportByOrderId($orderId);
227 }
228
229 /**
230 * @param WC_Order $order
231 *
232 * @throws Exception
233 */
234 public function showPostNLSettings(WC_Order $order): void
235 {
236 $isAllowedDestination = WCPN_Country_Codes::isAllowedDestination(WCX_Order::get_prop($order, 'shipping_country'));
237
238 if (! $isAllowedDestination) {
239 return;
240 }
241
242 $order_id = WCX_Order::get_id($order);
243 $consignments = WCPOST_Admin::get_order_shipments($order);
244 $deliveryOptions = self::getDeliveryOptionsFromOrder($order);
245 $packageType = $deliveryOptions->getPackageType() ?? WCPOST()->export->getPackageTypeFromOrder($order);
246
247 echo '<div class="wcpn__shipment-settings-wrapper" style="display: none;">';
248
249 $this->printDeliveryDate($deliveryOptions);
250
251 // if we have shipments, then we show status & link to Track & Trace, settings under i
252 if (! empty($consignments)) :
253 // only use last shipment
254 $lastShipment = array_pop($consignments);
255 $lastShipmentId = $lastShipment['shipment_id'];
256
257 ?>
258 <a class="wcpn__shipment-summary__show">
259 <span class="wcpn__encircle wcpn__shipment-summary__show">i</span>
260 </a>
261 <div
262 class="wcpn__box wcpn__shipment-summary__list"
263 data-loaded=""
264 data-shipment_id="<?php echo $lastShipmentId; ?>"
265 data-order_id="<?php echo $order_id; ?>"
266 style="display: none;">
267 <?php self::renderSpinner(); ?>
268 </div>
269 <?php endif;
270
271 printf(
272 '<a href="#" class="wcpn__shipment-options__show" data-order-id="%d">%s &#x25BE;</a>',
273 $order->get_id(),
274 WCPN_Export::getPackageTypeHuman($packageType)
275 );
276
277 echo "</div>";
278 }
279
280 /**
281 * Get shipment status + Track & Trace link via AJAX
282 *
283 * @throws Exception
284 */
285 public function order_list_ajax_get_shipment_summary()
286 {
287 check_ajax_referer(WCPOST::NONCE_ACTION, 'security');
288
289 include('views/html-order-shipment-summary.php');
290 die();
291 }
292
293 /**
294 * Add export option to bulk action drop down menu.
295 *
296 * @param array $actions
297 *
298 * @return array
299 * @since WordPress 4.7.0
300 */
301 public function addBulkActions(array $actions): array
302 {
303 $actions = array_merge(
304 $actions,
305 [
306 self::BULK_ACTION_EXPORT => __("PostNL: Prepare shipment", "woocommerce-postnl"),
307 self::BULK_ACTION_PRINT => __("PostNL: Print", "woocommerce-postnl"),
308 self::BULK_ACTION_EXPORT_PRINT => __("PostNL: Prepare & print shipment", "woocommerce-postnl"),
309 ]
310 );
311
312 self::renderSpinner('bulkAction');
313
314 return $actions;
315 }
316
317 /**
318 * Add export option to bulk action drop down menu
319 * Using Javascript until WordPress core fixes: http://core.trac.wordpress.org/ticket/16031
320 *
321 * Used pre WordPress 4.7.0
322 *
323 * @access public
324 * @return void
325 */
326 public function bulk_actions()
327 {
328 global $post_type;
329 $bulk_actions = [
330 self::BULK_ACTION_EXPORT => __("PostNL: Prepare shipment", "woocommerce-postnl"),
331 self::BULK_ACTION_PRINT => __("PostNL: Print", "woocommerce-postnl"),
332 self::BULK_ACTION_EXPORT_PRINT => __("PostNL: Prepare & print shipment", "woocommerce-postnl"),
333 ];
334
335 if ('shop_order' == $post_type) {
336 ?>
337 <script type="text/javascript">
338 jQuery(document).ready(function () {
339 <?php foreach ($bulk_actions as $action => $title) { ?>
340 jQuery('<option>')
341 .val('<?php echo $action; ?>')
342 .html('<?php echo esc_attr($title); ?>')
343 .appendTo('select[name=\'action\'], select[name=\'action2\']');
344 <?php } ?>
345 });
346 </script>
347 <?php
348 self::renderSpinner();
349 }
350 }
351
352 /**
353 * Show dialog to choose print position (offset)
354 *
355 * @access public
356 * @return void
357 */
358 public function renderOffsetDialog(): void
359 {
360 if (! WCPOST()->setting_collection->isEnabled(WCPOST_Settings::SETTING_ASK_FOR_PRINT_POSITION)) {
361 return;
362 }
363
364 $field = [
365 "name" => "offset",
366 "class" => ["wcpn__d--inline-block"],
367 "input_class" => ["wcpn__offset-dialog__offset"],
368 "type" => "number",
369 "label" => __("Labels to skip", "woocommerce-postnl"),
370 "custom_attributes" => [
371 "step" => "1",
372 "min" => "0",
373 "max" => "4",
374 "size" => "2",
375 ],
376 ];
377
378 $class = new SettingsFieldArguments($field);
379 ?>
380
381 <div
382 class="wcpn wcpn__box wcpn__offset-dialog"
383 style="display: none;">
384 <div class="wcpn__offset-dialog__inner wcpn__d--flex">
385 <div>
386 <?php woocommerce_form_field($field["name"], $class->getArguments(false), ""); ?>
387
388 <img
389 src="<?php echo WCPOST()->plugin_url() . "/assets/img/print-offset-icon.png"; ?>"
390 alt="<?php implode(", ", WCPN_Export::DEFAULT_POSITIONS) ?>"
391 class="wcpn__offset-dialog__icon"/>
392 <div>
393 <a
394 href="#"
395 class="wcpn__action wcpn__offset-dialog__button button">
396 <?php _e("Print", "woocommerce-postnl"); ?>
397 <?php WCPOST_Admin::renderSpinner(); ?>
398 </a>
399 </div>
400 </div>
401 <div class="wcpn__close-button dashicons dashicons-no-alt wcpn__offset-dialog__close"></div>
402 </div>
403 </div>
404 <?php
405 }
406
407 /**
408 * Hide an empty shipment options form in the footer.
409 */
410 public function renderShipmentOptionsForm(): void
411 {
412 echo '<div class="wcpn__box wcpn__shipment-options-dialog" style="display: none; position: absolute;"></div>';
413 }
414
415 /**
416 * Get the new html content for the shipment options form based on the passed order id.
417 */
418 public function ajaxGetShipmentOptions(): void
419 {
420 // Order is used in views/html-order-shipment-options.php
421 $order = wc_get_order((int) $_POST['orderId']);
422
423 include('views/html-order-shipment-options.php');
424
425 die();
426 }
427
428 /**
429 * Add print actions to the orders listing
430 *
431 * @param $order
432 *
433 * @throws Exception
434 */
435 public function showOrderActions($order): void
436 {
437 if (empty($order)) {
438 return;
439 }
440
441 $shipping_country = WCX_Order::get_prop($order, 'shipping_country');
442
443 if (! WCPN_Country_Codes::isAllowedDestination($shipping_country)) {
444 return;
445 }
446
447 $order_id = WCX_Order::get_id($order);
448
449 $baseUrl = "admin-ajax.php?action=" . WCPN_Export::EXPORT;
450 $addShipments = WCPN_Export::ADD_SHIPMENTS;
451 $getLabels = WCPN_Export::GET_LABELS;
452
453 $listing_actions = [
454 $addShipments => [
455 "url" => admin_url("$baseUrl&request=$addShipments&order_ids=$order_id"),
456 "img" => WCPOST()->plugin_url() . "/assets/img/postnl-up.png",
457 "alt" => __("Export to PostNL", "woocommerce-postnl"),
458 ],
459 $getLabels => [
460 "url" => admin_url("$baseUrl&request=$getLabels&order_ids=$order_id"),
461 "img" => WCPOST()->plugin_url() . "/assets/img/postnl-pdf.png",
462 "alt" => __("Print PostNL label", "woocommerce-postnl"),
463 ],
464 ];
465
466 $consignments = WCPOST_Admin::get_order_shipments($order);
467
468 if (empty($consignments)) {
469 unset($listing_actions[$getLabels]);
470 }
471
472 $display = WCPOST()->setting_collection->getByName(WCPOST_Settings::SETTING_DOWNLOAD_DISPLAY) === 'display';
473
474 $attributes = [];
475
476 if ($display) {
477 $attributes["target"] = "_blank";
478 }
479
480 foreach ($listing_actions as $request => $data) {
481 self::renderAction(
482 $data['url'],
483 $data['alt'],
484 $data["img"],
485 $attributes
486 );
487 }
488 }
489
490 /**
491 * @param WC_Order $order
492 * @param bool $exclude_concepts
493 *
494 * @return array
495 */
496 public static function get_order_shipments(WC_Order $order, bool $exclude_concepts = false): array
497 {
498 $consignments = WCX_Order::get_meta($order, self::META_SHIPMENTS);
499
500 // fallback to legacy consignment data (v1.X)
501 if (empty($consignments)) {
502 if ($consignment_id = WCX_Order::get_meta($order, self::META_CONSIGNMENT_ID)) {
503 $consignments = [
504 [
505 "shipment_id" => $consignment_id,
506 "track_trace" => WCX_Order::get_meta($order, self::META_TRACK_TRACE),
507 ],
508 ];
509 } elseif ($legacy_consignments = WCX_Order::get_meta($order, self::META_CONSIGNMENTS)) {
510 $consignments = [];
511 foreach ($legacy_consignments as $consignment) {
512 if (isset($consignment["consignment_id"])) {
513 $consignments[] = [
514 "shipment_id" => $consignment["consignment_id"],
515 "track_trace" => $consignment["track_trace"],
516 ];
517 }
518 }
519 }
520 }
521
522 if (empty($consignments) || ! is_array($consignments)) {
523 return [];
524 }
525
526 /**
527 * Filter out concepts.
528 */
529 if ($exclude_concepts) {
530 $consignments = array_filter($consignments,
531 function($consignment) {
532 return isset($consignment["track_trace"]);
533 }
534 );
535 }
536
537 return $consignments;
538 }
539
540 /**
541 * On saving shipment options from the bulk options form.
542 *
543 * @throws Exception
544 * @see admin/views/html-order-shipment-options.php
545 */
546 public function save_shipment_options_ajax(): void
547 {
548 parse_str($_POST["form_data"], $form_data);
549
550 foreach ($form_data[self::SHIPMENT_OPTIONS_FORM_NAME] as $order_id => $data) {
551 $order = WCX::get_order($order_id);
552 $shippingCountry = $order->get_shipping_country();
553 $data = self::removeDisallowedDeliveryOptions($data, $shippingCountry);
554 $deliveryOptions = self::getDeliveryOptionsFromOrder($order, $data);
555
556 error_log(
557 sprintf(
558 "Saving delivery options for order %d\n%s",
559 $order_id,
560 json_encode(
561 $deliveryOptions->toArray(),
562 JSON_PRETTY_PRINT
563 )
564 )
565 );
566
567 WCX_Order::update_meta_data(
568 $order,
569 self::META_DELIVERY_OPTIONS,
570 $deliveryOptions
571 );
572
573 // Save extra options
574 WCX_Order::update_meta_data(
575 $order,
576 self::META_SHIPMENT_OPTIONS_EXTRA,
577 $data["extra_options"]
578 );
579 }
580
581 die();
582 }
583
584 /**
585 * Add the meta box on the single order page
586 */
587 public function add_order_meta_box(): void
588 {
589 add_meta_box(
590 "postnl",
591 __("PostNL", "woocommerce-postnl"),
592 [$this, "createMetaBox"],
593 "shop_order",
594 "side",
595 "default"
596 );
597 }
598
599 /**
600 * Callback: Create the meta box content on the single order page
601 *
602 * @throws Exception
603 */
604 public function createMetaBox(): void
605 {
606 global $post_id;
607 // get order
608 $order = WCX::get_order($post_id);
609
610 if (! $order) {
611 return;
612 }
613
614 $order_id = WCX_Order::get_id($order);
615
616 $shipping_country = WCX_Order::get_prop($order, 'shipping_country');
617 if (! WCPN_Country_Codes::isAllowedDestination($shipping_country)) {
618 return;
619 }
620
621 $class = version_compare(WOOCOMMERCE_VERSION, '3.3.0', '>=') ? "single_wc_actions" : "single_order_actions";
622 // show buttons and check if WooCommerce > 3.3.0 is used and select the correct function and class
623 echo "<div class=\"$class\">";
624 $this->showOrderActions($order);
625 echo '</div>';
626
627 $downloadDisplay = WCPOST()->setting_collection->getByName(WCPOST_Settings::SETTING_DOWNLOAD_DISPLAY) === 'display';
628 $consignments = WCPOST_Admin::get_order_shipments($order);
629
630 // show shipments if available
631 if (empty($consignments)) {
632 return;
633 }
634
635 include('views/html-order-track-trace-table.php');
636 }
637
638 /**
639 * @param $order
640 *
641 * @throws Exception
642 */
643 public function single_order_shipment_options(WC_Order $order)
644 {
645 $shipping_country = WCX_Order::get_prop($order, "shipping_country");
646
647 if (! WCPN_Country_Codes::isAllowedDestination($shipping_country)) {
648 return;
649 }
650
651 $this->showPostNLSettings($order);
652 }
653
654 /**
655 * @param \WC_Order $order
656 *
657 * @throws \Exception
658 */
659 public function showDeliveryDateForOrder(WC_Order $order): void
660 {
661 $deliveryOptions = self::getDeliveryOptionsFromOrder($order);
662 $this->printDeliveryDate($deliveryOptions);
663 }
664
665 /**
666 * @param $order_id
667 * @param $track_trace
668 *
669 * @return string|void
670 * @throws Exception
671 */
672 public static function getTrackTraceUrl($order_id, $track_trace)
673 {
674 if (empty($order_id)) {
675 return;
676 }
677
678 $order = WCX::get_order($order_id);
679 $country = WCX_Order::get_prop($order, 'shipping_country');
680 $postcode = preg_replace('/\s+/', '', WCX_Order::get_prop($order, 'shipping_postcode'));
681
682 // set url for NL or foreign orders
683 if ($country === 'NL') {
684 $deliveryOptions = self::getDeliveryOptionsFromOrder($order);
685
686 // use billing postcode for pickup/pakjegemak
687 if ($deliveryOptions->isPickup()) {
688 $postcode = preg_replace('/\s+/', '', WCX_Order::get_prop($order, 'billing_postcode'));
689 }
690
691 $trackTraceUrl = sprintf(
692 'https://jouw.postnl.nl/track-and-trace/%s/%s/%s',
693 $track_trace,
694 $country,
695 $postcode
696 );
697 } else {
698 $trackTraceUrl = sprintf(
699 'https://www.internationalparceltracking.com/Main.aspx#/track/%s/%s/%s',
700 $track_trace,
701 $country,
702 $postcode
703 );
704 }
705
706 return $trackTraceUrl;
707 }
708
709 public function productHsCodeField()
710 {
711 echo '<div class="options_group">';
712 woocommerce_wp_text_input(
713 [
714 'id' => self::META_HS_CODE,
715 'label' => __('HS Code', 'woocommerce-postnl'),
716 'description' => sprintf(
717 __('HS Codes are used for PostNL world shipments, you can find the appropriate code on the %ssite of the Dutch Customs%s.',
718 'woocommerce-postnl'
719 ),
720 '<a href="http://tarief.douane.nl/arctictariff-public-web/#!/home" target="_blank">',
721 '</a>'
722 ),
723 ]
724 );
725 echo '</div>';
726 }
727
728 public function productHsCodeFieldSave($post_id)
729 {
730 // check if hs code is passed and not an array (=variation hs code)
731 if (isset($_POST[self::META_HS_CODE]) && ! is_array($_POST[self::META_HS_CODE])) {
732 $product = wc_get_product($post_id);
733 $hs_code = $_POST[self::META_HS_CODE];
734 if (! empty($hs_code)) {
735 WCX_Product::update_meta_data($product, self::META_HS_CODE, esc_attr($hs_code));
736 } else {
737 if (isset($_POST[self::META_HS_CODE]) && empty($hs_code)) {
738 WCX_Product::delete_meta_data($product, self::META_HS_CODE);
739 }
740 }
741 }
742 }
743
744 public function productCountryOfOriginField()
745 {
746 echo '<div class="options_group">';
747 woocommerce_wp_text_input(
748 [
749 'id' => self::META_COUNTRY_OF_ORIGIN,
750 'label' => __('Country of Origin', 'woocommerce-postnl'),
751 'type' => 'select',
752 'options' => (new WC_Countries())->get_countries(),
753 'description' => sprintf(
754 __('Country of origin is required for world shipments. Defaults to shop base.')
755 ),
756 ]
757 );
758 echo '</div>';
759 }
760
761 public function productCountryOfOriginFieldSave($postId)
762 {
763 if (isset($_POST[self::META_COUNTRY_OF_ORIGIN]) && ! is_array($_POST[self::META_COUNTRY_OF_ORIGIN])) {
764 $product = wc_get_product($postId);
765 $countryOfOrigin = $_POST[self::META_COUNTRY_OF_ORIGIN];
766 if (! empty($countryOfOrigin)) {
767 WCX_Product::update_meta_data($product, self::META_COUNTRY_OF_ORIGIN, esc_attr($countryOfOrigin));
768
769 return;
770 }
771 if (isset($_POST[self::META_COUNTRY_OF_ORIGIN]) && empty($countryOfOrigin)) {
772 WCX_Product::delete_meta_data($product, self::META_COUNTRY_OF_ORIGIN);
773 }
774 }
775
776 return;
777 }
778
779 /**
780 * @snippet Add Column to Orders Table (e.g. Barcode) - WooCommerce
781 *
782 * @param $columns
783 *
784 * @return mixed
785 */
786 public function barcode_add_new_order_admin_list_column($columns)
787 {
788 // I want to display Barcode column just after the date column
789 return array_slice($columns, 0, 6, true) + ['barcode' => 'Barcode'] + array_slice($columns, 6, null, true);
790 }
791
792 /**
793 * @param $column
794 *
795 * @throws Exception
796 */
797 public function addBarcodeToOrderColumn($column)
798 {
799 global $post;
800
801 if ("barcode" === $column) {
802 $this->renderBarcodes(WCX::get_order($post->ID));
803 }
804 }
805
806 /**
807 * @param WC_Order $order
808 *
809 * @return void
810 * @throws Exception
811 */
812 public function renderBarcodes(WC_Order $order): void
813 {
814 $shipments = WCPOST_Admin::get_order_shipments($order, true);
815
816 if (empty($shipments)) {
817 echo __("No label has been created yet.", "woocommerce-postnl");
818
819 return;
820 }
821
822 echo '<div class="wcpn__barcodes">';
823 foreach ($shipments as $shipment_id => $shipment) {
824 if (empty($shipment["track_trace"])) {
825 echo __("Concept created but not printed.", "woocommerce-postnl");
826 } else {
827 printf(
828 '<a target="_blank" class="wcpn__barcode-link" title="%2$s" href="%1$s">%2$s</a><br>',
829 WCPOST_Admin::getTrackTraceUrl($order, $shipment["track_trace"]),
830 $shipment["track_trace"]
831 );
832 }
833 }
834 echo "</div>";
835 }
836
837 /**
838 * Get DeliveryOptions object from the given order's meta data. Uses legacy delivery options if found, if that
839 * data is invalid it falls back to defaults.
840 *
841 * @param WC_Order $order
842 * @param array $inputData
843 *
844 * @return DeliveryOptions
845 * @throws \Exception
846 * @see \WCPN_Checkout::save_delivery_options
847 */
848 public static function getDeliveryOptionsFromOrder(WC_Order $order, array $inputData = []): DeliveryOptions
849 {
850 $meta = WCX_Order::get_meta($order, self::META_DELIVERY_OPTIONS) ?: null;
851
852 // $meta is a json string, create an instance
853 if (! empty($meta) && ! $meta instanceof DeliveryOptions) {
854 if (is_string($meta)) {
855 $meta = json_decode(stripslashes($meta), true);
856 }
857
858 $meta["carrier"] = WCPN_Data::DEFAULT_CARRIER;
859
860 try {
861 // create new instance from known json
862 $meta = DeliveryOptionsAdapterFactory::create((array) $meta);
863 } catch (BadMethodCallException $e) {
864 // create new instance from unknown json data
865 $meta = new WCPN_DeliveryOptionsFromOrderAdapter(null, (array) $meta);
866 }
867 }
868
869 // Create or update immutable adapter from order with a instanceof DeliveryOptionsAdapter
870 if (empty($meta) || ! empty($inputData)) {
871 $meta = new WCPN_DeliveryOptionsFromOrderAdapter($meta, $inputData);
872 }
873
874 return $meta;
875 }
876
877 /**
878 * Output the delivery date if there is a date and the show delivery day setting is enabled.
879 *
880 * @param DeliveryOptions $deliveryOptions
881 *
882 * @throws Exception
883 */
884 private function printDeliveryDate(DeliveryOptions $deliveryOptions): void
885 {
886 $showDeliveryDay = WCPOST()->setting_collection->isEnabled(WCPOST_Settings::SETTING_SHOW_DELIVERY_DAY);
887
888 if ($deliveryOptions->getDate() && $showDeliveryDay) {
889 printf(
890 '<div class="delivery-date"><strong>%s</strong><br />%s, %s</div>',
891 __("PostNL shipment:", "woocommerce-postnl"),
892 WCPN_Data::getDeliveryTypesHuman()[$deliveryOptions->getDeliveryType()],
893 wc_format_datetime(new WC_DateTime($deliveryOptions->getDate()), 'D d-m')
894 );
895 }
896 }
897
898 /**
899 * Output a spinner.
900 *
901 * @param string $state
902 * @param array $args
903 */
904 public static function renderSpinner(string $state = "", array $args = []): void
905 {
906 $spinners = [
907 "loading" => get_site_url() . "/wp-admin/images/spinner.gif",
908 "success" => get_site_url() . "/wp-admin/images/yes.png",
909 "failed" => get_site_url() . "/wp-admin/images/no.png",
910 ];
911
912 $arguments = [];
913
914 $args["class"][] = "wcpn__spinner";
915
916 if ($state) {
917 $args["class"][] = "wcpn__spinner--$state";
918 }
919
920 foreach ($args as $arg => $value) {
921 if (is_array($value)) {
922 $value = implode(" ", $value);
923 }
924 $arguments[] = "$arg=\"$value\"";
925 }
926
927 $attributes = implode(" ", $arguments);
928
929 echo "<span $attributes>";
930 foreach ($spinners as $spinnerState => $icon) {
931 printf(
932 '<img class="wcpn__spinner__%1$s" alt="%1$s" src="%2$s" style="display: %3$s;" />',
933 $spinnerState,
934 $icon,
935 $state === $spinnerState ? "block" : "none"
936 );
937 }
938 echo '</span>';
939 }
940
941 /**
942 * @param string $url
943 * @param string $alt
944 * @param string $icon
945 * @param array $rawAttributes
946 */
947 public static function renderAction(string $url, string $alt, string $icon, array $rawAttributes = []): void
948 {
949 printf(
950 '<a href="%1$s"
951 class="button tips wcpn__action wcpn__d--flex"
952 data-tip="%2$s"
953 %4$s>
954 <img class="wcpn__action__img" src="%3$s" alt="%2$s" />',
955 wp_nonce_url($url, WCPOST::NONCE_ACTION),
956 $alt,
957 $icon,
958 wc_implode_html_attributes($rawAttributes)
959 );
960
961 self::renderSpinner();
962 echo "</a>";
963 }
964
965 /**
966 * @param array $shipment
967 * @param int $order_id
968 *
969 * @throws Exception
970 */
971 public static function renderTrackTraceLink(array $shipment, int $order_id): void
972 {
973 $track_trace = $shipment["track_trace"] ?? null;
974
975 if ($track_trace) {
976 $track_trace_url = WCPOST_Admin::getTrackTraceUrl($order_id, $track_trace);
977 $track_trace_link = sprintf(
978 '<a href="%s" target="_blank">%s</a>',
979 $track_trace_url,
980 $track_trace
981 );
982 } elseif (isset($shipment["shipment"]) && isset($shipment["shipment"]["options"])) {
983 $package_type = WCPN_Export::getPackageTypeHuman($shipment["shipment"]["options"]["package_type"]);
984 $track_trace_link = "($package_type)";
985 } else {
986 $track_trace_link = __("(Unknown)", "woocommerce-postnl");
987 }
988
989 echo $track_trace_link;
990 }
991
992 /**
993 * @param array $shipment
994 * @param int $order_id
995 */
996 public static function renderStatus(array $shipment, int $order_id): void
997 {
998 echo $shipment["status"] ?? "–";
999
1000 if (self::shipmentIsStatus($shipment, self::ORDER_STATUS_DELIVERED_AT_RECIPIENT)
1001 || self::shipmentIsStatus($shipment, self::ORDER_STATUS_DELIVERED_READY_FOR_PICKUP)
1002 || self::shipmentIsStatus($shipment, self::ORDER_STATUS_DELIVERED_PACKAGE_PICKED_UP)
1003 ) {
1004 $order = WCX::get_order($order_id);
1005 // This will be addressed in MY-24881
1006 // $order->update_status('wc-custom-delivered');
1007 }
1008 }
1009
1010 /**
1011 * @param array $shipment
1012 * @param int $status
1013 *
1014 * @return bool
1015 */
1016 public static function shipmentIsStatus(array $shipment, int $status): bool
1017 {
1018 return strstr($shipment['status'], (new WCPN_Export())->getShipmentStatusName($status));
1019 }
1020
1021 /**
1022 * Remove options that aren't allowed and return the edited array.
1023 *
1024 * @param array $data
1025 * @param string $country
1026 *
1027 * @return mixed
1028 */
1029 private static function removeDisallowedDeliveryOptions(array $data, string $country): array
1030 {
1031 $data['package_type'] = $data['package_type'] ?? AbstractConsignment::DEFAULT_PACKAGE_TYPE_NAME;
1032 $isHomeCountry = WCPN_Data::isHomeCountry($country);
1033 $isEuCountry = WCPN_Country_Codes::isEuCountry($country);
1034
1035 if (! $isHomeCountry) {
1036 $data['package_type'] = AbstractConsignment::DEFAULT_PACKAGE_TYPE_NAME;
1037 }
1038
1039 $isPackage = AbstractConsignment::PACKAGE_TYPE_PACKAGE_NAME === $data['package_type'];
1040
1041 if (! $isHomeCountry || ! $isPackage) {
1042 $data['shipment_options']['age_check'] = false;
1043 $data['shipment_options']['return_shipment'] = false;
1044 $data['shipment_options']['insured'] = false;
1045 $data['shipment_options']['insured_amount'] = 0;
1046
1047 $data['extra_options']['collo_amount'] = 1;
1048 }
1049
1050 return $data;
1051 }
1052 }
1053
1054 return new WCPOST_Admin();
1055