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 / 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
400 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 $object_id = apply_filters( 'tutor_subscription_course_by_plan', $item->id, $order );
311
312 /**
313 * Do not process enrollment for membership plan.
314 *
315 * @since 3.2.0
316 */
317 $plan_info = apply_filters( 'tutor_get_plan_info', new \stdClass(), $object_id );
318 if ( $plan_info && isset( $plan_info->is_membership_plan ) && $plan_info->is_membership_plan ) {
319 continue;
320 }
321 }
322
323 $has_enrollment = tutor_utils()->is_enrolled( $object_id, $student_id, false );
324 if ( $has_enrollment ) {
325 // Update enrollment status based on order status.
326 $update = tutor_utils()->update_enrollments( $enrollment_status, array( $has_enrollment->ID ) );
327 if ( $update ) {
328 if ( $this->is_bundle_order( $order, $object_id ) && $this->order_model->is_single_order( $order ) ) {
329 if ( 'completed' === $enrollment_status ) {
330 BundleModel::enroll_to_bundle_courses( $object_id, $student_id );
331 } else {
332 BundleModel::disenroll_from_bundle_courses( $object_id, $student_id );
333 }
334 }
335
336 /**
337 * For subscription, renewal no need to update order id.
338 */
339 if ( $this->order_model->is_single_order( $order ) ) {
340 update_post_meta( $has_enrollment->ID, '_tutor_enrolled_by_order_id', $order_id );
341
342 /**
343 * Update enrollment expiry date if it is set in a course.
344 */
345 if ( tutor()->course_post_type === get_post_type( $object_id ) ) {
346 $is_set_enrollment_expiry = (int) get_tutor_course_settings( $object_id, 'enrollment_expiry' );
347 $enrollment_expiry_enabled = (bool) get_tutor_option( 'enrollment_expiry_enabled' );
348 if ( $enrollment_expiry_enabled && $is_set_enrollment_expiry ) {
349 global $wpdb;
350 QueryHelper::update(
351 $wpdb->posts,
352 array(
353 'post_date' => current_time( 'mysql' ),
354 'post_date_gmt' => current_time( 'mysql', true ),
355 ),
356 array(
357 'ID' => $has_enrollment->ID,
358 'post_type' => tutor()->enrollment_post_type,
359 )
360 );
361 }
362 }
363
364 if ( OrderModel::ORDER_COMPLETED === $order_status ) {
365 do_action( 'tutor_after_enrolled', $object_id, $student_id, $has_enrollment->ID );
366 }
367 }
368
369 if ( 'completed' === $enrollment_status ) {
370 do_action( 'tutor_order_enrolled', $order, $has_enrollment->ID );
371 }
372 }
373 } else {
374 if ( $order->order_status === $this->order_model::ORDER_COMPLETED ) {
375 // Insert enrollment.
376 add_filter( 'tutor_enroll_data', fn( $enroll_data) => array_merge( $enroll_data, array( 'post_status' => 'completed' ) ) );
377
378 $enrollment_id = tutor_utils()->do_enroll( $object_id, $order_id, $student_id );
379 if ( $enrollment_id ) {
380 if ( $this->is_bundle_order( $order, $object_id ) && $this->order_model->is_single_order( $order ) ) {
381 BundleModel::enroll_to_bundle_courses( $object_id, $student_id );
382 }
383 update_post_meta( $enrollment_id, '_tutor_enrolled_by_order_id', $order_id );
384
385 do_action( 'tutor_order_enrolled', $order, $enrollment_id );
386 } else {
387 // Log error message with student id and course id.
388 error_log( "Error updating enrollment for student {$student_id} and course {$object_id}" );
389 }
390 }
391 }
392 }
393
394 // Update earnings.
395 $earnings->prepare_order_earnings( $order_id );
396 $earnings->remove_before_store_earnings();
397 }
398 }
399
400