PluginProbe ʕ •ᴥ•ʔ
Tutor LMS – eLearning and online course solution / 3.6.1
Tutor LMS – eLearning and online course solution v3.6.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 / CouponController.php
tutor / ecommerce Last commit date
Cart 1 year ago 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
CouponController.php
889 lines
1 <?php
2 /**
3 * Manage Coupon
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\Backend_Page_Trait;
14 use TUTOR\BaseController;
15 use TUTOR\Course;
16 use Tutor\Helpers\DateTimeHelper;
17 use Tutor\Helpers\HttpHelper;
18 use Tutor\Helpers\ValidationHelper;
19 use TUTOR\Input;
20 use Tutor\Models\CouponModel;
21 use Tutor\Models\CourseModel;
22 use Tutor\Models\OrderModel;
23 use Tutor\Traits\JsonResponse;
24 use TutorPro\CourseBundle\Models\BundleModel;
25
26 if ( ! defined( 'ABSPATH' ) ) {
27 exit;
28 }
29 /**
30 * CouponController class
31 *
32 * @since 3.0.0
33 */
34 class CouponController extends BaseController {
35
36 /**
37 * Page slug
38 *
39 * @var string
40 */
41 const PAGE_SLUG = 'tutor_coupons';
42
43 /**
44 * Coupon model
45 *
46 * @since 3.0.0
47 *
48 * @var CouponModel
49 */
50 public $model;
51
52 /**
53 * Checkout controller instance.
54 *
55 * @since 3.6.0
56 *
57 * @var CheckoutController
58 */
59 private $checkout_ctrl;
60
61 /**
62 * Trait for utilities
63 *
64 * @var $page_title
65 */
66 use Backend_Page_Trait;
67
68 /**
69 * Trait for sending JSON response
70 */
71 use JsonResponse;
72
73 /**
74 * Bulk Action
75 *
76 * @var $bulk_action
77 */
78 public $bulk_action = true;
79
80 /**
81 * Constructor.
82 *
83 * Initializes the Coupons class, sets the page title, and optionally registers
84 * hooks for handling AJAX requests related to coupon data, bulk actions, coupon status updates,
85 * and coupon deletions.
86 *
87 * @param bool $register_hooks Whether to register hooks for handling requests. Default is true.
88 *
89 * @since 3.0.0
90 *
91 * @return void
92 */
93 public function __construct( $register_hooks = true ) {
94 $this->model = new CouponModel();
95 $this->checkout_ctrl = new CheckoutController( false );
96
97 if ( $register_hooks ) {
98 // Register hooks here.
99 add_action( 'wp_ajax_tutor_coupon_bulk_action', array( $this, 'bulk_action_handler' ) );
100 add_action( 'wp_ajax_tutor_coupon_permanent_delete', array( $this, 'coupon_permanent_delete' ) );
101 /**
102 * Handle AJAX request for getting coupon related data by coupon ID.
103 *
104 * @since 3.0.0
105 */
106 add_action( 'wp_ajax_tutor_coupon_details', array( $this, 'ajax_coupon_details' ) );
107 /**
108 * Handle AJAX request for getting courses for coupon.
109 *
110 * @since 3.0.0
111 */
112 add_action( 'wp_ajax_tutor_get_coupon_applies_to', array( $this, 'get_coupon_applies_to' ) );
113
114 add_action( 'wp_ajax_tutor_coupon_create', array( $this, 'ajax_create_coupon' ) );
115 add_action( 'wp_ajax_tutor_coupon_update', array( $this, 'ajax_update_coupon' ) );
116 add_action( 'wp_ajax_tutor_coupon_applies_to_list', array( $this, 'ajax_coupon_applies_to_list' ) );
117 add_action( 'wp_ajax_tutor_apply_coupon', array( $this, 'ajax_apply_coupon' ) );
118 }
119 }
120
121 /**
122 * Page title fallback
123 *
124 * @since 3.5.0
125 *
126 * @param string $name Property name.
127 *
128 * @return string
129 */
130 public function __get( $name ) {
131 if ( 'page_title' === $name ) {
132 return esc_html__( 'Coupons', 'tutor' );
133 }
134 }
135
136
137 /**
138 * Get coupon model object
139 *
140 * @since 3.0.0
141 *
142 * @return CouponModel
143 */
144 public function get_model() {
145 return $this->model;
146 }
147
148 /**
149 * Check if applies to items exist and are valid
150 *
151 * @since 3.5.0
152 *
153 * @param array $data The data to validate.
154 *
155 * @return bool Whether applies to items exist and are valid
156 */
157 private function has_applies_to_items( $data ) {
158 return isset( $data['applies_to_items'] ) && is_array( $data['applies_to_items'] ) && count( $data['applies_to_items'] );
159 }
160
161 /**
162 * Validate applies to item
163 *
164 * @since 3.5.0
165 *
166 * @param array $data The data to validate.
167 *
168 * @return void send wp_json response when validation fails
169 */
170 public function validate_applies_to_item( $data ) {
171 $is_specific_applies_to = $this->model->is_specific_applies_to( $data['applies_to'] );
172
173 if ( $is_specific_applies_to && ! $this->has_applies_to_items( $data ) ) {
174 $this->json_response(
175 __( 'Add items first', 'tutor' ),
176 null,
177 HttpHelper::STATUS_UNPROCESSABLE_ENTITY
178 );
179 }
180 }
181
182 /**
183 * Handle ajax request for creating coupon
184 *
185 * @since 3.0.0
186 *
187 * @return void send wp_json response
188 */
189 public function ajax_create_coupon() {
190 tutor_utils()->check_nonce();
191 tutor_utils()->check_current_user_capability();
192
193 $data = $this->get_allowed_fields( Input::sanitize_array( $_POST ), true );//phpcs:ignore --sanitized already
194
195 if ( $this->model::TYPE_AUTOMATIC === $data['coupon_type'] ) {
196 $data['coupon_code'] = time();
197 }
198
199 $validation = $this->validate( $data );
200 if ( ! $validation->success ) {
201 $this->json_response(
202 tutor_utils()->error_message( 'validation_error' ),
203 $validation->errors,
204 HttpHelper::STATUS_UNPROCESSABLE_ENTITY
205 );
206 }
207
208 $this->validate_applies_to_item( $data );
209
210 if ( $this->model->get_coupon( array( 'coupon_code' => $data['coupon_code'] ) ) ) {
211 $this->json_response(
212 __( 'Coupon code already exists!', 'tutor' ),
213 null,
214 HttpHelper::STATUS_UNPROCESSABLE_ENTITY
215 );
216 }
217
218 // Convert start & expire date time into gmt.
219 $data['start_date_gmt'] = $data['start_date_gmt'];
220 $data['created_by'] = get_current_user_id();
221 $data['created_at_gmt'] = current_time( 'mysql', true );
222 $data['updated_at_gmt'] = current_time( 'mysql', true );
223 $applies_to_items = isset( $data['applies_to_items'] ) ? $data['applies_to_items'] : array();
224 unset( $data['applies_to_items'] );
225
226 // Set expire date if isset.
227 if ( isset( $data['expire_date_gmt'] ) ) {
228 $data['expire_date_gmt'] = $data['expire_date_gmt'];
229 }
230
231 try {
232 $coupon_id = $this->model->create_coupon( $data );
233 if ( $coupon_id ) {
234 if ( is_array( $applies_to_items ) && count( $applies_to_items ) ) {
235 $this->model->insert_applies_to( $data['applies_to'], $applies_to_items, $data['coupon_code'] );
236 }
237
238 $this->json_response( __( 'Coupon created successfully!', 'tutor' ) );
239 } else {
240 $this->json_response(
241 __( 'Failed to create!', 'tutor' ),
242 null,
243 HttpHelper::STATUS_INTERNAL_SERVER_ERROR
244 );
245 }
246 } catch ( \Throwable $th ) {
247 $this->json_response(
248 tutor_utils()->error_message( 'server_error' ),
249 $th->getMessage(),
250 HttpHelper::STATUS_INTERNAL_SERVER_ERROR
251 );
252 }
253 }
254
255 /**
256 * Handle ajax request for updating coupon
257 *
258 * @since 3.0.0
259 *
260 * @return void send wp_json response
261 */
262 public function ajax_update_coupon() {
263 tutor_utils()->check_nonce();
264 tutor_utils()->check_current_user_capability();
265
266 $data = $this->get_allowed_fields( Input::sanitize_array( $_POST ), false );//phpcs:ignore --sanitized already
267
268 $coupon_id = Input::post( 'id', null, Input::TYPE_INT );
269 $data['coupon_id'] = $coupon_id;
270 $data['updated_at_gmt'] = current_time( 'mysql', true );
271
272 $validation = $this->validate( $data );
273 if ( ! $validation->success ) {
274 $this->json_response(
275 tutor_utils()->error_message( 'validation_error' ),
276 $validation->errors,
277 HttpHelper::STATUS_UNPROCESSABLE_ENTITY
278 );
279 }
280
281 $this->validate_applies_to_item( $data );
282
283 unset( $data['coupon_id'] );
284 unset( $data['coupon_type'] );
285
286 if ( ! isset( $data['expire_date_gmt'] ) ) {
287 $data['expire_date_gmt'] = null;
288 }
289
290 // Set updated by.
291 $data['updated_by'] = get_current_user_id();
292
293 try {
294 $update = $this->model->update_coupon( $coupon_id, $data );
295 if ( $update ) {
296 $coupon_data = $this->model->get_coupon( array( 'id' => $coupon_id ) );
297 $this->model->delete_applies_to( $coupon_data->coupon_code );
298
299 if ( $this->has_applies_to_items( $data ) ) {
300 $this->model->insert_applies_to( $data['applies_to'], $data['applies_to_items'], $coupon_data->coupon_code );
301 }
302
303 $this->json_response( __( 'Coupon updated successfully!', 'tutor' ) );
304 } else {
305 $this->json_response(
306 __( 'Failed to update!', 'tutor' ),
307 null,
308 HttpHelper::STATUS_INTERNAL_SERVER_ERROR
309 );
310 }
311 } catch ( \Throwable $th ) {
312 $this->json_response(
313 tutor_utils()->error_message( 'server_error' ),
314 $th->getMessage(),
315 HttpHelper::STATUS_INTERNAL_SERVER_ERROR
316 );
317 }
318 }
319
320 /**
321 * Get list of coupon applies to on which coupon
322 * will be applicable
323 *
324 * @since 3.0.0
325 *
326 * @return void send wp_json response
327 */
328 public function ajax_coupon_applies_to_list() {
329 tutor_utils()->check_nonce();
330 tutor_utils()->check_current_user_capability();
331
332 $applies_to = Input::post( 'applies_to' );
333 $limit = Input::post( 'limit', 10, Input::TYPE_INT );
334 $offset = Input::post( 'offset', 0, Input::TYPE_INT );
335 $search_term = '';
336
337 $filter = json_decode( wp_unslash( $_POST['filter'] ) ); //phpcs:ignore --sanitized already
338 if ( ! empty( $filter ) && property_exists( $filter, 'search' ) ) {
339 $search_term = Input::sanitize( $filter->search );
340 }
341
342 if ( $this->model->is_specific_applies_to( $applies_to ) ) {
343 try {
344 $list = $this->get_application_list( $applies_to, $limit, $offset, $search_term );
345 if ( $list ) {
346 $this->json_response(
347 __( 'Coupon application list retrieved successfully!', 'tutor' ),
348 $list
349 );
350 } else {
351 $this->json_response(
352 tutor_utils()->error_message( 'not_found' ),
353 null,
354 HttpHelper::STATUS_NOT_FOUND
355 );
356 }
357 } catch ( \Throwable $th ) {
358 $this->json_response(
359 tutor_utils()->error_message( 'server_error' ),
360 $th->getMessage(),
361 HttpHelper::STATUS_INTERNAL_SERVER_ERROR
362 );
363 }
364 } else {
365 $this->json_response(
366 tutor_utils()->error_message( 'invalid_req' ),
367 null,
368 HttpHelper::STATUS_UNPROCESSABLE_ENTITY
369 );
370 }
371 }
372
373 /**
374 * Prepare bulk actions that will show on dropdown options
375 *
376 * @return array
377 * @since 3.0.0
378 */
379 public function prepare_bulk_actions(): array {
380 $actions = array(
381 $this->bulk_action_default(),
382 $this->bulk_action_active(),
383 $this->bulk_action_inactive(),
384 );
385
386 $active_tab = Input::get( 'data', '' );
387
388 if ( 'trash' === $active_tab ) {
389 array_push( $actions, $this->bulk_action_delete() );
390 } else {
391 array_push( $actions, $this->bulk_action_trash() );
392 }
393
394 return apply_filters( 'tutor_coupon_bulk_actions', $actions );
395 }
396
397 /**
398 * Get coupon page url
399 *
400 * @since 3.0.0
401 *
402 * @param boolean $is_admin Whether to get admin or frontend url.
403 *
404 * @return string
405 */
406 public static function get_coupon_page_url( bool $is_admin = true ) {
407 if ( $is_admin ) {
408 return admin_url( 'admin.php?page=' . self::PAGE_SLUG );
409 } else {
410 return tutor_utils()->get_tutor_dashboard_url() . '/coupons';
411 }
412 }
413
414 /**
415 * Available tabs that will visible on the right side of page navbar
416 *
417 * @return array
418 *
419 * @since 3.0.0
420 */
421 public function tabs_key_value(): array {
422 $url = apply_filters( 'tutor_data_tab_base_url', get_pagenum_link() );
423
424 $date = Input::get( 'date', '' );
425 $coupon_status = Input::get( 'coupon-status', '' );
426 $applies_to = Input::get( 'applies_to', '' );
427 $search = Input::get( 'search', '' );
428
429 $where = array();
430
431 if ( ! empty( $date ) ) {
432 $where['date(created_at_gmt)'] = tutor_get_formated_date( 'Y-m-d', $date );
433 }
434
435 if ( ! empty( $coupon_status ) ) {
436 $where['coupon_status'] = $coupon_status;
437 }
438
439 if ( ! empty( $applies_to ) ) {
440 $where['applies_to'] = $applies_to;
441 }
442
443 if ( ! tutor_utils()->is_addon_enabled( 'subscription' ) && empty( $applies_to ) ) {
444 $where['applies_to'] = $this->model->get_course_bundle_applies_to( true );
445 }
446
447 $coupon_status = $this->model->get_coupon_status();
448
449 $tabs = array();
450
451 $tabs [] = array(
452 'key' => 'all',
453 'title' => __( 'All', 'tutor' ),
454 'value' => $this->model->get_coupon_count( $where, $search ),
455 'url' => $url . '&data=all',
456 );
457
458 foreach ( $coupon_status as $key => $value ) {
459 $where['coupon_status'] = $key;
460
461 $tabs[] = array(
462 'key' => $key,
463 'title' => $value,
464 'value' => $this->model->get_coupon_count( $where, $search ),
465 'url' => $url . '&data=' . $key,
466 );
467 }
468
469 return apply_filters( 'tutor_coupon_tabs', $tabs );
470 }
471
472 /**
473 * Get coupons
474 *
475 * @since 3.0.0
476 *
477 * @param integer $limit List limit.
478 * @param integer $offset List offset.
479 *
480 * @return array
481 */
482 public function get_coupons( $limit = 10, $offset = 0 ) {
483
484 $active_tab = Input::get( 'data', 'all' );
485
486 $date = Input::get( 'date', '' );
487 $search_term = Input::get( 'search', '' );
488 $coupon_status = Input::get( 'coupon-status' );
489 $applies_to = Input::get( 'applies_to' );
490
491 $where_clause = array();
492
493 if ( $date ) {
494 $where_clause['date(created_at_gmt)'] = tutor_get_formated_date( '', $date );
495 }
496
497 if ( $coupon_status ) {
498 $where_clause['coupon_status'] = $coupon_status;
499 }
500
501 if ( 'all' !== $active_tab && in_array( $active_tab, array_keys( $this->model->get_coupon_status() ), true ) ) {
502 $where_clause['coupon_status'] = $active_tab;
503 }
504
505 if ( $applies_to ) {
506 $where_clause['applies_to'] = $applies_to;
507 }
508
509 $list_order = Input::get( 'order', 'DESC' );
510 $list_order_by = 'id';
511
512 if ( ! tutor_utils()->is_addon_enabled( 'subscription' ) && ! $applies_to ) {
513 $where_clause['applies_to'] = $this->model->get_course_bundle_applies_to( true );
514 }
515
516 return $this->model->get_coupons( $where_clause, $search_term, $limit, $offset, $list_order_by, $list_order );
517 }
518
519 /**
520 * Handle bulk action AJAX request.
521 *
522 * Bulk actions: active, inactive, trash, delete
523 *
524 * @since 3.0.0
525 *
526 * @return void send wp_json response
527 */
528 public function bulk_action_handler() {
529 tutor_utils()->checking_nonce();
530
531 if ( ! current_user_can( 'manage_options' ) ) {
532 tutor_utils()->error_message();
533 }
534
535 // Get and sanitize input data.
536 $request = Input::sanitize_array( $_POST ); //phpcs:ignore --sanitized already
537 $bulk_action = $request['bulk-action'];
538
539 $bulk_ids = isset( $request['bulk-ids'] ) ? array_map( 'intval', explode( ',', $request['bulk-ids'] ) ) : array();
540
541 if ( empty( $bulk_ids ) ) {
542 wp_send_json_error( __( 'No items selected for the bulk action.', 'tutor' ) );
543 }
544
545 $allowed_bulk_actions = array_keys( $this->model->get_coupon_status() );
546 array_push( $allowed_bulk_actions, 'delete' );
547
548 if ( ! in_array( $bulk_action, $allowed_bulk_actions, true ) ) {
549 wp_send_json_error( __( 'Invalid bulk action.', 'tutor' ) );
550 }
551
552 do_action( 'tutor_before_coupon_bulk_action', $bulk_action, $bulk_ids );
553
554 $response = false;
555 if ( 'delete' === $bulk_action ) {
556 $response = $this->model->delete_coupon( $bulk_ids );
557 } else {
558 $data = array(
559 'coupon_status' => $bulk_action,
560 );
561 $response = $this->model->update_coupon( $bulk_ids, $data );
562 }
563
564 do_action( 'tutor_after_coupon_bulk_action', $bulk_action, $bulk_ids );
565
566 if ( $response ) {
567 wp_send_json_success( __( 'Coupon updated successfully.', 'tutor' ) );
568 } else {
569 wp_send_json_error( __( 'Failed to update coupon.', 'tutor' ) );
570 }
571 }
572
573 /**
574 * Handle coupon permanent delete
575 *
576 * @since 3.0.0
577 *
578 * @return void send wp_json response
579 */
580 public function coupon_permanent_delete() {
581 tutor_utils()->checking_nonce();
582
583 if ( ! current_user_can( 'manage_options' ) ) {
584 tutor_utils()->error_message();
585 }
586
587 // Get and sanitize input data.
588 $id = Input::post( 'id', 0, Input::TYPE_INT );
589 if ( ! $id ) {
590 wp_send_json_error( __( 'Invalid coupon ID', 'tutor' ) );
591 }
592
593 do_action( 'tutor_before_coupon_permanent_delete', $id );
594
595 $response = $this->model->delete_coupon( $id );
596 if ( $response ) {
597 do_action( 'tutor_after_coupon_permanent_delete', $id );
598
599 wp_send_json_success( __( 'Coupon delete successfully.', 'tutor' ) );
600 } else {
601 wp_send_json_error( __( 'Failed to delete coupon.', 'tutor' ) );
602 }
603 }
604
605 /**
606 * Ajax handler to retrieve coupon details.
607 *
608 * @since 3.0.0
609 *
610 * @return void Sends a JSON response with the coupon data or an error message.
611 */
612 public function ajax_coupon_details() {
613 if ( ! tutor_utils()->is_nonce_verified() ) {
614 $this->json_response( tutor_utils()->error_message( 'nonce' ), null, HttpHelper::STATUS_BAD_REQUEST );
615 }
616
617 $coupon_id = Input::post( 'id' );
618
619 if ( empty( $coupon_id ) ) {
620 $this->json_response(
621 __( 'Coupon code is required', 'tutor' ),
622 null,
623 HttpHelper::STATUS_BAD_REQUEST
624 );
625 }
626
627 $coupon_data = $this->model->get_coupon( array( 'id' => $coupon_id ) );
628
629 if ( ! $coupon_data ) {
630 $this->json_response(
631 __( 'Coupon not found', 'tutor' ),
632 null,
633 HttpHelper::STATUS_NOT_FOUND
634 );
635 }
636
637 $applications = $this->model->get_formatted_coupon_applications( $coupon_data );
638
639 // Set applies to items.
640 $coupon_data->applies_to_items = apply_filters( 'tutor_coupon_details_applies_to_items_response', $applications, $coupon_data );
641
642 // Set coupon usage.
643 $coupon_data->coupon_usage = $this->model->get_coupon_usage_count( $coupon_data->coupon_code );
644
645 // Set created & updated by.
646 $coupon_data->coupon_created_by = tutor_utils()->display_name( $coupon_data->created_by );
647 $coupon_data->coupon_update_by = tutor_utils()->display_name( $coupon_data->updated_by );
648
649 $coupon_data->start_date_readable = empty( $coupon_data->start_date_gmt ) ? '' : DateTimeHelper::get_gmt_to_user_timezone_date( $coupon_data->start_date_gmt );
650 $coupon_data->expire_date_readable = empty( $coupon_data->expire_date_gmt ) ? '' : DateTimeHelper::get_gmt_to_user_timezone_date( $coupon_data->expire_date_gmt );
651 $coupon_data->created_at_readable = DateTimeHelper::get_gmt_to_user_timezone_date( $coupon_data->created_at_gmt );
652 $coupon_data->updated_at_readable = empty( $coupon_data->updated_at_gmt ) ? '' : DateTimeHelper::get_gmt_to_user_timezone_date( $coupon_data->updated_at_gmt );
653
654 $this->json_response(
655 __( 'Coupon retrieved successfully', 'tutor' ),
656 $coupon_data
657 );
658 }
659
660 /**
661 * Get application if applies to a specific category or bundle.
662 *
663 * @since 3.0.0
664 *
665 * @param string $applies_to Applies to.
666 * @param int $limit Number of items to fetch.
667 * @param int $offset Offset for fetching items.
668 * @param int $search_term Search term.
669 *
670 * @return array
671 */
672 public function get_application_list( string $applies_to, int $limit = 10, int $offset = 0, $search_term = '' ) {
673
674 $response = array(
675 'total_items' => 0,
676 'results' => array(),
677 );
678
679 if ( $this->model::APPLIES_TO_SPECIFIC_COURSES === $applies_to ) {
680 $args = array(
681 'post_type' => tutor()->course_post_type,
682 'posts_per_page' => $limit,
683 'offset' => $offset,
684 );
685
686 // Add search.
687 if ( $search_term ) {
688 $args['s'] = $search_term;
689 }
690
691 $courses = ( new CourseModel() )->get_paid_courses( $args );
692
693 $response['total_items'] = is_a( $courses, 'WP_Query' ) ? $courses->found_posts : 0;
694
695 if ( is_a( $courses, 'WP_Query' ) && $courses->have_posts() ) {
696 $courses = $courses->get_posts();
697 foreach ( $courses as $course ) {
698 $response['results'][] = Course::get_mini_info( $course );
699 }
700 }
701 } elseif ( $this->model::APPLIES_TO_SPECIFIC_BUNDLES === $applies_to && tutor_utils()->is_addon_enabled( 'tutor-pro/addons/course-bundle/course-bundle.php' ) ) {
702 $args = array(
703 'post_type' => 'course-bundle',
704 'posts_per_page' => $limit,
705 'offset' => $offset,
706 );
707
708 // Add search.
709 if ( $search_term ) {
710 $args['s'] = $search_term;
711 }
712
713 $bundles = ( new CourseModel() )->get_paid_courses( $args );
714
715 $response['total_items'] = is_a( $bundles, 'WP_Query' ) ? $bundles->found_posts : 0;
716
717 if ( is_a( $bundles, 'WP_Query' ) && $bundles->have_posts() ) {
718 $bundles = $bundles->get_posts();
719 foreach ( $bundles as $bundle ) {
720 $response['results'][] = Course::get_mini_info( $bundle );
721 }
722 }
723 } elseif ( $this->model::APPLIES_TO_SPECIFIC_CATEGORY === $applies_to ) {
724 $args = array(
725 'number' => $limit,
726 'offset' => $offset,
727 'hide_empty' => true,
728 );
729
730 $total_arg = array(
731 'fields' => 'ids',
732 'taxonomy' => CourseModel::COURSE_CATEGORY,
733 'hide_empty' => true,
734 );
735
736 // Add search.
737 if ( $search_term ) {
738 $args['search'] = $search_term;
739 $total_arg['search'] = $search_term;
740 }
741
742 $terms = tutor_utils()->get_course_categories( 0, $args );
743 $total = get_terms( $total_arg );
744
745 $response['total_items'] = is_array( $total ) ? count( $total ) : 0;
746
747 if ( ! is_wp_error( $terms ) ) {
748 foreach ( $terms as $term ) {
749 $thumb_id = get_term_meta( $term->term_id, 'thumbnail_id', true );
750
751 $response['results'][] = array(
752 'id' => $term->term_id,
753 'title' => $term->name,
754 'image' => $thumb_id ? wp_get_attachment_thumb_url( $thumb_id ) : tutor()->url . 'assets/images/placeholder.svg',
755 'total_courses' => (int) $term->count,
756 );
757 }
758 }
759 }
760
761 return $response;
762 }
763
764 /**
765 * Ajax handler for applying coupon
766 *
767 * @since 3.0.0
768 *
769 * @return void send wp_json response
770 */
771 public function ajax_apply_coupon() {
772 tutor_utils()->check_nonce();
773
774 if ( ! Settings::is_coupon_usage_enabled() ) {
775 $this->json_response(
776 __( 'Coupon usage is disabled', 'tutor' ),
777 null,
778 HttpHelper::STATUS_BAD_REQUEST
779 );
780 }
781
782 $object_ids = Input::post( 'object_ids' ); // Course/bundle ids.
783 $object_ids = array_filter( explode( ',', $object_ids ), 'is_numeric' );
784
785 if ( empty( $object_ids ) ) {
786 $this->json_response(
787 tutor_utils()->error_message( 'invalid_req' ),
788 null,
789 HttpHelper::STATUS_BAD_REQUEST
790 );
791 }
792
793 try {
794 $coupon_code = Input::post( 'coupon_code' );
795 $plan = Input::post( 'plan', 0, Input::TYPE_INT );
796 $order_type = $plan ? OrderModel::TYPE_SUBSCRIPTION : OrderModel::TYPE_SINGLE_ORDER;
797
798 $checkout_data = $this->checkout_ctrl->prepare_checkout_items( $object_ids, $order_type, $coupon_code );
799
800 if ( $checkout_data->is_coupon_applied ) {
801 $this->json_response(
802 __( 'Coupon applied successfully', 'tutor' ),
803 $checkout_data
804 );
805 } else {
806 global $tutor_coupon_apply_err_msg;
807 $this->json_response(
808 $tutor_coupon_apply_err_msg,
809 null,
810 HttpHelper::STATUS_BAD_REQUEST
811 );
812 }
813 } catch ( \Throwable $th ) {
814 $this->json_response(
815 $th->getMessage(),
816 null,
817 HttpHelper::STATUS_INTERNAL_SERVER_ERROR
818 );
819 }
820 }
821
822 /**
823 * Manage coupon usage
824 *
825 * Store usage upon order completion
826 *
827 * @since 3.0.0
828 *
829 * @param int $order_id Order id.
830 *
831 * @return void
832 */
833 public function store_coupon_usage( $order_id ) {
834 $order_model = ( new OrderModel() );
835
836 $order = $order_model->get_order_by_id( $order_id );
837 if ( $order ) {
838 if ( $order->coupon_amount > 0 && $order_model::ORDER_COMPLETED === $order->order_status ) {
839 // Store coupon usage.
840 $data = array(
841 'coupon_code' => $order->coupon_code,
842 'user_id' => $order->user_id,
843 );
844
845 try {
846 $this->model->store_coupon_usage( $data );
847 } catch ( \Throwable $th ) {
848 tutor_log( $th );
849 }
850 }
851 }
852 }
853
854 /**
855 * Validate input data based on predefined rules.
856 *
857 * @since 3.0.0
858 *
859 * @param array $data The data array to validate.
860 *
861 * @return object The validation result. It returns validation object.
862 */
863 protected function validate( array $data ) {
864
865 $validation_rules = array(
866 'coupon_id' => 'numeric',
867 'coupon_status' => 'required',
868 'coupon_type' => 'required',
869 'coupon_code' => 'required',
870 'coupon_title' => 'required',
871 'discount_type' => 'required',
872 'discount_amount' => 'required',
873 'applies_to' => 'required',
874 'total_usage_limit' => 'numeric',
875 'per_user_usage_limit' => 'numeric',
876 'start_date_gmt' => 'required|date_format:Y-m-d H:i:s',
877 );
878
879 // Skip validation rules for not available fields in data.
880 foreach ( $validation_rules as $key => $value ) {
881 if ( ! array_key_exists( $key, $data ) ) {
882 unset( $validation_rules[ $key ] );
883 }
884 }
885
886 return ValidationHelper::validate( $validation_rules, $data );
887 }
888 }
889