PluginProbe ʕ •ᴥ•ʔ
Tutor LMS – eLearning and online course solution / 3.3.1
Tutor LMS – eLearning and online course solution v3.3.1
3.9.15 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 / ecommerce / HooksHandler.php
tutor / ecommerce Last commit date
PaymentGateways 1 year ago AdminMenu.php 1 year ago BillingController.php 1 year ago CartController.php 1 year ago CheckoutController.php 1 year ago CouponController.php 1 year ago Ecommerce.php 1 year ago EmailController.php 1 year ago HooksHandler.php 1 year ago OptionKeys.php 1 year ago OrderActivitiesController.php 1 year ago OrderController.php 1 year ago PaymentHandler.php 1 year ago Settings.php 1 year ago Tax.php 1 year ago currency.php 1 year ago
HooksHandler.php
410 lines
1 <?php
2 /**
3 * Handle ecommerce hooks
4 *
5 * @package Tutor\Ecommerce
6 * @author Themeum <support@themeum.com>
7 * @link https://themeum.com
8 * @since 3.0.0
9 */
10
11 namespace Tutor\Ecommerce;
12
13 use TUTOR\Course;
14 use Tutor\Models\OrderActivitiesModel;
15 use TUTOR\Earnings;
16 use Tutor\Helpers\QueryHelper;
17 use TUTOR\Input;
18 use Tutor\Models\CartModel;
19 use Tutor\Models\OrderModel;
20 use TutorPro\CourseBundle\Models\BundleModel;
21
22 /**
23 * Handle custom hooks
24 */
25 class HooksHandler {
26
27 /**
28 * OrderModel
29 *
30 * @since 3.0.0
31 *
32 * @var OrderModel
33 */
34 private $order_model;
35
36 /**
37 * OrderActivitiesModel
38 *
39 * @since 3.0.0
40 *
41 * @var OrderActivitiesModel
42 */
43 private $order_activities_model;
44
45 /**
46 * Register hooks & resolve props
47 *
48 * @since 3.0.0
49 */
50 public function __construct() {
51 $this->order_activities_model = new OrderActivitiesModel();
52 $this->order_model = new OrderModel();
53
54 // Register hooks.
55 add_filter( 'tutor_course_sell_by', array( $this, 'alter_course_sell_by' ) );
56 add_filter( 'get_tutor_course_price', array( $this, 'alter_course_price' ), 10, 2 );
57
58 // Order hooks.
59 add_action( 'tutor_order_payment_updated', array( $this, 'handle_payment_updated_webhook' ) );
60
61 add_action( 'tutor_order_payment_status_changed', array( $this, 'handle_payment_status_changed' ), 10, 3 );
62
63 add_action( 'tutor_order_placement_success', array( $this, 'handle_order_placement_success' ) );
64
65 /**
66 * Clear order menu badge count
67 *
68 * @since 3.0.0
69 */
70 add_action( 'tutor_order_placed', array( $this, 'clear_order_badge_count' ) );
71 add_action( 'tutor_order_payment_status_changed', array( $this, 'clear_order_badge_count' ) );
72 add_action( 'tutor_before_order_bulk_action', array( $this, 'clear_order_badge_count' ) );
73 }
74
75 /**
76 * Clear order menu badge count
77 *
78 * @since 3.0.0
79 *
80 * @return void
81 */
82 public function clear_order_badge_count() {
83 delete_transient( OrderModel::TRANSIENT_ORDER_BADGE_COUNT );
84 }
85
86 /**
87 * Store order activity before bulk action.
88 *
89 * @since 3.0.0
90 *
91 * @param string $bulk_action The bulk action being performed.
92 * @param array $bulk_ids The IDs of the orders being acted upon.
93 *
94 * @return void
95 */
96 public function after_order_bulk_action( $bulk_action, $bulk_ids ) {
97 $order_status = $this->order_model->get_order_status_by_payment_status( $bulk_action );
98
99 $cancel_reason = Input::post( 'cancel_reason', '' );
100 foreach ( $bulk_ids as $order_id ) {
101 try {
102 $this->manage_earnings_and_enrollments( $order_status, $order_id );
103 $data = (object) array(
104 'order_id' => $order_id,
105 'meta_key' => $this->order_activities_model::META_KEY_HISTORY,
106 'meta_value' => "Order mark as {$bulk_action} {$cancel_reason}",
107 );
108 $this->order_activities_model->add_order_meta( $data );
109 } catch ( \Throwable $th ) {
110 // Log message with line & file.
111 error_log( $th->getMessage() . ' in ' . $th->getFile() . ' at line ' . $th->getLine() );
112 }
113 }
114 }
115
116 /**
117 * Alter course sell by value
118 *
119 * @since 3.0.0
120 *
121 * @param mixed $sell_by Default sell by.
122 *
123 * @return mixed
124 */
125 public function alter_course_sell_by( $sell_by ) {
126 if ( tutor_utils()->is_monetize_by_tutor() ) {
127 $sell_by = Ecommerce::MONETIZE_BY;
128 }
129
130 return $sell_by;
131 }
132
133 /**
134 * Alter course price to show price on the course
135 * entry box
136 *
137 * @since 3.0.0
138 *
139 * @param mixed $price Course price.
140 * @param int $course_id Course id.
141 *
142 * @return mixed
143 */
144 public function alter_course_price( $price, $course_id ) {
145 $price_type = tutor_utils()->price_type( $course_id );
146 if ( tutor_utils()->is_monetize_by_tutor() && Course::PRICE_TYPE_PAID === $price_type ) {
147 $price = tutor_get_course_formatted_price_html( $course_id, false );
148 }
149
150 return $price;
151 }
152
153 /**
154 * Handle payment updated webhook
155 *
156 * @since 3.0.0
157 *
158 * @param object $res Response data.
159 * {order_id, transaction_id, payment_status, payment_method, redirectUrl}.
160 *
161 * @return void
162 */
163 public function handle_payment_updated_webhook( $res ) {
164 $order_id = $res->id;
165 $new_payment_status = $res->payment_status;
166 $transaction_id = $res->transaction_id;
167
168 $order_details = $this->order_model->get_order_by_id( $order_id );
169 if ( $order_details ) {
170 $prev_payment_status = $order_details->payment_status;
171
172 $order_data = array(
173 'order_status' => $order_details->order_status,
174 'payment_status' => $new_payment_status,
175 'payment_payloads' => $res->payment_payload,
176 'transaction_id' => $transaction_id,
177 'earnings' => $res->earnings,
178 'fees' => $res->fees,
179 'updated_at_gmt' => current_time( 'mysql', true ),
180 );
181
182 switch ( $new_payment_status ) {
183 case $this->order_model::PAYMENT_PAID:
184 $order_data['order_status'] = $this->order_model::ORDER_COMPLETED;
185 break;
186 case $this->order_model::PAYMENT_FAILED:
187 case $this->order_model::PAYMENT_REFUNDED:
188 $order_data['order_status'] = $this->order_model::ORDER_CANCELLED;
189 break;
190 }
191
192 $update = $this->order_model->update_order( $order_id, $order_data );
193 if ( $update ) {
194 // Provide hook after update order.
195 do_action( 'tutor_order_payment_status_changed', $order_id, $prev_payment_status, $new_payment_status );
196 }
197 }
198
199 }
200
201 /**
202 * Update enrollment & earnings based on payment status
203 *
204 * @since 3.0.0
205 *
206 * @param int $order_id Order id.
207 * @param string $prev_payment_status previous payment status.
208 * @param string $new_payment_status new payment status.
209 *
210 * @return void
211 */
212 public function handle_payment_status_changed( $order_id, $prev_payment_status, $new_payment_status ) {
213
214 $order_status = $this->order_model->get_order_status_by_payment_status( $new_payment_status );
215
216 $cancel_reason = Input::post( 'cancel_reason' );
217 $remove_enrollment = Input::post( 'is_remove_enrolment', false, Input::TYPE_BOOL );
218
219 // Store activity.
220 $data = (object) array(
221 'order_id' => $order_id,
222 'meta_key' => $this->order_activities_model::META_KEY_HISTORY,
223 'meta_value' => 'Order marked as ' . $new_payment_status,
224 );
225
226 if ( $cancel_reason ) {
227 $meta_value = array(
228 'message' => 'Order marked as ' . $new_payment_status,
229 'cancel_reason' => $cancel_reason,
230 );
231 $data->meta_value = json_encode( $meta_value );
232 }
233
234 $this->order_activities_model->add_order_meta( $data );
235
236 if ( $remove_enrollment ) {
237 $order_status = OrderModel::ORDER_CANCELLED;
238 }
239
240 $this->manage_earnings_and_enrollments( $order_status, $order_id );
241
242 // Store coupon usage.
243 ( new CouponController( false ) )->store_coupon_usage( $order_id );
244 }
245
246 /**
247 * Handle new order placement
248 *
249 * Clear cart items, managing enrollment & earnings
250 *
251 * @since 3.0.0
252 *
253 * @param int $order_id Order id.
254 *
255 * @return void
256 */
257 public function handle_order_placement_success( int $order_id ) {
258 $order_data = $this->order_model->get_order_by_id( $order_id );
259 if ( $order_data ) {
260 $user_id = $order_data->student->id;
261
262 ( new CartModel() )->clear_user_cart( $user_id );
263
264 // Manage enrollment & earnings.
265 $order = ( new OrderModel() )->get_order_by_id( $order_id );
266 $payment_status = $order->payment_status;
267
268 $order_status = $this->order_model->get_order_status_by_payment_status( $payment_status );
269
270 $this->manage_earnings_and_enrollments( $order_status, $order_id );
271 }
272 }
273
274 /**
275 * Check if order is bundle order
276 *
277 * @since 3.0.0
278 *
279 * @param object $order order object.
280 * @param int $object_id object id.
281 *
282 * @return boolean
283 */
284 private function is_bundle_order( $order, $object_id ) {
285 return tutor_utils()->is_addon_enabled( 'course-bundle' )
286 && in_array( $order->order_type, array( $this->order_model::TYPE_SINGLE_ORDER, $this->order_model::TYPE_SUBSCRIPTION ), true )
287 && 'course-bundle' === get_post_type( $object_id );
288 }
289
290 /**
291 * Manage earnings after order bulk action
292 *
293 * @since 3.0.0
294 *
295 * @param string $order_status Order status.
296 * @param int $order_id Order ID.
297 *
298 * @return void
299 */
300 public function manage_earnings_and_enrollments( string $order_status, int $order_id ) {
301 $earnings = Earnings::get_instance();
302 $order = $this->order_model->get_order_by_id( $order_id );
303 $student_id = $order->student->id;
304
305 $enrollment_status = ( OrderModel::ORDER_COMPLETED === $order_status ? 'completed' : ( OrderModel::ORDER_INCOMPLETE === $order->order_status ? 'pending' : 'cancel' ) );
306
307 foreach ( $order->items as $item ) {
308 $object_id = $item->id; // It could be course/bundle/plan id.
309 if ( $this->order_model::TYPE_SINGLE_ORDER !== $order->order_type ) {
310 /**
311 * Do not process enrollment for membership plan.
312 *
313 * @since 3.2.0
314 */
315 $plan_info = apply_filters( 'tutor_get_plan_info', new \stdClass(), $object_id );
316 if ( $plan_info && isset( $plan_info->is_membership_plan ) && $plan_info->is_membership_plan ) {
317 continue;
318 } else {
319 $object_id = apply_filters( 'tutor_subscription_course_by_plan', $item->id, $order );
320 }
321
322 /**
323 * Do not process enrollment for subscription order refund.
324 * It will be handled by subscription controller's handle_order_refund method.
325 *
326 * @since 3.3.0
327 */
328 if ( Input::has( 'is_cancel_subscription' ) ) {
329 continue;
330 }
331 }
332
333 $has_enrollment = tutor_utils()->is_enrolled( $object_id, $student_id, false );
334 if ( $has_enrollment ) {
335 // Update enrollment status based on order status.
336 $update = tutor_utils()->update_enrollments( $enrollment_status, array( $has_enrollment->ID ) );
337 if ( $update ) {
338 if ( $this->is_bundle_order( $order, $object_id ) && $this->order_model->is_single_order( $order ) ) {
339 if ( 'completed' === $enrollment_status ) {
340 BundleModel::enroll_to_bundle_courses( $object_id, $student_id );
341 } else {
342 BundleModel::disenroll_from_bundle_courses( $object_id, $student_id );
343 }
344 }
345
346 /**
347 * For subscription, renewal no need to update order id.
348 */
349 if ( $this->order_model->is_single_order( $order ) ) {
350 update_post_meta( $has_enrollment->ID, '_tutor_enrolled_by_order_id', $order_id );
351
352 /**
353 * Update enrollment expiry date if it is set in a course.
354 */
355 if ( tutor()->course_post_type === get_post_type( $object_id ) ) {
356 $is_set_enrollment_expiry = (int) get_tutor_course_settings( $object_id, 'enrollment_expiry' );
357 $enrollment_expiry_enabled = (bool) get_tutor_option( 'enrollment_expiry_enabled' );
358 if ( $enrollment_expiry_enabled && $is_set_enrollment_expiry ) {
359 global $wpdb;
360 QueryHelper::update(
361 $wpdb->posts,
362 array(
363 'post_date' => current_time( 'mysql' ),
364 'post_date_gmt' => current_time( 'mysql', true ),
365 ),
366 array(
367 'ID' => $has_enrollment->ID,
368 'post_type' => tutor()->enrollment_post_type,
369 )
370 );
371 }
372 }
373
374 if ( OrderModel::ORDER_COMPLETED === $order_status ) {
375 do_action( 'tutor_after_enrolled', $object_id, $student_id, $has_enrollment->ID );
376 }
377 }
378
379 if ( 'completed' === $enrollment_status ) {
380 do_action( 'tutor_order_enrolled', $order, $has_enrollment->ID );
381 }
382 }
383 } else {
384 if ( $order->order_status === $this->order_model::ORDER_COMPLETED ) {
385 // Insert enrollment.
386 add_filter( 'tutor_enroll_data', fn( $enroll_data) => array_merge( $enroll_data, array( 'post_status' => 'completed' ) ) );
387
388 $enrollment_id = tutor_utils()->do_enroll( $object_id, $order_id, $student_id );
389 if ( $enrollment_id ) {
390 if ( $this->is_bundle_order( $order, $object_id ) && $this->order_model->is_single_order( $order ) ) {
391 BundleModel::enroll_to_bundle_courses( $object_id, $student_id );
392 }
393 update_post_meta( $enrollment_id, '_tutor_enrolled_by_order_id', $order_id );
394
395 do_action( 'tutor_order_enrolled', $order, $enrollment_id );
396 } else {
397 // Log error message with student id and course id.
398 error_log( "Error updating enrollment for student {$student_id} and course {$object_id}" );
399 }
400 }
401 }
402 }
403
404 // Update earnings.
405 $earnings->prepare_order_earnings( $order_id );
406 $earnings->remove_before_store_earnings();
407 }
408 }
409
410