PluginProbe
PostNL for WooCommerce / 4.4.1
PostNL for WooCommerce v4.4.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.4.1, at includes/admin/class-wcpost-admin.php

1,254 lines 42.9 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_RETURN_SHIPMENT_IDS = "_postnl_return_shipment_ids";
30 public const META_ORDER_VERSION = "_postnl_order_version";
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_AGE_CHECK = "_postnl_age_check";
39
40 /**
41 * @deprecated use weight property in META_SHIPMENT_OPTIONS_EXTRA.
42 */
43 public const META_ORDER_WEIGHT = "_postnl_order_weight";
44
45 /**
46 * Legacy meta keys.
47 */
48 public const META_SHIPMENT_OPTIONS_LT_4_0_0 = "_postnl_shipment_options";
49
50 // Ids referring to shipment statuses.
51 public const ORDER_STATUS_DELIVERED_AT_RECIPIENT = 7;
52 public const ORDER_STATUS_DELIVERED_READY_FOR_PICKUP = 8;
53 public const ORDER_STATUS_DELIVERED_PACKAGE_PICKED_UP = 9;
54 public const ORDER_STATUS_PRINTED_LETTER = 12;
55 public const ORDER_STATUS_PRINTED_DIGITAL_STAMP = 14;
56
57 public const SHIPMENT_OPTIONS_FORM_NAME = "postnl_options";
58
59 public const BULK_ACTION_EXPORT = "wcpn_export";
60 public const BULK_ACTION_PRINT = "wcpn_print";
61 public const BULK_ACTION_EXPORT_PRINT = "wcpn_export_print";
62
63 public const PRODUCT_OPTIONS_ENABLED = "yes";
64 public const PRODUCT_OPTIONS_DISABLED = "no";
65
66 public function __construct()
67 {
68 if (is_wp_version_compatible("4.7.0")) {
69 add_action("bulk_actions-edit-shop_order", [$this, "addBulkActions"], 100);
70 } else {
71 add_action("admin_footer", [$this, "bulk_actions"]);
72 }
73
74 add_action("admin_footer", [$this, "renderOffsetDialog"]);
75 add_action("admin_footer", [$this, "renderShipmentOptionsForm"]);
76
77 /**
78 * Orders page
79 * --
80 * showPostNLSettings is on the woocommerce_admin_order_actions_end hook because there is no hook to put it
81 * in the shipping address column... It is put in the right place after loading using JavaScript.
82 *
83 * @see wcpn-admin.js -> runTriggers()
84 */
85 add_action("woocommerce_admin_order_actions_end", [$this, "showPostNLSettings"], 9999);
86 add_action("woocommerce_admin_order_actions_end", [$this, "showOrderActions"], 20);
87
88 /*
89 * Single order page
90 */
91 add_action("add_meta_boxes_shop_order", [$this, "add_order_meta_box"]);
92 add_action("woocommerce_admin_order_data_after_shipping_address", [$this, "single_order_shipment_options"]);
93
94 add_action("wp_ajax_wcpn_save_shipment_options", [$this, "save_shipment_options_ajax"]);
95 add_action("wp_ajax_wcpn_get_shipment_summary_status", [$this, "order_list_ajax_get_shipment_summary"]);
96 add_action("wp_ajax_wcpn_get_shipment_options", [$this, "ajaxGetShipmentOptions"]);
97
98 // Add barcode in order grid
99 add_filter("manage_edit-shop_order_columns", [$this, "barcode_add_new_order_admin_list_column"], 10, 1);
100 add_action("manage_shop_order_posts_custom_column", [$this, "addBarcodeToOrderColumn"], 10, 2);
101
102 add_action('woocommerce_payment_complete', [$this, 'automaticExportOrder'], 1000);
103
104 add_action("init", [$this, "registerDeliveredPostStatus"], 10, 1);
105 add_filter("wc_order_statuses", [$this, "displayDeliveredPostStatus"], 10, 2);
106
107 add_action('woocommerce_product_after_variable_attributes', [$this, 'variation_hs_code_field'], 10, 3);
108 add_action('woocommerce_save_product_variation', [$this, 'save_variation_hs_code_field'], 10, 2);
109 add_filter('woocommerce_available_variation', [$this, 'load_variation_hs_code_field'], 10, 1);
110
111 add_action("woocommerce_product_options_shipping", [$this, "productOptionsFields"]);
112 add_action("woocommerce_process_product_meta", [$this, "productOptionsFieldSave"]);
113 }
114
115 /**
116 * @param \MyParcelNL\Sdk\src\Adapter\DeliveryOptions\AbstractDeliveryOptionsAdapter $deliveryOptions
117 */
118 public static function renderPickupLocation(DeliveryOptions $deliveryOptions): void
119 {
120 $pickup = $deliveryOptions->getPickupLocation();
121
122 if (! $pickup || ! $deliveryOptions->isPickup()) {
123 return;
124 }
125
126 printf(
127 "<div class=\"pickup-location\"><strong>%s:</strong><br /> %s<br />%s %s<br />%s %s</div>",
128 __("Pickup location", "woocommerce-postnl"),
129 $pickup->getLocationName(),
130 $pickup->getStreet(),
131 $pickup->getNumber(),
132 $pickup->getPostalCode(),
133 $pickup->getCity()
134 );
135
136 echo "<hr>";
137 }
138
139 /**
140 * @param $loop
141 * @param $variationData
142 * @param $variation
143 */
144 public function variation_hs_code_field($loop, $variationData, $variation)
145 {
146 woocommerce_wp_text_input(
147 [
148 'id' => self::META_HS_CODE_VARIATION . "[{$loop}]",
149 'name' => self::META_HS_CODE_VARIATION . "[{$loop}]",
150 'value' => get_post_meta($variation->ID, self::META_HS_CODE_VARIATION, true),
151 'label' => __('HS Code', 'woocommerce'),
152 'desc_tip' => true,
153 'description' => __('This HS Code overwrites the parents HS Code.', 'woocommerce'),
154 'wrapper_class' => 'form-row form-row-full',
155 ]
156 );
157 }
158
159 /**
160 * @param $variationId
161 * @param $loop
162 */
163 public function save_variation_hs_code_field($variationId, $loop)
164 {
165 $hsCodeValue = $_POST[self::META_HS_CODE_VARIATION][$loop];
166
167 if (! empty($hsCodeValue)) {
168 update_post_meta($variationId, self::META_HS_CODE_VARIATION, esc_attr($hsCodeValue));
169 }
170 }
171
172 /**
173 * @param $variation
174 *
175 * @return mixed
176 */
177 public function load_variation_hs_code_field($variation)
178 {
179 $variation[self::META_HS_CODE_VARIATION] = get_post_meta($variation['variation_id'], self::META_HS_CODE_VARIATION, true);
180
181 return $variation;
182 }
183
184 /**
185 * Add delivered post type to order statuses list
186 */
187 public function registerDeliveredPostStatus(): void
188 {
189 register_post_status('wc-custom-delivered',
190 [
191 'label' => 'Delivered',
192 'public' => true,
193 'exclude_from_search' => false,
194 'show_in_admin_all_list' => true,
195 'show_in_admin_status_list' => true,
196 'label_count' => _n_noop('Delivered (%s)', 'Delivered (%s)'),
197 ]
198 );
199 }
200
201 /**
202 * @param array $order_statuses
203 *
204 * @return array
205 */
206 public function displayDeliveredPostStatus(array $order_statuses): array
207 {
208 $new_order_statuses = [];
209
210 foreach ($order_statuses as $key => $status) {
211 $new_order_statuses[$key] = $status;
212
213 // if ('wc-processing' === $key) {
214 // $new_order_statuses['wc-custom-delivered'] = 'Delivered';
215 // }
216 }
217
218 return $new_order_statuses;
219 }
220
221 /**
222 * @param $orderId
223 *
224 * @throws ErrorException
225 * @throws \MyParcelNL\Sdk\src\Exception\ApiException
226 * @throws \MyParcelNL\Sdk\src\Exception\MissingFieldException
227 */
228 public function automaticExportOrder($orderId): void
229 {
230 (new WCPN_Export())->exportByOrderId($orderId);
231 }
232
233 /**
234 * @param WC_Order $order
235 *
236 * @throws Exception
237 */
238 public function showPostNLSettings(WC_Order $order): void
239 {
240 $orderSettings = new OrderSettings($order);
241 $isAllowedDestination = WCPN_Country_Codes::isAllowedDestination($orderSettings->getShippingCountry());
242
243 if (! $isAllowedDestination) {
244 return;
245 }
246
247 echo '<div class="wcpn__shipment-settings-wrapper" style="display: none;">';
248 $this->printDeliveryDate($orderSettings->getDeliveryOptions());
249
250 $consignments = self::get_order_shipments($order);
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->get_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_Data::getPackageTypeHuman(
275 $orderSettings->getPackageType() ?? AbstractConsignment::DEFAULT_PACKAGE_TYPE_NAME
276 )
277 );
278
279 echo "</div>";
280 }
281
282 /**
283 * Get shipment status + Track & Trace link via AJAX
284 *
285 * @throws Exception
286 */
287 public function order_list_ajax_get_shipment_summary(): void
288 {
289 check_ajax_referer(WCPOST::NONCE_ACTION, 'security');
290
291 include('views/html-order-shipment-summary.php');
292 die();
293 }
294
295 /**
296 * Add export option to bulk action drop down menu.
297 *
298 * @param array $actions
299 *
300 * @return array
301 * @since WordPress 4.7.0
302 */
303 public function addBulkActions(array $actions): array
304 {
305 $actions = array_merge(
306 $actions,
307 [
308 self::BULK_ACTION_EXPORT => __("bulk_action_export", "woocommerce-postnl"),
309 self::BULK_ACTION_PRINT => __("bulk_action_print", "woocommerce-postnl"),
310 self::BULK_ACTION_EXPORT_PRINT => __("bulk_action_export_print", "woocommerce-postnl"),
311 ]
312 );
313
314 self::renderSpinner('bulkAction');
315
316 return $actions;
317 }
318
319 /**
320 * Add export option to bulk action drop down menu
321 * Using Javascript until WordPress core fixes: http://core.trac.wordpress.org/ticket/16031
322 *
323 * Used pre WordPress 4.7.0
324 *
325 * @access public
326 * @return void
327 */
328 public function bulk_actions()
329 {
330 global $post_type;
331 $bulk_actions = [
332 self::BULK_ACTION_EXPORT => __("PostNL: Export", "woocommerce-postnl"),
333 self::BULK_ACTION_PRINT => __("PostNL: Print", "woocommerce-postnl"),
334 self::BULK_ACTION_EXPORT_PRINT => __("PostNL: Export & Print", "woocommerce-postnl"),
335 ];
336
337 if ('shop_order' == $post_type) {
338 ?>
339 <script type="text/javascript">
340 jQuery(document).ready(function () {
341 <?php foreach ($bulk_actions as $action => $title) { ?>
342 jQuery('<option>')
343 .val('<?php echo $action; ?>')
344 .html('<?php echo esc_attr($title); ?>')
345 .appendTo('select[name=\'action\'], select[name=\'action2\']');
346 <?php } ?>
347 });
348 </script>
349 <?php
350 self::renderSpinner();
351 }
352 }
353
354 /**
355 * Show dialog to choose print position (offset)
356 *
357 * @access public
358 * @return void
359 */
360 public function renderOffsetDialog(): void
361 {
362 if (! WCPOST()->setting_collection->isEnabled(WCPOST_Settings::SETTING_ASK_FOR_PRINT_POSITION)) {
363 return;
364 }
365
366 $field = [
367 "name" => "offset",
368 "class" => ["wcpn__d--inline-block"],
369 "input_class" => ["wcpn__offset-dialog__offset"],
370 "type" => "number",
371 "custom_attributes" => [
372 "step" => "1",
373 "min" => "0",
374 "max" => "4",
375 "size" => "2",
376 ],
377 ];
378
379 $class = new SettingsFieldArguments($field, false);
380 ?>
381
382 <div
383 class="wcpn wcpn__box wcpn__offset-dialog wcpn__ws--nowrap"
384 style="display: none;">
385 <div class="wcpn__offset-dialog__inner wcpn__d--flex">
386 <div>
387 <div class="wcpn__pb--2">
388 <?php printf(
389 '<label for="%s">%s</label>',
390 $class->getId(),
391 __("Labels to skip", "woocommerce-postnl")
392 ); ?>
393 </div>
394 <div class="wcpn__d--flex wcpn__pb--2">
395 <?php woocommerce_form_field($field["name"], $class->getArguments(false), ""); ?>
396 <img
397 src="<?php echo WCPOST()->plugin_url() . "/assets/img/offset.svg"; ?>"
398 alt="<?php implode(", ", WCPN_Export::DEFAULT_POSITIONS) ?>"
399 class="wcpn__offset-dialog__icon wcpn__pl--1"/>
400 </div>
401 <div>
402 <a
403 href="#"
404 class="wcpn__offset-dialog__button button"
405 style="display: none;"
406 target="_blank">
407 <?php _e("Print", "woocommerce-postnl"); ?>
408 <?php self::renderSpinner(); ?>
409 </a>
410 </div>
411 </div>
412 <div class="wcpn__close-button dashicons dashicons-no-alt wcpn__offset-dialog__close wcpn__pl--2"></div>
413 </div>
414 </div>
415 <?php
416 }
417
418 /**
419 * Hide an empty shipment options form in the footer.
420 */
421 public function renderShipmentOptionsForm(): void
422 {
423 echo '<div class="wcpn__box wcpn__shipment-options-dialog" style="display: none; position: absolute;"></div>';
424 }
425
426 /**
427 * Get the new html content for the shipment options form based on the passed order id.
428 */
429 public function ajaxGetShipmentOptions(): void
430 {
431 // Order is used in views/html-order-shipment-options.php
432 $order = wc_get_order((int) $_POST['orderId']);
433
434 include('views/html-order-shipment-options.php');
435
436 die();
437 }
438
439 /**
440 * Add print actions to the orders listing
441 *
442 * @param $order
443 *
444 * @throws Exception
445 */
446 public function showOrderActions($order): void
447 {
448 if (empty($order)) {
449 return;
450 }
451
452 $shipping_country = WCX_Order::get_prop($order, 'shipping_country');
453
454 if (! WCPN_Country_Codes::isAllowedDestination($shipping_country)) {
455 return;
456 }
457
458 $order_id = WCX_Order::get_id($order);
459
460 $baseUrl = "admin-ajax.php?action=" . WCPN_Export::EXPORT;
461 $addShipments = WCPN_Export::ADD_SHIPMENTS;
462 $getLabels = WCPN_Export::GET_LABELS;
463 $addReturn = WCPN_Export::ADD_RETURN;
464
465 $returnShipmentId = $order->get_meta(self::META_RETURN_SHIPMENT_IDS);
466
467 $listing_actions = [
468 $addShipments => [
469 "url" => admin_url("$baseUrl&request=$addShipments&order_ids=$order_id"),
470 "img" => WCPOST()->plugin_url() . "/assets/img/postnl-export.png",
471 "alt" => __("Export to PostNL", "woocommerce-postnl"),
472 ],
473 $getLabels => [
474 "url" => admin_url("$baseUrl&request=$getLabels&order_ids=$order_id"),
475 "img" => WCPOST()->plugin_url() . "/assets/img/postnl-print.png",
476 "alt" => __("Print PostNL label", "woocommerce-postnl"),
477 ],
478 // $addReturn => [
479 // "url" => admin_url("$baseUrl&request=$addReturn&order_ids=$order_id"),
480 // "img" => WCPOST()->plugin_url() . "/assets/img/postnl-return.svg",
481 // "alt" => __("Email return label", "woocommerce-postnl"),
482 // ],
483 ];
484
485 $consignments = self::get_order_shipments($order);
486
487 if (empty($consignments)) {
488 unset($listing_actions[$getLabels]);
489 }
490
491 $processed_shipments = self::get_order_shipments($order);
492 if (empty($processed_shipments) || $shipping_country !== 'NL') {
493 unset($listing_actions[$addReturn]);
494 }
495
496 $display = WCPOST()->setting_collection->getByName(WCPOST_Settings::SETTING_DOWNLOAD_DISPLAY) === 'display';
497 $attributes = [];
498
499 if ($display) {
500 $attributes["target"] = "_blank";
501 }
502
503 foreach ($listing_actions as $request => $data) {
504 self::renderAction(
505 $data['url'],
506 $data['alt'],
507 $data["img"],
508 $attributes
509 );
510 }
511 }
512
513 /**
514 * @param WC_Order $order
515 * @param bool $exclude_concepts
516 *
517 * @return array
518 */
519 public static function get_order_shipments(WC_Order $order, bool $exclude_concepts = false): array
520 {
521 $consignments = WCX_Order::get_meta($order, self::META_SHIPMENTS);
522
523 // fallback to legacy consignment data (v1.X)
524 if (empty($consignments)) {
525 if ($consignment_id = WCX_Order::get_meta($order, self::META_CONSIGNMENT_ID)) {
526 $consignments = [
527 [
528 "shipment_id" => $consignment_id,
529 "track_trace" => WCX_Order::get_meta($order, self::META_TRACK_TRACE),
530 ],
531 ];
532 } elseif ($legacy_consignments = WCX_Order::get_meta($order, self::META_CONSIGNMENTS)) {
533 $consignments = [];
534 foreach ($legacy_consignments as $consignment) {
535 if (isset($consignment["consignment_id"])) {
536 $consignments[] = [
537 "shipment_id" => $consignment["consignment_id"],
538 "track_trace" => $consignment["track_trace"],
539 ];
540 }
541 }
542 }
543 }
544
545 if (empty($consignments) || ! is_array($consignments)) {
546 return [];
547 }
548
549 /**
550 * Filter out concepts.
551 */
552 if ($exclude_concepts) {
553 $consignments = array_filter($consignments,
554 function($consignment) {
555 return isset($consignment["track_trace"]);
556 }
557 );
558 }
559
560 return $consignments;
561 }
562
563 /**
564 * On saving shipment options from the bulk options form.
565 *
566 * @throws Exception
567 * @see admin/views/html-order-shipment-options.php
568 */
569 public function save_shipment_options_ajax(): void
570 {
571 parse_str($_POST["form_data"], $form_data);
572
573 foreach ($form_data[self::SHIPMENT_OPTIONS_FORM_NAME] as $order_id => $data) {
574 $order = WCX::get_order($order_id);
575 $data = self::removeDisallowedDeliveryOptions($data, $order->get_shipping_country());
576 $orderSettings = new OrderSettings($order, $data);
577
578 WCX_Order::update_meta_data(
579 $order,
580 self::META_DELIVERY_OPTIONS,
581 $orderSettings->getDeliveryOptions()->toArray()
582 );
583
584 // Save extra options
585 WCX_Order::update_meta_data(
586 $order,
587 self::META_SHIPMENT_OPTIONS_EXTRA,
588 array_merge(
589 $orderSettings->getExtraOptions(),
590 $data["extra_options"]
591 )
592 );
593 }
594
595 die();
596 }
597
598 /**
599 * Add the meta box on the single order page
600 */
601 public function add_order_meta_box(): void
602 {
603 add_meta_box(
604 "postnl",
605 __("PostNL", "woocommerce-postnl"),
606 [$this, "createMetaBox"],
607 "shop_order",
608 "side",
609 "default"
610 );
611 }
612
613 /**
614 * Callback: Create the meta box content on the single order page
615 *
616 * @throws Exception
617 */
618 public function createMetaBox(): void
619 {
620 global $post_id;
621 // get order
622 $order = WCX::get_order($post_id);
623
624 if (! $order) {
625 return;
626 }
627
628 $order_id = WCX_Order::get_id($order);
629
630 $shipping_country = WCX_Order::get_prop($order, 'shipping_country');
631 if (! WCPN_Country_Codes::isAllowedDestination($shipping_country)) {
632 return;
633 }
634
635 $class = version_compare(WOOCOMMERCE_VERSION, '3.3.0', '>=') ? "single_wc_actions" : "single_order_actions";
636 // show buttons and check if WooCommerce > 3.3.0 is used and select the correct function and class
637 echo "<div class=\"$class\">";
638 $this->showOrderActions($order);
639 echo '</div>';
640
641 $downloadDisplay = WCPOST()->setting_collection->getByName(WCPOST_Settings::SETTING_DOWNLOAD_DISPLAY) === 'display';
642 $consignments = self::get_order_shipments($order);
643
644 // show shipments if available
645 if (empty($consignments)) {
646 return;
647 }
648
649 include('views/html-order-track-trace-table.php');
650 }
651
652 /**
653 * @param $order
654 *
655 * @throws Exception
656 */
657 public function single_order_shipment_options(WC_Order $order)
658 {
659 $shipping_country = WCX_Order::get_prop($order, "shipping_country");
660
661 if (! WCPN_Country_Codes::isAllowedDestination($shipping_country)) {
662 return;
663 }
664
665 $this->showPostNLSettings($order);
666 }
667
668 /**
669 * @param WC_Order $order
670 *
671 * @throws Exception
672 */
673 public function showDeliveryDateForOrder(WC_Order $order): void
674 {
675 $deliveryOptions = self::getDeliveryOptionsFromOrder($order);
676 $this->printDeliveryDate($deliveryOptions);
677 }
678
679 /**
680 * @param \WC_Order $order
681 * @param bool $isEmail
682 *
683 * @throws \Exception
684 */
685 public function showShipmentConfirmation(WC_Order $order, bool $isEmail): void
686 {
687 $deliveryOptions = self::getDeliveryOptionsFromOrder($order);
688 $confirmationData = $this->getConfirmationData($deliveryOptions);
689 $isEmail
690 ? $this->printEmailConfirmation($confirmationData)
691 : $this->printThankYouConfirmation($confirmationData);
692 }
693
694 /**
695 * Go through all getProductOptions and show them on the screen
696 */
697 public function productOptionsFields(): void
698 {
699 echo '<div class="options_group">';
700 foreach ($this->getProductOptions() as $productOption) {
701 $type = $productOption['type'];
702 if ('text' === $type) {
703 woocommerce_wp_text_input(
704 [
705 'id' => $productOption['id'],
706 'label' => $productOption['label'],
707 'description' => $productOption['description'],
708 ]
709 );
710 } elseif ('select' === $type) {
711 woocommerce_wp_select(
712 [
713 'id' => $productOption['id'],
714 'label' => $productOption['label'],
715 'options' => $productOption['options'],
716 'description' => $productOption['description'],
717 ]
718 );
719 }
720 }
721 echo '</div>';
722 }
723
724 /**
725 * @param int $postId
726 */
727 public function productOptionsFieldSave(int $postId): void
728 {
729 foreach ($this->getProductOptions() as $productOption) {
730 // check if hs code is passed and not an array (=variation hs code)
731 if (isset($_POST[$productOption['id']]) && ! is_array($_POST[$productOption['id']])) {
732 $product = wc_get_product($postId);
733 $productId = $_POST[$productOption['id']];
734
735 if (! empty($productId)) {
736 WCX_Product::update_meta_data($product, $productOption['id'], esc_attr($productId));
737 } else {
738 if (isset($_POST[$productOption['id']]) && empty($productId)) {
739 WCX_Product::delete_meta_data($product, $productOption['id']);
740 }
741 }
742 }
743 }
744 }
745
746 /**
747 * @param $order_id
748 * @param $track_trace
749 *
750 * @return string|void
751 * @throws Exception
752 */
753 public static function getTrackTraceUrl($order_id, $track_trace)
754 {
755 if (empty($order_id)) {
756 return;
757 }
758
759 $order = WCX::get_order($order_id);
760 $country = WCX_Order::get_prop($order, 'shipping_country');
761 $postcode = preg_replace('/\s+/', '', WCX_Order::get_prop($order, 'shipping_postcode'));
762
763 // set url for NL or foreign orders
764 if ($country === 'NL') {
765 $deliveryOptions = self::getDeliveryOptionsFromOrder($order);
766
767 // use billing postcode for pickup/pakjegemak
768 if ($deliveryOptions->isPickup()) {
769 $postcode = preg_replace('/\s+/', '', WCX_Order::get_prop($order, 'billing_postcode'));
770 }
771
772 $trackTraceUrl = sprintf(
773 'https://jouw.postnl.nl/track-and-trace/%s/%s/%s',
774 $track_trace,
775 $country,
776 $postcode
777 );
778 } else {
779 $trackTraceUrl = sprintf(
780 'https://www.internationalparceltracking.com/Main.aspx#/track/%s/%s/%s',
781 $track_trace,
782 $country,
783 $postcode
784 );
785 }
786
787 return $trackTraceUrl;
788 }
789
790 /**
791 * @return array
792 */
793 public function getProductOptions(): array
794 {
795 return [
796 'HS-Code' => [
797 'id' => self::META_HS_CODE,
798 'label' => __('HS Code', 'woocommerce-postnl'),
799 'type' => 'text',
800 'description' => wc_help_tip(
801 sprintf(
802 __(
803 'HS Codes are used for PostNL world shipments, you can find the appropriate code on the %ssite of the Dutch Customs%s',
804 'woocommerce-postnl'
805 ),
806 '<a href="https://tarief.douane.nl/arctictariff-public-web/#!/home" target="_blank">',
807 '</a>'
808 )
809 ),
810 ],
811 'Country-of-origin' => [
812 'id' => self::META_COUNTRY_OF_ORIGIN,
813 'label' => __('product_options_country_of_origin', 'woocommerce-postnl'),
814 'type' => 'select',
815 'options' => array_merge(
816 [
817 null => __('Default', 'woocommerce-postnl'),
818 ],
819 (new WC_Countries())->get_countries()
820 ),
821 'description' => wc_help_tip(
822 __('setting_country_of_origin_help_text', 'woocommerce-postnl')
823 ),
824 ],
825 'Age-check' => [
826 'id' => self::META_AGE_CHECK,
827 'label' => __('shipment_options_age_check', 'woocommerce-postnl'),
828 'type' => 'select',
829 'options' => [
830 null => __('Default', 'woocommerce-postnl'),
831 self::PRODUCT_OPTIONS_DISABLED => __('Disabled', 'woocommerce-postnl'),
832 self::PRODUCT_OPTIONS_ENABLED => __('Enabled', 'woocommerce-postnl'),
833 ],
834 'description' => wc_help_tip(__('shipment_options_age_check_help_text', 'woocommerce-postnl')),
835 ],
836 ];
837 }
838
839 /**
840 * @snippet Add Column to Orders Table (e.g. Barcode) - WooCommerce
841 *
842 * @param $columns
843 *
844 * @return mixed
845 */
846 public function barcode_add_new_order_admin_list_column($columns)
847 {
848 // I want to display Barcode column just after the date column
849 return array_slice($columns, 0, 6, true) + ['barcode' => 'Barcode'] + array_slice($columns, 6, null, true);
850 }
851
852 /**
853 * @param $column
854 *
855 * @throws Exception
856 */
857 public function addBarcodeToOrderColumn($column)
858 {
859 global $post;
860
861 if ("barcode" === $column) {
862 $this->renderBarcodes(WCX::get_order($post->ID));
863 }
864 }
865
866 /**
867 * @param WC_Order $order
868 *
869 * @return void
870 * @throws Exception
871 */
872 public function renderBarcodes(WC_Order $order): void
873 {
874 $shipments = self::get_order_shipments($order, true);
875
876 if (empty($shipments)) {
877 echo __("No label has been created yet.", "woocommerce-postnl");
878
879 return;
880 }
881
882 echo '<div class="wcpn__barcodes">';
883 foreach ($shipments as $shipment_id => $shipment) {
884 $shipmentStatusId = $shipment['shipment']['status'];
885 $printedStatuses = [WCPOST_Admin::ORDER_STATUS_PRINTED_DIGITAL_STAMP, WCPOST_Admin::ORDER_STATUS_PRINTED_LETTER];
886
887 if (in_array($shipmentStatusId, $printedStatuses)) {
888 echo __("The label has been printed.", "woocommerce-postnl");
889 continue;
890 }
891
892 if (empty($shipment["track_trace"])) {
893 echo __("Concept created but not printed.", "woocommerce-postnl");
894 continue;
895 }
896
897 printf(
898 '<a target="_blank" class="wcpn__barcode-link" title="%2$s" href="%1$s">%2$s</a><br>',
899 self::getTrackTraceUrl($order, $shipment["track_trace"]),
900 $shipment["track_trace"]
901 );
902 }
903 echo "</div>";
904 }
905
906 /**
907 * Get DeliveryOptions object from the given order's meta data. Uses legacy delivery options if found, if that
908 * data is invalid it falls back to defaults.
909 *
910 * @param WC_Order $order
911 * @param array $inputData
912 *
913 * @return DeliveryOptions
914 * @throws \Exception
915 * @see \WCPN_Checkout::save_delivery_options
916 */
917 public static function getDeliveryOptionsFromOrder(WC_Order $order, array $inputData = []): DeliveryOptions
918 {
919 $meta = WCX_Order::get_meta($order, self::META_DELIVERY_OPTIONS) ?: null;
920
921 // $meta is a json string, create an instance
922 if (! empty($meta) && ! $meta instanceof DeliveryOptions) {
923 if (is_string($meta)) {
924 $meta = json_decode(stripslashes($meta), true);
925 }
926
927 $meta["carrier"] = WCPN_Data::DEFAULT_CARRIER;
928
929 try {
930 // create new instance from known json
931 $meta = DeliveryOptionsAdapterFactory::create((array) $meta);
932 } catch (BadMethodCallException $e) {
933 // create new instance from unknown json data
934 $meta = new WCPN_DeliveryOptionsFromOrderAdapter(null, (array) $meta);
935 }
936 }
937
938 // Create or update immutable adapter from order with a instanceof DeliveryOptionsAdapter
939 if (empty($meta) || ! empty($inputData)) {
940 $meta = new WCPN_DeliveryOptionsFromOrderAdapter($meta, $inputData);
941 }
942
943 return $meta;
944 }
945
946 /**
947 * Output the delivery date if there is a date and the show delivery day setting is enabled.
948 *
949 * @param DeliveryOptions $deliveryOptions
950 *
951 * @throws Exception
952 */
953 private function printDeliveryDate(DeliveryOptions $deliveryOptions): void
954 {
955 $showDeliveryDay = WCPOST()->setting_collection->isEnabled(WCPOST_Settings::SETTING_SHOW_DELIVERY_DAY);
956
957 if ($showDeliveryDay && $deliveryOptions->getDate()) {
958 printf(
959 '<div class="delivery-date"><strong>%s</strong><br />%s, %s</div>',
960 __("PostNL shipment:", "woocommerce-postnl"),
961 WCPN_Data::getDeliveryTypesHuman()[$deliveryOptions->getDeliveryType()],
962 wc_format_datetime(new WC_DateTime($deliveryOptions->getDate()), 'D d-m')
963 );
964 }
965 }
966
967 /**
968 * Output the chosen delivery options or the chosen pickup options.
969 *
970 * @param DeliveryOptions $deliveryOptions
971 *
972 * @return array[]|null
973 * @throws \Exception
974 */
975 private function getConfirmationData(DeliveryOptions $deliveryOptions): ?array
976 {
977 $deliveryOptionsEnabled = WCPOST()->setting_collection->isEnabled(
978 WCPOST_Settings::SETTING_DELIVERY_OPTIONS_ENABLED
979 );
980
981 if (! $deliveryOptionsEnabled || ! $deliveryOptions->getCarrier()) {
982 return null;
983 }
984
985 $signatureTitle = WCPN_Checkout::getDeliveryOptionsTitle(WCPOST_Settings::SETTING_SIGNATURE_TITLE);
986 $onlyRecipientTitle = WCPN_Checkout::getDeliveryOptionsTitle(WCPOST_Settings::SETTING_ONLY_RECIPIENT_TITLE);
987 $hasSignature = $deliveryOptions->getShipmentOptions()->hasSignature();
988 $hasOnlyRecipient = $deliveryOptions->getShipmentOptions()->hasOnlyRecipient();
989
990 if (AbstractConsignment::DELIVERY_TYPE_PICKUP_NAME === $deliveryOptions->getDeliveryType()) {
991 $pickupLocation = $deliveryOptions->getPickupLocation();
992 return [
993 __("delivery_type", "woocommerce-postnl") => WCPN_Data::getDeliveryTypesHuman()[$deliveryOptions->getDeliveryType()],
994 __("pickup_location", "woocommerce-postnl") =>
995 sprintf("%s<br>%s %s<br>%s %s",
996 $pickupLocation->getLocationName(),
997 $pickupLocation->getStreet(),
998 $pickupLocation->getNumber(),
999 $pickupLocation->getPostalCode(),
1000 $pickupLocation->getCity()
1001 )
1002 ];
1003 }
1004
1005 $confirmationData = [
1006 __("delivery_type", "woocommerce-postnl") => WCPN_Data::getDeliveryTypesHuman()[$deliveryOptions->getDeliveryType()],
1007 ];
1008
1009 if (WCPOST()->setting_collection->isEnabled(WCPOST_Settings::SETTING_SHOW_DELIVERY_DAY)) {
1010 $confirmationData[__("Date:", 'woocommerce')] = wc_format_datetime(new WC_DateTime($deliveryOptions->getDate()));;
1011 }
1012
1013 if ($hasSignature || $hasOnlyRecipient) {
1014 $confirmationData[__("extra_options", "woocommerce-postnl")] =
1015 sprintf("%s<br>%s",
1016 $hasSignature ? $signatureTitle : null,
1017 $hasOnlyRecipient ? $onlyRecipientTitle : null);
1018 }
1019
1020 return $confirmationData;
1021 }
1022
1023 /**
1024 * Print a table with the chosen delivery options on the confirmation page.
1025 *
1026 * @param array[]|null $selectedDeliveryOptions
1027 */
1028 public function printThankYouConfirmation(?array $selectedDeliveryOptions): void
1029 {
1030 printf($this->generateThankYouConfirmation($selectedDeliveryOptions));
1031 }
1032
1033 /**
1034 * Print a table with the chosen delivery options in the confirmation email.
1035 *
1036 * @param array[]|null $selectedDeliveryOptions
1037 */
1038 public function printEmailConfirmation(?array $selectedDeliveryOptions): void
1039 {
1040 printf($this->generateEmailConfirmation($selectedDeliveryOptions));
1041 }
1042
1043 /**
1044 * @param array[]|null $options
1045 *
1046 * @return string|null
1047 */
1048 public function generateThankYouConfirmation(?array $options): ?string
1049 {
1050 if ($options) {
1051 $htmlHeader = "<h2 class='woocommerce-column__title'> " . __("Delivery information:", "woocommerce-postnl") . "</h2><table>";
1052
1053 foreach ($options as $key => $option) {
1054 if ($option) {
1055 $htmlHeader .= "<tr><td>$key</td><td>" . __($option, "woocommerce-postnl") . "</td></tr>";
1056 }
1057 }
1058
1059 return $htmlHeader . "</table>";
1060 }
1061
1062 return null;
1063 }
1064
1065 /**
1066 * @param array[]|null $options
1067 *
1068 * @return string|null
1069 */
1070 public function generateEmailConfirmation(?array $options): ?string
1071 {
1072 if ($options) {
1073 $htmlHeader = "<h2 class='woocommerce-column__title'> " . __("Delivery information:", "woocommerce-postnl") . "</h2>";
1074 $htmlHeader .= "<table cellspacing='0' style='border: 1px solid #e5e5e5; margin-bottom: 20px;>";
1075
1076 foreach ($options as $key => $option) {
1077 if ($option) {
1078 $htmlHeader .= "<tr style='border: 1px solid #d5d5d5;'>
1079 <td style='border: 1px solid #e5e5e5;'>$key</td>
1080 <td style='border: 1px solid #e5e5e5;'>" . __($option, "woocommerce-postnl") . "</td>
1081 </tr>";
1082 }
1083 }
1084
1085 return $htmlHeader . "</table>";
1086 }
1087
1088 return null;
1089 }
1090
1091 /**
1092 * Output a spinner.
1093 *
1094 * @param string $state
1095 * @param array $args
1096 */
1097 public static function renderSpinner(string $state = "", array $args = []): void
1098 {
1099 $spinners = [
1100 "loading" => get_site_url() . "/wp-admin/images/spinner.gif",
1101 "success" => get_site_url() . "/wp-admin/images/yes.png",
1102 "failed" => get_site_url() . "/wp-admin/images/no.png",
1103 ];
1104
1105 $arguments = [];
1106
1107 $args["class"][] = "wcpn__spinner";
1108
1109 if ($state) {
1110 $args["class"][] = "wcpn__spinner--$state";
1111 }
1112
1113 foreach ($args as $arg => $value) {
1114 if (is_array($value)) {
1115 $value = implode(" ", $value);
1116 }
1117 $arguments[] = "$arg=\"$value\"";
1118 }
1119
1120 $attributes = implode(" ", $arguments);
1121
1122 echo "<span $attributes>";
1123 foreach ($spinners as $spinnerState => $icon) {
1124 printf(
1125 '<img class="wcpn__spinner__%1$s" alt="%1$s" src="%2$s" style="display: %3$s;" />',
1126 $spinnerState,
1127 $icon,
1128 $state === $spinnerState ? "block" : "none"
1129 );
1130 }
1131 echo '</span>';
1132 }
1133
1134 /**
1135 * @param string $url
1136 * @param string $alt
1137 * @param string $icon
1138 * @param array $rawAttributes
1139 */
1140 public static function renderAction(string $url, string $alt, string $icon, array $rawAttributes = []): void
1141 {
1142 printf(
1143 '<a href="%1$s"
1144 class="button tips wcpn__action"
1145 data-tip="%2$s"
1146 %4$s>
1147 <img class="wcpn__action__img wcpn__m--auto" src="%3$s" alt="%2$s" />',
1148 wp_nonce_url($url, WCPOST::NONCE_ACTION),
1149 $alt,
1150 $icon,
1151 wc_implode_html_attributes($rawAttributes)
1152 );
1153
1154 self::renderSpinner();
1155 echo "</a>";
1156 }
1157
1158 /**
1159 * @param array $shipment
1160 * @param int $order_id
1161 *
1162 * @throws Exception
1163 */
1164 public static function renderTrackTraceLink(array $shipment, int $order_id): void
1165 {
1166 $track_trace = $shipment["track_trace"] ?? null;
1167
1168 if ($track_trace) {
1169 $track_trace_url = self::getTrackTraceUrl($order_id, $track_trace);
1170 $track_trace_link = sprintf(
1171 '<a href="%s" target="_blank">%s</a>',
1172 $track_trace_url,
1173 $track_trace
1174 );
1175 } elseif (isset($shipment["shipment"]) && isset($shipment["shipment"]["options"])) {
1176 $package_type = WCPN_Export::getPackageTypeHuman($shipment["shipment"]["options"]["package_type"]);
1177 $track_trace_link = "($package_type)";
1178 } else {
1179 $track_trace_link = __("(Unknown)", "woocommerce-postnl");
1180 }
1181
1182 echo $track_trace_link;
1183 }
1184
1185 /**
1186 * @param array $shipment
1187 * @param int $order_id
1188 */
1189 public static function renderStatus(array $shipment, int $order_id): void
1190 {
1191 echo $shipment["status"] ?? "";
1192
1193 if (self::shipmentIsStatus($shipment, self::ORDER_STATUS_DELIVERED_AT_RECIPIENT)
1194 || self::shipmentIsStatus($shipment, self::ORDER_STATUS_DELIVERED_READY_FOR_PICKUP)
1195 || self::shipmentIsStatus($shipment, self::ORDER_STATUS_DELIVERED_PACKAGE_PICKED_UP)
1196 ) {
1197 $order = WCX::get_order($order_id);
1198 // This will be addressed in MY-24881
1199 // $order->update_status('wc-custom-delivered');
1200 }
1201 }
1202
1203 /**
1204 * @param array $shipment
1205 * @param int $status
1206 *
1207 * @return bool
1208 */
1209 public static function shipmentIsStatus(array $shipment, int $status): bool
1210 {
1211 return strstr($shipment['status'], (new WCPN_Export())->getShipmentStatusName($status));
1212 }
1213
1214 /**
1215 * Remove options that aren't allowed and return the edited array.
1216 *
1217 * @param array $data
1218 * @param string $country
1219 *
1220 * @return mixed
1221 */
1222 public static function removeDisallowedDeliveryOptions(array $data, string $country): array
1223 {
1224 $data['package_type'] = $data['package_type'] ?? AbstractConsignment::DEFAULT_PACKAGE_TYPE_NAME;
1225 $isHomeCountry = WCPN_Data::isHomeCountry($country);
1226 $isEuCountry = WCPN_Country_Codes::isEuCountry($country);
1227 $isBelgium = AbstractConsignment::CC_BE === $country;
1228 $isPackage = AbstractConsignment::PACKAGE_TYPE_PACKAGE_NAME === $data['package_type'];
1229 $isDigitalStamp = AbstractConsignment::PACKAGE_TYPE_DIGITAL_STAMP_NAME === $data['package_type'];
1230
1231 if (! $isHomeCountry || ! $isPackage) {
1232 $data['shipment_options']['age_check'] = false;
1233 $data['shipment_options']['return_shipment'] = false;
1234
1235 if (! $isBelgium || ! $isPackage) {
1236 $data['shipment_options']['insured'] = false;
1237 $data['shipment_options']['insured_amount'] = 0;
1238 }
1239 }
1240
1241 if (! $isPackage || (! $isHomeCountry && ! $isEuCountry)) {
1242 $data['shipment_options']['large_format'] = false;
1243 }
1244
1245 if (! $isDigitalStamp) {
1246 unset($data['extra_options']['weight']);
1247 }
1248
1249 return $data;
1250 }
1251 }
1252
1253 return new WCPOST_Admin();
1254