PluginProbe
MakeCommerce for WooCommerce / trunk
MakeCommerce for WooCommerce vtrunk
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 trunk, at shipping/method/method.php

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