Diff: STRATO-apps/wordpress_03/app/wp-content/plugins/tutor/ecommerce/CouponController.php
Keine Baseline-Datei – Diff nur gegen leer.
1
-
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' => '',
453
+
'title' => __( 'All', 'tutor' ),
454
+
'value' => $this->model->get_coupon_count( $where, $search ),
455
+
'url' => $url . '&data=all',
456
+
);
457
+
458
+
$gm_date = DateTimeHelper::now()->to_date_time_string();
459
+
460
+
foreach ( $coupon_status as $key => $value ) {
461
+
if ( CouponModel::STATUS_EXPIRED === $key ) {
462
+
$where['coupon_status'] = CouponModel::STATUS_ACTIVE;
463
+
$raw_query = '( start_date_gmt > %s OR expire_date_gmt < %s )';
464
+
$where[ $raw_query ] = array(
465
+
'RAW',
466
+
array( $gm_date, $gm_date ),
467
+
);
468
+
} else {
469
+
470
+
$where['coupon_status'] = $key;
471
+
$where['start_date_gmt'] = array(
472
+
'<=',
473
+
$gm_date,
474
+
);
475
+
476
+
$where[ "IFNULL( expire_date_gmt, '{$gm_date}' )" ] = array(
477
+
'>=',
478
+
$gm_date,
479
+
);
480
+
481
+
}
482
+
483
+
$tabs[] = array(
484
+
'key' => $key,
485
+
'title' => $value,
486
+
'value' => $this->model->get_coupon_count( $where, $search ),
487
+
'url' => $url . '&data=' . $key,
488
+
);
489
+
490
+
if ( isset( $where['start_date_gmt'] ) ) {
491
+
unset( $where['start_date_gmt'] );
492
+
}
493
+
494
+
if ( isset( $where[ "IFNULL( expire_date_gmt, '{$gm_date}' )" ] ) ) {
495
+
unset( $where[ "IFNULL( expire_date_gmt, '{$gm_date}' )" ] );
496
+
}
497
+
}
498
+
499
+
return apply_filters( 'tutor_coupon_tabs', $tabs );
500
+
}
501
+
502
+
/**
503
+
* Get coupons
504
+
*
505
+
* @since 3.0.0
506
+
*
507
+
* @param integer $limit List limit.
508
+
* @param integer $offset List offset.
509
+
*
510
+
* @return array
511
+
*/
512
+
public function get_coupons( $limit = 10, $offset = 0 ) {
513
+
514
+
$active_tab = Input::get( 'data', 'all' );
515
+
516
+
$date = Input::get( 'date', '' );
517
+
$search_term = Input::get( 'search', '' );
518
+
$coupon_status = Input::get( 'coupon-status' );
519
+
$applies_to = Input::get( 'applies_to' );
520
+
521
+
$where_clause = array();
522
+
523
+
if ( $date ) {
524
+
$where_clause['date(created_at_gmt)'] = tutor_get_formated_date( '', $date );
525
+
}
526
+
527
+
if ( $coupon_status ) {
528
+
$where_clause['coupon_status'] = $coupon_status;
529
+
}
530
+
531
+
$gm_date = DateTimeHelper::now()->to_date_time_string();
532
+
533
+
if ( 'all' !== $active_tab && in_array( $active_tab, array_keys( $this->model->get_coupon_status() ), true ) ) {
534
+
if ( CouponModel::STATUS_EXPIRED === $active_tab ) {
535
+
$where_clause['coupon_status'] = CouponModel::STATUS_ACTIVE;
536
+
$raw_query = '( start_date_gmt > %s OR expire_date_gmt < %s )';
537
+
$where_clause[ $raw_query ] = array(
538
+
'RAW',
539
+
array( $gm_date, $gm_date ),
540
+
);
541
+
} else {
542
+
$where_clause['coupon_status'] = $active_tab;
543
+
$where_clause['start_date_gmt'] = array(
544
+
'<=',
545
+
$gm_date,
546
+
);
547
+
548
+
$where_clause[ "IFNULL( expire_date_gmt, '{$gm_date}' )" ] = array(
549
+
'>=',
550
+
$gm_date,
551
+
);
552
+
}
553
+
}
554
+
555
+
if ( $applies_to ) {
556
+
$where_clause['applies_to'] = $applies_to;
557
+
}
558
+
559
+
$list_order = Input::get( 'order', 'DESC' );
560
+
$list_order_by = 'id';
561
+
562
+
if ( ! tutor_utils()->is_addon_enabled( 'subscription' ) && ! $applies_to ) {
563
+
$where_clause['applies_to'] = $this->model->get_course_bundle_applies_to( true );
564
+
}
565
+
566
+
return $this->model->get_coupons( $where_clause, $search_term, $limit, $offset, $list_order_by, $list_order );
567
+
}
568
+
569
+
/**
570
+
* Handle bulk action AJAX request.
571
+
*
572
+
* Bulk actions: active, inactive, trash, delete
573
+
*
574
+
* @since 3.0.0
575
+
*
576
+
* @return void send wp_json response
577
+
*/
578
+
public function bulk_action_handler() {
579
+
tutor_utils()->checking_nonce();
580
+
tutor_utils()->check_current_user_capability();
581
+
582
+
// Get and sanitize input data.
583
+
$request = Input::sanitize_array( $_POST ); //phpcs:ignore --sanitized already
584
+
$bulk_action = $request['bulk-action'];
585
+
586
+
$bulk_ids = isset( $request['bulk-ids'] ) ? array_map( 'intval', explode( ',', $request['bulk-ids'] ) ) : array();
587
+
588
+
if ( empty( $bulk_ids ) ) {
589
+
wp_send_json_error( __( 'No items selected for the bulk action.', 'tutor' ) );
590
+
}
591
+
592
+
$allowed_bulk_actions = array_keys( $this->model->get_coupon_status() );
593
+
array_push( $allowed_bulk_actions, 'delete' );
594
+
595
+
if ( ! in_array( $bulk_action, $allowed_bulk_actions, true ) ) {
596
+
wp_send_json_error( __( 'Invalid bulk action.', 'tutor' ) );
597
+
}
598
+
599
+
do_action( 'tutor_before_coupon_bulk_action', $bulk_action, $bulk_ids );
600
+
601
+
$response = false;
602
+
if ( 'delete' === $bulk_action ) {
603
+
$response = $this->model->delete_coupon( $bulk_ids );
604
+
} else {
605
+
$data = array(
606
+
'coupon_status' => $bulk_action,
607
+
);
608
+
$response = $this->model->update_coupon( $bulk_ids, $data );
609
+
}
610
+
611
+
do_action( 'tutor_after_coupon_bulk_action', $bulk_action, $bulk_ids );
612
+
613
+
if ( $response ) {
614
+
wp_send_json_success( __( 'Coupon updated successfully.', 'tutor' ) );
615
+
} else {
616
+
wp_send_json_error( __( 'Failed to update coupon.', 'tutor' ) );
617
+
}
618
+
}
619
+
620
+
/**
621
+
* Handle coupon permanent delete
622
+
*
623
+
* @since 3.0.0
624
+
*
625
+
* @return void send wp_json response
626
+
*/
627
+
public function coupon_permanent_delete() {
628
+
tutor_utils()->checking_nonce();
629
+
630
+
tutor_utils()->check_current_user_capability();
631
+
632
+
// Get and sanitize input data.
633
+
$id = Input::post( 'id', 0, Input::TYPE_INT );
634
+
if ( ! $id ) {
635
+
wp_send_json_error( __( 'Invalid coupon ID', 'tutor' ) );
636
+
}
637
+
638
+
do_action( 'tutor_before_coupon_permanent_delete', $id );
639
+
640
+
$response = $this->model->delete_coupon( $id );
641
+
if ( $response ) {
642
+
do_action( 'tutor_after_coupon_permanent_delete', $id );
643
+
644
+
wp_send_json_success( __( 'Coupon delete successfully.', 'tutor' ) );
645
+
} else {
646
+
wp_send_json_error( __( 'Failed to delete coupon.', 'tutor' ) );
647
+
}
648
+
}
649
+
650
+
/**
651
+
* Ajax handler to retrieve coupon details.
652
+
*
653
+
* @since 3.0.0
654
+
*
655
+
* @return void Sends a JSON response with the coupon data or an error message.
656
+
*/
657
+
public function ajax_coupon_details() {
658
+
if ( ! tutor_utils()->is_nonce_verified() ) {
659
+
$this->json_response( tutor_utils()->error_message( 'nonce' ), null, HttpHelper::STATUS_BAD_REQUEST );
660
+
}
661
+
662
+
$coupon_id = Input::post( 'id' );
663
+
664
+
if ( empty( $coupon_id ) ) {
665
+
$this->json_response(
666
+
__( 'Coupon code is required', 'tutor' ),
667
+
null,
668
+
HttpHelper::STATUS_BAD_REQUEST
669
+
);
670
+
}
671
+
672
+
$coupon_data = $this->model->get_coupon( array( 'id' => $coupon_id ) );
673
+
674
+
if ( ! $coupon_data ) {
675
+
$this->json_response(
676
+
__( 'Coupon not found', 'tutor' ),
677
+
null,
678
+
HttpHelper::STATUS_NOT_FOUND
679
+
);
680
+
}
681
+
682
+
$applications = $this->model->get_formatted_coupon_applications( $coupon_data );
683
+
684
+
// Set applies to items.
685
+
$coupon_data->applies_to_items = apply_filters( 'tutor_coupon_details_applies_to_items_response', $applications, $coupon_data );
686
+
687
+
// Set coupon usage.
688
+
$coupon_data->coupon_usage = $this->model->get_coupon_usage_count( $coupon_data->coupon_code );
689
+
690
+
// Set created & updated by.
691
+
$coupon_data->coupon_created_by = tutor_utils()->display_name( $coupon_data->created_by );
692
+
$coupon_data->coupon_update_by = tutor_utils()->display_name( $coupon_data->updated_by );
693
+
694
+
$coupon_data->start_date_readable = empty( $coupon_data->start_date_gmt ) ? '' : DateTimeHelper::get_gmt_to_user_timezone_date( $coupon_data->start_date_gmt );
695
+
$coupon_data->expire_date_readable = empty( $coupon_data->expire_date_gmt ) ? '' : DateTimeHelper::get_gmt_to_user_timezone_date( $coupon_data->expire_date_gmt );
696
+
$coupon_data->created_at_readable = DateTimeHelper::get_gmt_to_user_timezone_date( $coupon_data->created_at_gmt );
697
+
$coupon_data->updated_at_readable = empty( $coupon_data->updated_at_gmt ) ? '' : DateTimeHelper::get_gmt_to_user_timezone_date( $coupon_data->updated_at_gmt );
698
+
699
+
$this->json_response(
700
+
__( 'Coupon retrieved successfully', 'tutor' ),
701
+
$coupon_data
702
+
);
703
+
}
704
+
705
+
/**
706
+
* Get application if applies to a specific category or bundle.
707
+
*
708
+
* @since 3.0.0
709
+
*
710
+
* @param string $applies_to Applies to.
711
+
* @param int $limit Number of items to fetch.
712
+
* @param int $offset Offset for fetching items.
713
+
* @param int $search_term Search term.
714
+
*
715
+
* @return array
716
+
*/
717
+
public function get_application_list( string $applies_to, int $limit = 10, int $offset = 0, $search_term = '' ) {
718
+
719
+
$response = array(
720
+
'total_items' => 0,
721
+
'results' => array(),
722
+
);
723
+
724
+
if ( $this->model::APPLIES_TO_SPECIFIC_COURSES === $applies_to ) {
725
+
$args = array(
726
+
'post_type' => tutor()->course_post_type,
727
+
'posts_per_page' => $limit,
728
+
'offset' => $offset,
729
+
);
730
+
731
+
// Add search.
732
+
if ( $search_term ) {
733
+
$args['s'] = $search_term;
734
+
}
735
+
736
+
$courses = ( new CourseModel() )->get_paid_courses( $args );
737
+
738
+
$response['total_items'] = is_a( $courses, 'WP_Query' ) ? $courses->found_posts : 0;
739
+
740
+
if ( is_a( $courses, 'WP_Query' ) && $courses->have_posts() ) {
741
+
$courses = $courses->get_posts();
742
+
foreach ( $courses as $course ) {
743
+
$response['results'][] = Course::get_mini_info( $course );
744
+
}
745
+
}
746
+
} elseif ( $this->model::APPLIES_TO_SPECIFIC_BUNDLES === $applies_to && tutor_utils()->is_addon_enabled( 'tutor-pro/addons/course-bundle/course-bundle.php' ) ) {
747
+
$args = array(
748
+
'post_type' => 'course-bundle',
749
+
'posts_per_page' => $limit,
750
+
'offset' => $offset,
751
+
);
752
+
753
+
// Add search.
754
+
if ( $search_term ) {
755
+
$args['s'] = $search_term;
756
+
}
757
+
758
+
$bundles = ( new CourseModel() )->get_paid_courses( $args );
759
+
760
+
$response['total_items'] = is_a( $bundles, 'WP_Query' ) ? $bundles->found_posts : 0;
761
+
762
+
if ( is_a( $bundles, 'WP_Query' ) && $bundles->have_posts() ) {
763
+
$bundles = $bundles->get_posts();
764
+
foreach ( $bundles as $bundle ) {
765
+
$response['results'][] = Course::get_mini_info( $bundle );
766
+
}
767
+
}
768
+
} elseif ( $this->model::APPLIES_TO_SPECIFIC_CATEGORY === $applies_to ) {
769
+
$args = array(
770
+
'number' => $limit,
771
+
'offset' => $offset,
772
+
'hide_empty' => true,
773
+
);
774
+
775
+
$total_arg = array(
776
+
'fields' => 'ids',
777
+
'taxonomy' => CourseModel::COURSE_CATEGORY,
778
+
'hide_empty' => true,
779
+
);
780
+
781
+
// Add search.
782
+
if ( $search_term ) {
783
+
$args['search'] = $search_term;
784
+
$total_arg['search'] = $search_term;
785
+
}
786
+
787
+
$terms = tutor_utils()->get_course_categories( 0, $args );
788
+
$total = get_terms( $total_arg );
789
+
790
+
$response['total_items'] = is_array( $total ) ? count( $total ) : 0;
791
+
792
+
if ( ! is_wp_error( $terms ) ) {
793
+
foreach ( $terms as $term ) {
794
+
$thumb_id = get_term_meta( $term->term_id, 'thumbnail_id', true );
795
+
796
+
$response['results'][] = array(
797
+
'id' => $term->term_id,
798
+
'title' => $term->name,
799
+
'image' => $thumb_id ? wp_get_attachment_thumb_url( $thumb_id ) : tutor()->url . 'assets/images/placeholder.svg',
800
+
'total_courses' => (int) $term->count,
801
+
);
802
+
}
803
+
}
804
+
}
805
+
806
+
return $response;
807
+
}
808
+
809
+
/**
810
+
* Ajax handler for applying coupon
811
+
*
812
+
* @since 3.0.0
813
+
*
814
+
* @return void send wp_json response
815
+
*/
816
+
public function ajax_apply_coupon() {
817
+
tutor_utils()->check_nonce();
818
+
819
+
if ( ! Settings::is_coupon_usage_enabled() ) {
820
+
$this->json_response(
821
+
__( 'Coupon usage is disabled', 'tutor' ),
822
+
null,
823
+
HttpHelper::STATUS_BAD_REQUEST
824
+
);
825
+
}
826
+
827
+
$object_ids = Input::post( 'object_ids' ); // Course/bundle ids.
828
+
$object_ids = array_filter( explode( ',', $object_ids ), 'is_numeric' );
829
+
830
+
if ( empty( $object_ids ) ) {
831
+
$this->json_response(
832
+
tutor_utils()->error_message( 'invalid_req' ),
833
+
null,
834
+
HttpHelper::STATUS_BAD_REQUEST
835
+
);
836
+
}
837
+
838
+
try {
839
+
$coupon_code = Input::post( 'coupon_code' );
840
+
$plan = Input::post( 'plan', 0, Input::TYPE_INT );
841
+
$order_type = $plan ? OrderModel::TYPE_SUBSCRIPTION : OrderModel::TYPE_SINGLE_ORDER;
842
+
843
+
$checkout_data = $this->checkout_ctrl->prepare_checkout_items( $object_ids, $order_type, $coupon_code );
844
+
845
+
if ( $checkout_data->is_coupon_applied ) {
846
+
$this->json_response(
847
+
__( 'Coupon applied successfully', 'tutor' ),
848
+
$checkout_data
849
+
);
850
+
} else {
851
+
global $tutor_coupon_apply_err_msg;
852
+
$this->json_response(
853
+
$tutor_coupon_apply_err_msg,
854
+
null,
855
+
HttpHelper::STATUS_BAD_REQUEST
856
+
);
857
+
}
858
+
} catch ( \Throwable $th ) {
859
+
$this->json_response(
860
+
$th->getMessage(),
861
+
null,
862
+
HttpHelper::STATUS_INTERNAL_SERVER_ERROR
863
+
);
864
+
}
865
+
}
866
+
867
+
/**
868
+
* Manage coupon usage
869
+
*
870
+
* Store usage upon order completion
871
+
*
872
+
* @since 3.0.0
873
+
*
874
+
* @param int $order_id Order id.
875
+
*
876
+
* @return void
877
+
*/
878
+
public function store_coupon_usage( $order_id ) {
879
+
$order_model = ( new OrderModel() );
880
+
881
+
$order = $order_model->get_order_by_id( $order_id );
882
+
if ( $order ) {
883
+
if ( $order->coupon_amount > 0 && $order_model::ORDER_COMPLETED === $order->order_status ) {
884
+
// Store coupon usage.
885
+
$data = array(
886
+
'coupon_code' => $order->coupon_code,
887
+
'user_id' => $order->user_id,
888
+
);
889
+
890
+
try {
891
+
$this->model->store_coupon_usage( $data );
892
+
} catch ( \Throwable $th ) {
893
+
tutor_log( $th );
894
+
}
895
+
}
896
+
}
897
+
}
898
+
899
+
/**
900
+
* Validate input data based on predefined rules.
901
+
*
902
+
* @since 3.0.0
903
+
*
904
+
* @param array $data The data array to validate.
905
+
*
906
+
* @return object The validation result. It returns validation object.
907
+
*/
908
+
protected function validate( array $data ) {
909
+
910
+
$validation_rules = array(
911
+
'coupon_id' => 'numeric',
912
+
'coupon_status' => 'required',
913
+
'coupon_type' => 'required',
914
+
'coupon_code' => 'required',
915
+
'coupon_title' => 'required',
916
+
'discount_type' => 'required',
917
+
'discount_amount' => 'required',
918
+
'applies_to' => 'required',
919
+
'total_usage_limit' => 'numeric',
920
+
'per_user_usage_limit' => 'numeric',
921
+
'start_date_gmt' => 'required|date_format:Y-m-d H:i:s',
922
+
);
923
+
924
+
// Skip validation rules for not available fields in data.
925
+
foreach ( $validation_rules as $key => $value ) {
926
+
if ( ! array_key_exists( $key, $data ) ) {
927
+
unset( $validation_rules[ $key ] );
928
+
}
929
+
}
930
+
931
+
return ValidationHelper::validate( $validation_rules, $data );
932
+
}
933
+
}
934
+