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