PluginProbe
MakeCommerce for WooCommerce / 3.5.0
MakeCommerce for WooCommerce v3.5.0
4.1.0 4.0.8 trunk 1.0 1.0.1 1.0.2 1.0.3 1.1.0 1.1.1 1.1.2 1.1.3 1.1.4 1.1.5 1.1.6 1.1.7 2.0.0 2.1.0 2.1.1 2.1.2 2.1.3 2.1.4 2.1.5 2.2.0 2.2.1 2.2.2 All 96 releases
makecommerce / shipping / method / method.php

method.php in MakeCommerce for WooCommerce 3.5.0, at shipping/method/method.php

705 lines 24.5 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2
3 namespace MakeCommerce\Shipping;
4
5 /**
6 * Creates minimal functionality needed for WooCommerce shipping method
7 *
8 * @since 3.0.0
9 */
10
11 use WC_Shipping_Method;
12 use MakeCommerce\Shipping\Method\ShippingClasses;
13
14 abstract class Method extends WC_Shipping_Method {
15 use ShippingClasses;
16
17 public $id;
18 public $instance_id;
19
20 public $supports = array(
21 'shipping-zones',
22 'instance-settings',
23 'instance-settings-modal',
24 'settings'
25 );
26
27 /**
28 * Construct WooCommerce shipping method
29 *
30 * @since 3.0.0
31 */
32 public function __construct( $instance_id = 0 ) {
33
34 //set required properties
35 $this->instance_id = absint( $instance_id );
36 $this->id = $this->identifier . '_' . $this->carrier_id;
37 $this->ext = $this->carrier_id;
38 $this->title = $this->return_method_title();
39 $this->method_title = $this->title;
40 $this->name_ext = $this->title;
41
42 //get settings from WC_Shipping_Method
43 $this->init_settings();
44
45 //set default settings used by all shipping methods
46 $this->set_default_settings();
47
48 //initialize form fields for admin
49 $this->initialize_form_fields();
50
51 //set hooks
52 $this->set_hooks();
53
54 //initilize method type
55 $this->initialize();
56
57 $this->fields_validation( $this->settings );
58 }
59
60 /**
61 * Set hooks used by all WooCommerce shipping methods
62 *
63 * @since 3.0.0
64 */
65 final public function set_hooks() {
66
67 //update options
68 add_action( 'woocommerce_update_options_shipping_' . $this->id, array( $this, 'process_admin_options' ) );
69 add_filter( 'woocommerce_settings_api_sanitized_fields_' . $this->id, array( $this, 'fields_validation' ) );
70
71 //Woocommerce Multilingual overrides (change some titles)
72 if ( \MakeCommerce\i18n::using_language_plugins() ) {
73 add_filter( 'woocommerce_package_rates', array( $this, 'override_label_translation' ), 50 );
74 add_filter( 'woocommerce_order_get_items', array( $this, 'override_shipping_title_translation' ), 50, 2 );
75 }
76
77 //check if a parcelmachine has been chosen in checkout
78 add_action( 'woocommerce_checkout_process', array( $this, 'check_checkout_fields' ) );
79 add_action( 'woocommerce_after_checkout_validation', array( $this, 'check_checkout_fields' ) );
80 }
81
82 /**
83 * Add check for shipping settings
84 * Checks if certain fields are valid
85 *
86 * @since 3.2.0
87 */
88 function fields_validation( $fields ) {
89 // Phone not set, can not validate
90 if ( !isset( $fields['shop_phone'] ) ) {
91 return $fields;
92 }
93 $fields['shop_phone'] = $this->sanitize_phone_number( $fields['shop_phone'] );
94 if ( $fields['shop_phone'] != '' && !$this->valid_phone_number($fields['shop_phone']) ) {
95 add_action( 'admin_notices', array( $this, 'invalid_phone_number_notice' ) );
96 }
97 return $fields;
98 }
99
100 /**
101 * Add notice for invalid phone number
102 * Adds shipping method specific notice to wp admin
103 *
104 * @since 3.2.0
105 */
106 function invalid_phone_number_notice() {
107
108 // keep track of the added notices
109 static $added = [];
110
111 $user_id = get_current_user_id();
112
113 $unique_id = 'mc_phone_number_notice_dismissed_' . $this->id;
114 $dismiss_href = '?' . $unique_id;
115
116 if ( isset( $_GET ) && !empty( $_GET ) ) {
117 $dismiss_href = '&' . $unique_id;
118 }
119 // dismiss button pressed, add to db
120 if ( isset( $_GET[$unique_id] ) ) {
121 add_user_meta( $user_id, $unique_id, 'true', true );
122 }
123 // value is dismissed in db, do not display error
124 if ( get_user_meta( $user_id, $unique_id ) ) {
125 return;
126 }
127 // error already displayed, do not add duplicate
128 if ( in_array( $unique_id, $added ) ) {
129 return;
130 } else {
131 // add value to array to eliminate duplicates
132 array_push( $added, $unique_id );
133 // display error
134 echo '
135 <div class="notice notice-error">
136 <p>
137 ' . $this->phone_number_validation_error() . '
138 <a href="admin.php?page=wc-settings&tab=shipping&section='. $this->id . '">' . __('Update here', 'wc_makecommerce_domain' ) . '</a>
139 <a style="float: right; " href="' . $dismiss_href . '">' . __( 'Dismiss', 'wc_makecommerce_domain' ) . '</a>
140 </p>
141 </div>';
142 }
143 }
144
145 /**
146 * Add check for parcelmachine selectbox
147 * Checks if something is chosen
148 *
149 * @since 3.0.4
150 */
151 public function check_checkout_fields() {
152
153 //those checks are needed, otherwise the notification will be added more than once
154 static $added1 = false;
155 static $added2 = false;
156
157 $shipping_method = !empty( $_POST['shipping_method'] ) ? $_POST['shipping_method'] : false;
158
159 if ( !empty( $shipping_method[0] ) ) {
160 $shipping_method_ext = explode( ':', $shipping_method[0] );
161 }
162
163 if ( !$added1 && $shipping_method_ext[0] === $this->id && empty( $_POST[$shipping_method_ext[0]] ) && $this->type == "apt" ) {
164 wc_add_notice( __( '<strong>Parcel machine</strong> is a required field.', 'wc_makecommerce_domain' ), 'error' );
165 $added1 = true;
166 }
167
168 $shipping_phone_number = null;
169 if ( isset( $_POST['ship_to_different_address'] ) && isset( $_POST['shipping_phone'] ) ) {
170 $_POST['shipping_phone'] = $this->sanitize_phone_number( $_POST['shipping_phone'] );
171 $shipping_phone_number = $_POST['shipping_phone'];
172 } else {
173 $_POST['billing_phone'] = $this->sanitize_phone_number( $_POST['billing_phone'] );
174 $shipping_phone_number = $_POST['billing_phone'];
175 }
176
177 if ( $shipping_phone_number !== null ) {
178 if ( !$added2 && $shipping_method_ext[0] === $this->id && !$this->valid_phone_number( $shipping_phone_number ) ) {
179 wc_add_notice( $this->phone_number_validation_error(), 'error' );
180 $added2 = true;
181 }
182 }
183 }
184
185 /**
186 * Sanitize phone number
187 *
188 * @since 3.0.4
189 */
190 public function sanitize_phone_number( $phone_number ) {
191
192 $phone_number = filter_var ( $phone_number, FILTER_SANITIZE_NUMBER_INT );
193 $phone_number = trim( $phone_number, '-' );
194 //convert first 2 zeros to + if needed
195 if ( substr( $phone_number, 0, 2 ) == "00" ) {
196 $phone_number = '+' . substr( $phone_number, 2 );
197 }
198
199 return $phone_number;
200 }
201
202 /**
203 * Check if provided phonenumber is valid
204 *
205 * @since 3.0.4
206 */
207 public function valid_phone_number( $phone_number ) {
208
209 //could be that all that we need to check is international format
210 if ( $this->international_number_format ) {
211 return $this->check_international_number( $phone_number );
212 }
213
214 //allow all numbers if validation is not defined
215 if ( !isset( $this->valid_phonenumber_country_codes ) ) {
216 return true;
217 }
218
219 //remove + signs
220 $phone_number = trim( $phone_number, '+' );
221
222 //check if it contains country code
223 foreach ( $this->valid_phonenumber_country_codes as $country_code=>$valid_numbers ) {
224
225 //country code matched
226 if ( substr( $phone_number, 0, strlen( $country_code ) ) == $country_code ) {
227 return $this->check_number( substr( $phone_number, strlen( $country_code ) ), $valid_numbers );
228 }
229 }
230
231 //country code didn't match, there is nothing we can do but check if its valid in any of the provided countries
232 foreach ( $this->valid_phonenumber_country_codes as $country_code=>$valid_numbers ) {
233 if ( $this->check_number( $phone_number, $valid_numbers ) === true ) {
234 return true;
235 }
236 }
237
238 return false;
239 }
240
241 /**
242 * Check if phone number is international format
243 *
244 * This function could be improved. Either by using https://github.com/google/libphonenumber or creating our own library
245 *
246 * @since 3.0.4
247 */
248 public function check_international_number( $number ) {
249
250 //https://en.wikipedia.org/wiki/E.164 (Solomon islands has 8)
251 $min = 8;
252 $max = 15;
253
254 //without going into any more detail we are just going to check if the number starts with a + and has a minimum amount of numbers and a maximum
255 if ( substr( $number, 0, strlen( '+' ) ) && strlen( $number ) >= ( $min + 1 ) && strlen( $number ) <= ( $max + 1 ) ) {
256 return true;
257 }
258
259 return false;
260 }
261
262 /**
263 * Check if number is valid according to an array
264 *
265 * @since 3.0.4
266 */
267 public function check_number( $number, $valid_numbers ) {
268
269 foreach ( $valid_numbers as $start_digits=>$lenghts ) {
270 foreach ( $lenghts as $lenght ) {
271 // If the starting digit(s) matches OR it is '*' for any starting number
272 // AND the length matches
273 if ( ( substr( $number, 0, strlen( $start_digits ) ) == $start_digits || $start_digits === '*' ) && strlen( $number ) == $lenght ) {
274 return true;
275 }
276 }
277 }
278
279 return false;
280 }
281
282 /**
283 * Label translation override for WPML (woocommerce_package_rates)
284 *
285 * @since 3.0.0
286 */
287 public function override_label_translation( $rates ) {
288
289 $id_instance = $this->id . ':' . $this->instance_id;
290
291 if ( array_key_exists( $id_instance, $rates ) ) {
292 $rates[$id_instance]->label = $this->title;
293 }
294
295 return $rates;
296 }
297
298 /**
299 * Title translation override for WPML (woocommerce_order_get_items)
300 *
301 * @since 3.0.0
302 */
303 public function override_shipping_title_translation( $items, $order ) {
304
305 foreach ( $items as $key => $item ) {
306
307 if ( $item['type'] == 'shipping' ) {
308 foreach ( $item['item_meta_array'] as $meta ) {
309
310 if ( $meta->key === 'method_id' ) {
311
312 $tmp = explode( ':', $meta->value );
313
314 if ( $tmp[0] == $this->id ) {
315 $items[$key]['name'] = $this->title;
316 }
317 }
318 }
319 }
320 }
321
322 return $items;
323 }
324
325 /**
326 * Sets default settings used by all shipping methods
327 *
328 * @since 3.0.0
329 */
330 final public function set_default_settings() {
331
332 $this->enable = !empty( $this->settings['active'] ) ? $this->settings['active'] : null;
333 $this->availability = 'specific';
334 $this->order_country = 'unknown';
335 $this->maximum_weight = !empty( $this->settings['maximum_weight']) ? ( double )$this->settings['maximum_weight'] : 0;
336 $this->free_shipping_min_amount = !empty( $this->settings['free_shipping_min_amount'] ) ? $this->settings['free_shipping_min_amount'] : 0;
337 $this->countries = !empty( $this->settings['countries'] ) ? $this->settings['countries'] : array();
338
339 //Overrides title if its set in settings already
340 $this->override_title();
341 }
342
343 /**
344 * Check if the shipping method is available (automatically called by parent \WC_Shipping_Method)
345 *
346 * @since 3.0.0
347 */
348 public function is_available( $package ) {
349
350 //check if cart weight exceeds max weight
351 if ( WC()->cart->cart_contents_weight > $this->maximum_weight ) {
352 return false;
353 }
354
355 $is_available = $this->enable === 'yes';
356
357 if ( !$is_available || !\MakeCommerce::is_api_set() ) {
358 return false;
359 }
360
361 $this->order_country = $package['destination']['country'];
362
363 return apply_filters( 'woocommerce_shipping_' . $this->id . '_is_available', $is_available, $package );
364 }
365
366 /**
367 * Calculate price for shipping
368 *
369 * @since 3.0.0
370 */
371 public function calculate_shipping( $package = array() ) {
372
373 $price = isset( $this->instance_settings['price'] ) ? $this->instance_settings['price'] : ( isset( $this->settings['price_'.$package['destination']['country']] ) ? $this->settings['price_'.$package['destination']['country']] : 0 );
374
375 $rate = array(
376 'id' => $this->get_rate_id(),
377 'label' => $this->title,
378 'cost' => $price,
379 'package' => $package,
380 'calc_tax' => 'per_order',
381 );
382
383 //check shippingclasses
384 $rate = $this->calculate_shipping_class_price( $rate, $package );
385
386 //check all free shipping options:
387 $free_shipping_min_amount = $this->instance_settings['free_shipping_min_amount'];
388
389 if ( $free_shipping_min_amount && $package['cart_subtotal'] >= $free_shipping_min_amount ) {
390 $rate["cost"] = 0;
391 }
392
393 //check only for parcelmachines
394 if ( $this->type == "apt" ) {
395
396 $free_shipping = true;
397 foreach ( $package['contents'] as $line ) {
398 $free_shipping = get_post_meta( $line['product_id'], '_no_shipping_cost', true ) === 'yes' ? $free_shipping : false;
399 }
400
401 if ( $free_shipping ) {
402 $rate["cost"] = 0;
403 }
404 }
405
406 //check if there is a free shipping coupon (if it's free then allow free shipping)
407 if ( isset( $package['applied_coupons'] ) && $this->instance_settings["allow_free_shipping_coupons"] === "yes" ) {
408 foreach ( $package['applied_coupons'] as $coupon_code ) {
409
410 $coupon = new \WC_Coupon( $coupon_code );
411 if ( $coupon->get_free_shipping() === true ) {
412 $rate["cost"] = 0;
413 break;
414 }
415 }
416 }
417
418 $this->add_rate( $rate );
419 }
420
421 /**
422 * This function overrides shipping method title
423 * if you have set language specific titles in admin.
424 *
425 * @since 3.0.0
426 */
427 final public function override_title() {
428
429 $language_code = \MakeCommerce\i18n::get_two_char_locale();
430
431 //default (no language)
432 if ( !empty( $this->settings['method_name'] ) ) {
433 $this->title = $this->settings['method_name'];
434 }
435
436 //if method name setting has been set with current language code
437 if ( !empty( $this->settings['method_name_' . $language_code] ) ) {
438 $this->title = $this->settings['method_name_' . $language_code];
439 }
440 }
441
442 /**
443 * Initializes basic fields used by all shipping methods.
444 *
445 * @since 3.0.0
446 */
447 public function initialize_form_fields() {
448
449 //Initialize instance form fields first
450 $this->initialize_instance_form_fields();
451
452 if ( isset ( $_GET['section'] ) && $_GET['section'] == $this->id ) {
453
454 //Initialize basic form fields
455 $this->initialize_basic_form_fields();
456
457 //Initialize method specific form fields
458 $this->initialize_method_type_form_fields();
459
460 //Initialize return address fields
461 $this->initialize_return_address_form_fields();
462 }
463 }
464
465 /**
466 * Initializes basic shared form fields
467 * Used by all shipping methods
468 *
469 * @since 3.0.0
470 */
471 final public function initialize_basic_form_fields() {
472
473 $this->form_fields = array();
474
475 $this->form_fields['logo'] = array(
476 'type' => 'title',
477 'description' => \MakeCommerce::get_logo_html()
478 );
479
480 $this->form_fields['generic'] = array(
481 'type' => 'title',
482 'title' => __( 'Generic and pricing options', 'wc_makecommerce_domain' ),
483 );
484
485 $this->form_fields['active'] = array(
486 'title' => __( 'Enable', 'wc_makecommerce_domain' ),
487 'type' => 'checkbox',
488 'label' => __( 'enabled', 'wc_makecommerce_domain' ),
489 'default' => 'no',
490 );
491
492 $this->form_fields['maximum_weight'] = array(
493 'title' => sprintf( __( 'Maximum weight allowed for shipping (%s)', 'wc_makecommerce_domain' ), get_option( 'woocommerce_weight_unit' ) ),
494 'type' => 'text',
495 'default' => $this->default_max_weight
496 );
497
498 $this->form_fields['look_and_feel'] = array(
499 'type' => 'title',
500 'title' => '<hr><br>'.__( 'Look and feel options', 'wc_makecommerce_domain' ),
501 'description' => __( 'Options for presentation on check-out page', 'wc_makecommerce_domain' )
502 );
503
504 //get a list of all active languages
505 $languages = \MakeCommerce\i18n::get_active_languages();
506
507 if ( empty( $languages ) ) {
508
509 $this->form_fields['method_name'] = array(
510 'title' => __( 'Shipping Method Title', 'wc_makecommerce_domain' ),
511 'type' => 'text',
512 'default' => $this->carrier_title . " " . \MakeCommerce\i18n::get_string_from_mo( $this->identifier, 'wc_makecommerce_domain', \MakeCommerce\i18n::get_two_char_locale() )
513 );
514 } else {
515 foreach ( $languages as $language_code => $language ) {
516 $this->form_fields['method_name_'.substr( $language_code, 0, 2 )] = array(
517 'title' => __('Shipping Method Title', 'wc_makecommerce_domain').sprintf(' (%s)', substr($language_code, 0, 2)),
518 'type' => 'text',
519 'default' => $this->carrier_title . " " . \MakeCommerce\i18n::get_string_from_mo( $this->identifier, 'wc_makecommerce_domain', substr( $language_code, 0, 2 ) )
520 );
521 }
522 }
523
524 $this->initialize_method_type_checkout_fields();
525
526 if ( \MakeCommerce::new_woo_version() ) {
527 $a = 'admin.php?page=wc-settings&tab=advanced&section=mk_api';
528 } else {
529 $a = 'admin.php?page=wc-settings&tab=api&section=mk_api';
530 }
531
532 $this->form_fields['api_access'] = array(
533 'type' => 'title',
534 'title' => '<hr><br>'.__( 'API access for', 'wc_makecommerce_domain' ).' '.$this->carrier,
535 'description' => sprintf(__('You can automatically create shipments into %s system and print the out the package labels right here, at the shop orders view. <br> Please set your %s web services account credentials below here. <br>(see more on <a href="https://makecommerce.net/en/integration-modules/makecommerce-woocommerce-payment-plugin/#carriers-integration">MakeCommerce plugin page</a>. Don\'t forget to enable also <a href="%s">MC API keys</a>!)', 'wc_makecommerce_domain' ), $this->carrier, $this->carrier, admin_url( $a ) )
536 );
537 }
538
539 /**
540 * Initializes basic instance fields used by all shipping methods.
541 * Automatically called by \WC_Shipping_Method
542 *
543 * @since 3.0.0
544 */
545 final public function initialize_instance_form_fields() {
546
547 $this->instance_form_fields = array();
548
549 $this->instance_form_fields['logo'] = array(
550 'type' => 'title',
551 'description' => \MakeCommerce::get_logo_html()
552 );
553
554 $this->instance_form_fields['price'] = array(
555 'title' => __('Shipping price', 'wc_makecommerce_domain'),
556 'type' => 'text',
557 'default' => $this->default_price
558 );
559
560 $this->instance_form_fields['free_shipping_min_amount'] = array(
561 'title' => __('Free shipping amount', 'wc_makecommerce_domain'),
562 'type' => 'number',
563 'default' => '0',
564 'desc_tip' => __( '(0 means no free shipping)', 'wc_makecommerce_domain' ),
565 );
566
567 $this->instance_form_fields['allow_free_shipping_coupons'] = array(
568 'title' => __( 'Free shipping coupons', 'wc_makecommerce_domain' ),
569 'type' => 'checkbox',
570 'default' => 'no',
571 'desc_tip' => __( 'Allow using free shipping coupons to be used with this method', 'wc_makecommerce_domain' ),
572 );
573
574 $this->instance_form_fields = $this->add_form_fields( $this->instance_form_fields );
575 }
576
577 /**
578 * Initializes return address form fields
579 * Used by all shipping methods
580 *
581 * @since 3.0.0
582 */
583 public function initialize_return_address_form_fields() {
584
585 $this->form_fields['return_address'] = array(
586 'type' => 'title',
587 'title' => __( 'Return address', 'wc_makecommerce_domain' ),
588 'description' => sprintf( __( 'Please define return address for %s shipments.<br><b>All fields are required.</b>', 'wc_makecommerce_domain' ), $this->carrier )
589 );
590
591 $this->form_fields['shop_name'] = array(
592 'type' => 'text',
593 'title' => __( 'Shop name', 'wc_makecommerce_domain' ),
594 'class' => 'input-text regular-input',
595 );
596
597 $this->form_fields['shop_phone'] = array(
598 'type' => 'text',
599 'title' => __( 'Shop phone (mobile)', 'wc_makecommerce_domain' ),
600 'class' => 'input-text regular-input',
601 );
602
603 $this->form_fields['shop_email'] = array(
604 'type' => 'text',
605 'title' => __( 'Shop email', 'wc_makecommerce_domain' ),
606 'class' => 'input-text regular-input',
607 );
608
609 $this->form_fields['shop_address_country'] = array(
610 'type' => 'select',
611 'title' => __( 'Shop address country', 'wc_makecommerce_domain' ),
612 'options' => array(
613 'EE' => 'EE',
614 'LV' => 'LV',
615 'LT' => 'LT',
616 ),
617 'default' => 'EE',
618 );
619
620 $this->form_fields['shop_address_city'] = array(
621 'type' => 'text',
622 'title' => __( 'Shop address city', 'wc_makecommerce_domain' ),
623 'class' => 'input-text regular-input',
624 );
625
626 $this->form_fields['shop_postal_code'] = array(
627 'type' => 'text',
628 'title' => __( 'Shop postal code', 'wc_makecommerce_domain' ),
629 'class' => 'input-text regular-input',
630 );
631
632 $this->form_fields['shop_address_street'] = array(
633 'type' => 'text',
634 'title' => __( 'Shop address street', 'wc_makecommerce_domain' ),
635 'class' => 'input-text regular-input',
636 );
637 }
638
639 /**
640 * Initializes method type specific form fields
641 * Loads method specific fields
642 *
643 * @since 3.0.0
644 */
645 public function initialize_method_type_form_fields() {
646
647 //Initialize method specific fields
648 $this->initialize_method_form_fields();
649
650 $this->form_fields['service_user'] = array(
651 'title' => sprintf( __( '%s web services username', 'wc_makecommerce_domain' ), $this->service_name ),
652 'type' => 'text',
653 'default' => ''
654 );
655
656 $this->form_fields['service_password'] = array(
657 'title' => sprintf( __( '%s web services password', 'wc_makecommerce_domain' ), $this->service_name ),
658 'type' => 'text',
659 'default' => ''
660 );
661 }
662
663 /**
664 * Checks if an object of a class already has the filter initialized
665 * Returns true if it is added, false if it is not
666 *
667 * @since 3.4.3
668 */
669 public function check_filter_hook_initialization( $filter, $priority = 10 ) {
670
671 global $wp_filter;
672 $hook = $wp_filter[$filter];
673
674 // Check if the object instance is already registered as a callback
675 foreach ( $hook->callbacks[$priority] ?? [] as $callback ) {
676 if ( is_array($callback['function'] ) && $callback['function'][0]->id === $this->id ) {
677 return true;
678 }
679 }
680
681 return false;
682 }
683
684 /**
685 * Initializes method type, required function for all method types
686 *
687 * @since 3.0.0
688 */
689 abstract public function initialize();
690
691 /**
692 * Sets title of the method
693 *
694 * @since 3.0.0
695 */
696 abstract public function return_method_title();
697
698 /**
699 * Returns phone validation error
700 *
701 * @since 3.0.4
702 */
703 abstract public function phone_number_validation_error();
704 }
705