PluginProbe
PostNL for WooCommerce / trunk
PostNL for WooCommerce vtrunk
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 4.4.2 All 71 releases
woo-postnl / src / Order / Single.php

Single.php in PostNL for WooCommerce trunk, at src/Order/Single.php

794 lines 25.4 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 /**
3 * Class Order\Single file.
4 *
5 * @package PostNLWooCommerce\Order
6 */
7
8 namespace PostNLWooCommerce\Order;
9
10 use PostNLWooCommerce\Utils;
11 use PostNLWooCommerce\Helper\Mapping;
12 use WC_Order_Item;
13 use WC_Order_Item_Product;
14
15 if ( ! defined( 'ABSPATH' ) ) {
16 exit;
17 }
18
19 /**
20 * Class Single
21 *
22 * @package PostNLWooCommerce\Order
23 */
24 class Single extends Base {
25 /**
26 * Collection of hooks when initiation.
27 */
28 public function init_hooks() {
29 add_filter( 'woocommerce_admin_shipping_fields', array( $this, 'admin_address_fields' ) );
30 add_filter( 'woocommerce_admin_billing_fields', array( $this, 'admin_address_fields' ) );
31 add_filter( 'woocommerce_order_formatted_shipping_address', array( $this, 'display_shipping_house_number' ), 10, 2 );
32 add_filter( 'woocommerce_order_formatted_billing_address', array( $this, 'display_billing_house_number' ), 10, 2 );
33
34 add_action( 'admin_enqueue_scripts', array( $this, 'enqueue_order_single_css_script' ) );
35 add_action( 'add_meta_boxes', array( $this, 'add_meta_box' ), 20, 2 );
36
37 add_action( 'wp_ajax_postnl_order_save_form', array( $this, 'save_meta_box_ajax' ) );
38 add_action( 'wp_ajax_nopriv_postnl_order_save_form', array( $this, 'save_meta_box_ajax' ) );
39
40 add_action( 'wp_ajax_postnl_order_delete_data', array( $this, 'delete_meta_data_ajax' ) );
41 add_action( 'wp_ajax_nopriv_postnl_order_delete_data', array( $this, 'delete_meta_data_ajax' ) );
42
43 add_action( 'init', array( $this, 'get_label_file' ), 10 );
44
45 add_action( 'wp_ajax_postnl_activate_return_function', array( $this, 'postnl_activate_return_function' ) );
46 add_action( 'wp_ajax_nopriv_postnl_activate_return_function', array( $this, 'postnl_activate_return_function' ) );
47
48 add_action( 'wp_ajax_postnl_send_smart_return_email', array( $this, 'postnl_send_smart_return_email' ) );
49 add_action( 'wp_ajax_nopriv_postnl_send_smart_return_email', array( $this, 'postnl_send_smart_return_email' ) );
50
51 add_action( 'woocommerce_after_order_itemmeta', array( $this, 'add_adult_product_label_admin_order_page' ), 10, 2 );
52 }
53
54 /**
55 * Enqueue CSS file in order detail page.
56 */
57 public function enqueue_order_single_css_script() {
58 $screen = get_current_screen();
59
60 if ( empty( $screen->id ) ) {
61 return;
62 }
63
64 if ( ! empty( $screen->id ) && 'woocommerce_page_wc-settings' === $screen->id && ! empty( $_GET['section'] ) && POSTNL_SETTINGS_ID === wp_unslash( $_GET['section'] ) ) {
65 wp_enqueue_script(
66 'postnl-admin-settings',
67 POSTNL_WC_PLUGIN_DIR_URL . '/assets/js/admin-settings.js',
68 array( 'jquery' ),
69 POSTNL_WC_VERSION,
70 true
71 );
72 wp_enqueue_style(
73 'postnl-admin-settings-styles',
74 POSTNL_WC_PLUGIN_DIR_URL . '/assets/css/admin-settings.css',
75 array(),
76 POSTNL_WC_VERSION
77 );
78 }
79
80 if ( ( 'shop_order' === $screen->id && 'post' === $screen->base ) || 'woocommerce_page_wc-orders' === $screen->id ) {
81 wp_enqueue_style( 'postnl-admin-order-single', POSTNL_WC_PLUGIN_DIR_URL . '/assets/css/admin-order-single.css', array(), POSTNL_WC_VERSION );
82
83 wp_enqueue_script(
84 'postnl-admin-order-single',
85 POSTNL_WC_PLUGIN_DIR_URL . '/assets/js/admin-order-single.js',
86 array( 'jquery' ),
87 POSTNL_WC_VERSION,
88 true
89 );
90
91 wp_enqueue_script(
92 'postnl-admin-shipment-track-trace',
93 POSTNL_WC_PLUGIN_DIR_URL . '/assets/js/admin-shipment-track-trace.js',
94 array( 'jquery' ),
95 POSTNL_WC_VERSION,
96 true
97 );
98
99 wp_localize_script(
100 'postnl-admin-order-single',
101 'postnl_admin_order_obj',
102 array(
103 'prefix' => $this->prefix,
104 'fields' => array_map(
105 function ( $meta_field ) {
106 return empty( $meta_field['id'] ) ? '' : $meta_field['id'];
107 },
108 $this->meta_box_fields(),
109 ),
110 )
111 );
112 }
113 }
114
115 /**
116 * Adding meta box in order admin page.
117 *
118 * @param String $post_type Post type for current admin page.
119 * @param WP_POST|WC_Order $post_or_order_object Either WP_Post or WC_Order object.
120 */
121 public function add_meta_box( $post_type, $post_or_order_object ) {
122 $order = $this->init_order_object( $post_or_order_object );
123
124 if ( ! is_a( $order, 'WC_Order' ) || ! $this->is_postnl_shipping_method( $order ) ) {
125 return;
126 }
127
128 // translators: %s will be replaced by service name.
129 add_meta_box(
130 'woocommerce-shipment-postnl-label',
131 esc_html__( 'Label & Tracking', 'postnl-for-woocommerce' ),
132 array(
133 $this,
134 'meta_box_html',
135 ),
136 Utils::get_order_screen_id(),
137 'side',
138 'high'
139 );
140 }
141
142 /**
143 * Add value to meta box fields.
144 *
145 * @param WC_Order $order current order object.
146 *
147 * @return array
148 */
149 public function add_meta_box_value( $order ) {
150 if ( ! is_a( $order, 'WC_Order' ) ) {
151 return array();
152 }
153 $meta_fields = $this->meta_box_fields( $order );
154
155 $order_data = $order->get_meta( $this->meta_name );
156 $option_map = Mapping::option_available_list();
157 $from_country = Utils::get_base_country();
158 $to_country = $order->get_shipping_country();
159 $to_state = $order->get_shipping_state();
160 $destination = Utils::get_shipping_zone( $to_country, $to_state );
161
162 // The persisted backend collapses the 24h/48h choice onto the generic
163 // 'letterbox' feature. Swap in the resolved variant so the override loop
164 // below re-checks (and, once a label exists, locks) the correct checkbox
165 // instead of re-checking the generic 24h box on top of the pre-selected
166 // 48h box, which would show both letterbox variants as selected.
167 if ( isset( $order_data['backend'] ) && is_array( $order_data['backend'] ) ) {
168 $order_data['backend'] = $this->apply_letterbox_display_variant( $order_data['backend'], $order );
169 }
170
171 foreach ( $meta_fields as $index => $field ) {
172 if ( isset( $field['nonce'] ) && true === $field['nonce'] ) {
173 continue;
174 }
175
176 $field_name = Utils::remove_prefix_field( $this->prefix, $field['id'] );
177
178 if ( ! empty( $order_data['frontend'][ $field_name ] ) ) {
179 $meta_fields[ $index ]['value'] = $order_data['frontend'][ $field_name ];
180 }
181
182 if ( isset( $order_data['backend'][ $field_name ] ) ) {
183 if ( $this->have_label_file( $order ) ) {
184 $meta_fields[ $index ]['custom_attributes']['disabled'] = 'disabled';
185 }
186 $meta_fields[ $index ]['value'] = $order_data['backend'][ $field_name ];
187 }
188
189 if ( isset( $option_map[ $from_country ][ $destination ] ) ) {
190 $meta_fields[ $index ]['standard_feat'] = in_array( $field_name, $option_map[ $from_country ][ $destination ] );
191 } else {
192 $meta_fields[ $index ]['standard_feat'] = false;
193 }
194 }
195
196 return $meta_fields;
197 }
198
199 /**
200 * Filter the fields to display the available fields only.
201 *
202 * @param Array $meta_fields Order meta fields.
203 * @param Array $available_options Available fields based on the countries and chosen option in checkout page.
204 *
205 * @return array
206 */
207 public function filter_available_fields( $meta_fields, $available_options ) {
208 $meta_fields = array_filter(
209 $meta_fields,
210 function ( $field ) use ( $available_options ) {
211 $field_name = Utils::remove_prefix_field( $this->prefix, $field['id'] );
212
213 if ( true === $field['standard_feat'] ) {
214 return true;
215 }
216
217 if ( true === $field['const_field'] ) {
218 return true;
219 }
220
221 return in_array( $field_name, $available_options, true );
222 }
223 );
224
225 return $meta_fields;
226 }
227
228 /**
229 * Get dropoff points information.
230 *
231 * @param WC_Order $order Order object.
232 *
233 * @return Array.
234 */
235 public function get_pickup_points_info( $order ) {
236 return $this->get_order_frontend_info( $order, 'dropoff_points_' );
237 }
238
239 /**
240 * Get delivery day information.
241 *
242 * @param WC_Order $order Order object.
243 *
244 * @return Array.
245 */
246 public function get_delivery_day_info( $order ) {
247 return $this->get_order_frontend_info( $order, 'delivery_day_' );
248 }
249
250 /**
251 * Generate the dropoff points html information.
252 *
253 * @param WC_Order $order Order object.
254 */
255 public function generate_delivery_type_html( $order ) {
256 if ( ! is_a( $order, 'WC_Order' ) ) {
257 return;
258 }
259
260 $delivery_type = $this->get_delivery_type( $order );
261
262 if ( empty( $delivery_type ) ) {
263 return;
264 }
265 ?>
266 <div class="postnl-info-container delivery-type-info">
267 <label for="postnl_delivery_type"><?php esc_html_e( 'Delivery Type:', 'postnl-for-woocommerce' ); ?></label>
268 <div class="postnl-info delivery-type">
269 <?php echo esc_html( $delivery_type ); ?>
270 </div>
271 </div>
272 <?php
273 }
274
275 /**
276 * Generate the dropoff points html information.
277 *
278 * @param Array $infos Dropoff points informations.
279 */
280 public function generate_delivery_date_html( $infos ) {
281 $filtered_infos = array_filter(
282 $infos,
283 function ( $info ) {
284 $displayed_info = array(
285 'delivery_day_date',
286 );
287
288 return in_array( $info, $displayed_info, true );
289 },
290 ARRAY_FILTER_USE_KEY
291 );
292
293 if ( empty( $filtered_infos ) ) {
294 return;
295 }
296 ?>
297 <div class="postnl-info-container delivery-date-info">
298 <label for="postnl_pickup_points"><?php esc_html_e( 'Delivery Date:', 'postnl-for-woocommerce' ); ?></label>
299 <?php
300 foreach ( $filtered_infos as $info_idx => $info_val ) {
301 // Convert to the Dutch date format, falling back to the stored value
302 // when it does not parse as Y-m-d so a malformed date cannot fatal the
303 // whole order edit screen via date_format( false, ... ).
304 $date_obj = date_create_from_format( 'Y-m-d', $info_val );
305 $dutch_date = ( false !== $date_obj ) ? date_format( $date_obj, 'd/m/Y' ) : $info_val;
306 ?>
307 <div class="postnl-info <?php echo esc_attr( $info_idx ); ?>">
308 <?php echo esc_html( $dutch_date ); ?>
309 </div>
310 <?php
311 }
312 ?>
313 </div>
314 <?php
315 }
316
317 /**
318 * Generate the dropoff points html information.
319 *
320 * @param Array $infos Dropoff points informations.
321 */
322 public function generate_pickup_points_html( $infos ) {
323 $filtered_infos = Utils::get_filtered_pickup_points_infos( $infos );
324
325 if ( empty( $filtered_infos ) ) {
326 return;
327 }
328 ?>
329 <div class="postnl-info-container pickup-points-info">
330 <label for="postnl_pickup_points"><?php esc_html_e( 'Pickup Address:', 'postnl-for-woocommerce' ); ?></label>
331 <?php
332 foreach ( $filtered_infos as $info_idx => $info_val ) {
333 switch ( $info_idx ) {
334 case 'dropoff_points_date':
335 $additional_text = esc_html__( 'Date:', 'postnl-for-woocommerce' );
336 break;
337
338 case 'dropoff_points_time':
339 $additional_text = esc_html__( 'Time:', 'postnl-for-woocommerce' );
340 break;
341
342 default:
343 $additional_text = '';
344 break;
345 }
346 ?>
347 <div class="postnl-info <?php echo esc_attr( $info_idx ); ?>">
348 <?php echo esc_html( $additional_text . ' ' . $info_val ); ?>
349 </div>
350 <?php
351 }
352 ?>
353 </div>
354 <?php
355 }
356
357 /**
358 * Adds an 'Activate return function' button.
359 */
360 public function activate_return_function_html( $order ) {
361 if ( 'shipping_return' === $this->settings->get_return_shipment_and_labels() && 'no' === $this->settings->get_return_shipment_and_labels_all() && 'NL' === $order->get_shipping_country() && 'NL' === Utils::get_base_country() && ! Utils::is_order_eligible_auto_letterbox( $order ) ) {
362 ?>
363 <hr id="postnl_break_2">
364 <p class="form-field">
365
366 <?php wp_nonce_field( 'postnl_activate_return_function', 'activate_return_function_nonce' ); ?>
367 <button type="button" class="button button-activate-return" <?php disabled( $this->is_return_function_activated( $order ) ); ?>><?php esc_html_e( 'Activate return function', 'postnl-for-woocommerce' ); ?></button>
368
369 <div class="postnl-info activated-return-info" <?php echo ( $this->is_return_function_activated( $order ) ) ? '' : 'style="display:none;"'; ?> >
370 <?php esc_html_e( 'Return function is activated for this label', 'postnl-for-woocommerce' ); ?>
371 </div>
372 <div class="postnl-info activate-return-info" <?php echo ( $this->is_return_function_activated( $order ) ) ? 'style="display:none;"' : ''; ?> >
373 <?php esc_html_e( 'Click here to activate the return function of this label', 'postnl-for-woocommerce' ); ?>
374 </div>
375 </p>
376 <?php
377 }
378 }
379
380 /**
381 * Adds an 'Send Smart Return' button.
382 */
383 public function send_smart_return_email_html( $order ) {
384 if ( 'NL' === $order->get_shipping_country() && $this->settings->is_smart_return_enabled() ) {
385 $check_for_barcode = empty( $this->get_backend_data( $order->get_ID() ) );
386 ?>
387 <hr id="postnl_break_2">
388 <p class="form-field">
389 <?php wp_nonce_field( 'postnl_send_smart_return_email', 'send_smart_return_email_nonce' ); ?>
390 <button type="button"
391 class="button button-send-smart-return" <?php disabled( $check_for_barcode ); ?>><?php esc_html_e( 'Send email with Smart Return', 'postnl-for-woocommerce' ); ?></button>
392 </p>
393 <?php
394 }
395 }
396
397 /**
398 * Additional fields of the meta box for child class.
399 *
400 * @param WP_Post|WC_Order $post_or_order_object current order object.
401 */
402 public function meta_box_html( $post_or_order_object ) {
403 $order = $this->init_order_object( $post_or_order_object );
404 if ( ! is_a( $order, 'WC_Order' ) ) {
405 return;
406 }
407
408 $form_class = ( $this->have_label_file( $order ) ) ? 'generated' : '';
409 if ( $this->have_backend_data( $order, 'create_return_label' ) ) {
410 $form_class .= ' has-return';
411 }
412
413 if ( $this->have_backend_data( $order, 'letterbox' ) ) {
414 $form_class .= ' has-letterbox';
415 }
416
417 $pickup_info = $this->get_pickup_points_info( $order );
418 $delivery_info = $this->get_delivery_day_info( $order );
419 $fields_with_value = $this->add_meta_box_value( $order );
420 $available_options = $this->get_available_options( $order );
421 $available_fields = $this->filter_available_fields( $fields_with_value, $available_options );
422 ?>
423 <div id="shipment-postnl-label-form" class="<?php echo esc_attr( $form_class ); ?>">
424 <?php $this->generate_delivery_type_html( $order ); ?>
425 <?php $this->generate_delivery_date_html( $delivery_info ); ?>
426 <?php $this->generate_pickup_points_html( $pickup_info ); ?>
427 <?php Utils::fields_generator( $available_fields ); ?>
428 <div class="button-container">
429 <button class="button button-primary button-save-form"><?php esc_html_e( 'Create Shipment', 'postnl-for-woocommerce' ); ?></button>
430 <a href="<?php echo esc_url( $this->get_download_label_url( $order->get_id() ) ); ?>"
431 class="button button-primary button-download-label"><?php esc_html_e( 'Print Label', 'postnl-for-woocommerce' ); ?></a>
432 <a class="button button-secondary delete-label"
433 href="#"><?php esc_html_e( 'Delete Label', 'postnl-for-woocommerce' ); ?></a>
434 </div>
435 <?php $this->activate_return_function_html( $order ); ?>
436 <?php $this->send_smart_return_email_html( $order ); ?>
437 <!--
438 <div class="button-container return-container">
439 <a href="<?php echo esc_url( $this->get_download_label_url( $order->get_id(), 'return-label' ) ); ?>" class="button button-primary button-download-label"><?php esc_html_e( 'Print Return Label', 'postnl-for-woocommerce' ); ?></a>
440 </div>
441 -->
442 <!--
443 <div class="button-container letterbox-container">
444 <a href="<?php echo esc_url( $this->get_download_label_url( $order->get_id(), 'buspakjeextra' ) ); ?>" class="button button-primary button-download-label"><?php esc_html_e( 'Print Letterbox', 'postnl-for-woocommerce' ); ?></a>
445 </div>
446 -->
447 <div id="shipment-postnl-error-text"></div>
448 </div>
449 <?php
450 }
451
452 /**
453 * Saving meta box in order admin page using ajax.
454 *
455 * @throws \Exception Throw error for invalid nonce.
456 */
457 public function save_meta_box_ajax() {
458 try {
459 // Get array of nonce fields.
460 $nonce_fields = array_values( $this->get_nonce_fields() );
461
462 if ( empty( $nonce_fields ) ) {
463 throw new \Exception( esc_html__( 'Cannot find nonce field!', 'postnl-for-woocommerce' ) );
464 }
465
466 // Check nonce before proceed.
467 $nonce_result = check_ajax_referer( $this->nonce_key, $nonce_fields[0]['id'], false );
468 if ( false === $nonce_result ) {
469 throw new \Exception( esc_html__( 'Nonce is invalid!', 'postnl-for-woocommerce' ) );
470 }
471
472 $order_id = ! empty( $_REQUEST['order_id'] ) ? sanitize_text_field( wp_unslash( $_REQUEST['order_id'] ) ) : 0;
473
474 $result = $this->save_meta_value( $order_id, $_REQUEST );
475 $return_data = $result['saved_data'];
476 $labels = $result['labels'];
477 $tracking_note = $this->get_tracking_note( $order_id );
478
479 if ( ! empty( $tracking_note ) ) {
480 $return_data = array_merge(
481 $result['saved_data'],
482 array(
483 'tracking_note' => $tracking_note,
484 'note_type' => $this->settings->is_woocommerce_email_enabled() ? 'customer' : 'private',
485 )
486 );
487 }
488
489 wp_send_json_success( $return_data );
490 } catch ( \Exception $e ) {
491 wp_send_json_error(
492 array( 'message' => $e->getMessage() ),
493 );
494 }
495 }
496
497 /**
498 * Delete meta data in order admin page using ajax.
499 *
500 * @throws \Exception Throw error for invalid nonce.
501 */
502 public function delete_meta_data_ajax() {
503 try {
504 // Get array of nonce fields.
505 $nonce_fields = array_values( $this->get_nonce_fields() );
506
507 if ( empty( $nonce_fields ) ) {
508 throw new \Exception( esc_html__( 'Cannot find nonce field!', 'postnl-for-woocommerce' ) );
509 }
510
511 // Check nonce before proceed.
512 $nonce_result = check_ajax_referer( $this->nonce_key, $nonce_fields[0]['id'], false );
513 if ( false === $nonce_result ) {
514 throw new \Exception( esc_html__( 'Nonce is invalid!', 'postnl-for-woocommerce' ) );
515 }
516
517 $order_id = ! empty( $_REQUEST['order_id'] ) ? sanitize_text_field( wp_unslash( $_REQUEST['order_id'] ) ) : 0;
518
519 // Check if order id is really an ID from shop_order post type.
520 $order = wc_get_order( $order_id );
521 if ( ! is_a( $order, 'WC_Order' ) ) {
522 throw new \Exception( esc_html__( 'Order does not exist!', 'postnl-for-woocommerce' ) );
523 }
524
525 $saved_data = $this->delete_meta_value( $order_id );
526 wp_send_json_success( $saved_data );
527 } catch ( \Exception $e ) {
528 wp_send_json_error(
529 array( 'message' => $e->getMessage() ),
530 );
531 }
532 }
533
534 /**
535 * Add house number field to the order address fields within the dashboard.
536 *
537 * @param array $fields address fields.
538 *
539 * @return array.
540 */
541 public function admin_address_fields( $fields ) {
542 $new_fields = array();
543 foreach ( $fields as $key => $field ) {
544 if ( 'address_1' === $key ) {
545 $new_fields['house_number'] = array(
546 'label' => __( 'House number', 'postnl-for-woocommerce' ),
547 'show' => false,
548 );
549 }
550
551 $new_fields[ $key ] = $field;
552 }
553
554 return $new_fields;
555 }
556
557 /**
558 * Modify address and add house number.
559 *
560 * @param array $address Array of shipping address.
561 * @param \WC_Order $order Order object.
562 * @param String $type Address type.
563 *
564 * @return mixed
565 */
566 public function add_house_number_to_address( $address, $order, $type = 'shipping' ) {
567 if ( 'shipping' === $type ) {
568 $house_number_meta = '_shipping_house_number';
569 } else {
570 $house_number_meta = '_billing_house_number';
571 }
572
573 $house_number = $order->get_meta( $house_number_meta );
574
575 if ( $house_number ) {
576 $address['address_1'] .= ' ' . $house_number;
577 }
578
579 return $address;
580 }
581
582 /**
583 * Add house number to the shipping address within the order.
584 *
585 * @param array $address Array of shipping address.
586 * @param \WC_Order $order Order object.
587 *
588 * @return array
589 */
590 public function display_shipping_house_number( $address, $order ) {
591 return $this->add_house_number_to_address( $address, $order, 'shipping' );
592 }
593
594 /**
595 * Add house number to the billing address within the order.
596 *
597 * @param array $address Array of shipping address.
598 * @param \WC_Order $order Order object.
599 *
600 * @return array
601 */
602 public function display_billing_house_number( $address, $order ) {
603 return $this->add_house_number_to_address( $address, $order, 'billing' );
604 }
605
606 /**
607 * Ajax action to activate return function.
608 *
609 * @return void
610 */
611 public function postnl_activate_return_function() {
612 try {
613 if ( ! isset( $_POST['security'] ) ) {
614 throw new \Exception( esc_html__( 'Cannot find nonce field!', 'postnl-for-woocommerce' ) );
615 }
616
617 // Check nonce before proceed.
618 if ( ! wp_verify_nonce( $_POST['security'], 'postnl_activate_return_function' ) ) {
619 throw new \Exception( esc_html__( 'Nonce is invalid', 'postnl-for-woocommerce' ) );
620 }
621
622 $order_id = ! empty( $_REQUEST['order_id'] ) ? sanitize_text_field( wp_unslash( $_REQUEST['order_id'] ) ) : 0;
623
624 // Check if order id is really an ID from shop_order post type.
625 $order = wc_get_order( $order_id );
626 if ( ! is_a( $order, 'WC_Order' ) ) {
627 throw new \Exception( esc_html__( 'Order does not exist!', 'postnl-for-woocommerce' ) );
628 }
629
630 if ( $this->is_return_function_activated( $order ) ) {
631 throw new \Exception( esc_html__( 'Already activated!', 'postnl-for-woocommerce' ) );
632 }
633
634 $response = $this->service_factory()->return_label_service()->activate( (int) $order_id );
635
636 if ( ! empty( $response['successFulBarcodes'] ) && is_array( $response['successFulBarcodes'] ) ) {
637 $order->update_meta_data( $this->is_return_activated_meta, 'yes' );
638 $order->save_meta_data();
639
640 wp_send_json_success();
641
642 return;
643 }
644
645 $error_message = esc_html__( 'Unknown error.', 'postnl-for-woocommerce' );
646 $error_items = array();
647
648 if ( isset( $response['errorsPerBarcode'] ) && is_array( $response['errorsPerBarcode'] ) ) {
649 foreach ( $response['errorsPerBarcode'] as $barcode_errors ) {
650 if ( ! isset( $barcode_errors['errors'] ) || ! is_array( $barcode_errors['errors'] ) ) {
651 continue;
652 }
653
654 foreach ( $barcode_errors['errors'] as $error ) {
655 if ( empty( $error['description'] ) ) {
656 continue;
657 }
658
659 $error_items[] = sprintf( '<li>%s</li>', esc_html( $error['description'] ) );
660 }
661 }
662 }
663
664 if ( ! empty( $error_items ) ) {
665 $error_message = '<ul>' . implode( '', $error_items ) . '</ul>';
666 }
667
668 // Translators: %s is the error message.
669 throw new \Exception( sprintf( esc_html__( 'Error: %s', 'postnl-for-woocommerce' ), $error_message ) );
670 } catch ( \Exception $e ) {
671 wp_send_json_error(
672 array( 'message' => $e->getMessage() ),
673 );
674 }
675 }
676
677 /**
678 * Ajax action to send smart return email.
679 *
680 * @return void
681 */
682 public function postnl_send_smart_return_email() {
683 try {
684 if ( ! isset( $_POST['security'] ) ) {
685 throw new \Exception( esc_html__( 'Cannot find nonce field!', 'postnl-for-woocommerce' ) );
686 }
687
688 // Check nonce before proceed.
689 if ( ! wp_verify_nonce( $_POST['security'], 'postnl_send_smart_return_email' ) ) {
690 throw new \Exception( esc_html__( 'Nonce is invalid', 'postnl-for-woocommerce' ) );
691 }
692
693 $order_id = ! empty( $_REQUEST['order_id'] ) ? sanitize_text_field( wp_unslash( $_REQUEST['order_id'] ) ) : 0;
694
695 // Check if order id is really an ID from shop_order post type.
696 $order = wc_get_order( $order_id );
697 if ( ! is_a( $order, 'WC_Order' ) ) {
698 throw new \Exception( esc_html__( 'Order does not exist!', 'postnl-for-woocommerce' ) );
699 }
700
701 $response = $this->service_factory()->smart_returns_service()->generate( $order );
702
703 $to = $order->get_billing_email();
704 $emails = WC()->mailer()->get_emails();
705
706 if ( empty( $emails ) || ! isset( $emails['WC_Smart_Return_Email'] ) ) {
707 throw new \Exception( esc_html__( 'Email could not be sent', 'postnl-for-woocommerce' ) );
708 }
709
710 $email = $emails['WC_Smart_Return_Email'];
711 $email->recipient = $to;
712
713 if ( 'v4' === ( $response['api_version'] ?? '' ) ) {
714 // V4 retailPrint: show the printcode/barcode image in the email body, no PDF attachment.
715 if ( empty( $response['content'] ) ) {
716 throw new \Exception( esc_html__( 'Printcode could not be found', 'postnl-for-woocommerce' ) );
717 }
718
719 $email->printcode_content = $response['content'];
720 $email->printcode_mime = $response['mime'] ?? 'image/png';
721 $email->printcode_barcode = (string) ( $response['barcode'] ?? '' );
722 } else {
723 // Legacy: pull the PrintcodeLabel PDF from the response and attach it.
724 $printcode_label_content = null;
725
726 if ( ! empty( $response['ResponseShipments'] ) ) {
727 foreach ( $response['ResponseShipments'] as $shipment ) {
728 if ( empty( $shipment['Labels'] ) ) {
729 continue;
730 }
731
732 foreach ( $shipment['Labels'] as $label ) {
733 if ( isset( $label['Labeltype'] ) && 'PrintcodeLabel' === $label['Labeltype'] ) {
734 $printcode_label_content = $label['Content'];
735 break 2;
736 }
737 }
738 }
739 }
740
741 if ( ! $printcode_label_content ) {
742 throw new \Exception( esc_html__( 'The Smart Return printcode could not be found.', 'postnl-for-woocommerce' ) );
743 }
744
745 $upload_dir = wp_upload_dir();
746 $file_path = $upload_dir['path'] . '/printcode_label.pdf';
747
748 // phpcs:ignore WordPress.WP.AlternativeFunctions.file_system_operations_file_put_contents, WordPress.PHP.DiscouragedPHPFunctions.obfuscation_base64_decode -- Writing the PostNL printcode PDF returned base64-encoded by the API.
749 file_put_contents( $file_path, base64_decode( $printcode_label_content ) );
750
751 $email->attachment = $file_path;
752 }
753
754 $is_successful = $email->trigger( $order_id );
755
756 if ( $is_successful ) {
757 wp_send_json_success( $to );
758 }
759
760 throw new \Exception( esc_html__( 'Email could not be sent', 'postnl-for-woocommerce' ) );
761 } catch ( \Exception $e ) {
762 wp_send_json_error(
763 array( 'message' => $e->getMessage() ),
764 );
765 }
766 }
767
768 /**
769 * Add "18+" label beside adult products in the WooCommerce admin order page.
770 *
771 * @param int $item_id Order item ID.
772 * @param WC_Order_Item $item Order item object.
773 *
774 * @return void
775 */
776 public function add_adult_product_label_admin_order_page( int $item_id, WC_Order_Item $item ): void {
777 if ( ! $item instanceof WC_Order_Item_Product ) {
778 return; // Only handle product items.
779 }
780
781 $product = $item->get_product();
782
783 if ( ! $product ) {
784 return;
785 }
786
787 $is_adult = Utils::is_adults_only_product( $product );
788
789 if ( $is_adult ) {
790 echo '<p>' . esc_html__( '18+ Adults Only', 'postnl-for-woocommerce' ) . '</p>';
791 }
792 }
793 }
794