PluginProbe ʕ •ᴥ•ʔ
Tutor LMS – eLearning and online course solution / 3.6.2
Tutor LMS – eLearning and online course solution v3.6.2
3.9.15 3.9.14 3.9.13 3.9.12 3.9.11 trunk 1.0.0 1.0.0-alpha 1.0.1 1.0.2 1.0.3 1.0.4 1.0.5 1.0.6 1.0.7 1.0.8 1.0.9 1.1.0 1.1.1 1.2.0 1.2.1 1.2.11 1.2.12 1.2.13 1.2.20 1.3.0 1.3.1 1.3.2 1.3.3 1.3.4 1.3.5 1.3.6 1.3.7 1.3.8 1.3.9 1.4.0 1.4.1 1.4.2 1.4.3 1.4.4 1.4.5 1.4.6 1.4.7 1.4.8 1.4.9 1.5.0 1.5.1 1.5.2 1.5.3 1.5.4 1.5.5 1.5.6 1.5.7 1.5.8 1.5.9 1.6.0 1.6.1 1.6.2 1.6.3 1.6.4 1.6.5 1.6.6 1.6.7 1.6.8 1.6.9 1.7.0 1.7.1 1.7.2 1.7.3 1.7.4 1.7.5 1.7.6 1.7.7 1.7.8 1.7.9 1.8.0 1.8.1 1.8.10 1.8.2 1.8.3 1.8.4 1.8.5 1.8.6 1.8.7 1.8.8 1.8.9 1.9.0 1.9.1 1.9.10 1.9.11 1.9.12 1.9.13 1.9.14 1.9.15 1.9.16 1.9.2 1.9.3 1.9.4 1.9.5 1.9.6 1.9.7 1.9.8 1.9.9 2.0.0 2.0.1 2.0.10 2.0.2 2.0.3 2.0.4 2.0.5 2.0.6 2.0.7 2.0.8 2.0.9 2.1.0 2.1.1 2.1.10 2.1.2 2.1.3 2.1.4 2.1.5 2.1.6 2.1.7 2.1.8 2.1.9 2.2.0 2.2.1 2.2.2 2.2.3 2.2.4 2.3.0 2.4.0 2.5.0 2.6.0 2.6.1 2.6.2 2.7.0 2.7.1 2.7.2 2.7.3 2.7.4 2.7.5 2.7.6 2.7.7 3.0.0 3.0.1 3.0.2 3.1.0 3.2.0 3.2.1 3.2.2 3.2.3 3.3.0 3.3.1 3.4.0 3.4.1 3.4.2 3.5.0 3.6.0 3.6.1 3.6.2 3.6.3 3.6.4 3.7.0 3.7.1 3.7.2 3.7.3 3.7.4 3.8.0 3.8.1 3.8.2 3.8.3 3.9.0 3.9.1 3.9.10 3.9.2 3.9.3 3.9.4 3.9.5 3.9.6 3.9.7 3.9.8 3.9.9
tutor / classes / WooCommerce.php
tutor / classes Last commit date
Addons.php 1 year ago Admin.php 1 year ago Ajax.php 1 year ago Announcements.php 1 year ago Assets.php 1 year ago Backend_Page_Trait.php 1 year ago BaseController.php 1 year ago Course.php 1 year ago Course_Embed.php 3 years ago Course_Filter.php 1 year ago Course_List.php 1 year ago Course_Settings_Tabs.php 1 year ago Course_Widget.php 1 year ago Custom_Validation.php 3 years ago Dashboard.php 1 year ago Earnings.php 1 year ago FormHandler.php 2 years ago Frontend.php 1 year ago Gutenberg.php 1 year ago Input.php 1 year ago Instructor.php 1 year ago Instructors_List.php 1 year ago Lesson.php 1 year ago Options_V2.php 1 year ago Permalink.php 2 years ago Post_types.php 1 year ago Private_Course_Access.php 1 year ago Q_And_A.php 1 year ago Question_Answers_List.php 3 years ago Quiz.php 1 year ago QuizBuilder.php 1 year ago Quiz_Attempts_List.php 1 year ago RestAPI.php 2 years ago Reviews.php 3 years ago Rewrite_Rules.php 2 years ago Shortcode.php 1 year ago Singleton.php 1 year ago Student.php 1 year ago Students_List.php 1 year ago Taxonomies.php 1 year ago Template.php 1 year ago Theme_Compatibility.php 3 years ago Tools.php 1 year ago Tools_V2.php 1 year ago Tutor.php 1 year ago TutorEDD.php 1 year ago Tutor_Base.php 2 years ago Tutor_Setup.php 1 year ago Upgrader.php 1 year ago User.php 1 year ago Utils.php 1 year ago Video_Stream.php 3 years ago WhatsNew.php 2 years ago Withdraw.php 1 year ago Withdraw_Requests_List.php 1 year ago WooCommerce.php 1 year ago
WooCommerce.php
877 lines
1 <?php
2 /**
3 * Manage WooCommerce integration
4 *
5 * @package Tutor\WooCommerce
6 * @author Themeum <support@themeum.com>
7 * @link https://themeum.com
8 * @since 1.0.0
9 */
10
11 namespace TUTOR;
12
13 if ( ! defined( 'ABSPATH' ) ) {
14 exit;
15 }
16
17 /**
18 * Handle woocommerce hooks
19 *
20 * @since 1.0.0
21 */
22 class WooCommerce extends Tutor_Base {
23
24
25 const TUTOR_WC_GUEST_CUSTOMER_ID = '_tutor_wc_guest_customer_id';
26 const WC_STORE_API_DRAFT_ORDER = 'store_api_draft_order';
27 const TUTOR_COURSE_PRODUCT_ID_META = '_tutor_course_product_id';
28
29 /**
30 * Register hooks
31 *
32 * @since 1.0.0
33 */
34 public function __construct() {
35 parent::__construct();
36
37 add_action( 'admin_notices', array( $this, 'notice_on_disabled_wc' ) );
38
39 // Add option settings.
40 add_filter( 'tutor_monetization_options', array( $this, 'tutor_monetization_options' ) );
41
42 $monetize_by = tutor_utils()->get_option( 'monetize_by' );
43 if ( 'wc' !== $monetize_by ) {
44 return;
45 }
46
47 /**
48 * Is Course Purchasable
49 */
50 add_filter( 'is_course_purchasable', array( $this, 'is_course_purchasable' ), 10, 2 );
51 add_filter( 'get_tutor_course_price', array( $this, 'get_tutor_course_price' ), 10, 2 );
52 add_filter( 'tutor_course_sell_by', array( $this, 'tutor_course_sell_by' ) );
53
54 add_filter( 'product_type_options', array( $this, 'add_tutor_type_in_wc_product' ) );
55
56 add_action( 'save_post_' . $this->course_post_type, array( $this, 'save_course_meta' ), 10, 2 );
57 add_action( 'save_post_product', array( $this, 'save_wc_product_meta' ) );
58
59 add_action( 'tutor_course/single/before/enroll', 'wc_print_notices' );
60
61 /**
62 * After place new order
63 */
64 add_action( 'woocommerce_new_order', array( $this, 'course_placing_order_from_admin' ), 10, 3 );
65 add_action( 'woocommerce_new_order_item', array( $this, 'course_placing_order_from_customer' ), 10, 3 );
66
67 /**
68 * Order Status Hook
69 *
70 * Remove course from active courses if an order is cancelled or refunded
71 */
72 add_action( 'woocommerce_order_status_changed', array( $this, 'enrolled_courses_status_change' ), 10, 3 );
73
74 /**
75 * Add Earning Data
76 */
77 add_action( 'woocommerce_new_order_item', array( $this, 'add_earning_data' ), 10, 3 );
78 add_action( 'woocommerce_order_status_changed', array( $this, 'add_earning_data_status_change' ), 10, 3 );
79
80 /**
81 * WC Print Notices After Enroll
82 *
83 * @since 1.3.5
84 */
85 if ( tutor_utils()->has_wc() ) {
86 add_action( 'tutor_course/single/before/inner-wrap', 'wc_print_notices', 10 );
87 add_action( 'tutor_course/single/enrolled/before/inner-wrap', 'wc_print_notices', 10 );
88 }
89
90 /**
91 * Manage WooCommerce plugin dependency
92 *
93 * @since 1.7.8
94 */
95 $woocommerce_path = dirname( dirname( __DIR__ ) ) . DIRECTORY_SEPARATOR . 'woocommerce' . DIRECTORY_SEPARATOR . 'woocommerce.php';
96 register_deactivation_hook( $woocommerce_path, array( $this, 'woocommerce_deactivation_handler' ) );
97 /**
98 * Redirect student on enrolled courses after course
99 * Enrollment complete
100 *
101 * @since 1.9.0
102 */
103 add_action( 'woocommerce_thankyou', array( $this, 'redirect_to_enrolled_courses' ) );
104
105 /**
106 * Change woo commerce cart product link if it is tutor product
107 */
108 add_filter( 'woocommerce_cart_item_permalink', array( $this, 'tutor_update_product_url' ), 10, 2 );
109 add_filter( 'woocommerce_order_item_permalink', array( $this, 'filter_order_item_permalink_callback' ), 10, 3 );
110
111 /**
112 * On WC product delete clear course linked product
113 *
114 * @since 2.0.7
115 */
116 add_action( 'delete_post', array( $this, 'clear_course_linked_product' ) );
117
118 add_action( 'before_woocommerce_init', array( $this, 'declare_tutor_compatibility_with_hpos' ) );
119
120 add_action( 'woocommerce_order_after_calculate_totals', array( $this, 'add_coupon_to_order' ), 10, 2 );
121
122 add_action( 'woocommerce_guest_session_to_user_id', array( $this, 'enroll_guest_user' ), 10, 2 );
123
124 add_filter( 'woocommerce_shortcode_products_query_results', array( $this, 'filter_products_query_results' ), 10, 2 );
125 }
126
127 /**
128 * Filter woocommerce shop query result to remove scheduled posts.
129 *
130 * @since 3.7.0
131 *
132 * @param \WP_Query $results the query result object.
133 * @param object $wc_shortcode_products_obj the shortcode product class object.
134 *
135 * @return array
136 */
137 public function filter_products_query_results( $results, $wc_shortcode_products_obj ) {
138 $ids = $results->ids ?? array();
139
140 if ( $ids ) {
141 $filtered_ids = array_filter(
142 $ids,
143 function ( $val ) {
144 $course_id = (int) $this->get_post_id_by_meta_key_and_value( self::TUTOR_COURSE_PRODUCT_ID_META, $val );
145 return $course_id && 'publish' === get_post_status( $course_id );
146 }
147 );
148
149 $results->ids = $filtered_ids;
150 }
151
152 return $results;
153 }
154
155 /**
156 * Enroll students to course after guest checkout.
157 *
158 * @since 3.2.0
159 *
160 * @param int $guest_customer_id the customer id of guest user.
161 * @param int $customer_id the customer id of registered user.
162 *
163 * @return void
164 */
165 public function enroll_guest_user( $guest_customer_id, $customer_id ) {
166 global $wpdb;
167 $course_ids = $wpdb->get_col(
168 $wpdb->prepare(
169 "SELECT post_id FROM {$wpdb->postmeta} WHERE meta_key=%s AND meta_value=%s",
170 self::TUTOR_WC_GUEST_CUSTOMER_ID,
171 $guest_customer_id
172 )
173 );
174
175 $order_id = WC()->session->get( self::WC_STORE_API_DRAFT_ORDER, 0 );
176
177 if ( $course_ids && $order_id ) {
178 foreach ( $course_ids as $course_id ) {
179 tutor_utils()->do_enroll( $course_id, $order_id, $customer_id );
180 }
181 }
182 }
183
184
185 /**
186 * Add manual coupon discount to wc order items.
187 *
188 * @since 3.0.0
189 *
190 * @param bool $taxes whether to consider the taxes.
191 * @param object $order the order object.
192 *
193 * @return void
194 */
195 public function add_coupon_to_order( $taxes, $order ) {
196 global $wpdb;
197 $earning_id = $wpdb->get_var(
198 $wpdb->prepare(
199 "SELECT earning_id FROM {$wpdb->prefix}tutor_earnings WHERE order_id=%d",
200 $order->get_id()
201 )
202 );
203
204 if ( $earning_id ) {
205 $items = $order->get_items();
206 foreach ( $items as $item ) {
207
208 $item = new \WC_Order_Item_Product( $item );
209
210 $product_id = $item->get_product_id();
211 $if_has_course = tutor_utils()->product_belongs_with_course( $product_id );
212 if ( $if_has_course ) {
213 $course_id = $if_has_course->post_id;
214 $user_id = get_post_field( 'post_author', $course_id );
215 $order_status = "wc-{$order->get_status()}";
216 $total_price = $item->get_total();
217
218 list( $admin_amount, $instructor_amount ) = array_values( tutor_split_amounts( $total_price ) );
219
220 $earnings = Earnings::get_instance();
221 $earning_data = apply_filters( 'tutor_new_earning_data', $earnings->prepare_earning_data( $total_price, $course_id, $order->get_id(), $order_status, $admin_amount, $instructor_amount ) );
222
223 $wpdb->update( $wpdb->prefix . 'tutor_earnings', $earning_data, array( 'earning_id' => $earning_id ) );
224 }
225 }
226 }
227 }
228
229 /**
230 * Show admin notice if user disable the WC plugin.
231 *
232 * @since 1.0.0
233 *
234 * @return void
235 */
236 public function notice_on_disabled_wc() {
237 $show = get_option( 'tutor_show_woocommerce_notice' ) && 'free' === tutor_utils()->get_option( 'monetize_by', 'free' );
238
239 if ( $show ) {
240 $message = __( 'Since monetization is currently disabled, your courses are set to free. Enable Tutor LMS monetization to start selling your courses.', 'tutor' );
241 echo '<div class="notice notice-error"><p>' . esc_html( $message ) . '</p></div>';
242 }
243 }
244
245 /**
246 * Check HPOS feature enabled.
247 * WC declared HPOS (Hight Performance Order Storage) feature stable on october 2023 from WC v8.2
248 *
249 * @see https://woo.com/document/high-performance-order-storage
250 *
251 * @since 2.6.0
252 *
253 * @return bool
254 */
255 public static function hpos_enabled() {
256 $hpos = false;
257
258 if ( tutor_utils()->has_wc() && version_compare( WC()->version, '8.2.0', '>=' ) ) {
259 $hpos = 'yes' === get_option( 'woocommerce_custom_orders_table_enabled' );
260 }
261
262 return $hpos;
263 }
264
265 /**
266 * Declare tutor compatibility with WC HPOS feature
267 *
268 * @since 2.6.0
269 *
270 * @return void
271 */
272 public function declare_tutor_compatibility_with_hpos() {
273 if ( class_exists( \Automattic\WooCommerce\Utilities\FeaturesUtil::class ) ) {
274 \Automattic\WooCommerce\Utilities\FeaturesUtil::declare_compatibility( 'custom_order_tables', TUTOR_FILE, true );
275 }
276 }
277
278 /**
279 * On WC product delete, clear course linked product
280 *
281 * @since 2.0.7
282 *
283 * @param int $post_id post id.
284 *
285 * @return void
286 */
287 public function clear_course_linked_product( $post_id ) {
288 if ( get_post_type( $post_id ) === 'product' ) {
289 global $wpdb;
290 $wpdb->query(
291 $wpdb->prepare( "DELETE FROM {$wpdb->postmeta} WHERE meta_key=%s AND meta_value=%d", '_tutor_course_product_id', $post_id )
292 );
293 }
294 }
295
296 /**
297 * Order item callback handler
298 *
299 * @since 1.0.0
300 *
301 * @param string $product_permalink permalink.
302 * @param mixed $item order item.
303 * @param mixed $order orders.
304 *
305 * @return string permalink of course
306 */
307 public function filter_order_item_permalink_callback( $product_permalink, $item, $order ) {
308
309 // For product variations.
310 if ( $item->get_variation_id() > 0 ) {
311 $product = $item->get_product();
312
313 $is_visible = $product && $product->is_visible();
314
315 // Get the instance of the parent variable product Object.
316 $parent_product = wc_get_product( $item->get_product_id() );
317
318 // Return the parent product permalink (if product is visible).
319 return $is_visible ? $parent_product->get_permalink() : '';
320 }
321
322 $course_id = $this->get_post_id_by_meta_key_and_value( '_tutor_course_product_id', $item->get_product_id() );
323
324 return get_permalink( $course_id );
325 }
326
327 /**
328 * Get post id my meta key & value
329 *
330 * @since 1.0.0
331 *
332 * @param mixed $key meta key.
333 * @param mixed $value meta value.
334 *
335 * @return mixed post id on success, false on failure
336 */
337 public function get_post_id_by_meta_key_and_value( $key, $value ) {
338 global $wpdb;
339 $meta = $wpdb->get_results( 'SELECT * FROM `' . $wpdb->postmeta . "` WHERE meta_key='" . esc_sql( $key ) . "' AND meta_value='" . esc_sql( $value ) . "'" );
340 if ( is_array( $meta ) && ! empty( $meta ) && isset( $meta[0] ) ) {
341 $meta = $meta[0];
342 }
343 if ( is_object( $meta ) ) {
344 return $meta->post_id;
345 } else {
346 return false;
347 }
348 }
349
350 /**
351 * Check if course is purchase able
352 *
353 * @since 1.0.0
354 *
355 * @param bool $bool default value.
356 * @param int $course_id course id.
357 *
358 * @return boolean
359 */
360 public function is_course_purchasable( $bool, $course_id ) {
361 if ( ! tutor_utils()->has_wc() ) {
362 return false;
363 }
364
365 $course_id = tutor_utils()->get_post_id( $course_id );
366 $price_type = tutor_utils()->price_type( $course_id );
367 $has_product_id = tutor_utils()->get_course_product_id( $course_id );
368
369 /**
370 * WC course bundle don't have free or paid price types.
371 * Check if the bundle has a WC product attached.
372 */
373 if ( $has_product_id && tutor()->bundle_post_type === get_post_type( $course_id ) ) {
374 return true;
375 }
376
377 if ( Course::PRICE_TYPE_PAID === $price_type && $has_product_id ) {
378 return true;
379 }
380
381 return false;
382 }
383
384 /**
385 * Get course price
386 *
387 * @since 1.0.0
388 *
389 * @param mixed $price course price.
390 * @param int $course_id course id.
391 *
392 * @return string
393 */
394 public function get_tutor_course_price( $price, $course_id ) {
395 $price = null;
396
397 if ( tutor_utils()->is_course_purchasable( $course_id ) ) {
398 if ( tutor_utils()->has_wc() ) {
399 $product_id = tutor_utils()->get_course_product_id( $course_id );
400 $product = wc_get_product( $product_id );
401
402 if ( $product ) {
403 ob_start();
404 ?>
405 <div class="price">
406 <?php echo $product->get_price_html(); //phpcs:ignore ?>
407 </div>
408 <?php
409 return ob_get_clean();
410 }
411 }
412 }
413
414 return $price;
415 }
416
417 /**
418 * Sell by filter handler
419 *
420 * @since 1.0.0
421 *
422 * @return string
423 */
424 public function tutor_course_sell_by() {
425 return 'woocommerce';
426 }
427
428 /**
429 * Add tutor type in WC product
430 *
431 * @since 1.0.0
432 *
433 * @param array $types types.
434 *
435 * @return array
436 */
437 public function add_tutor_type_in_wc_product( $types ) {
438 $types['tutor_product'] = array(
439 'id' => '_tutor_product',
440 'wrapper_class' => 'show_if_simple',
441 'label' => __( 'For Tutor', 'tutor' ),
442 'description' => __( 'This checkmark ensure that you will sell a specific course via this product.', 'tutor' ),
443 'default' => 'no',
444 );
445
446 return $types;
447 }
448
449 /**
450 * Save course meta for attaching WC product
451 *
452 * @since 1.0.0
453 *
454 * @param int $post_ID this is course ID.
455 * @param mixed $post course details.
456 *
457 * @return void
458 */
459 public function save_course_meta( $post_ID, $post ) {
460 do_action( 'save_tutor_course', $post_ID, $post );
461 }
462
463 /**
464 * Save WC product meta
465 *
466 * @since 1.0.0
467 *
468 * @param int $post_ID post id.
469 *
470 * @return void
471 */
472 public function save_wc_product_meta( $post_ID ) {
473 $is_tutor_product = Input::post( '_tutor_product', '' );
474 if ( 'on' === $is_tutor_product ) {
475 update_post_meta( $post_ID, '_tutor_product', 'yes' );
476 } else {
477 delete_post_meta( $post_ID, '_tutor_product' );
478 }
479 }
480
481 /**
482 * Take enrolled course action based on order status change
483 *
484 * Order auto complete
485 *
486 * @param int $order_id wc order id.
487 * @param string $status_from from status.
488 * @param string $status_to to status.
489 *
490 * @return void
491 */
492 public function enrolled_courses_status_change( $order_id, $status_from, $status_to ) {
493 if ( ! tutor_utils()->is_tutor_order( $order_id ) ) {
494 return;
495 }
496
497 $enrolled_ids_with_course = tutor_utils()->get_course_enrolled_ids_by_order_id( $order_id );
498
499 if ( $enrolled_ids_with_course ) {
500 $enrolled_ids = wp_list_pluck( $enrolled_ids_with_course, 'enrolled_id' );
501
502 if ( is_array( $enrolled_ids ) && count( $enrolled_ids ) ) {
503 foreach ( $enrolled_ids as $enrolled_id ) {
504 /**
505 * If order status is processing and payment is not cash on
506 * delivery then mark enrollment as completed.
507 *
508 * Note: Order status processing simply mean customer have done
509 * payment.
510 *
511 * @since v2.0.5
512 */
513 if ( self::should_order_auto_complete( $order_id ) ) {
514 // Mark enrollment as completed.
515 tutor_utils()->course_enrol_status_change( $enrolled_id, 'completed' );
516 // Mark WC order as completed.
517 self::mark_order_complete( $order_id );
518 } else {
519 tutor_utils()->course_enrol_status_change( $enrolled_id, $status_to );
520 }
521
522 // Invoke enrolled hook.
523 if ( 'completed' === $status_to ) {
524 $user_id = get_post_field( 'post_author', $enrolled_id );
525 $course_id = get_post_field( 'post_parent', $enrolled_id );
526 do_action( 'tutor_after_enrolled', $course_id, $user_id, $enrolled_id );
527 }
528 }
529 }
530 }
531 }
532
533 /**
534 * Returning monetization options
535 *
536 * @since v.1.3.5
537 *
538 * @param array $arr attrs.
539 *
540 * @return mixed
541 */
542 public function tutor_monetization_options( $arr ) {
543 $has_wc = tutor_utils()->has_wc();
544 if ( $has_wc ) {
545 $arr['wc'] = __( 'WooCommerce', 'tutor' );
546 }
547 return $arr;
548 }
549
550 /**
551 * Adding Earning Data processing WooCommerce
552 *
553 * @param int $item_id item id.
554 * @param mixed $item item.
555 * @param int $order_id order id.
556 *
557 * @since 1.1.2
558 */
559 public function add_earning_data( $item_id, $item, $order_id ) {
560
561 if ( 'wc' !== tutor_utils()->get_option( 'monetize_by' ) ) {
562 return;
563 }
564
565 global $wpdb;
566 $item = new \WC_Order_Item_Product( $item );
567
568 $product_id = $item->get_product_id();
569 $order = wc_get_order( $order_id );
570 $if_has_course = tutor_utils()->product_belongs_with_course( $product_id );
571
572 if ( $if_has_course && is_object( $order ) ) {
573 $course_id = $if_has_course->post_id;
574 $user_id = get_post_field( 'post_author', $course_id );
575 $order_status = "wc-{$order->get_status()}";
576
577 /**
578 * Return here if already added this product from this order
579 *
580 * @since v1.9.7
581 */
582 $exist_count = (int) $wpdb->get_var(
583 $wpdb->prepare(
584 "SELECT COUNT(earning_id)
585 FROM {$wpdb->prefix}tutor_earnings
586 WHERE course_id=%d
587 AND order_id=%d
588 AND user_id=%d",
589 $course_id,
590 $order_id,
591 $user_id
592 )
593 );
594
595 if ( $exist_count > 0 ) {
596 return;
597 }
598
599 $total_price = $item->get_total();
600
601 list( $admin_amount, $instructor_amount ) = array_values( tutor_split_amounts( $total_price ) );
602
603 $earnings = Earnings::get_instance();
604 $earning_data = apply_filters( 'tutor_new_earning_data', $earnings->prepare_earning_data( $total_price, $course_id, $order_id, $order_status, $admin_amount, $instructor_amount ) );
605
606 $wpdb->insert( $wpdb->prefix . 'tutor_earnings', $earning_data );
607 }
608 }
609
610 /**
611 * Change Earning data status
612 *
613 * @since 1.0.0
614 *
615 * @param int $order_id wc order id.
616 * @param string $status_from previous status.
617 * @param string $status_to current status.
618 *
619 * @return void
620 */
621 public function add_earning_data_status_change( $order_id, $status_from, $status_to ) {
622 if ( ! tutor_utils()->is_tutor_order( $order_id ) ) {
623 tutor_log( 'not tutor order' );
624 return;
625 }
626
627 /**
628 * If it is auto complete order then make earning status complete
629 * to reflect earning for admin & instructor
630 *
631 * @since 2.0.9
632 */
633 if ( self::should_order_auto_complete( $order_id ) ) {
634 $status_to = 'completed';
635 }
636
637 tutor_utils()->change_earning_status( $order_id, $status_to );
638 }
639
640 /**
641 * Course placing order from admin
642 *
643 * @since v.1.6.7
644 *
645 * @param int $order_id wc order id.
646 *
647 * @return void
648 */
649 public function course_placing_order_from_admin( $order_id ) {
650 if ( ! is_admin() ) {
651 return;
652 }
653
654 $order = wc_get_order( $order_id );
655 /**
656 * Loop though each WC order item.
657 *
658 * @var WC_Order_Item $item WC order item object.
659 */
660 foreach ( $order->get_items() as $item ) {
661 $product_id = $item->get_product_id();
662 $if_has_course = tutor_utils()->product_belongs_with_course( $product_id );
663 if ( $if_has_course ) {
664 $course_id = $if_has_course->post_id;
665 $customer_id = $order->get_customer_id();
666 tutor_utils()->do_enroll( $course_id, $order_id, $customer_id );
667 }
668 }
669 }
670
671 /**
672 * Course placing order from customer
673 *
674 * @since 1.6.7
675 *
676 * @param int $item_id item id.
677 * @param mixed $item order item.
678 * @param int $order_id wc order id.
679 *
680 * @return void
681 */
682 public function course_placing_order_from_customer( $item_id, $item, $order_id ) {
683 if ( is_admin() ) {
684 return;
685 }
686
687 $item = new \WC_Order_Item_Product( $item );
688 $product_id = $item->get_product_id();
689 $if_has_course = tutor_utils()->product_belongs_with_course( $product_id );
690
691 if ( $if_has_course ) {
692 $order = wc_get_order( $order_id );
693
694 /**
695 * Get customer ID from from order
696 *
697 * @since 2.1.7
698 */
699 $customer_id = $order->get_customer_id();
700 $course_id = $if_has_course->post_id;
701 if ( ! $customer_id && WC()->session->has_session() ) {
702 $guest_customer_id = WC()->session->get_customer_unique_id();
703 update_post_meta( $course_id, self::TUTOR_WC_GUEST_CUSTOMER_ID, $guest_customer_id );
704 return;
705 }
706 tutor_utils()->do_enroll( $course_id, $order_id, $customer_id );
707 }
708 }
709
710 /**
711 * Handle disabling WooCommerce monetization on WooCommerce plugin deactivation
712 *
713 * @since 1.7.8
714 *
715 * @return void
716 */
717 public function woocommerce_deactivation_handler() {
718 if ( tutor_utils()->get_option( 'monetize_by' ) === 'wc' ) {
719 tutor_utils()->update_option( 'monetize_by', 'free' );
720 /**
721 * Show a reminder to re-enable Tutor monetization to
722 * monetize courses after re-activating WooCommerce:
723 *
724 * Possible follow-up fix: Only show a notice when
725 * WooCommerce was re-activated after this forced
726 * disabling of WooCommerce monetization took place:
727 */
728 update_option( 'tutor_show_woocommerce_notice', true );
729 }
730 }
731
732 /**
733 * Redirect student on enrolled courses after course
734 * enrollment complete if course is purchasable
735 *
736 * @since 1.0.0
737 *
738 * @param int $order_id wc order id.
739 *
740 * @return void
741 */
742 public function redirect_to_enrolled_courses( $order_id ) {
743 if ( ! tutor_utils()->get_option( 'wc_automatic_order_complete_redirect_to_courses' ) ) {
744 // Since 1.9.1.
745 return;
746 }
747
748 // get woo order details.
749 $order = wc_get_order( $order_id );
750 $tutor_product = false;
751 $url = tutor_utils()->tutor_dashboard_url() . 'enrolled-courses/';
752
753 /**
754 * Loop though each WC order item.
755 *
756 * @var WC_Order_Item $item WC order item object.
757 */
758 foreach ( $order->get_items() as $item ) {
759 $product_id = $item->get_product_id();
760 // check if product associated with tutor course.
761 $if_has_course = tutor_utils()->product_belongs_with_course( $product_id );
762 if ( $if_has_course ) {
763 $tutor_product = true;
764 }
765 }
766
767 // filter redirection url after woocommerce product purchase.
768 $redirect_url = apply_filters( 'tutor_woocommerce_redirect_url', $url );
769
770 // if tutor product & order status completed.
771 if ( $order->has_status( 'completed' ) && $tutor_product ) {
772 wp_safe_redirect( $redirect_url );
773 exit;
774 }
775 }
776
777 /**
778 * Change product url on cart page if product is tutor course
779 *
780 * @since 1.9.8
781 *
782 * @param string $permalink permalink.
783 * @param mixed $cart_item cart item.
784 *
785 * @return mixed
786 */
787 public function tutor_update_product_url( $permalink, $cart_item ) {
788
789 $woo_product_id = $cart_item['product_id'];
790 $product_meta = get_post_meta( $woo_product_id );
791
792 if ( isset( $product_meta['_tutor_product'] ) && $product_meta['_tutor_product'][0] ) {
793
794 global $wpdb;
795 $table = $wpdb->base_prefix . 'postmeta';
796 $post_id = $wpdb->get_var( $wpdb->prepare( "SELECT post_id FROM {$table} WHERE meta_key = '_tutor_course_product_id' AND meta_value = %d ", $woo_product_id ) ); //phpcs:ignore
797
798 if ( $post_id ) {
799 $data = get_post_permalink( $post_id );
800 return $data;
801 }
802 }
803 }
804
805 /**
806 * Mark woocommerce order as complete only from the
807 * client side.
808 *
809 * @since 2.0.5
810 *
811 * @param int $order_id wc order id.
812 *
813 * @return bool
814 */
815 public static function mark_order_complete( int $order_id ): bool {
816 if ( is_admin() ) {
817 return false;
818 }
819
820 $order = \wc_get_order( $order_id );
821 $order->set_status( 'completed' );
822 $update = $order->save();
823
824 return (bool) $update;
825 }
826
827 /**
828 * Check if order should auto complete
829 *
830 * @since 2.0.9
831 *
832 * Bank transfer & check payments will consider as manual
833 * payments. Hence, if user pay with bank & check will not
834 * be auto complete
835 *
836 * @since 2.1.4
837 *
838 * @param int $order_id wc order id.
839 *
840 * @return boolean return true if it should auto complete, otherwise false
841 */
842 public static function should_order_auto_complete( int $order_id ): bool {
843 $auto_complete = false;
844
845 $order = wc_get_order( $order_id );
846 $order_data = is_object( $order ) && method_exists( $order, 'get_data' ) ? $order->get_data() : array();
847
848 $payment_method = isset( $order_data['payment_method'] ) ? $order_data['payment_method'] : '';
849 $monetize_by = tutor_utils()->get_option( 'monetize_by' );
850
851 $should_auto_complete = tutor_utils()->get_option( 'tutor_woocommerce_order_auto_complete' );
852 $is_enabled_auto_complete = 'wc' === $monetize_by && $should_auto_complete ? true : false;
853
854 $manual_payments = array( 'cod', 'cheque', 'bacs' );
855 $order_status = method_exists( $order, 'get_status' ) ? $order->get_status() : '';
856
857 if ( 'completed' !== $order_status ) {
858 $is_tutor_order = tutor_utils()->is_tutor_order( $order->get_id() );
859
860 /**
861 * Is tutor order condition added with other conditions,
862 * to prevent order other than Tutor get completed
863 *
864 * @since 2.1.6
865 */
866 if ( ! is_admin() && $is_enabled_auto_complete && 'processing' === $order_status && ! in_array( $payment_method, $manual_payments ) && $is_tutor_order ) {
867 $auto_complete = true;
868 }
869 } else {
870 $auto_complete = true;
871 }
872
873 return $auto_complete;
874 }
875 }
876
877