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