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