PluginProbe ʕ •ᴥ•ʔ
Tutor LMS – eLearning and online course solution / 3.8.2
Tutor LMS – eLearning and online course solution v3.8.2
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 9 months ago Ajax.php 9 months ago Announcements.php 1 year ago Assets.php 10 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 9 months ago Course_Embed.php 3 years ago Course_Filter.php 1 year ago Course_List.php 10 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 9 months ago Input.php 1 year ago Instructor.php 1 year ago Instructors_List.php 11 months ago Lesson.php 10 months ago Options_V2.php 9 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 9 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 9 months ago TutorEDD.php 1 year ago Tutor_Base.php 2 years ago Tutor_Setup.php 1 year ago Upgrader.php 9 months ago User.php 1 year ago Utils.php 9 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 9 months ago
WooCommerce.php
930 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 $order_type = $order->get_type();
607
608 if ( 'shop_subscription' === $order_type ) {
609 return;
610 }
611
612 $if_has_course = tutor_utils()->product_belongs_with_course( $product_id );
613
614 if ( $if_has_course && is_object( $order ) ) {
615 $course_id = $if_has_course->post_id;
616 $user_id = get_post_field( 'post_author', $course_id );
617 $order_status = "wc-{$order->get_status()}";
618
619 /**
620 * Return here if already added this product from this order
621 *
622 * @since v1.9.7
623 */
624 $exist_count = (int) $wpdb->get_var(
625 $wpdb->prepare(
626 "SELECT COUNT(earning_id)
627 FROM {$wpdb->prefix}tutor_earnings
628 WHERE course_id=%d
629 AND order_id=%d
630 AND user_id=%d",
631 $course_id,
632 $order_id,
633 $user_id
634 )
635 );
636
637 if ( $exist_count > 0 ) {
638 return;
639 }
640
641 $total_price = $item->get_total();
642
643 list( $admin_amount, $instructor_amount ) = array_values( tutor_split_amounts( $total_price ) );
644
645 $earnings = Earnings::get_instance();
646 $earning_data = apply_filters( 'tutor_new_earning_data', $earnings->prepare_earning_data( $total_price, $course_id, $order_id, $order_status, $admin_amount, $instructor_amount ) );
647
648 $wpdb->insert( $wpdb->prefix . 'tutor_earnings', $earning_data );
649 }
650 }
651
652 /**
653 * Change Earning data status
654 *
655 * @since 1.0.0
656 *
657 * @param int $order_id wc order id.
658 * @param string $status_from previous status.
659 * @param string $status_to current status.
660 *
661 * @return void
662 */
663 public function add_earning_data_status_change( $order_id, $status_from, $status_to ) {
664 if ( ! tutor_utils()->is_tutor_order( $order_id ) ) {
665 tutor_log( 'not tutor order' );
666 return;
667 }
668
669 /**
670 * If it is auto complete order then make earning status complete
671 * to reflect earning for admin & instructor
672 *
673 * @since 2.0.9
674 */
675 if ( self::should_order_auto_complete( $order_id ) ) {
676 $status_to = 'completed';
677 }
678
679 tutor_utils()->change_earning_status( $order_id, $status_to );
680 }
681
682 /**
683 * Course placing order from admin
684 *
685 * @since v.1.6.7
686 *
687 * @param int $order_id wc order id.
688 *
689 * @return void
690 */
691 public function course_placing_order_from_admin( $order_id ) {
692 if ( ! is_admin() ) {
693 return;
694 }
695
696 $order = wc_get_order( $order_id );
697 /**
698 * Loop though each WC order item.
699 *
700 * @var WC_Order_Item $item WC order item object.
701 */
702 foreach ( $order->get_items() as $item ) {
703 $product_id = $item->get_product_id();
704 $if_has_course = tutor_utils()->product_belongs_with_course( $product_id );
705 if ( $if_has_course ) {
706 $course_id = $if_has_course->post_id;
707 $customer_id = $order->get_customer_id();
708 tutor_utils()->do_enroll( $course_id, $order_id, $customer_id );
709 }
710 }
711 }
712
713 /**
714 * Course placing order from customer
715 *
716 * @since 1.6.7
717 *
718 * @since 3.8.0 Filter hook: tutor_is_gift_item added
719 *
720 * @param int $item_id item id.
721 * @param mixed $item order item.
722 * @param int $order_id wc order id.
723 *
724 * @return void
725 */
726 public function course_placing_order_from_customer( $item_id, $item, $order_id ) {
727 if ( is_admin() ) {
728 return;
729 }
730
731 $is_gift_item = apply_filters( 'tutor_is_gift_item', false, $item );
732 if ( $is_gift_item ) {
733 return;
734 }
735
736 $item = new \WC_Order_Item_Product( $item );
737 $product_id = $item->get_product_id();
738 $if_has_course = tutor_utils()->product_belongs_with_course( $product_id );
739
740 if ( $if_has_course ) {
741 $order = wc_get_order( $order_id );
742
743 /**
744 * Get customer ID from from order
745 *
746 * @since 2.1.7
747 */
748 $customer_id = $order->get_customer_id();
749 $course_id = $if_has_course->post_id;
750 if ( ! $customer_id && WC()->session->has_session() ) {
751 $guest_customer_id = WC()->session->get_customer_unique_id();
752 update_post_meta( $course_id, self::TUTOR_WC_GUEST_CUSTOMER_ID, $guest_customer_id );
753 return;
754 }
755
756 $has_enrollment = tutor_utils()->is_enrolled( $course_id, $customer_id, false );
757 if ( ! $has_enrollment ) {
758 tutor_utils()->do_enroll( $course_id, $order_id, $customer_id );
759 }
760 }
761 }
762
763 /**
764 * Handle disabling WooCommerce monetization on WooCommerce plugin deactivation
765 *
766 * @since 1.7.8
767 *
768 * @return void
769 */
770 public function woocommerce_deactivation_handler() {
771 if ( tutor_utils()->get_option( 'monetize_by' ) === 'wc' ) {
772 tutor_utils()->update_option( 'monetize_by', 'free' );
773 /**
774 * Show a reminder to re-enable Tutor monetization to
775 * monetize courses after re-activating WooCommerce:
776 *
777 * Possible follow-up fix: Only show a notice when
778 * WooCommerce was re-activated after this forced
779 * disabling of WooCommerce monetization took place:
780 */
781 update_option( 'tutor_show_woocommerce_notice', true );
782 }
783 }
784
785 /**
786 * Redirect student on enrolled courses after course
787 * enrollment complete if course is purchasable
788 *
789 * @since 1.0.0
790 *
791 * @param int $order_id wc order id.
792 *
793 * @return void
794 */
795 public function redirect_to_enrolled_courses( $order_id ) {
796 if ( ! tutor_utils()->get_option( 'wc_automatic_order_complete_redirect_to_courses' ) ) {
797 // Since 1.9.1.
798 return;
799 }
800
801 // get woo order details.
802 $order = wc_get_order( $order_id );
803 $tutor_product = false;
804 $url = tutor_utils()->tutor_dashboard_url() . 'enrolled-courses/';
805
806 /**
807 * Loop though each WC order item.
808 *
809 * @var WC_Order_Item $item WC order item object.
810 */
811 foreach ( $order->get_items() as $item ) {
812 $product_id = $item->get_product_id();
813 // check if product associated with tutor course.
814 $if_has_course = tutor_utils()->product_belongs_with_course( $product_id );
815 if ( $if_has_course ) {
816 $tutor_product = true;
817 }
818 }
819
820 // filter redirection url after woocommerce product purchase.
821 $redirect_url = apply_filters( 'tutor_woocommerce_redirect_url', $url );
822
823 // if tutor product & order status completed.
824 if ( $order->has_status( 'completed' ) && $tutor_product ) {
825 wp_safe_redirect( $redirect_url );
826 exit;
827 }
828 }
829
830 /**
831 * Change product url on cart page if product is tutor course
832 *
833 * @since 1.9.8
834 *
835 * @param string $permalink permalink.
836 * @param mixed $cart_item cart item.
837 *
838 * @return mixed
839 */
840 public function tutor_update_product_url( $permalink, $cart_item ) {
841
842 $woo_product_id = $cart_item['product_id'];
843 $product_meta = get_post_meta( $woo_product_id );
844
845 if ( isset( $product_meta['_tutor_product'] ) && $product_meta['_tutor_product'][0] ) {
846
847 global $wpdb;
848 $table = $wpdb->base_prefix . 'postmeta';
849 $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
850
851 if ( $post_id ) {
852 $data = get_post_permalink( $post_id );
853 return $data;
854 }
855 }
856 }
857
858 /**
859 * Mark woocommerce order as complete only from the
860 * client side.
861 *
862 * @since 2.0.5
863 *
864 * @param int $order_id wc order id.
865 *
866 * @return bool
867 */
868 public static function mark_order_complete( int $order_id ): bool {
869 if ( is_admin() ) {
870 return false;
871 }
872
873 $order = \wc_get_order( $order_id );
874 $order->set_status( 'completed' );
875 $update = $order->save();
876
877 return (bool) $update;
878 }
879
880 /**
881 * Check if order should auto complete
882 *
883 * @since 2.0.9
884 *
885 * Bank transfer & check payments will consider as manual
886 * payments. Hence, if user pay with bank & check will not
887 * be auto complete
888 *
889 * @since 2.1.4
890 *
891 * @param int $order_id wc order id.
892 *
893 * @return boolean return true if it should auto complete, otherwise false
894 */
895 public static function should_order_auto_complete( int $order_id ): bool {
896 $auto_complete = false;
897
898 $order = wc_get_order( $order_id );
899 $order_data = is_object( $order ) && method_exists( $order, 'get_data' ) ? $order->get_data() : array();
900
901 $payment_method = isset( $order_data['payment_method'] ) ? $order_data['payment_method'] : '';
902 $monetize_by = tutor_utils()->get_option( 'monetize_by' );
903
904 $should_auto_complete = tutor_utils()->get_option( 'tutor_woocommerce_order_auto_complete' );
905 $is_enabled_auto_complete = 'wc' === $monetize_by && $should_auto_complete ? true : false;
906
907 $manual_payments = array( 'cod', 'cheque', 'bacs' );
908 $order_status = method_exists( $order, 'get_status' ) ? $order->get_status() : '';
909
910 if ( 'completed' !== $order_status ) {
911 $is_tutor_order = tutor_utils()->is_tutor_order( $order->get_id() );
912
913 /**
914 * Is tutor order condition added with other conditions,
915 * to prevent order other than Tutor get completed
916 *
917 * @since 2.1.6
918 */
919 if ( ! is_admin() && $is_enabled_auto_complete && 'processing' === $order_status && ! in_array( $payment_method, $manual_payments ) && $is_tutor_order ) {
920 $auto_complete = true;
921 }
922 } else {
923 $auto_complete = true;
924 }
925
926 return $auto_complete;
927 }
928 }
929
930