PluginProbe ʕ •ᴥ•ʔ
Tutor LMS – eLearning and online course solution / 3.0.2
Tutor LMS – eLearning and online course solution v3.0.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 1 year ago Admin.php 1 year ago Ajax.php 1 year ago Announcements.php 1 year ago Assets.php 1 year ago Backend_Page_Trait.php 1 year ago BaseController.php 1 year 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 1 year ago Course_Widget.php 3 years 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 Input.php 1 year 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 1 year ago Q_And_A.php 1 year ago Question_Answers_List.php 3 years ago Quiz.php 1 year ago QuizBuilder.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 Singleton.php 1 year ago Student.php 1 year ago Students_List.php 3 years ago Taxonomies.php 3 years ago Template.php 1 year ago Theme_Compatibility.php 3 years ago Tools.php 3 years ago Tools_V2.php 1 year ago Tutor.php 1 year ago TutorEDD.php 1 year ago Tutor_Base.php 2 years ago Tutor_Setup.php 1 year ago Upgrader.php 1 year ago User.php 1 year ago Utils.php 1 year ago Video_Stream.php 3 years ago WhatsNew.php 2 years ago Withdraw.php 1 year ago Withdraw_Requests_List.php 3 years ago WooCommerce.php 1 year ago
WooCommerce.php
796 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 monetization is currently disabled, your courses are set to free. Enable Tutor LMS monetization to start selling your courses.', '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 $price_type = tutor_utils()->price_type( $course_id );
257 $has_product_id = tutor_utils()->get_course_product_id( $course_id );
258
259 /**
260 * WC course bundle don't have free or paid price types.
261 * Check if the bundle has a WC product attached.
262 */
263 if ( $has_product_id && tutor()->bundle_post_type === get_post_type( $course_id ) ) {
264 return true;
265 }
266
267 if ( Course::PRICE_TYPE_PAID === $price_type && $has_product_id ) {
268 return true;
269 }
270
271 return false;
272 }
273
274 /**
275 * Get course price
276 *
277 * @since 1.0.0
278 *
279 * @param mixed $price course price.
280 * @param int $course_id course id.
281 *
282 * @return string
283 */
284 public function get_tutor_course_price( $price, $course_id ) {
285 $price = null;
286
287 if ( tutor_utils()->is_course_purchasable( $course_id ) ) {
288 if ( tutor_utils()->has_wc() ) {
289 $product_id = tutor_utils()->get_course_product_id( $course_id );
290 $product = wc_get_product( $product_id );
291
292 if ( $product ) {
293 ob_start();
294 ?>
295 <div class="price">
296 <?php echo $product->get_price_html(); //phpcs:ignore ?>
297 </div>
298 <?php
299 return ob_get_clean();
300 }
301 }
302 }
303
304 return $price;
305 }
306
307 /**
308 * Sell by filter handler
309 *
310 * @since 1.0.0
311 *
312 * @return string
313 */
314 public function tutor_course_sell_by() {
315 return 'woocommerce';
316 }
317
318 /**
319 * Add tutor type in WC product
320 *
321 * @since 1.0.0
322 *
323 * @param array $types types.
324 *
325 * @return array
326 */
327 public function add_tutor_type_in_wc_product( $types ) {
328 $types['tutor_product'] = array(
329 'id' => '_tutor_product',
330 'wrapper_class' => 'show_if_simple',
331 'label' => __( 'For Tutor', 'tutor' ),
332 'description' => __( 'This checkmark ensure that you will sell a specific course via this product.', 'tutor' ),
333 'default' => 'no',
334 );
335
336 return $types;
337 }
338
339 /**
340 * Save course meta for attaching WC product
341 *
342 * @since 1.0.0
343 *
344 * @param int $post_ID this is course ID.
345 * @param mixed $post course details.
346 *
347 * @return void
348 */
349 public function save_course_meta( $post_ID, $post ) {
350 do_action( 'save_tutor_course', $post_ID, $post );
351 }
352
353 /**
354 * Register meta box
355 *
356 * @since 1.0.0
357 *
358 * @return void
359 */
360 public function register_meta_box() {
361 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' );
362 }
363
364 /**
365 * Meta box view
366 *
367 * @since 1.0.0
368 *
369 * @return void
370 */
371 public function course_add_product_metabox() {
372 include tutor()->path . 'views/metabox/course-add-product-metabox.php';
373 }
374
375 /**
376 * Save WC product meta
377 *
378 * @since 1.0.0
379 *
380 * @param int $post_ID post id.
381 *
382 * @return void
383 */
384 public function save_wc_product_meta( $post_ID ) {
385 $is_tutor_product = Input::post( '_tutor_product', '' );
386 if ( 'on' === $is_tutor_product ) {
387 update_post_meta( $post_ID, '_tutor_product', 'yes' );
388 } else {
389 delete_post_meta( $post_ID, '_tutor_product' );
390 }
391 }
392
393 /**
394 * Take enrolled course action based on order status change
395 *
396 * Order auto complete
397 *
398 * @param int $order_id wc order id.
399 * @param string $status_from from status.
400 * @param string $status_to to status.
401 *
402 * @return void
403 */
404 public function enrolled_courses_status_change( $order_id, $status_from, $status_to ) {
405 if ( ! tutor_utils()->is_tutor_order( $order_id ) ) {
406 return;
407 }
408
409 $enrolled_ids_with_course = tutor_utils()->get_course_enrolled_ids_by_order_id( $order_id );
410
411 if ( $enrolled_ids_with_course ) {
412 $enrolled_ids = wp_list_pluck( $enrolled_ids_with_course, 'enrolled_id' );
413
414 if ( is_array( $enrolled_ids ) && count( $enrolled_ids ) ) {
415 foreach ( $enrolled_ids as $enrolled_id ) {
416 /**
417 * If order status is processing and payment is not cash on
418 * delivery then mark enrollment as completed.
419 *
420 * Note: Order status processing simply mean customer have done
421 * payment.
422 *
423 * @since v2.0.5
424 */
425 if ( self::should_order_auto_complete( $order_id ) ) {
426 // Mark enrollment as completed.
427 tutor_utils()->course_enrol_status_change( $enrolled_id, 'completed' );
428 // Mark WC order as completed.
429 self::mark_order_complete( $order_id );
430 } else {
431 tutor_utils()->course_enrol_status_change( $enrolled_id, $status_to );
432 }
433
434 // Invoke enrolled hook.
435 if ( 'completed' === $status_to ) {
436 $user_id = get_post_field( 'post_author', $enrolled_id );
437 $course_id = get_post_field( 'post_parent', $enrolled_id );
438 do_action( 'tutor_after_enrolled', $course_id, $user_id, $enrolled_id );
439 }
440 }
441 }
442 }
443 }
444
445 /**
446 * Add option for WooCommerce settings
447 *
448 * @since 1.0.0
449 *
450 * @param array $attr option attrs.
451 *
452 * @return mixed
453 */
454 public function add_options( $attr ) {
455 $attr['monetization']['blocks']['block_woocommerce']['fields'][] = array(
456 'key' => 'enable_guest_course_cart',
457 'type' => 'toggle_switch',
458 'label' => __( 'Enable Guest Mode', 'tutor' ),
459 'label_title' => '',
460 'default' => 'off',
461 'desc' => __( 'Allow customers to place orders without an account.', 'tutor' ),
462 );
463
464 return $attr;
465 }
466
467 /**
468 * Returning monetization options
469 *
470 * @since v.1.3.5
471 *
472 * @param array $arr attrs.
473 *
474 * @return mixed
475 */
476 public function tutor_monetization_options( $arr ) {
477 $has_wc = tutor_utils()->has_wc();
478 if ( $has_wc ) {
479 $arr['wc'] = __( 'WooCommerce', 'tutor' );
480 }
481 return $arr;
482 }
483
484 /**
485 * Adding Earning Data processing WooCommerce
486 *
487 * @param int $item_id item id.
488 * @param mixed $item item.
489 * @param int $order_id order id.
490 *
491 * @since 1.1.2
492 */
493 public function add_earning_data( $item_id, $item, $order_id ) {
494
495 if ( 'wc' !== tutor_utils()->get_option( 'monetize_by' ) ) {
496 return;
497 }
498
499 global $wpdb;
500 $item = new \WC_Order_Item_Product( $item );
501
502 $product_id = $item->get_product_id();
503 $if_has_course = tutor_utils()->product_belongs_with_course( $product_id );
504
505 if ( $if_has_course ) {
506 $order = wc_get_order( $order_id );
507 $course_id = $if_has_course->post_id;
508 $user_id = get_post_field( 'post_author', $course_id );
509 $order_status = "wc-{$order->get_status()}";
510
511 /**
512 * Return here if already added this product from this order
513 *
514 * @since v1.9.7
515 */
516 $exist_count = (int) $wpdb->get_var(
517 $wpdb->prepare(
518 "SELECT COUNT(earning_id)
519 FROM {$wpdb->prefix}tutor_earnings
520 WHERE course_id=%d
521 AND order_id=%d
522 AND user_id=%d",
523 $course_id,
524 $order_id,
525 $user_id
526 )
527 );
528
529 if ( $exist_count > 0 ) {
530 return;
531 }
532
533 $total_price = $item->get_total();
534
535 list( $admin_amount, $instructor_amount ) = array_values( tutor_split_amounts( $total_price ) );
536
537 $earnings = Earnings::get_instance();
538 $earning_data = apply_filters( 'tutor_new_earning_data', $earnings->prepare_earning_data( $total_price, $course_id, $order_id, $order_status, $admin_amount, $instructor_amount ) );
539
540 $wpdb->insert( $wpdb->prefix . 'tutor_earnings', $earning_data );
541 }
542 }
543
544 /**
545 * Change Earning data status
546 *
547 * @since 1.0.0
548 *
549 * @param int $order_id wc order id.
550 * @param string $status_from previous status.
551 * @param string $status_to current status.
552 *
553 * @return void
554 */
555 public function add_earning_data_status_change( $order_id, $status_from, $status_to ) {
556 if ( ! tutor_utils()->is_tutor_order( $order_id ) ) {
557 tutor_log( 'not tutor order' );
558 return;
559 }
560
561 /**
562 * If it is auto complete order then make earning status complete
563 * to reflect earning for admin & instructor
564 *
565 * @since 2.0.9
566 */
567 if ( self::should_order_auto_complete( $order_id ) ) {
568 $status_to = 'completed';
569 }
570
571 tutor_utils()->change_earning_status( $order_id, $status_to );
572 }
573
574 /**
575 * Course placing order from admin
576 *
577 * @since v.1.6.7
578 *
579 * @param int $order_id wc order id.
580 *
581 * @return void
582 */
583 public function course_placing_order_from_admin( $order_id ) {
584 if ( ! is_admin() ) {
585 return;
586 }
587
588 $order = wc_get_order( $order_id );
589 /**
590 * Loop though each WC order item.
591 *
592 * @var WC_Order_Item $item WC order item object.
593 */
594 foreach ( $order->get_items() as $item ) {
595 $product_id = $item->get_product_id();
596 $if_has_course = tutor_utils()->product_belongs_with_course( $product_id );
597 if ( $if_has_course ) {
598 $course_id = $if_has_course->post_id;
599 $customer_id = $order->get_customer_id();
600 tutor_utils()->do_enroll( $course_id, $order_id, $customer_id );
601 }
602 }
603 }
604
605 /**
606 * Course placing order from customer
607 *
608 * @since 1.6.7
609 *
610 * @param int $item_id item id.
611 * @param mixed $item order item.
612 * @param int $order_id wc order id.
613 *
614 * @return void
615 */
616 public function course_placing_order_from_customer( $item_id, $item, $order_id ) {
617 if ( is_admin() ) {
618 return;
619 }
620
621 $item = new \WC_Order_Item_Product( $item );
622 $product_id = $item->get_product_id();
623 $if_has_course = tutor_utils()->product_belongs_with_course( $product_id );
624
625 if ( $if_has_course ) {
626 $order = wc_get_order( $order_id );
627
628 /**
629 * Get customer ID from from order
630 *
631 * @since 2.1.7
632 */
633 $customer_id = $order->get_customer_id();
634 $course_id = $if_has_course->post_id;
635 tutor_utils()->do_enroll( $course_id, $order_id, $customer_id );
636 }
637 }
638
639 /**
640 * Disable course monetization on woocommerce deactivation
641 *
642 * @since 1.7.8
643 *
644 * @return void
645 */
646 public function disable_tutor_monetization() {
647 tutor_utils()->update_option( 'monetize_by', 'free' );
648 update_option( 'tutor_show_woocommerce_notice', true );
649 }
650
651 /**
652 * Redirect student on enrolled courses after course
653 * enrollment complete if course is purchasable
654 *
655 * @since 1.0.0
656 *
657 * @param int $order_id wc order id.
658 *
659 * @return void
660 */
661 public function redirect_to_enrolled_courses( $order_id ) {
662 if ( ! tutor_utils()->get_option( 'wc_automatic_order_complete_redirect_to_courses' ) ) {
663 // Since 1.9.1.
664 return;
665 }
666
667 // get woo order details.
668 $order = wc_get_order( $order_id );
669 $tutor_product = false;
670 $url = tutor_utils()->tutor_dashboard_url() . 'enrolled-courses/';
671
672 /**
673 * Loop though each WC order item.
674 *
675 * @var WC_Order_Item $item WC order item object.
676 */
677 foreach ( $order->get_items() as $item ) {
678 $product_id = $item->get_product_id();
679 // check if product associated with tutor course.
680 $if_has_course = tutor_utils()->product_belongs_with_course( $product_id );
681 if ( $if_has_course ) {
682 $tutor_product = true;
683 }
684 }
685
686 // filter redirection url after woocommerce product purchase.
687 $redirect_url = apply_filters( 'tutor_woocommerce_redirect_url', $url );
688
689 // if tutor product & order status completed.
690 if ( $order->has_status( 'completed' ) && $tutor_product ) {
691 wp_safe_redirect( $redirect_url );
692 exit;
693 }
694 }
695
696 /**
697 * Change product url on cart page if product is tutor course
698 *
699 * @since 1.9.8
700 *
701 * @param string $permalink permalink.
702 * @param mixed $cart_item cart item.
703 *
704 * @return mixed
705 */
706 public function tutor_update_product_url( $permalink, $cart_item ) {
707
708 $woo_product_id = $cart_item['product_id'];
709 $product_meta = get_post_meta( $woo_product_id );
710
711 if ( isset( $product_meta['_tutor_product'] ) && $product_meta['_tutor_product'][0] ) {
712
713 global $wpdb;
714 $table = $wpdb->base_prefix . 'postmeta';
715 $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
716
717 if ( $post_id ) {
718 $data = get_post_permalink( $post_id );
719 return $data;
720 }
721 }
722 }
723
724 /**
725 * Mark woocommerce order as complete only from the
726 * client side.
727 *
728 * @since 2.0.5
729 *
730 * @param int $order_id wc order id.
731 *
732 * @return bool
733 */
734 public static function mark_order_complete( int $order_id ): bool {
735 if ( is_admin() ) {
736 return false;
737 }
738
739 $order = \wc_get_order( $order_id );
740 $order->set_status( 'completed' );
741 $update = $order->save();
742
743 return (bool) $update;
744 }
745
746 /**
747 * Check if order should auto complete
748 *
749 * @since 2.0.9
750 *
751 * Bank transfer & check payments will consider as manual
752 * payments. Hence, if user pay with bank & check will not
753 * be auto complete
754 *
755 * @since 2.1.4
756 *
757 * @param int $order_id wc order id.
758 *
759 * @return boolean return true if it should auto complete, otherwise false
760 */
761 public static function should_order_auto_complete( int $order_id ): bool {
762 $auto_complete = false;
763
764 $order = wc_get_order( $order_id );
765 $order_data = is_object( $order ) && method_exists( $order, 'get_data' ) ? $order->get_data() : array();
766
767 $payment_method = isset( $order_data['payment_method'] ) ? $order_data['payment_method'] : '';
768 $monetize_by = tutor_utils()->get_option( 'monetize_by' );
769
770 $should_auto_complete = tutor_utils()->get_option( 'tutor_woocommerce_order_auto_complete' );
771 $is_enabled_auto_complete = 'wc' === $monetize_by && $should_auto_complete ? true : false;
772
773 $manual_payments = array( 'cod', 'cheque', 'bacs' );
774 $order_status = method_exists( $order, 'get_status' ) ? $order->get_status() : '';
775
776 if ( 'completed' !== $order_status ) {
777 $is_tutor_order = tutor_utils()->is_tutor_order( $order->get_id() );
778
779 /**
780 * Is tutor order condition added with other conditions,
781 * to prevent order other than Tutor get completed
782 *
783 * @since 2.1.6
784 */
785 if ( ! is_admin() && $is_enabled_auto_complete && 'processing' === $order_status && ! in_array( $payment_method, $manual_payments ) && $is_tutor_order ) {
786 $auto_complete = true;
787 }
788 } else {
789 $auto_complete = true;
790 }
791
792 return $auto_complete;
793 }
794 }
795
796