PluginProbe
Yatra – Travel Booking & Tour Operator Software / 3.0.2.6
Yatra – Travel Booking & Tour Operator Software v3.0.2.6
3.0.16 3.0.15 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 All 84 releases
yatra / app / Services / TravelerCategoryService.php

TravelerCategoryService.php in Yatra – Travel Booking & Tour Operator Software 3.0.2.6, at app/Services/TravelerCategoryService.php

453 lines 15.7 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\Services;
6
7 use Yatra\Repositories\TravelerCategoryRepository;
8 use Yatra\Helpers\SlugHelper;
9
10 /**
11 * Traveler Category Service
12 * Contains business logic for traveler categories
13 */
14 class TravelerCategoryService extends BaseService
15 {
16 /**
17 * @var TravelerCategoryRepository
18 */
19 private TravelerCategoryRepository $repository;
20
21 /**
22 * Constructor
23 */
24 public function __construct()
25 {
26 $this->repository = new TravelerCategoryRepository();
27 }
28
29 /**
30 * Get repository
31 */
32 protected function getRepository(): TravelerCategoryRepository
33 {
34 return $this->repository;
35 }
36
37 /**
38 * Validate data for creation with field mapping
39 */
40 public function validateCreate(array $data): void
41 {
42 // Handle field mapping for backward compatibility BEFORE validation
43 if (isset($data['label']) && !isset($data['name'])) {
44 $data['name'] = $data['label'];
45 // Remove the old label field to prevent database errors
46 unset($data['label']);
47 }
48
49 // Call parent validation with mapped data
50 parent::validateCreate($data);
51 }
52
53 /**
54 * Validate data for update with field mapping
55 */
56 public function validateUpdate(int $id, array $data): void
57 {
58 // Handle field mapping for backward compatibility BEFORE validation
59 if (isset($data['label']) && !isset($data['name'])) {
60 $data['name'] = $data['label'];
61 // Remove the old label field to prevent database errors
62 unset($data['label']);
63 }
64
65 // Call parent validation with mapped data
66 parent::validateUpdate($id, $data);
67 }
68
69 /**
70 * Validate traveler category data
71 */
72 protected function validate(array $data, ?int $id = null): void
73 {
74 if (empty($data['name'])) {
75 throw new \InvalidArgumentException('Category name is required');
76 }
77
78 // Slug will be auto-generated from name, so we don't need to validate it here
79 // The SlugHelper will ensure uniqueness
80
81 // Validate status
82 $allowed_statuses = ['draft', 'publish', 'trash'];
83 if (isset($data['status']) && !in_array($data['status'], $allowed_statuses, true)) {
84 throw new \InvalidArgumentException('Invalid status. Must be one of: ' . implode(', ', $allowed_statuses));
85 }
86
87 // Validate age range
88 if (isset($data['age_min']) && $data['age_min'] !== '' && $data['age_min'] !== null) {
89 $age_min = (int) $data['age_min'];
90 if ($age_min < 0) {
91 throw new \InvalidArgumentException('Minimum age cannot be negative');
92 }
93 }
94
95 if (isset($data['age_max']) && $data['age_max'] !== '' && $data['age_max'] !== null) {
96 $age_max = (int) $data['age_max'];
97 if ($age_max < 0) {
98 throw new \InvalidArgumentException('Maximum age cannot be negative');
99 }
100 }
101
102 // Validate age range logic
103 if (isset($data['age_min']) && isset($data['age_max'])
104 && $data['age_min'] !== '' && $data['age_max'] !== ''
105 && $data['age_min'] !== null && $data['age_max'] !== null) {
106 $age_min = (int) $data['age_min'];
107 $age_max = (int) $data['age_max'];
108 if ($age_min >= $age_max) {
109 throw new \InvalidArgumentException('Maximum age must be greater than minimum age');
110 }
111 }
112
113 // Validate pricing mode
114 if (isset($data['pricing_mode']) && $data['pricing_mode'] !== '') {
115 $allowed_pricing_modes = ['per_person', 'per_group'];
116 if (!in_array($data['pricing_mode'], $allowed_pricing_modes, true)) {
117 throw new \InvalidArgumentException('Invalid pricing mode. Must be per_person or per_group');
118 }
119 }
120
121 // Validate group size when pricing per group
122 if (($data['pricing_mode'] ?? 'per_person') === 'per_group') {
123 if (isset($data['min_pax']) && $data['min_pax'] !== '' && $data['min_pax'] !== null) {
124 $min_pax = (int) $data['min_pax'];
125 if ($min_pax <= 0) {
126 throw new \InvalidArgumentException('Minimum group size must be greater than zero');
127 }
128 }
129
130 if (isset($data['max_pax']) && $data['max_pax'] !== '' && $data['max_pax'] !== null) {
131 $max_pax = (int) $data['max_pax'];
132 if ($max_pax <= 0) {
133 throw new \InvalidArgumentException('Maximum group size must be greater than zero');
134 }
135 }
136
137 if (
138 isset($data['min_pax'], $data['max_pax']) &&
139 $data['min_pax'] !== '' && $data['max_pax'] !== '' &&
140 $data['min_pax'] !== null && $data['max_pax'] !== null
141 ) {
142 $min_pax = (int) $data['min_pax'];
143 $max_pax = (int) $data['max_pax'];
144 if ($min_pax > $max_pax) {
145 throw new \InvalidArgumentException('Maximum group size must be greater than or equal to minimum group size');
146 }
147 }
148 }
149 }
150
151 /**
152 * Process before create
153 */
154 protected function processBeforeCreate(array $data): array
155 {
156 // Handle field mapping for backward compatibility
157 if (isset($data['label']) && !isset($data['name'])) {
158 $data['name'] = $data['label'];
159 // Remove the old label field to prevent database errors
160 unset($data['label']);
161 }
162
163 // Sanitize name
164 if (isset($data['name'])) {
165 $data['name'] = sanitize_text_field($data['name']);
166 }
167
168 // Always auto-generate slug from name (backend ensures uniqueness)
169 if (!empty($data['name'])) {
170 $data['slug'] = SlugHelper::generateUniqueFromDatabase(
171 $data['name'],
172 'yatra_new_classifications',
173 'slug'
174 );
175 } elseif (isset($data['slug'])) {
176 // If name is empty but slug is provided, sanitize it
177 $data['slug'] = SlugHelper::generate($data['slug']);
178 }
179
180 // Sanitize description
181 if (isset($data['description'])) {
182 $data['description'] = sanitize_textarea_field($data['description']);
183 }
184
185 // Sanitize status
186 if (isset($data['status'])) {
187 $allowed_statuses = ['draft', 'publish', 'trash'];
188 $data['status'] = in_array($data['status'], $allowed_statuses, true)
189 ? $data['status']
190 : 'draft';
191 }
192
193 // Prepare metadata for traveler type
194 $metadata = [];
195
196 if (isset($data['age_min']) && $data['age_min'] !== '' && $data['age_min'] !== null) {
197 $metadata['age_min'] = (int) $data['age_min'];
198 }
199
200 if (isset($data['age_max']) && $data['age_max'] !== '' && $data['age_max'] !== null) {
201 $metadata['age_max'] = (int) $data['age_max'];
202 }
203
204 if (isset($data['pricing_mode'])) {
205 $metadata['pricing_mode'] = sanitize_text_field($data['pricing_mode']);
206 }
207
208 if (isset($data['min_pax']) && $data['min_pax'] !== '' && $data['min_pax'] !== null) {
209 $metadata['min_pax'] = (int) $data['min_pax'];
210 }
211
212 if (isset($data['max_pax']) && $data['max_pax'] !== '' && $data['max_pax'] !== null) {
213 $metadata['max_pax'] = (int) $data['max_pax'];
214 }
215
216 // Set the type for traveler categories
217 $data['type'] = 'traveler_type';
218
219 // Store metadata as JSON
220 $data['metadata'] = wp_json_encode($metadata);
221
222 // Remove old field names that don't exist in ClassificationsTable
223 unset($data['age_min'], $data['age_max'], $data['pricing_mode'], $data['min_pax'], $data['max_pax']);
224
225 // Set created_by and updated_by to current user
226 $current_user_id = get_current_user_id();
227 $data['created_by'] = absint($current_user_id);
228 $data['updated_by'] = absint($current_user_id);
229
230 // Sanitize and serialize icon if it's an array
231 if (isset($data['icon'])) {
232 if (is_array($data['icon'])) {
233 // Sanitize icon array
234 $icon = [
235 'type' => isset($data['icon']['type']) && in_array($data['icon']['type'], ['icon', 'image'], true)
236 ? $data['icon']['type']
237 : 'icon',
238 'value' => isset($data['icon']['value'])
239 ? sanitize_text_field($data['icon']['value'])
240 : '',
241 ];
242 $data['icon'] = maybe_serialize($icon);
243 } elseif (is_string($data['icon'])) {
244 // If it's already a string, sanitize it
245 $data['icon'] = sanitize_text_field($data['icon']);
246 }
247 }
248
249 return $data;
250 }
251
252 /**
253 * Process before update
254 */
255 protected function processBeforeUpdate(int $id, array $data): array
256 {
257 // Handle field mapping for backward compatibility
258 if (isset($data['label']) && !isset($data['name'])) {
259 $data['name'] = $data['label'];
260 // Remove the old label field to prevent database errors
261 unset($data['label']);
262 }
263
264 // Sanitize name
265 if (isset($data['name'])) {
266 $data['name'] = sanitize_text_field($data['name']);
267 }
268
269 // Handle slug: preserve manually edited slug, otherwise auto-generate from name
270 $preserveSlug = isset($data['preserve_slug']) && $data['preserve_slug'] === true;
271 unset($data['preserve_slug']); // Remove flag from data array
272
273 if ($preserveSlug && isset($data['slug']) && !empty($data['slug'])) {
274 // Slug was manually edited - preserve it but ensure uniqueness
275 $data['slug'] = SlugHelper::generateUniqueFromDatabase(
276 $data['slug'],
277 'yatra_new_classifications',
278 'slug',
279 $id // Exclude current record when checking uniqueness
280 );
281 } elseif (!empty($data['name'])) {
282 // Auto-generate slug from name if name is provided and slug not manually edited
283 $data['slug'] = SlugHelper::generateUniqueFromDatabase(
284 $data['name'],
285 'yatra_new_classifications',
286 'slug',
287 $id // Exclude current record when checking uniqueness
288 );
289 } elseif (isset($data['slug'])) {
290 // If name is not provided but slug is, sanitize the slug
291 $data['slug'] = SlugHelper::generate($data['slug']);
292 }
293
294 // Sanitize description
295 if (isset($data['description'])) {
296 $data['description'] = sanitize_textarea_field($data['description']);
297 }
298
299 // Sanitize status
300 if (isset($data['status'])) {
301 $allowed_statuses = ['draft', 'publish', 'trash'];
302 $data['status'] = in_array($data['status'], $allowed_statuses, true)
303 ? $data['status']
304 : 'draft';
305 }
306
307 // Get existing metadata to merge with new data
308 $existing = $this->repository->find($id);
309 $metadata = [];
310 if ($existing && !empty($existing->metadata)) {
311 $metadata = json_decode($existing->metadata, true) ?: [];
312 }
313
314 // Update metadata fields
315 if (isset($data['age_min'])) {
316 if ($data['age_min'] === '' || $data['age_min'] === null) {
317 unset($metadata['age_min']);
318 } else {
319 $metadata['age_min'] = absint($data['age_min']);
320 }
321 }
322
323 if (isset($data['age_max'])) {
324 if ($data['age_max'] === '' || $data['age_max'] === null) {
325 unset($metadata['age_max']);
326 } else {
327 $metadata['age_max'] = absint($data['age_max']);
328 }
329 }
330
331 if (isset($data['pricing_mode'])) {
332 $allowed_pricing_modes = ['per_person', 'per_group'];
333 if (in_array($data['pricing_mode'], $allowed_pricing_modes, true)) {
334 $metadata['pricing_mode'] = $data['pricing_mode'];
335 } else {
336 $metadata['pricing_mode'] = 'per_person';
337 }
338 }
339
340 if (isset($data['min_pax'])) {
341 if ($data['min_pax'] === '' || $data['min_pax'] === null) {
342 unset($metadata['min_pax']);
343 } else {
344 $metadata['min_pax'] = absint($data['min_pax']);
345 }
346 }
347
348 if (isset($data['max_pax'])) {
349 if ($data['max_pax'] === '' || $data['max_pax'] === null) {
350 unset($metadata['max_pax']);
351 } else {
352 $metadata['max_pax'] = absint($data['max_pax']);
353 }
354 }
355
356 // Store updated metadata as JSON
357 $data['metadata'] = wp_json_encode($metadata);
358
359 // Remove old field names that don't exist in ClassificationsTable
360 unset($data['age_min'], $data['age_max'], $data['pricing_mode'], $data['min_pax'], $data['max_pax']);
361
362 // Set updated_by to current user
363 $data['updated_by'] = absint(get_current_user_id());
364
365 return $data;
366 }
367
368 /**
369 * Get status counts for admin list views
370 *
371 * Provides stable counts for All / Published / Draft / Trash that
372 * do not change when filters (status/search) are applied in the UI.
373 */
374 public function getStatusCounts(): array
375 {
376 // All statuses combined (no status filter)
377 $all = $this->count([]);
378
379 // Individual statuses
380 $publish = $this->count(['status' => 'publish']);
381 $draft = $this->count(['status' => 'draft']);
382 $trash = $this->count(['status' => 'trash']);
383
384 return [
385 'all' => (int) $all,
386 'publish' => (int) $publish,
387 'draft' => (int) $draft,
388 'trash' => (int) $trash,
389 ];
390 }
391
392 /**
393 * Get all items with search and filters
394 */
395 public function getAll(array $args = []): array
396 {
397 // Sanitize and handle search
398 if (!empty($args['search'])) {
399 $search = sanitize_text_field($args['search']);
400 return $this->repository->search($search, $args);
401 }
402
403 // Sanitize and handle status filter
404 if (!empty($args['status']) && $args['status'] !== 'all') {
405 $allowed_statuses = ['draft', 'publish', 'trash'];
406 $status = in_array($args['status'], $allowed_statuses, true)
407 ? $args['status']
408 : null;
409 if ($status) {
410 $args['where']['status'] = $status;
411 }
412 }
413
414 return $this->repository->all($args);
415 }
416
417 /**
418 * Get published categories
419 */
420 public function getPublished(array $args = []): array
421 {
422 $args['where']['status'] = 'publish';
423 return $this->repository->all($args);
424 }
425
426 /**
427 * Count items
428 */
429 public function count(array $args = []): int
430 {
431 // Sanitize and handle search
432 if (!empty($args['search'])) {
433 $search = sanitize_text_field($args['search']);
434 $items = $this->repository->search($search, $args);
435 return count($items);
436 }
437
438 // Sanitize and handle status filter
439 if (!empty($args['status']) && $args['status'] !== 'all') {
440 $allowed_statuses = ['draft', 'publish', 'trash'];
441 $status = in_array($args['status'], $allowed_statuses, true)
442 ? $args['status']
443 : null;
444 if ($status) {
445 $args['where']['status'] = $status;
446 }
447 }
448
449 return $this->repository->count($args);
450 }
451 }
452
453