PluginProbe
Yatra – Travel Booking & Tour Operator Software / 3.0.2.7
Yatra – Travel Booking & Tour Operator Software v3.0.2.7
3.0.14 3.0.14.1 3.0.14.2 3.0.12 3.0.13 3.0.11 3.0.10 3.0.9 3.0.8 3.0.7 3.0.6 3.0.5 3.0.5.1 3.0.4 3.0.3 3.0.2.9 3.0.2.7 3.0.2.8 3.0.2.6 trunk 1.0.0 2.0.0 2.0.1 2.0.10 2.0.11 All 82 releases
yatra / app / Controllers / DiscountController.php

DiscountController.php in Yatra – Travel Booking & Tour Operator Software 3.0.2.7, at app/Controllers/DiscountController.php

429 lines 14.6 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2
3 declare(strict_types=1);
4
5 namespace Yatra\Controllers;
6
7 use WP_REST_Request;
8 use WP_REST_Response;
9 use WP_Error;
10 use Yatra\Services\DiscountService;
11
12 /**
13 * Discount REST API Controller
14 *
15 * Endpoints:
16 * - GET /discounts - List discounts
17 * - POST /discounts - Create discount
18 * - GET /discounts/{id} - Get single discount
19 * - PUT /discounts/{id} - Update discount
20 * - DELETE /discounts/{id} - Delete discount
21 */
22 class DiscountController extends BaseController
23 {
24 protected string $rest_base = 'discounts';
25
26 private DiscountService $service;
27
28 public function __construct()
29 {
30 $this->service = new DiscountService();
31 }
32
33 public function register_routes(): void
34 {
35 $this->registerCrudRoutes(array_merge(
36 $this->getStatusArg(),
37 [
38 'type' => [
39 'default' => 'all',
40 'sanitize_callback' => 'sanitize_text_field',
41 ],
42 ]
43 ));
44
45 register_rest_route($this->namespace, '/' . $this->rest_base . '/stats', [
46 [
47 'methods' => \WP_REST_Server::READABLE,
48 'callback' => [$this, 'get_stats'],
49 'permission_callback' => [$this, 'check_permission'],
50 ],
51 ]);
52
53 // Group discount discoverability endpoint
54 register_rest_route($this->namespace, '/' . $this->rest_base . '/group-discounts', [
55 [
56 'methods' => \WP_REST_Server::READABLE,
57 'callback' => [$this, 'get_group_discounts'],
58 'permission_callback' => [$this, 'check_public_permission'],
59 'args' => [
60 'trip_ids' => [
61 'required' => false,
62 'type' => 'array',
63 'items' => ['type' => 'integer'],
64 'description' => 'Array of trip IDs to check for group discounts',
65 ],
66 ],
67 ],
68 ]);
69 }
70
71 public function check_public_permission(?WP_REST_Request $request = null): bool
72 {
73 // Allow public access to group discount discoverability
74 return true;
75 }
76
77 /**
78 * Get group discount availability for trips
79 * Public endpoint for frontend discoverability
80 */
81 public function get_group_discounts(WP_REST_Request $request)
82 {
83 try {
84 $tripIds = $request->get_param('trip_ids');
85
86 if (empty($tripIds) || !is_array($tripIds)) {
87 return $this->error_response(__('Trip IDs are required', 'yatra'), 400);
88 }
89
90 // Sanitize trip IDs
91 $tripIds = array_map('absint', array_filter($tripIds));
92
93 if (empty($tripIds)) {
94 return $this->error_response(__('Valid trip IDs are required', 'yatra'), 400);
95 }
96
97 $result = [];
98
99 foreach ($tripIds as $tripId) {
100 $groupDiscounts = $this->getTripGroupDiscounts($tripId);
101 $result[$tripId] = [
102 'has_group_discounts' => !empty($groupDiscounts),
103 'discounts' => $groupDiscounts,
104 'summary' => $this->generateGroupDiscountSummary($groupDiscounts),
105 ];
106 }
107
108 return $this->success_response($result);
109
110 } catch (\Exception $e) {
111 return $this->error_response($e->getMessage(), 500);
112 }
113 }
114
115 /**
116 * Get group discounts for a specific trip
117 */
118 private function getTripGroupDiscounts(int $tripId): array
119 {
120 // Check if Advanced Discount module is enabled - group discounts are a Pro feature
121 if (!apply_filters('yatra_advanced_discount_enabled', false)) {
122 return [];
123 }
124
125 $discounts = \Yatra\Models\Discount::where('is_group_discount', true)
126 ->where('status', 'publish')
127 ->where(function($query) {
128 $query->whereNull('valid_from')
129 ->orWhere('valid_from', '<=', date('Y-m-d'));
130 })
131 ->where(function($query) {
132 $query->whereNull('expiry_date')
133 ->orWhere('expiry_date', '>=', date('Y-m-d'));
134 })
135 ->where(function($query) use ($tripId) {
136 $query->where('applicable_to', 'all')
137 ->orWhere(function($subQuery) use ($tripId) {
138 $subQuery->where('applicable_to', 'specific_trips')
139 ->whereJsonContains('trip_ids', $tripId);
140 });
141 })
142 ->orderBy('min_group_size', 'asc')
143 ->get();
144
145 $result = [];
146 foreach ($discounts as $discount) {
147 $result[] = [
148 'id' => $discount->id,
149 'min_group_size' => $discount->min_group_size,
150 'max_group_size' => $discount->max_group_size,
151 'discount_type' => $discount->group_discount_type,
152 'discount_amount' => $discount->group_discount_amount,
153 'discount_mode' => $discount->group_discount_mode,
154 'category_discounts' => $discount->category_discounts,
155 'range_label' => $this->formatGroupSizeRange($discount),
156 'discount_label' => $this->formatDiscountLabel($discount),
157 ];
158 }
159
160 return $result;
161 }
162
163 /**
164 * Generate summary text for group discounts
165 */
166 private function generateGroupDiscountSummary(array $discounts): string
167 {
168 if (empty($discounts)) {
169 return '';
170 }
171
172 $ranges = array_map(function($discount) {
173 return $discount['range_label'];
174 }, $discounts);
175
176 $uniqueRanges = array_unique($ranges);
177
178 if (count($uniqueRanges) === 1) {
179 return "Up to {$discounts[0]['discount_label']} for {$uniqueRanges[0]}";
180 } else {
181 $firstDiscount = $discounts[0];
182 return "Up to {$firstDiscount['discount_label']} for groups starting at {$firstDiscount['min_group_size']} people";
183 }
184 }
185
186 /**
187 * Format group size range for display
188 */
189 private function formatGroupSizeRange($discount): string
190 {
191 if ($discount->max_group_size) {
192 return "{$discount->min_group_size}-{$discount->max_group_size} people";
193 } else {
194 return "{$discount->min_group_size}+ people";
195 }
196 }
197
198 /**
199 * Format discount label for display
200 */
201 private function formatDiscountLabel($discount): string
202 {
203 if ($discount->group_discount_type === 'percentage') {
204 return "{$discount->group_discount_amount}% off";
205 } else {
206 return "$" . number_format($discount->group_discount_amount, 2) . " off";
207 }
208 }
209
210 public function check_permission(?WP_REST_Request $request = null): bool
211 {
212 if ($request === null) {
213 return true;
214 }
215
216 if (!is_user_logged_in()) {
217 return false;
218 }
219
220 if (current_user_can('manage_options')) {
221 return true;
222 }
223
224 switch ($request->get_method()) {
225 case 'GET':
226 return current_user_can('yatra_view_bookings');
227 case 'POST':
228 case 'PUT':
229 case 'PATCH':
230 case 'DELETE':
231 return current_user_can('yatra_edit_bookings');
232 default:
233 return current_user_can('manage_options');
234 }
235 }
236
237 /**
238 * GET /discounts/stats — counts per status for admin toolbar tabs
239 */
240 public function get_stats(WP_REST_Request $request)
241 {
242 try {
243 return $this->success_response($this->service->getAdminStatusCounts());
244 } catch (\Exception $e) {
245 return $this->error_response($e->getMessage(), 500);
246 }
247 }
248
249 public function get_items(WP_REST_Request $request)
250 {
251 try {
252 $params = $this->getPaginationParams($request);
253
254 // Override default orderby
255 $orderby = $request->get_param('orderby') ?: 'created_at';
256
257 $args = [
258 'limit' => $params['per_page'],
259 'offset' => ($params['page'] - 1) * $params['per_page'],
260 'order_by' => $orderby,
261 'order' => $params['order'],
262 ];
263
264 if (!empty($params['search'])) {
265 $args['search'] = $params['search'];
266 }
267
268 $status = $request->get_param('status');
269 if ($status && $status !== 'all') {
270 $args['status'] = sanitize_text_field($status);
271 }
272
273 $type = $request->get_param('type');
274 if ($type && $type !== 'all' && in_array($type, ['percentage', 'fixed'], true)) {
275 $args['type'] = $type;
276 }
277
278 $items = $this->service->getAll($args);
279 $total = $this->service->count($args);
280
281 $prepared = array_map([$this, 'prepareItem'], $items);
282
283 return $this->paginated_response($prepared, $total, $params['page'], $params['per_page']);
284 } catch (\Exception $e) {
285 return $this->error_response($e->getMessage(), 500);
286 }
287 }
288
289 public function get_item(WP_REST_Request $request)
290 {
291 try {
292 $item = $this->service->getById($this->getId($request));
293
294 if (!$item) {
295 return $this->not_found(__('Discount not found', 'yatra'));
296 }
297
298 return $this->success_response($this->prepareItem($item));
299 } catch (\Exception $e) {
300 return $this->error_response($e->getMessage(), 500);
301 }
302 }
303
304 public function create_item(WP_REST_Request $request)
305 {
306 try {
307 $data = $this->getBody($request);
308
309 // Check if Advanced Discount module is required for this discount type
310 $discount_mode = $data['discount_mode'] ?? 'promo';
311 $is_group_discount = !empty($data['is_group_discount']);
312
313 if (($discount_mode === 'group' || $discount_mode === 'both' || $is_group_discount)
314 && !apply_filters('yatra_advanced_discount_enabled', false)) {
315 return $this->validation_error(__('Advanced Discount module is required for Group Discounts. Please enable it in Modules.', 'yatra'));
316 }
317
318 $id = $this->service->create($data);
319
320 return $this->success_response([
321 'id' => $id,
322 'message' => __('Discount created successfully', 'yatra'),
323 ], 201);
324 } catch (\InvalidArgumentException $e) {
325 return $this->validation_error($e->getMessage());
326 } catch (\Exception $e) {
327 return $this->error_response($e->getMessage(), 500);
328 }
329 }
330
331 public function update_item(WP_REST_Request $request)
332 {
333 try {
334 $data = $this->getBody($request);
335
336 // Check if Advanced Discount module is required for this discount type
337 $discount_mode = $data['discount_mode'] ?? 'promo';
338 $is_group_discount = !empty($data['is_group_discount']);
339
340 if (($discount_mode === 'group' || $discount_mode === 'both' || $is_group_discount)
341 && !apply_filters('yatra_advanced_discount_enabled', false)) {
342 return $this->validation_error(__('Advanced Discount module is required for Group Discounts. Please enable it in Modules.', 'yatra'));
343 }
344
345 $result = $this->service->update($this->getId($request), $data);
346
347 if (!$result) {
348 return $this->error_response(__('Failed to update discount', 'yatra'), 500);
349 }
350
351 return $this->success_response([
352 'message' => __('Discount updated successfully', 'yatra'),
353 ]);
354 } catch (\InvalidArgumentException $e) {
355 return $this->validation_error($e->getMessage());
356 } catch (\Exception $e) {
357 return $this->error_response($e->getMessage(), 500);
358 }
359 }
360
361 public function delete_item(WP_REST_Request $request)
362 {
363 try {
364 $result = $this->service->delete($this->getId($request));
365
366 if (!$result) {
367 return $this->error_response(__('Failed to delete discount', 'yatra'), 500);
368 }
369
370 return $this->success_response([
371 'message' => __('Discount deleted successfully', 'yatra'),
372 ]);
373 } catch (\Exception $e) {
374 return $this->error_response($e->getMessage(), 500);
375 }
376 }
377
378 private function prepareItem($item): array
379 {
380 $prepared = (array) $item;
381
382 if (isset($prepared['trip_ids']) && is_string($prepared['trip_ids'])) {
383 $prepared['trip_ids'] = maybe_unserialize($prepared['trip_ids']);
384 }
385
386 // Ensure trip_ids is always an array
387 if (!is_array($prepared['trip_ids'])) {
388 $prepared['trip_ids'] = [];
389 }
390
391 // Deserialize category_discounts if it's a JSON string
392 if (isset($prepared['category_discounts']) && is_string($prepared['category_discounts'])) {
393 $prepared['category_discounts'] = json_decode($prepared['category_discounts'], true) ?: [];
394 }
395
396 // Deserialize group_discount_ranges if it's a JSON string
397 if (isset($prepared['group_discount_ranges']) && is_string($prepared['group_discount_ranges'])) {
398 $prepared['group_discount_ranges'] = json_decode($prepared['group_discount_ranges'], true) ?: [];
399 }
400
401 $prepared['first_time_customer_only'] = (bool) ($prepared['first_time_customer_only'] ?? false);
402 $prepared['is_group_discount'] = (bool) ($prepared['is_group_discount'] ?? false);
403
404 if (!$prepared['is_group_discount']) {
405 $prepared['min_group_size'] = null;
406 $prepared['group_discount_type'] = null;
407 $prepared['group_discount_amount'] = null;
408 }
409
410 if (!empty($prepared['created_by'])) {
411 $user = get_userdata((int) $prepared['created_by']);
412 $prepared['created_by_name'] = $user ? esc_html($user->display_name) : null;
413 }
414
415 if (!empty($prepared['updated_by'])) {
416 $user = get_userdata((int) $prepared['updated_by']);
417 $prepared['updated_by_name'] = $user ? esc_html($user->display_name) : null;
418 }
419
420 // Calculate actual usage count from bookings
421 if (!empty($prepared['code'])) {
422 $discountRepository = new \Yatra\Repositories\DiscountRepository();
423 $prepared['usage_count'] = $discountRepository->countUsage($prepared['code']);
424 }
425
426 return $prepared;
427 }
428 }
429