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