PluginProbe
PostNL for WooCommerce / 3.1.6
PostNL for WooCommerce v3.1.6
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.6, at includes/class-wcmp-export.php

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