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