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