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