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