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-wcpost-admin.php

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

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