PluginProbe
PostNL for WooCommerce / 3.1.4
PostNL for WooCommerce v3.1.4
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 / class-wcmp-export.php

class-wcmp-export.php in PostNL for WooCommerce 3.1.4, at includes/class-wcmp-export.php

1,711 lines 69.1 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2
3 use WPO\WC\PostNL\Compatibility\WC_Core as WCX;
4 use WPO\WC\PostNL\Compatibility\Order as WCX_Order;
5 use WPO\WC\PostNL\Compatibility\Product as WCX_Product;
6
7 if ( ! defined('ABSPATH')) exit; // Exit if accessed directly
8
9 if ( ! class_exists('WooCommerce_PostNL_Export')) :
10
11 class WooCommerce_PostNL_Export {
12
13 // Package types
14 const PACKAGE = 1;
15 const MAILBOX_PACKAGE = 2;
16 const LETTER = 3;
17
18 public $order_id;
19 public $success;
20 public $errors;
21
22 private $prefix_message;
23 private $use_split_address_fields;
24
25 public function __construct() {
26 $this->success = array();
27 $this->errors = array();
28
29 $this->use_split_address_fields = array_key_exists('use_split_address_fields', get_option('woocommerce_postnl_checkout_settings'))
30 ? isset(get_option('woocommerce_postnl_checkout_settings')['use_split_address_fields'])
31 : false;
32
33 include('class-wcmp-rest.php');
34 include('class-wcmp-api.php');
35
36 add_action('admin_notices', array($this, 'admin_notices'));
37 add_action('wp_ajax_wc_postnl', array($this, 'export'));
38 add_action('wp_ajax_wc_postnl_frontend', array($this, 'frontend_api_request'));
39 add_action('wp_ajax_nopriv_wc_postnl_frontend', array($this, 'frontend_api_request'));
40 }
41
42 public function admin_notices() {
43 if (isset($_GET['postnl_done'])) { // only do this when for the user that initiated this
44 $action_return = get_option('wcpostnl_admin_notices');
45 $print_queue = get_option('wcpostnl_print_queue', array());
46 if ( ! empty($action_return)) {
47 foreach ($action_return as $type => $message) {
48 if (in_array($type, array('success', 'error'))) {
49 if ($type == 'success' && ! empty($print_queue)) {
50 $print_queue_store = sprintf('<input type="hidden" value="%s" id="wcmp_printqueue">', json_encode(array_keys($print_queue['order_ids'])));
51 $print_queue_offset_store = sprintf('<input type="hidden" value="%s" id="wcmp_printqueue_offset">', $print_queue['offset']);
52 // dequeue
53 delete_option('wcpostnl_print_queue');
54 }
55 printf('<div class="postnl_notice notice notice-%s"><p>%s</p>%s</div>', $type, $message, isset($print_queue_store) ? $print_queue_store . $print_queue_offset_store : '');
56 }
57 }
58 // destroy after reading
59 delete_option('wcpostnl_admin_notices');
60 wp_cache_delete('wcpostnl_admin_notices', 'options');
61 }
62 }
63
64 if (isset($_GET['postnl'])) {
65 switch($_GET['postnl']) {
66 case 'no_consignments':
67 $message = __('You have to export the orders to PostNL before you can print the labels!', 'woocommerce-postnl');
68 printf('<div class="postnl_notice notice notice-error"><p>%s</p></div>', $message);
69 break;
70 default:
71 break;
72 }
73 }
74 }
75
76 /**
77 * Export selected orders
78 * @access public
79 * @return void
80 */
81 public function export() {
82 // Check the nonce
83 check_ajax_referer('wc_postnl', 'security');
84
85 if ( ! is_user_logged_in()) {
86 wp_die(__('You do not have sufficient permissions to access this page.', 'woocommerce-postnl'));
87 }
88
89 $return = array();
90
91 // Check the user privileges (maybe use order ids for filter?)
92 if (apply_filters('wc_postnl_check_privs', ! current_user_can('manage_woocommerce_orders') && ! current_user_can('edit_shop_orders'))) {
93 $return['error'] = __('You do not have sufficient permissions to access this page.', 'woocommerce-postnl');
94 $json = json_encode($return);
95 echo $json;
96 die();
97 }
98
99 extract($_REQUEST); // $request, $order_ids, ...
100 // make sure $order_ids is a proper array
101 $order_ids = ! empty($order_ids) ? $this->sanitize_posted_array($order_ids) : array();
102
103 switch($request) {
104 case 'add_shipments':
105 // filter out non-postnl destinations
106 $order_ids = $this->filter_postnl_destination_orders($order_ids);
107
108 if (empty($order_ids)) {
109 $this->errors[] = __('You have not selected any orders!', 'woocommerce-postnl');
110 break;
111 }
112
113 // if we're going to print directly, we need to process the orders first, regardless of the settings
114 $process = (isset($print) && $print == 'yes') ? true : false;
115 $return = $this->add_shipments($order_ids);
116 break;
117 case 'add_return':
118 if (empty($postnl_options)) {
119 $this->errors[] = __('You have not selected any orders!', 'woocommerce-postnl');
120 break;
121 }
122 $return = $this->add_return($postnl_options);
123 break;
124 case 'get_labels':
125 $offset = ! empty($offset) && is_numeric($offset) ? $offset % 4 : 0;
126 if (empty($order_ids) && empty($shipment_ids)) {
127 $this->errors[] = __('You have not selected any orders!', 'woocommerce-postnl');
128 break;
129 }
130 $label_response_type = isset($label_response_type) ? $label_response_type : null;
131 if ( ! empty($shipment_ids)) {
132 $order_ids = ! empty($order_ids) ? $this->sanitize_posted_array($order_ids) : array();
133 $shipment_ids = $this->sanitize_posted_array($shipment_ids);
134 $return = $this->get_shipment_labels($shipment_ids, $order_ids, $label_response_type, $offset);
135 } else {
136 $order_ids = $this->filter_postnl_destination_orders($order_ids);
137 $return = $this->get_labels($order_ids, $label_response_type, $offset);
138 }
139 break;
140 case 'modal_dialog':
141 if (empty($order_ids)) {
142 $errors[] = __('You have not selected any orders!', 'woocommerce-postnl');
143 break;
144 }
145 $order_ids = $this->filter_postnl_destination_orders($order_ids);
146 $this->modal_dialog($order_ids, $dialog);
147 break;
148 }
149
150 // display errors directly if PDF requested or modal
151 if (in_array($request, array('add_return', 'get_labels', 'modal_dialog')) && ! empty($this->errors)) {
152 echo $this->parse_errors($this->errors);
153 die();
154 }
155
156 // format errors for html output
157 if ( ! empty($this->errors)) {
158 $return['error'] = $this->parse_errors($this->errors);
159 }
160
161 // When adding shipments, store $return for use in admin_notice
162 // This way we can refresh the page (JS) to show all new buttons
163 if ($request == 'add_shipments' && ! empty($print) && ($print == 'no' || $print == 'after_reload')) {
164 update_option('wcpostnl_admin_notices', $return);
165 if ($print == 'after_reload') {
166 $print_queue = array(
167 'order_ids' => $return['success_ids'],
168 'offset' => isset($offset) && is_numeric($offset) ? $offset % 4 : 0,
169 );
170 update_option('wcpostnl_print_queue', $print_queue);
171 }
172 }
173
174 // if we're directed here from modal, show proper result page
175 if (isset($modal)) {
176 $this->modal_success_page($request, $return);
177 } else {
178 // return JSON response
179 echo json_encode($return);
180 }
181
182 die();
183 }
184
185 public function sanitize_posted_array($array) {
186 // check for JSON
187 if (is_string($array) && strpos($array, '[') !== false) {
188 $array = json_decode(stripslashes($array));
189 }
190
191 // cast as array for single exports
192 $array = (array) $array;
193
194 return $array;
195 }
196
197 public function add_shipments($order_ids, $process = false) {
198 $return = array();
199
200 $this->log("*** Creating shipments started ***");
201
202 foreach ($order_ids as $order_id) {
203 $created_shipments = array();
204 $order = WCX::get_order($order_id);
205 $shipments = $this->get_order_shipment_data((array) $order_id);
206 $shipments = $this->validate_shipments($shipments);
207 if (empty($shipments)) {
208 $this->log("Export for order {$order_id} skipped (missing or invalidated shipment data)");
209 continue;
210 }
211
212 $this->log("Shipment data for order {$order_id}:\n" . var_export($shipments, true));
213
214 // check colli amount
215 $extra_params = WCX_Order::get_meta($order, '_postnl_shipment_options_extra');
216 $colli_amount = isset($extra_params['colli_amount']) ? $extra_params['colli_amount'] : 1;
217
218 for ($i = 0; $i < intval($colli_amount); $i++) {
219 try {
220 $api = $this->init_api();
221 $response = $api->add_shipments($shipments);
222 $this->log("API response (order {$order_id}):\n" . var_export($response, true));
223 if (isset($response['body']['data']['ids'])) {
224 $ids = array_shift($response['body']['data']['ids']);
225 $shipment_id = $ids['id'];
226 $this->success[$order_id] = $shipment_id;
227
228 $created_shipments[] = $shipment_id;
229 $shipment = array(
230 'shipment_id' => $shipment_id,
231 );
232
233 // save shipment data in order meta
234 $this->save_shipment_data($order, $shipment);
235
236 // process directly setting
237 if (isset(WooCommerce_PostNL()->general_settings['process_directly']) || $process === true) {
238 // flush cache until WC issue #13439 is fixed https://github.com/woocommerce/woocommerce/issues/13439
239 if (method_exists($order, 'save')) {
240 $order->save();
241 }
242 $this->get_labels((array) $order_id, 'url');
243 $this->get_shipment_data($shipment_id, $order);
244 }
245
246 // status automation
247 if (isset(WooCommerce_PostNL()->general_settings['order_status_automation']) && ! empty(WooCommerce_PostNL()->general_settings['automatic_order_status'])) {
248 $order->update_status(WooCommerce_PostNL()->general_settings['automatic_order_status'], __('PostNL shipment created:', 'woocommerce-postnl'));
249 }
250 } else {
251 $this->errors[$order_id] = __('Unknown error', 'woocommerce-postnl');
252 }
253 } catch ( Exception $e ) {
254 $this->errors[$order_id] = $e->getMessage();
255 }
256 }
257
258 // store shipment ids from this export
259 if ( ! empty($created_shipments)) {
260 WCX_Order::update_meta_data($order, '_postnl_last_shipment_ids', $created_shipments);
261 }
262 }
263 if ( ! empty($this->success)) {
264 $return['success'] = sprintf(__('%s shipments successfully exported to PostNL', 'woocommerce-postnl'), count($this->success));
265 $return['success_ids'] = $this->success;
266 }
267
268 return $return;
269 }
270
271 public function add_return($postnl_options) {
272 $return = array();
273
274 $this->log("*** Creating return shipments started ***");
275
276 foreach ($postnl_options as $order_id => $options) {
277 $return_shipments = array($this->prepare_return_shipment_data($order_id, $options));
278 $this->log("Return shipment data for order {$order_id}:\n" . var_export($return_shipments, true));
279
280 try {
281 $api = $this->init_api();
282 $response = $api->add_shipments($return_shipments, 'return');
283 $this->log("API response (order {$order_id}):\n" . var_export($response, true));
284 if (isset($response['body']['data']['ids'])) {
285 $order = WCX::get_order($order_id);
286 $ids = array_shift($response['body']['data']['ids']);
287 $shipment_id = $ids['id'];
288 $this->success[$order_id] = $shipment_id;
289
290 $shipment = array(
291 'shipment_id' => $shipment_id,
292 );
293
294 // save shipment data in order meta
295 $this->save_shipment_data($order, $shipment);
296 } else {
297 $this->errors[$order_id] = __('Unknown error', 'woocommerce-postnl');
298 }
299 } catch ( Exception $e ) {
300 $this->errors[$order_id] = $e->getMessage();
301 }
302 }
303
304 return $return;
305 }
306
307 public function get_shipment_labels($shipment_ids, $order_ids = array(), $label_response_type = null, $offset = 0) {
308 $return = array();
309
310 $this->log("*** Label request started ***");
311 $this->log("Shipment ID's: " . implode(', ', $shipment_ids));
312
313 try {
314 $api = $this->init_api();
315 $params = array();
316 if ( ! empty($offset) && is_numeric($offset)) {
317 $portrait_positions = array(2, 4, 1, 3); // positions are defined on landscape, but paper is filled portrait-wise
318 $params['positions'] = implode(';', array_slice($portrait_positions, $offset));
319 }
320
321 if (isset($label_response_type) && $label_response_type == 'url') {
322 $response = $api->get_shipment_labels($shipment_ids, $params, 'link');
323 $this->add_postnl_note_to_shipments($shipment_ids, $order_ids);
324 $this->log("API response:\n" . var_export($response, true));
325
326 if (isset($response['body']['data']['pdfs']['url'])) {
327 $url = untrailingslashit($api->APIURL) . $response['body']['data']['pdfs']['url'];
328 $return['url'] = $url;
329 } else {
330 $this->errors[] = __('Unknown error', 'woocommerce-postnl');
331 }
332 } else {
333 $response = $api->get_shipment_labels($shipment_ids, $params, 'pdf');
334 $this->add_postnl_note_to_shipments($shipment_ids, $order_ids);
335
336 if (isset($response['body'])) {
337 $this->log("PDF data received");
338 $pdf_data = $response['body'];
339 $output_mode = isset(WooCommerce_PostNL()->general_settings['download_display'])
340 ? WooCommerce_PostNL()->general_settings['download_display']
341 : '';
342 if ($output_mode == 'display') {
343 $this->stream_pdf($pdf_data, $order_ids);
344 } else {
345 $this->download_pdf($pdf_data, $order_ids);
346 }
347 } else {
348 $this->log("Unknown error, API response:\n" . var_export($response, true));
349 $this->errors[] = __('Unknown error', 'woocommerce-postnl');
350 }
351 }
352 } catch ( Exception $e ) {
353 $this->errors[] = $e->getMessage();
354 }
355
356 return $return;
357 }
358
359 public function get_labels($order_ids, $label_response_type = null, $offset = 0) {
360 $shipment_ids = $this->get_shipment_ids($order_ids, array('only_last' => true));
361
362 if (empty($shipment_ids)) {
363 $this->log("*** Failed label request (not exported yet) ***");
364 $this->errors[] = __('The selected orders have not been exported to PostNL yet!', 'woocommerce-postnl');
365
366 return array();
367 }
368
369 return $this->get_shipment_labels($shipment_ids, $order_ids, $label_response_type, $offset);
370 }
371
372 public function modal_dialog($order_ids, $dialog) {
373 // check for JSON
374 if (is_string($order_ids) && strpos($order_ids, '[') !== false) {
375 $order_ids = json_decode(stripslashes($order_ids));
376 }
377
378 // cast as array for single exports
379 $order_ids = (array) $order_ids;
380
381 include('views/wcmp-bulk-options-form.php');
382 die();
383 }
384
385 public function modal_success_page($request, $result) {
386 include('views/wcmp-modal-result-page.php');
387 die();
388 }
389
390 public function frontend_api_request() {
391 // TODO: check nonce
392 $params = $_REQUEST;
393
394 // filter non API params
395 $api_params = array(
396 'cc' => '',
397 'postal_code' => '',
398 'number' => '',
399 'carrier' => '',
400 'delivery_time' => '',
401 'delivery_date' => '',
402 'cutoff_time' => '',
403 'dropoff_days' => '',
404 'dropoff_delay' => '',
405 'deliverydays_window' => '',
406 'exclude_delivery_type' => '',
407 );
408 $params = array_intersect_key($params, $api_params);
409
410 $api = $this->init_api();
411
412 try {
413 $response = $api->get_delivery_options($params, true);
414
415 @header('Content-type: application/json; charset=utf-8');
416
417 echo $response['body'];
418 } catch ( Exception $e ) {
419 @header("HTTP/1.1 503 service unavailable");
420 }
421 die();
422 }
423
424 public function init_api() {
425 // $user = WooCommerce_PostNL()->general_settings['api_username'];
426 if ( ! isset(WooCommerce_PostNL()->general_settings['api_key'])) {
427 return false;
428 }
429
430 $key = WooCommerce_PostNL()->general_settings['api_key'];
431 $api = new WC_PostNL_API($key);
432
433 return $api;
434 }
435
436 public function get_order_shipment_data($order_ids, $type = 'standard') {
437 foreach ($order_ids as $order_id) {
438 // get order
439 $order = WCX::get_order($order_id);
440
441 $shipment = array(
442 'reference_identifier' => $this->replace_shortcodes(WooCommerce_PostNL()->export_defaults['label_description'], $order),
443 'recipient' => $this->get_recipient($order),
444 'options' => $this->get_options($order),
445 'carrier' => 1, // default to POSTNL for now
446 );
447
448 if ($pickup = $this->is_pickup($order)) {
449 $shipment['pickup'] = array(
450 'postal_code' => $pickup['postal_code'],
451 'street' => $pickup['street'],
452 'city' => $pickup['city'],
453 'number' => $pickup['number'],
454 'location_code' => $pickup['location_code'],
455 'retail_network_id' => $pickup['retail_network_id'],
456 'location_name' => $pickup['location'],
457 );
458 }
459
460 $shipping_country = WCX_Order::get_prop($order, 'shipping_country');
461 if ($this->is_world_shipment_country($shipping_country)) {
462 $customs_declaration = $this->get_customs_declaration($order);
463 $shipment['customs_declaration'] = $customs_declaration;
464 $shipment['physical_properties'] = array(
465 'weight' => $customs_declaration['weight'],
466 );
467 }
468
469 if ($shipment['options']['package_type'] == self::MAILBOX_PACKAGE ) {
470 unset($shipment['options']['weight']);
471 }
472
473 $shipments[] = apply_filters('wc_postnl_order_shipment', $shipment, $order, $type, $this);
474 }
475
476 return $shipments;
477 }
478
479 public function get_recipient($order) {
480 $is_using_old_fields =
481 (string) WCX_Order::get_meta($order, '_billing_street_name') != '' ||
482 (string) WCX_Order::get_meta($order, '_billing_house_number') != '';
483
484 $shipping_name = method_exists($order, 'get_formatted_shipping_full_name')
485 ? $order->get_formatted_shipping_full_name()
486 : trim($order->shipping_first_name . ' ' . $order->shipping_last_name);
487 $address = array(
488 'cc' => (string) WCX_Order::get_prop($order, 'shipping_country'),
489 'city' => (string) WCX_Order::get_prop($order, 'shipping_city'),
490 'person' => $shipping_name,
491 'company' => (string) WCX_Order::get_prop($order, 'shipping_company'),
492 'email' => WCX_Order::get_prop($order, 'billing_email'),
493 'phone' => isset(WooCommerce_PostNL()->export_defaults['connect_phone']) ? WCX_Order::get_prop($order, 'billing_phone') : '',
494 'street_additional_info' => WCX_Order::get_prop($order, 'shipping_address_2'),
495 );
496
497 $shipping_country = WCX_Order::get_prop( $order, 'shipping_country' );
498 if ( $shipping_country == 'NL' || $shipping_country == 'BE' ) {
499 // use billing address if old 'pakjegemak' (1.5.6 and older)
500 if ($pgaddress = WCX_Order::get_meta($order, '_postnl_pgaddress')) {
501 $billing_name = method_exists($order, 'get_formatted_billing_full_name')
502 ? $order->get_formatted_billing_full_name()
503 : trim($order->billing_first_name . ' ' . $order->billing_last_name);
504 $address_intl = array(
505 'city' => (string) WCX_Order::get_prop($order, 'billing_city'),
506 'person' => $billing_name,
507 'company' => (string) WCX_Order::get_prop($order, 'billing_company'),
508 'postal_code' => (string) WCX_Order::get_prop($order, 'billing_postcode'),
509 );
510
511 // If not using old fields
512 if (!$is_using_old_fields) {
513 // Split the address line 1 into three parts
514 preg_match(
515 Woocommerce_PostNL_Postcode_Fields::SPLIT_STREET_REGEX,
516 WCX_Order::get_prop($order, 'billing_address_1'),
517 $address_parts
518 );
519 $address_intl['street'] = (string) $address_parts['street'];
520 $address_intl['number'] = (string) $address_parts['number'];
521 $address_intl['number_suffix'] = array_key_exists('number_suffix', $address_parts) // optional
522 ? (string) $address_parts['number_suffix']
523 : '';
524 $address_intl['street_additional_info'] = WCX_Order::get_prop($order, 'billing_address_2');
525 } else {
526 $address_intl['street'] = (string) WCX_Order::get_meta($order, '_billing_street_name');
527 $address_intl['number'] = (string) WCX_Order::get_meta($order, '_billing_house_number');
528 $address_intl['number_suffix'] = (string) WCX_Order::get_meta($order, '_billing_house_number_suffix');
529 }
530 } else {
531 $address_intl = array(
532 'postal_code' => (string) WCX_Order::get_prop( $order, 'shipping_postcode' ),
533 );
534 // If not using old fields
535 if (!$is_using_old_fields) {
536 // Split the address line 1 into three parts
537 preg_match(
538 Woocommerce_PostNL_Postcode_Fields::SPLIT_STREET_REGEX,
539 WCX_Order::get_prop($order, 'shipping_address_1'),
540 $address_parts
541 );
542
543 $address_intl['street'] = (string) $address_parts['street'];
544 $address_intl['number'] = (string) $address_parts['number'];
545 $address_intl['number_suffix'] = array_key_exists('number_suffix', $address_parts) // optional
546 ? (string) $address_parts['number_suffix']
547 : '';
548 } else {
549 $address_intl['street'] = (string) WCX_Order::get_meta($order, '_shipping_street_name');
550 $address_intl['number'] = (string) WCX_Order::get_meta($order, '_shipping_house_number');
551 $address_intl['number_suffix'] = (string) WCX_Order::get_meta($order, '_shipping_house_number_suffix');
552 }
553 }
554 } else {
555 $address_intl = array(
556 'postal_code' => (string) WCX_Order::get_prop($order, 'shipping_postcode'),
557 'street' => (string) WCX_Order::get_prop($order, 'shipping_address_1'),
558 'street_additional_info' => (string) WCX_Order::get_prop($order, 'shipping_address_2'),
559 'region' => (string) WCX_Order::get_prop($order, 'shipping_state'),
560 );
561 }
562
563 $address = array_merge($address, $address_intl);
564
565 return apply_filters('wc_postnl_recipient', $address, $order);
566 }
567
568 /**
569 * @param $selected_shipment_ids
570 * @param $order_ids
571 *
572 * @internal param $shipment_ids
573 */
574 public function add_postnl_note_to_shipments($selected_shipment_ids, $order_ids) {
575 if ( ! isset(WooCommerce_PostNL()->general_settings['barcode_in_note'])) {
576 return;
577 }
578
579 // Select the barcode text of the PostNL settings
580 $this->prefix_message = WooCommerce_PostNL()->general_settings['barcode_in_note_title'];
581
582 foreach ($order_ids as $order_id) {
583 $order = WCX::get_order($order_id);
584 $order_shipments = WCX_Order::get_meta($order, '_postnl_shipments');
585 foreach ($order_shipments as $shipment) {
586 $shipment_id = $shipment['shipment_id'];
587 $this->add_postnl_note_to_shipment($selected_shipment_ids, $shipment_id, $order);
588 }
589 }
590
591 return;
592 }
593
594 public function get_options($order) {
595 // parse description
596 if (isset(WooCommerce_PostNL()->export_defaults['label_description'])) {
597 $description = $this->replace_shortcodes(WooCommerce_PostNL()->export_defaults['label_description'], $order);
598 } else {
599 $description = '';
600 }
601
602 // use shipment options from order when available
603 $shipment_options = WCX_Order::get_meta($order, '_postnl_shipment_options');
604
605 if ( ! empty($shipment_options)) {
606 $empty_defaults = array(
607 'package_type' => self::PACKAGE,
608 'only_recipient' => 0,
609 'signature' => 0,
610 'return' => 0,
611 'label_description' => '',
612 'insured_amount' => 0,
613 'age_check' => 0,
614 );
615 $options = array_merge($empty_defaults, $shipment_options);
616 } else {
617 if (isset(WooCommerce_PostNL()->export_defaults['insured'])
618 && WooCommerce_PostNL()->export_defaults['insured_amount'] == ''
619 && isset(WooCommerce_PostNL()->export_defaults['insured_amount_custom'])) {
620 $insured_amount = WooCommerce_PostNL()->export_defaults['insured_amount_custom'];
621 } else if (isset(WooCommerce_PostNL()->export_defaults['insured']) && isset( WooCommerce_PostNL()->export_defaults['insured_amount'] )) {
622 $insured_amount = WooCommerce_PostNL()->export_defaults['insured_amount'];
623 } else {
624 $insured_amount = 0;
625 }
626
627 $options = array(
628 'package_type' => $this->get_package_type_for_order($order),
629 'only_recipient' => (isset(WooCommerce_PostNL()->export_defaults['only_recipient'])) ? 1 : 0,
630 'signature' => (isset(WooCommerce_PostNL()->export_defaults['signature'])) ? 1 : 0,
631 'return' => (isset(WooCommerce_PostNL()->export_defaults['return'])) ? 1 : 0,
632 'label_description' => $description,
633 'insured_amount' => $insured_amount,
634 'age_check' => (isset(WooCommerce_PostNL()->export_defaults['age_check'])) ? 1 : 0,
635 );
636 }
637
638 // convert insurance option
639 if ( ! isset($options['insurance']) && isset($options['insured_amount'])) {
640 if ($options['insured_amount'] > 0) {
641 $options['insurance'] = array(
642 'amount' => (int) $options['insured_amount'] * 100,
643 'currency' => 'EUR',
644 );
645 }
646
647 unset($options['insured_amount']);
648 unset($options['insured']);
649 }
650
651 // set insurance amount to int if already set
652 if (isset($options['insurance'])) {
653 $options['insurance']['amount'] = (int) $options['insurance']['amount'];
654 }
655
656 // remove frontend insurance option values
657 if (isset($options['insured_amount'])) {
658 unset($options['insured_amount']);
659 }
660 if (isset($options['insured'])) {
661 unset($options['insured']);
662 }
663
664 // load delivery options
665 $postnl_delivery_options = WCX_Order::get_meta($order, '_postnl_delivery_options');
666
667 // set delivery type
668 $options['delivery_type'] = $this->get_delivery_type($order, $postnl_delivery_options);
669
670 // age check cann't be used in morning and evening delivery
671 if ($options['age_check'] == 1) {
672 $age_check_options = $this->get_age_check($order, $options, $postnl_delivery_options);
673
674 $options['age_check'] = $age_check_options[0];
675 $options['signature'] = $age_check_options[1];
676 $options['only_recipient'] = $age_check_options[2];
677 }
678
679 // Options for Pickup and Pickup express delivery types:
680 // always enable signature on receipt
681 if ($this->is_pickup($order, $postnl_delivery_options)) {
682 $options['signature'] = 1;
683 }
684
685 // delivery date (postponed delivery & pickup)
686 if ($delivery_date = $this->get_delivery_date($order, $postnl_delivery_options)) {
687 $date_time = explode(' ', $delivery_date); // split date and time
688 // only add if date is in the future
689 $timestamp = strtotime($date_time[0]);
690
691 if ($timestamp < time()) {
692 $new_timestamp = $this->get_next_delivery_day($timestamp);
693 $delivery_date = date('Y-m-d h:i:s', $new_timestamp);
694 }
695
696 $options['delivery_date'] = $delivery_date;
697 }
698
699 // options signature & recipient only
700 $postnl_signature = WCX_Order::get_meta($order, '_postnl_signature');
701 if ( ! empty($postnl_signature)) {
702 $options['signature'] = 1;
703 }
704 $postnl_only_recipient = WCX_Order::get_meta($order, '_postnl_only_recipient');
705 if ( ! empty($postnl_only_recipient)) {
706 $options['only_recipient'] = 1;
707 }
708
709 // allow prefiltering consignment data
710 $options = apply_filters('wc_postnl_order_shipment_options', $options, $order);
711
712 // PREVENT ILLEGAL SETTINGS
713 // convert numeric strings to int
714 $int_options = array('package_type', 'delivery_type', 'only_recipient', 'signature', 'return', 'age_check');
715 foreach ($options as $key => &$value) {
716 if (in_array($key, $int_options)) {
717 $value = (int) $value;
718 }
719 }
720
721 // disable options for mailbox package, unpaid letter
722 if ($options['package_type'] != self::PACKAGE) {
723 $illegal_options = array('delivery_type', 'only_recipient', 'signature', 'return', 'insurance', 'delivery_date', 'age_check');
724 foreach ($options as $key => $option) {
725 if (in_array($key, $illegal_options)) {
726 unset($options[$key]);
727 }
728 }
729 }
730
731 return $options;
732 }
733
734 /**
735 * @param int $timestamp
736 *
737 * @return false|string
738 */
739 private function get_next_delivery_day($timestamp) {
740 $weekDay = date('w', $timestamp);
741 $new_timestamp = strtotime( '+1 day', $timestamp );
742
743 if ($weekDay == 0 || $weekDay == 1 || $new_timestamp < time() ) {
744 $new_timestamp = $this->get_next_delivery_day( $new_timestamp );
745 }
746
747 return $new_timestamp;
748 }
749
750 public function get_customs_declaration( $order ) {
751 $invoice = $this->get_invoice_number($order);
752 $contents = (int) ((isset(WooCommerce_PostNL()->export_defaults['package_contents']))
753 ? WooCommerce_PostNL()->export_defaults['package_contents']
754 : 1);
755
756 // Item defaults:
757 // Classification
758 $default_hs_code = (isset(WooCommerce_PostNL()->export_defaults['hs_code']))
759 ? WooCommerce_PostNL()->export_defaults['hs_code']
760 : '';
761 // Country (=shop base)
762 $country = WC()->countries->get_base_country();
763
764 $items = array();
765 foreach ($order->get_items() as $item_id => $item) {
766 $product = $order->get_product_from_item($item);
767 if ( ! empty($product)) {
768 // Description
769 $description = $item['name'];
770 // Amount
771 $amount = (int) (isset($item['qty']) ? $item['qty'] : 1);
772 // Weight (total item weight in grams)
773 $weight = (int) round($this->get_item_weight_kg($item, $order) * 1000);
774 // Item value (in cents)
775 $item_value = array(
776 'amount' => (int) round(($item['line_total'] + $item['line_tax']) * 100),
777 'currency' => WCX_Order::get_prop($order, 'currency'),
778 );
779 // Classification / HS Code
780 $classification = WCX_Product::get_meta($product, '_postnl_hs_code', true);
781 if (empty($classification)) {
782 $classification = $default_hs_code;
783 }
784
785 // add item to item list
786 $items[] = compact('description', 'amount', 'weight', 'item_value', 'classification', 'country');
787 }
788 }
789 // Get the total weight of the package
790 $weight = (int) round($this->get_parcel_weight($order) * 1000);
791
792 return compact('weight', 'invoice', 'contents', 'items');
793 }
794
795 public function validate_shipments($shipments, $output_errors = true) {
796 $missing_hs_codes = 0;
797 foreach ($shipments as $key => $shipment) {
798 // check customs declaration for HS codes
799 if (isset($shipment['customs_declaration']) && ! empty($shipment['customs_declaration']['items'])) {
800 foreach ($shipment['customs_declaration']['items'] as $key => $item) {
801 if (empty($item['classification'])) {
802 unset($shipments[$key]);
803 $missing_hs_codes++;
804 break;
805 }
806 }
807 }
808 if ($output_errors === true && $missing_hs_codes > 0) {
809 $this->errors[] = sprintf(
810 __('%d shipments missing HS codes - not exported.', 'woocommerce-postnl'),
811 $missing_hs_codes
812 );
813 }
814 }
815
816 return $shipments;
817 }
818
819 public function get_shipment_ids($order_ids, $args) {
820 $shipment_ids = array();
821 foreach ($order_ids as $order_id) {
822 $order = WCX::get_order($order_id);
823 $order_shipments = WCX_Order::get_meta($order, '_postnl_shipments');
824 if ( ! empty($order_shipments)) {
825 $order_shipment_ids = array();
826 // exclude concepts or only concepts
827 foreach ($order_shipments as $key => $shipment) {
828 if (isset($args['exclude_concepts']) && empty($shipment['tracktrace'])) {
829 continue;
830 }
831 if (isset($args['only_concepts']) && ! empty($shipment['tracktrace'])) {
832 continue;
833 }
834
835 $order_shipment_ids[] = $shipment['shipment_id'];
836 }
837
838 if (isset($args['only_last'])) {
839 $last_shipment_ids = WCX_Order::get_meta($order, '_postnl_last_shipment_ids');
840 if ( ! empty($last_shipment_ids) && is_array($last_shipment_ids)) {
841 foreach ($order_shipment_ids as $order_shipment_id) {
842 if (in_array($order_shipment_id, $last_shipment_ids)) {
843 $shipment_ids[] = $order_shipment_id;
844 }
845 }
846 } else {
847 $shipment_ids[] = array_pop($order_shipment_ids);
848 }
849 } else {
850 $shipment_ids[] = array_merge($shipment_ids, $order_shipment_ids);
851 }
852 }
853 }
854
855 return $shipment_ids;
856 }
857
858 /**
859 * @param $order
860 * @param $shipment
861 *
862 * @return bool|void
863 */
864 public function save_shipment_data($order, $shipment) {
865 if (empty($shipment)) {
866 return false;
867 }
868
869 $new_shipments = array();
870 $new_shipments[$shipment['shipment_id']] = $shipment;
871
872 if (isset(WooCommerce_PostNL()->general_settings['keep_shipments'])) {
873 if ($old_shipments = WCX_Order::get_meta($order, '_postnl_shipments')) {
874 $shipments = $old_shipments;
875 foreach ($new_shipments as $shipment_id => $shipment) {
876 $shipments[$shipment_id] = $shipment;
877 }
878 }
879 }
880
881 $shipments = isset($shipments) ? $shipments : $new_shipments;
882
883 WCX_Order::update_meta_data($order, '_postnl_shipments', $shipments);
884
885 return;
886 }
887
888 public function get_package_type_from_shipping_method($shipping_method, $shipping_class, $shipping_country) {
889 $package_type = self::PACKAGE;
890 $shipping_method_id_class = "";
891 if (isset(WooCommerce_PostNL()->export_defaults['shipping_methods_package_types'])) {
892 if (strpos($shipping_method, "table_rate:") === 0 && class_exists('WC_Table_Rate_Shipping')) {
893 // Automattic / WooCommerce table rate
894 // use full method = method_id:instance_id:rate_id
895 $shipping_method_id = $shipping_method;
896 } else { // non table rates
897
898 if (strpos($shipping_method, ':') !== false) {
899 // means we have method_id:instance_id
900 $shipping_method = explode(':', $shipping_method);
901 $shipping_method_id = $shipping_method[0];
902 $shipping_method_instance = $shipping_method[1];
903 } else {
904 $shipping_method_id = $shipping_method;
905 }
906
907 // add class if we have one
908 if ( ! empty($shipping_class)) {
909 $shipping_method_id_class = "{$shipping_method_id}:{$shipping_class}";
910 }
911 }
912 foreach (WooCommerce_PostNL()->export_defaults['shipping_methods_package_types'] as $package_type_key => $package_type_shipping_methods) {
913 if ($this->isActiveMethod(
914 $shipping_method_id,
915 $package_type_shipping_methods,
916 $shipping_method_id_class,
917 $shipping_class
918 )) {
919 $package_type = $package_type_key;
920 break;
921 }
922 }
923 }
924
925 // disable mailbox package outside NL
926 if ($shipping_country != 'NL' && $package_type == self::MAILBOX_PACKAGE) {
927 $package_type = self::PACKAGE;
928 }
929
930 return $package_type;
931 }
932
933 // determine appropriate package type for this order
934 public function get_package_type_for_order($order) {
935 $shipping_country = WCX_Order::get_prop($order, 'shipping_country');
936
937 // get shipping methods from order
938 $order_shipping_methods = $order->get_items('shipping');
939
940 if ( ! empty($order_shipping_methods)) {
941 // we're taking the first (we're not handling multiple shipping methods as of yet)
942 $order_shipping_method = array_shift($order_shipping_methods);
943 $order_shipping_method = $order_shipping_method['method_id'];
944
945 $order_shipping_class = WCX_Order::get_meta($order, '_postnl_highest_shipping_class');
946
947 if (empty($order_shipping_class)) {
948 $order_shipping_class = $this->get_order_shipping_class($order, $order_shipping_method);
949 }
950
951 $package_type = $this->get_package_type_from_shipping_method(
952 $order_shipping_method,
953 $order_shipping_class,
954 $shipping_country
955 );
956 }
957
958 // fallback if no match from previous
959 if ( ! isset($package_type)) {
960 if ((isset(WooCommerce_PostNL()->export_defaults['package_type']))) {
961 $package_type = WooCommerce_PostNL()->export_defaults['package_type'];
962 } else {
963 $package_type = self::PACKAGE;
964 }
965 }
966
967 // always parcel for Pickup and Pickup express delivery types.
968 if ($this->is_pickup($order)) {
969 $package_type = self::PACKAGE;
970 }
971
972 return $package_type;
973 }
974
975 public function get_package_types($shipment_type = 'shipment') {
976 $package_types = array(
977 self::PACKAGE => __('Parcel', 'woocommerce-postnl'),
978 self::MAILBOX_PACKAGE => __('Mailbox package', 'woocommerce-postnl'),
979 self::LETTER => __('Unpaid letter', 'woocommerce-postnl'),
980 );
981 if ($shipment_type == 'return') {
982 unset($package_types[self::MAILBOX_PACKAGE]);
983 unset($package_types[self::LETTER]);
984 }
985
986 return $package_types;
987 }
988
989 public function get_package_name($package_type) {
990 $package_types = $this->get_package_types();
991 $package_name = isset($package_types[$package_type])
992 ? $package_types[$package_type]
993 : __('Unknown', 'woocommerce-postnl');
994 return $package_name;
995 }
996
997 public function parse_errors($errors) {
998 $parsed_errors = array();
999 foreach ($errors as $key => $error) {
1000 // check if we have an order_id
1001 if ($key > 10) {
1002 $parsed_errors[] = sprintf("<strong>%s %s:</strong> %s", __( 'Order', 'woocommerce-postnl' ), $key, $error );
1003 } else {
1004 $parsed_errors[] = $error;
1005 }
1006 }
1007
1008 if (count($parsed_errors) == 1) {
1009 $html = array_shift($parsed_errors);
1010 } else {
1011 foreach ($parsed_errors as &$parsed_error) {
1012 $parsed_error = "<li>{$parsed_error}</li>";
1013 }
1014 $html = sprintf("<ul>%s</ul>", implode("\n",$parsed_errors));
1015 }
1016
1017 return $html;
1018 }
1019
1020 public function stream_pdf($pdf_data, $order_ids) {
1021 header('Content-type: application/pdf');
1022 header('Content-Disposition: inline; filename="' . $this->get_filename($order_ids) . '"');
1023 echo $pdf_data;
1024 die();
1025 }
1026
1027 public function download_pdf($pdf_data, $order_ids) {
1028 header('Content-Description: File Transfer');
1029 header('Content-Type: application/octet-stream');
1030 header('Content-Disposition: attachment; filename="' . $this->get_filename($order_ids) . '"');
1031 header('Content-Transfer-Encoding: binary');
1032 header('Connection: Keep-Alive');
1033 header('Expires: 0');
1034 header('Cache-Control: must-revalidate, post-check=0, pre-check=0');
1035 header('Pragma: public');
1036 echo $pdf_data;
1037 die();
1038 }
1039
1040 public function get_filename($order_ids) {
1041 $filename = 'PostNL';
1042 $filename .= '-' . date('Y-m-d') . '.pdf';
1043
1044 return apply_filters('wcpostnl_filename', $filename, $order_ids);
1045 }
1046
1047 public function get_shipment_status_name($status_code) {
1048 $shipment_statuses = array(
1049 1 => __('pending - concept', 'woocommerce-postnl'),
1050 2 => __('pending - registered', 'woocommerce-postnl'),
1051 3 => __('enroute - handed to carrier', 'woocommerce-postnl'),
1052 4 => __('enroute - sorting', 'woocommerce-postnl'),
1053 5 => __('enroute - distribution', 'woocommerce-postnl'),
1054 6 => __('enroute - customs', 'woocommerce-postnl'),
1055 7 => __('delivered - at recipient', 'woocommerce-postnl'),
1056 8 => __('delivered - ready for pickup', 'woocommerce-postnl'),
1057 9 => __('delivered - package picked up', 'woocommerce-postnl'),
1058 30 => __('inactive - concept', 'woocommerce-postnl'),
1059 31 => __('inactive - registered', 'woocommerce-postnl'),
1060 32 => __('inactive - enroute - handed to carrier', 'woocommerce-postnl'),
1061 33 => __('inactive - enroute - sorting', 'woocommerce-postnl'),
1062 34 => __('inactive - enroute - distribution', 'woocommerce-postnl'),
1063 35 => __('inactive - enroute - customs', 'woocommerce-postnl'),
1064 36 => __('inactive - delivered - at recipient', 'woocommerce-postnl'),
1065 37 => __('inactive - delivered - ready for pickup', 'woocommerce-postnl'),
1066 38 => __('inactive - delivered - package picked up', 'woocommerce-postnl'),
1067 99 => __('inactive - unknown', 'woocommerce-postnl'),
1068 );
1069
1070 if (isset($shipment_statuses[$status_code])) {
1071 return $shipment_statuses[$status_code];
1072 } else {
1073 return __('Unknown status', 'woocommerce-postnl');
1074 }
1075 }
1076
1077 public function get_shipment_data($id, $order) {
1078 try {
1079 $api = $this->init_api();
1080 $response = $api->get_shipments($id);
1081
1082 if ( ! empty($response['body']['data']['shipments'])) {
1083 $shipments = $response['body']['data']['shipments'];
1084 $shipment = array_shift($shipments);
1085
1086 // if shipment id matches and status is not concept, get tracktrace barcode and status name
1087 if (isset($shipment['id']) && $shipment['id'] == $id && $shipment['status'] >= 2) {
1088 $status = $this->get_shipment_status_name($shipment['status']);
1089 $tracktrace = $shipment['barcode'];
1090 $shipment_id = $id;
1091 $shipment_data = compact('shipment_id', 'status', 'tracktrace', 'shipment');
1092 $this->save_shipment_data($order, $shipment_data);
1093 // If Channel Engine is active, add the created Track & Trace code and set shipping method to PostNL in their meta data
1094 if (WC_CHANNEL_ENGINE_ACTIVE and ! WCX_Order::get_meta($order, '_shipping_ce_track_and_trace')) {
1095 WCX_Order::update_meta_data($order, '_shipping_ce_track_and_trace', $tracktrace);
1096 WCX_Order::update_meta_data($order, '_shipping_ce_shipping_method', 'PostNL');
1097 }
1098
1099 return $shipment_data;
1100 } else {
1101 return false;
1102 }
1103 } else {
1104 // No shipments found with this ID
1105 return false;
1106 }
1107 } catch ( Exception $e ) {
1108 return false;
1109 }
1110 }
1111
1112 public function replace_shortcodes($description, $order) {
1113 $postnl_delivery_options = WCX_Order::get_meta($order, '_postnl_delivery_options');
1114 $replacements = array(
1115 '[ORDER_NR]' => $order->get_order_number(),
1116 '[DELIVERY_DATE]' => isset($postnl_delivery_options) && isset($postnl_delivery_options['date'])
1117 ? $postnl_delivery_options['date']
1118 : '',
1119 );
1120
1121 $description = str_replace(array_keys($replacements), array_values($replacements), $description);
1122
1123 return $description;
1124 }
1125
1126 public function get_item_display_name($item, $order) {
1127 // set base name
1128 $name = $item['name'];
1129
1130 // add variation name if available
1131 $product = $order->get_product_from_item($item);
1132 if ($product && isset($item['variation_id']) && $item['variation_id'] > 0 && method_exists($product, 'get_variation_attributes')) {
1133 $name .= woocommerce_get_formatted_variation($product->get_variation_attributes());
1134 }
1135
1136 return $name;
1137 }
1138
1139 public function get_parcel_weight($order, $unit = 'kg') {
1140 $parcel_weight = (isset(WooCommerce_PostNL()->general_settings['empty_parcel_weight']))
1141 ? preg_replace("/\D/", "", WooCommerce_PostNL()->general_settings['empty_parcel_weight']) / 1000
1142 : 0;
1143
1144 $items = $order->get_items();
1145 foreach ( $items as $item_id => $item ) {
1146 switch ($unit) {
1147 case 'kg':
1148 $parcel_weight += $this->get_item_weight_kg($item, $order);
1149 break;
1150 case 'g':
1151 $parcel_weight += $this->get_item_weight_g($item, $order);
1152 break;
1153 }
1154 }
1155
1156 return $parcel_weight;
1157 }
1158
1159 public function get_item_weight_kg($item, $order) {
1160 $product = $order->get_product_from_item($item);
1161
1162 if (empty($product)) {
1163 return 0;
1164 }
1165
1166 $weight = $product->get_weight();
1167 $weight_unit = get_option('woocommerce_weight_unit');
1168 switch($weight_unit) {
1169 case 'kg':
1170 $product_weight = $weight;
1171 break;
1172 case 'g':
1173 $product_weight = $weight / 1000;
1174 break;
1175 case 'lbs':
1176 $product_weight = $weight * 0.45359237;
1177 break;
1178 case 'oz':
1179 $product_weight = $weight * 0.0283495231;
1180 break;
1181 default:
1182 $product_weight = $weight;
1183 break;
1184 }
1185
1186 $item_weight = (float) $product_weight * (int) $item['qty'];
1187
1188 return $item_weight;
1189 }
1190
1191 public function get_item_weight_g($item, $order) {
1192 $item_weight = $this->get_item_weight_kg($item, $order);
1193 return $item_weight * 1000;
1194 }
1195
1196 public function is_pickup($order, $postnl_delivery_options = '') {
1197 if (empty($postnl_delivery_options)) {
1198 $postnl_delivery_options = WCX_Order::get_meta($order, '_postnl_delivery_options');
1199 }
1200
1201 $pickup_types = array('retail', 'retailexpress');
1202 if ( ! empty($postnl_delivery_options['price_comment']) && in_array($postnl_delivery_options['price_comment'],$pickup_types)) {
1203 return $postnl_delivery_options;
1204 }
1205
1206 // Backwards compatibility for pakjegemak data
1207 $pgaddress = WCX_Order::get_meta($order, '_postnl_pgaddress');
1208 if ( ! empty($pgaddress) && ! empty($pgaddress['postcode'])) {
1209 $pickup = array(
1210 'postal_code' => $pgaddress['postcode'],
1211 'street' => $pgaddress['street'],
1212 'city' => $pgaddress['town'],
1213 'number' => $pgaddress['house_number'],
1214 'location' => $pgaddress['name'],
1215 'price_comment' => 'retail',
1216 );
1217
1218 return $pickup;
1219 }
1220
1221 // no pickup
1222 return false;
1223 }
1224
1225 public function get_delivery_type($order, $postnl_delivery_options = '') {
1226 // delivery types
1227 $delivery_types = array(
1228 'morning' => 1,
1229 'standard' => 2, // 'default in JS API'
1230 'avond' => 3,
1231 'retail' => 4, // 'pickup'
1232 'retailexpress' => 5, // 'pickup_express'
1233 );
1234
1235 if (empty($postnl_delivery_options)) {
1236 $postnl_delivery_options = WCX_Order::get_meta($order, '_postnl_delivery_options');
1237 }
1238
1239 // standard = default, overwrite if options found
1240 $delivery_type = 'standard';
1241 if ( ! empty($postnl_delivery_options)) {
1242 // pickup & pickup express store the delivery type in the delivery options,
1243 // morning & night store it in the time data (...)
1244 if (empty($postnl_delivery_options['price_comment']) && ! empty($postnl_delivery_options['time'])) {
1245 // check if we have a price_comment in the time option
1246 $delivery_time = array_shift($postnl_delivery_options['time']); // take first element in time array
1247 if (isset($delivery_time['price_comment'])) {
1248 $delivery_type = $delivery_time['price_comment'];
1249 }
1250 } else {
1251 $delivery_type = $postnl_delivery_options['price_comment'];
1252 }
1253 }
1254
1255 // backwards compatibility for pakjegemak
1256 if ($pgaddress = WCX_Order::get_meta($order, '_postnl_pgaddress')) {
1257 $delivery_type = 'retail';
1258 }
1259
1260 // convert to int (default to 2 = standard for unknown types)
1261 $delivery_type = isset($delivery_types[$delivery_type]) ? $delivery_types[$delivery_type] : 2;
1262
1263 return $delivery_type;
1264 }
1265
1266 /**
1267 * @param $order
1268 * @param $options
1269 * @param string $postnl_delivery_options
1270 *
1271 * @return array|void
1272 */
1273 public function get_age_check($order, $options, $postnl_delivery_options = '') {
1274
1275 if (empty($postnl_delivery_options)) {
1276 $postnl_delivery_options = WCX_Order::get_meta($order, '_postnl_delivery_options');
1277 }
1278 $delivery_type = $this->get_delivery_type($order, $postnl_delivery_options);
1279
1280 $shipping_country = WCX_Order::get_prop($order, 'shipping_country');
1281
1282 if ( ! empty($postnl_delivery_options) && ! in_array($delivery_type, array(1, 3)) && $shipping_country == 'NL' ) {
1283
1284 $age_check = $options['age_check'] = 1;
1285 $signature = $options['signature'] = 1;
1286 $only_recipient = $options['only_recipient'] = 1;
1287
1288 return [$age_check, $signature, $only_recipient];
1289 }
1290 return;
1291 }
1292
1293 public function get_delivery_date($order, $postnl_delivery_options = '') {
1294 if (empty($postnl_delivery_options)) {
1295 $postnl_delivery_options = WCX_Order::get_meta($order, '_postnl_delivery_options');
1296 }
1297
1298 if ( ! empty($postnl_delivery_options) && ! empty($postnl_delivery_options['date'])) {
1299 $delivery_date = $postnl_delivery_options['date'];
1300
1301 $delivery_type = $this->get_delivery_type($order, $postnl_delivery_options);
1302 if (in_array($delivery_type, array(1, 3)) && ! empty($postnl_delivery_options['time'])) {
1303 $delivery_time_options = array_shift(
1304 $postnl_delivery_options['time']
1305 ); // take first element in time array
1306 $delivery_time = $delivery_time_options['start'];
1307 } else {
1308 $delivery_time = '00:00:00';
1309 }
1310 $delivery_date = "{$delivery_date} {$delivery_time}";
1311
1312 return $delivery_date;
1313 } else {
1314 return false;
1315 }
1316 }
1317
1318 public function get_order_shipping_class($order, $shipping_method_id = '') {
1319 if (empty($shipping_method_id)) {
1320 $order_shipping_methods = $order->get_items('shipping');
1321
1322 if ( ! empty($order_shipping_methods)) {
1323 // we're taking the first (we're not handling multiple shipping methods as of yet)
1324 $order_shipping_method = array_shift($order_shipping_methods);
1325 $shipping_method_id = $order_shipping_method['method_id'];
1326 } else {
1327 return false;
1328 }
1329 }
1330
1331 $shipping_method = $this->get_shipping_method($shipping_method_id);
1332
1333 if (empty($shipping_method)) {
1334 return false;
1335 }
1336
1337 // get shipping classes from order
1338 $found_shipping_classes = $this->find_order_shipping_classes($order);
1339
1340 $highest_class = $this->get_shipping_class($shipping_method, $found_shipping_classes);
1341
1342 return $highest_class;
1343 }
1344
1345 public function get_shipping_method($chosen_method) {
1346 if (version_compare(WOOCOMMERCE_VERSION, '2.6', '>=') && $chosen_method !== 'legacy_flat_rate') {
1347 $chosen_method = explode(':', $chosen_method); // slug:instance
1348 // only for flat rate
1349 if ($chosen_method[0] !== 'flat_rate') {
1350 return false;
1351 }
1352 if (empty($chosen_method[1])) {
1353 return false; // no instance known (=probably manual order)
1354 }
1355
1356 $method_slug = $chosen_method[0];
1357 $method_instance = $chosen_method[1];
1358
1359 $shipping_method = WC_Shipping_Zones::get_shipping_method($method_instance);
1360 } else {
1361 // only for flat rate or legacy flat rate
1362 if ( ! in_array($chosen_method, array('flat_rate', 'legacy_flat_rate'))) {
1363 return false;
1364 }
1365 $shipping_methods = WC()->shipping->load_shipping_methods($package);
1366
1367 if ( ! isset($shipping_methods[$chosen_method])) {
1368 return false;
1369 }
1370 $shipping_method = $shipping_methods[$chosen_method];
1371 }
1372
1373 return $shipping_method;
1374 }
1375
1376 public function get_shipping_class($shipping_method, $found_shipping_classes) {
1377 // get most expensive class
1378 // adapted from $shipping_method->calculate_shipping()
1379 $highest_class_cost = 0;
1380 $highest_class = false;
1381
1382 foreach ( $found_shipping_classes as $shipping_class => $products ) {
1383 // Also handles BW compatibility when slugs were used instead of ids
1384 $shipping_class_term = get_term_by('slug', $shipping_class, 'product_shipping_class');
1385 $shipping_class_term_id = $shipping_class_term->term_id;
1386
1387 $class_cost_string = $shipping_class_term && $shipping_class_term_id
1388 ? $shipping_method->get_option('class_cost_' . $shipping_class_term_id, $shipping_method->get_option('class_cost_' . $shipping_class, $shipping_class_term_id))
1389 : $shipping_method->get_option('no_class_cost', '');
1390
1391 if ($class_cost_string === '') {
1392 continue;
1393 }
1394
1395 $has_costs = true;
1396 $class_cost = $this->wc_flat_rate_evaluate_cost(
1397 $class_cost_string,
1398 array(
1399 'qty' => array_sum(wp_list_pluck($products, 'quantity')),
1400 'cost' => array_sum(wp_list_pluck($products, 'line_total'))
1401 ),
1402 $shipping_method
1403 );
1404 if ($class_cost > $highest_class_cost && ! empty($shipping_class_term_id)) {
1405 $highest_class_cost = $class_cost;
1406 $highest_class = $shipping_class_term_id;
1407 }
1408 }
1409
1410 return $highest_class;
1411 }
1412
1413 public function find_order_shipping_classes($order) {
1414 $found_shipping_classes = array();
1415 $order_items = $order->get_items();
1416 foreach ( $order_items as $item_id => $item ) {
1417 $product = $order->get_product_from_item($item);
1418 if ($product && $product->needs_shipping()) {
1419 $found_class = $product->get_shipping_class();
1420
1421 if ( ! isset($found_shipping_classes[$found_class])) {
1422 $found_shipping_classes[$found_class] = array();
1423 }
1424 // normally this should pass the $product object, but only in the checkout this contains
1425 // quantity & line_total (which is all we need), so we pass data from the $item instead
1426 $item_product = new stdClass;
1427 $item_product->quantity = $item['qty'];
1428 $item_product->line_total = $item['line_total'];
1429 $found_shipping_classes[$found_class][$item_id] = $item_product;
1430 }
1431 }
1432
1433 return $found_shipping_classes;
1434 }
1435
1436 /**
1437 * Adapted from WC_Shipping_Flat_Rate - Protected method
1438 * Evaluate a cost from a sum/string.
1439 *
1440 * @param string $sum
1441 * @param array $args
1442 *
1443 * @return string
1444 */
1445 public function wc_flat_rate_evaluate_cost($sum, $args, $flat_rate_method) {
1446 if (version_compare(WOOCOMMERCE_VERSION, '2.6', '>=')) {
1447 include_once(WC()->plugin_path() . '/includes/libraries/class-wc-eval-math.php');
1448 } else {
1449 include_once(WC()->plugin_path() . '/includes/shipping/flat-rate/includes/class-wc-eval-math.php');
1450 }
1451
1452 // Allow 3rd parties to process shipping cost arguments
1453 $args = apply_filters('woocommerce_evaluate_shipping_cost_args', $args, $sum, $flat_rate_method);
1454 $locale = localeconv();
1455 $decimals = array(
1456 wc_get_price_decimal_separator(),
1457 $locale['decimal_point'],
1458 $locale['mon_decimal_point'],
1459 ','
1460 );
1461 $this->fee_cost = $args['cost'];
1462
1463 // Expand shortcodes
1464 add_shortcode('fee', array($this, 'wc_flat_rate_fee'));
1465
1466 $sum = do_shortcode(
1467 str_replace(
1468 array('[qty]', '[cost]'),
1469 array($args['qty'], $args['cost']),
1470 $sum
1471 )
1472 );
1473
1474 remove_shortcode('fee', array($this, 'wc_flat_rate_fee'));
1475
1476 // Remove whitespace from string
1477 $sum = preg_replace('/\s+/', '', $sum);
1478
1479 // Remove locale from string
1480 $sum = str_replace($decimals, '.', $sum);
1481
1482 // Trim invalid start/end characters
1483 $sum = rtrim(ltrim($sum, "\t\n\r\0\x0B+*/"), "\t\n\r\0\x0B+-*/");
1484
1485 // Do the math
1486 return $sum ? WC_Eval_Math::evaluate($sum) : 0;
1487 }
1488
1489 /**
1490 * Adapted from WC_Shipping_Flat_Rate - Protected method
1491 * Work out fee (shortcode).
1492 *
1493 * @param array $atts
1494 *
1495 * @return string
1496 */
1497 public function wc_flat_rate_fee($atts) {
1498 $atts = shortcode_atts(
1499 array(
1500 'percent' => '',
1501 'min_fee' => '',
1502 'max_fee' => '',
1503 ),
1504 $atts
1505 );
1506
1507 $calculated_fee = 0;
1508
1509 if ($atts['percent']) {
1510 $calculated_fee = $this->fee_cost * (floatval($atts['percent']) / 100);
1511 }
1512
1513 if ($atts['min_fee'] && $calculated_fee < $atts['min_fee']) {
1514 $calculated_fee = $atts['min_fee'];
1515 }
1516
1517 if ($atts['max_fee'] && $calculated_fee > $atts['max_fee']) {
1518 $calculated_fee = $atts['max_fee'];
1519 }
1520
1521 return $calculated_fee;
1522 }
1523
1524 public function filter_postnl_destination_orders($order_ids) {
1525 foreach ( $order_ids as $key => $order_id ) {
1526 $order = WCX::get_order($order_id);
1527 $shipping_country = WCX_Order::get_prop($order, 'shipping_country');
1528 // skip non-postnl destination orders
1529 if ( ! $this->is_postnl_destination($shipping_country)) {
1530 unset($order_ids[$key]);
1531 }
1532 }
1533
1534 return $order_ids;
1535 }
1536
1537 public function is_postnl_destination($country_code) {
1538 return ($country_code == 'NL' || $this->is_eu_country($country_code)
1539 || $this->is_world_shipment_country(
1540 $country_code
1541 ));
1542 }
1543
1544 public function is_eu_country($country_code) {
1545 $euro_countries = array('AT','BE','BG','CZ','DK','EE','FI','FR','DE','GR','HU','IE','IT','LV','LT','LU','PL','PT','RO','SK','SI','ES','SE','MC','AL','AD','BA','IC','FO','GI','GL','GG','JE','HR','LI','MK','MD','ME','UA','SM','RS','VA','BY');
1546
1547 return in_array($country_code, $euro_countries);
1548 }
1549
1550 public function is_world_shipment_country($country_code) {
1551 $world_shipment_countries = array('AF','AQ','DZ','VI','AO','AG','AR','AM','AW','AU','AZ','BS','BH','BD','BB','BZ','BJ','BM','BT','BO','BW','BR','VG','BN','BF','BI','KH','CA','KY','CF','CL','CN','CO','KM','CG','CD','CR','CU','DJ','DM','DO','EC','EG','SV','GQ','ER','ET','FK','FJ','PH','GB','GF','PF','GA','GM','GE','GH','GD','GP','GT','GN','GW','GY','HT','HN','HK','IN','ID','IS','IQ','IR','IL','CI','JM','JP','YE','JO','CV','CM','KZ','KE','KG','KI','KW','LA','LS','LB','LR','LY','MO','MG','MW','MV','MY','ML','MA','MQ','MR','MU','MX','MN','MS','MZ','MM','NA','NR','NP','NI','NC','NZ','NE','NG','KP','UZ','OM','TL','PK','PA','PG','PY','PE','PN','PR','QA','RE','RU','RW','KN','LC','VC','PM','WS','ST','SA','SN','SC','SL','SG','SO','LK','SD','SR','SZ','SY','TJ','TW','TZ','TH','TG','TO','TT','TD','TN','TM','TC','TV','UG','UY','VU','VE','AE','US','VN','ZM','ZW','ZA','KR','AN','BQ','CW','SX','XK','IM','MT','CY','CH','TR','NO');
1552
1553 return in_array($country_code, $world_shipment_countries);
1554 }
1555
1556 public function get_invoice_number($order) {
1557 return (string) apply_filters('wc_postnl_invoice_number', $order->get_order_number());
1558 }
1559
1560 public function log($message) {
1561 if (isset(WooCommerce_PostNL()->general_settings['error_logging'])) {
1562
1563 // Starting with WooCommerce 3.0, logging can be grouped by context and severity.
1564 if (class_exists('WC_Logger') && version_compare(WOOCOMMERCE_VERSION, '3.0', '>=')) {
1565 $logger = wc_get_logger();
1566 $logger->debug( $message, array( 'source' => 'wc-postnl' ) );
1567
1568 return;
1569 }
1570
1571 if (class_exists('WC_Logger')) {
1572 $wc_logger = function_exists('wc_get_logger') ? wc_get_logger() : new WC_Logger();
1573 $wc_logger->add('wc-postnl', $message);
1574
1575 return;
1576 }
1577
1578 // Old WC versions didn't have a logger
1579 // log file in upload folder - wp-content/uploads
1580 $upload_dir = wp_upload_dir();
1581 $upload_base = trailingslashit($upload_dir['basedir']);
1582 $log_file = $upload_base . 'postnl_log.txt';
1583
1584 $current_date_time = date("Y-m-d H:i:s");
1585 $message = $current_date_time . ' ' . $message . "\n";
1586
1587 file_put_contents($log_file, $message, FILE_APPEND);
1588 return;
1589 }
1590 }
1591
1592 private function get_shipment_barcode_from_postnl_api($shipment_id) {
1593 $api = $this->init_api();
1594 $response = $api->get_shipments($shipment_id);
1595
1596 if ( ! isset($response['body']['data']['shipments'][0]['barcode'])) {
1597 throw new \ErrorException('No PostNL barcode found for shipment id; ' . $shipment_id);
1598 }
1599
1600 return $response['body']['data']['shipments'][0]['barcode'];
1601 }
1602
1603 /**
1604 * @param $selected_shipment_ids
1605 * @param $shipment_id
1606 * @param $order
1607 */
1608 private function add_postnl_note_to_shipment($selected_shipment_ids, $shipment_id, $order) {
1609 if ( ! in_array($shipment_id, $selected_shipment_ids)) {
1610 return;
1611 }
1612
1613 $barcode = $this->get_shipment_barcode_from_postnl_api($shipment_id);
1614
1615 $order->add_order_note($this->prefix_message . sprintf($barcode));
1616 }
1617
1618 /**
1619 * @param $shipping_method_id
1620 * @param $package_type_shipping_methods
1621 * @param $shipping_class
1622 *
1623 * @return bool
1624 */
1625 private function isActiveMethod($shipping_method_id, $package_type_shipping_methods, $shipping_method_id_class, $shipping_class) {
1626 //support WooCommerce flat rate
1627 // check if we have a match with the predefined methods
1628 if (in_array($shipping_method_id, $package_type_shipping_methods)) {
1629 return true;
1630 }
1631 if (in_array($shipping_method_id_class, $package_type_shipping_methods)) {
1632 return true;
1633 }
1634
1635 // fallback to bare method (without class) (if bare method also defined in settings)
1636 if ( ! empty($shipping_method_id_class) && in_array($shipping_method_id_class, $package_type_shipping_methods)) {
1637 return true;
1638 }
1639
1640 // support WooCommerce Table Rate Shipping by WooCommerce
1641 if ( ! empty($shipping_class) && in_array($shipping_class, $package_type_shipping_methods)) {
1642 return true;
1643 }
1644
1645 // support WooCommerce Table Rate Shipping by Bolder Elements
1646 $newShippingClass = str_replace(':', '_', $shipping_class);
1647 if ( ! empty($shipping_class) && in_array($newShippingClass, $package_type_shipping_methods)) {
1648 return true;
1649 }
1650
1651 return false;
1652 }
1653
1654 public static function get_tier_ranges($round = false) {
1655 // same as in backoffice, average is value of option
1656 $tier_ranges = array(
1657 1 => array(
1658 'min' => 0,
1659 'max' => 21,
1660 'average' => 15
1661 ),
1662 2 => array(
1663 'min' => 21,
1664 'max' => 51,
1665 'average' => 35
1666 ),
1667 3 => array(
1668 'min' => 51,
1669 'max' => 101,
1670 'average' => 75
1671 ),
1672 4 => array(
1673 'min' => 101,
1674 'max' => 351,
1675 'average' => 225
1676 ),
1677 5 => array(
1678 'min' => 351,
1679 'max' => 2001,
1680 'average' => 1175
1681 )
1682 );
1683
1684 // round values for display according to postnl standard
1685 if ($round) {
1686 foreach ($tier_ranges as &$tier_range) {
1687 $tier_range['min'] = $tier_range['min'] > 0 ? $tier_range['min'] - 1 : $tier_range['min'];
1688 $tier_range['max'] = $tier_range['max'] - 1;
1689 }
1690 }
1691
1692 return $tier_ranges;
1693 }
1694
1695 public static function find_tier_range($weight) {
1696 $current_range = false;
1697
1698 foreach (WooCommerce_PostNL_Export::get_tier_ranges() as $tier_range => $value) {
1699 if ($weight >= $value['min'] && $weight < $value['max']) {
1700 $current_range = $tier_range;
1701 }
1702 }
1703
1704 return $current_range;
1705 }
1706 }
1707
1708 endif; // class_exists
1709
1710 return new WooCommerce_PostNL_Export();
1711