PluginProbe
MakeCommerce for WooCommerce / 3.3.1
MakeCommerce for WooCommerce v3.3.1
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.3.1, at shipping/method/method.php

685 lines 23.9 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['contents_cost'] >= $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
399 $free_shipping = get_post_meta( $line['product_id'], '_no_shipping_cost', true ) === 'yes' ? $free_shipping : false;
400 }
401
402 if ( $free_shipping ) {
403 $rate["cost"] = 0;
404 }
405 }
406
407 //check if there is a free shipping coupon (if it's free then allow free shipping)
408 if ( isset( $package['applied_coupons'] ) && $this->instance_settings["allow_free_shipping_coupons"] === "yes" ) {
409 foreach ( $package['applied_coupons'] as $coupon_code ) {
410
411 $coupon = new \WC_Coupon( $coupon_code );
412 if ( $coupon->get_free_shipping() === true ) {
413 $rate["cost"] = 0;
414 break;
415 }
416 }
417 }
418
419 $this->add_rate( $rate );
420 }
421
422 /**
423 * This function overrides shipping method title
424 * if you have set language specific titles in admin.
425 *
426 * @since 3.0.0
427 */
428 final public function override_title() {
429
430 $language_code = \MakeCommerce\i18n::get_two_char_locale();
431
432 //default (no language)
433 if ( !empty( $this->settings['method_name'] ) ) {
434 $this->title = $this->settings['method_name'];
435 }
436
437 //if method name setting has been set with current language code
438 if ( !empty( $this->settings['method_name_' . $language_code] ) ) {
439 $this->title = $this->settings['method_name_' . $language_code];
440 }
441 }
442
443 /**
444 * Initializes basic fields used by all shipping methods.
445 *
446 * @since 3.0.0
447 */
448 public function initialize_form_fields() {
449
450 //Initialize instance form fields first
451 $this->initialize_instance_form_fields();
452
453 if ( isset ( $_GET['section'] ) && $_GET['section'] == $this->id ) {
454
455 //Initialize basic form fields
456 $this->initialize_basic_form_fields();
457
458 //Initialize method specific form fields
459 $this->initialize_method_type_form_fields();
460
461 //Initialize return address fields
462 $this->initialize_return_address_form_fields();
463 }
464 }
465
466 /**
467 * Initializes basic shared form fields
468 * Used by all shipping methods
469 *
470 * @since 3.0.0
471 */
472 final public function initialize_basic_form_fields() {
473
474 $this->form_fields = array();
475
476 $this->form_fields['logo'] = array(
477 'type' => 'title',
478 'description' => \MakeCommerce::get_logo_html()
479 );
480
481 $this->form_fields['generic'] = array(
482 'type' => 'title',
483 'title' => __( 'Generic and pricing options', 'wc_makecommerce_domain' ),
484 );
485
486 $this->form_fields['active'] = array(
487 'title' => __( 'Enable', 'wc_makecommerce_domain' ),
488 'type' => 'checkbox',
489 'label' => __( 'enabled', 'wc_makecommerce_domain' ),
490 'default' => 'no',
491 );
492
493 $this->form_fields['maximum_weight'] = array(
494 'title' => sprintf( __( 'Maximum weight allowed for shipping (%s)', 'wc_makecommerce_domain' ), get_option( 'woocommerce_weight_unit' ) ),
495 'type' => 'text',
496 'default' => $this->default_max_weight
497 );
498
499 $this->form_fields['look_and_feel'] = array(
500 'type' => 'title',
501 'title' => '<hr><br>'.__( 'Look and feel options', 'wc_makecommerce_domain' ),
502 'description' => __( 'Options for presentation on check-out page', 'wc_makecommerce_domain' )
503 );
504
505 //get a list of all active languages
506 $languages = \MakeCommerce\i18n::get_active_languages();
507
508 if ( empty( $languages ) ) {
509
510 $this->form_fields['method_name'] = array(
511 'title' => __( 'Shipping Method Title', 'wc_makecommerce_domain' ),
512 'type' => 'text',
513 'default' => $this->carrier_title . " " . \MakeCommerce\i18n::get_string_from_mo( $this->identifier, 'wc_makecommerce_domain', \MakeCommerce\i18n::get_two_char_locale() )
514 );
515 } else {
516 foreach ( $languages as $language_code => $language ) {
517 $this->form_fields['method_name_'.substr( $language_code, 0, 2 )] = array(
518 'title' => __('Shipping Method Title', 'wc_makecommerce_domain').sprintf(' (%s)', substr($language_code, 0, 2)),
519 'type' => 'text',
520 'default' => $this->carrier_title . " " . \MakeCommerce\i18n::get_string_from_mo( $this->identifier, 'wc_makecommerce_domain', substr( $language_code, 0, 2 ) )
521 );
522 }
523 }
524
525 $this->initialize_method_type_checkout_fields();
526
527 if ( \MakeCommerce::new_woo_version() ) {
528 $a = 'admin.php?page=wc-settings&tab=advanced&section=mk_api';
529 } else {
530 $a = 'admin.php?page=wc-settings&tab=api&section=mk_api';
531 }
532
533 $this->form_fields['api_access'] = array(
534 'type' => 'title',
535 'title' => '<hr><br>'.__( 'API access for', 'wc_makecommerce_domain' ).' '.$this->carrier,
536 '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 ) )
537 );
538 }
539
540 /**
541 * Initializes basic instance fields used by all shipping methods.
542 * Automatically called by \WC_Shipping_Method
543 *
544 * @since 3.0.0
545 */
546 final public function initialize_instance_form_fields() {
547
548 $this->instance_form_fields = array();
549
550 $this->instance_form_fields['logo'] = array(
551 'type' => 'title',
552 'description' => \MakeCommerce::get_logo_html()
553 );
554
555 $this->instance_form_fields['price'] = array(
556 'title' => __('Shipping price', 'wc_makecommerce_domain'),
557 'type' => 'text',
558 'default' => $this->default_price
559 );
560
561 $this->instance_form_fields['free_shipping_min_amount'] = array(
562 'title' => __('Free shipping amount', 'wc_makecommerce_domain'),
563 'type' => 'number',
564 'default' => '0',
565 'desc_tip' => __( '(0 means no free shipping)', 'wc_makecommerce_domain' ),
566 );
567
568 $this->instance_form_fields['allow_free_shipping_coupons'] = array(
569 'title' => __( 'Free shipping coupons', 'wc_makecommerce_domain' ),
570 'type' => 'checkbox',
571 'default' => 'no',
572 'desc_tip' => __( 'Allow using free shipping coupons to be used with this method', 'wc_makecommerce_domain' ),
573 );
574
575 $this->instance_form_fields = $this->add_form_fields( $this->instance_form_fields );
576 }
577
578 /**
579 * Initializes return address form fields
580 * Used by all shipping methods
581 *
582 * @since 3.0.0
583 */
584 public function initialize_return_address_form_fields() {
585
586 $this->form_fields['return_address'] = array(
587 'type' => 'title',
588 'title' => __( 'Return address', 'wc_makecommerce_domain' ),
589 'description' => sprintf( __( 'Please define return address for %s shipments.<br><b>All fields are required.</b>', 'wc_makecommerce_domain' ), $this->carrier )
590 );
591
592 $this->form_fields['shop_name'] = array(
593 'type' => 'text',
594 'title' => __( 'Shop name', 'wc_makecommerce_domain' ),
595 'class' => 'input-text regular-input',
596 );
597
598 $this->form_fields['shop_phone'] = array(
599 'type' => 'text',
600 'title' => __( 'Shop phone (mobile)', 'wc_makecommerce_domain' ),
601 'class' => 'input-text regular-input',
602 );
603
604 $this->form_fields['shop_email'] = array(
605 'type' => 'text',
606 'title' => __( 'Shop email', 'wc_makecommerce_domain' ),
607 'class' => 'input-text regular-input',
608 );
609
610 $this->form_fields['shop_address_country'] = array(
611 'type' => 'select',
612 'title' => __( 'Shop address country', 'wc_makecommerce_domain' ),
613 'options' => array(
614 'EE' => 'EE',
615 'LV' => 'LV',
616 'LT' => 'LT',
617 ),
618 'default' => 'EE',
619 );
620
621 $this->form_fields['shop_address_city'] = array(
622 'type' => 'text',
623 'title' => __( 'Shop address city', 'wc_makecommerce_domain' ),
624 'class' => 'input-text regular-input',
625 );
626
627 $this->form_fields['shop_postal_code'] = array(
628 'type' => 'text',
629 'title' => __( 'Shop postal code', 'wc_makecommerce_domain' ),
630 'class' => 'input-text regular-input',
631 );
632
633 $this->form_fields['shop_address_street'] = array(
634 'type' => 'text',
635 'title' => __( 'Shop address street', 'wc_makecommerce_domain' ),
636 'class' => 'input-text regular-input',
637 );
638 }
639
640 /**
641 * Initializes method type specific form fields
642 * Loads method specific fields
643 *
644 * @since 3.0.0
645 */
646 public function initialize_method_type_form_fields() {
647
648 //Initialize method specific fields
649 $this->initialize_method_form_fields();
650
651 $this->form_fields['service_user'] = array(
652 'title' => sprintf( __( '%s web services username', 'wc_makecommerce_domain' ), $this->service_name ),
653 'type' => 'text',
654 'default' => ''
655 );
656
657 $this->form_fields['service_password'] = array(
658 'title' => sprintf( __( '%s web services password', 'wc_makecommerce_domain' ), $this->service_name ),
659 'type' => 'text',
660 'default' => ''
661 );
662 }
663
664 /**
665 * Initializes method type, required function for all method types
666 *
667 * @since 3.0.0
668 */
669 abstract public function initialize();
670
671 /**
672 * Sets title of the method
673 *
674 * @since 3.0.0
675 */
676 abstract public function return_method_title();
677
678 /**
679 * Returns phone validation error
680 *
681 * @since 3.0.4
682 */
683 abstract public function phone_number_validation_error();
684 }
685