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 / TripCategoryService.php

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

355 lines 11.3 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\TripCategoryRepository;
8 use Yatra\Helpers\SlugHelper;
9 use Yatra\Helpers\FormatHelper;
10
11 /**
12 * Trip Category Service
13 * Contains business logic for trip categories
14 */
15 class TripCategoryService extends BaseService
16 {
17 /**
18 * @var TripCategoryRepository
19 */
20 private TripCategoryRepository $repository;
21
22 /**
23 * Constructor
24 */
25 public function __construct()
26 {
27 $this->repository = new TripCategoryRepository();
28 }
29
30 /**
31 * Get repository
32 */
33 protected function getRepository(): TripCategoryRepository
34 {
35 return $this->repository;
36 }
37
38 /**
39 * Validate category data
40 */
41 protected function validate(array $data, ?int $id = null): void
42 {
43 if (empty($data['name'])) {
44 throw new \InvalidArgumentException('Category name is required');
45 }
46
47 // Validate parent_id if provided
48 if (isset($data['parent_id']) && !empty($data['parent_id'])) {
49 $parentId = (int) $data['parent_id'];
50
51 // Check if parent exists
52 $parent = $this->repository->find($parentId);
53 if (!$parent) {
54 throw new \InvalidArgumentException('Parent category not found');
55 }
56
57 // Prevent circular reference - check if trying to set parent to itself
58 if ($id && $parentId === $id) {
59 throw new \InvalidArgumentException('Category cannot be its own parent');
60 }
61
62 // Prevent setting parent to a subcategory (only top-level categories can be parents)
63 if ($parent->parent_id !== null) {
64 throw new \InvalidArgumentException('Parent category must be a top-level category');
65 }
66 }
67
68 // Validate status
69 $allowed_statuses = ['draft', 'publish', 'trash'];
70 if (isset($data['status']) && !in_array($data['status'], $allowed_statuses, true)) {
71 throw new \InvalidArgumentException('Invalid status. Must be one of: ' . implode(', ', $allowed_statuses));
72 }
73 }
74
75 /**
76 * Process before create
77 */
78 protected function processBeforeCreate(array $data): array
79 {
80 // Sanitize name
81 if (isset($data['name'])) {
82 $data['name'] = sanitize_text_field($data['name']);
83 }
84
85 // Always auto-generate slug from name (backend ensures uniqueness)
86 if (!empty($data['name'])) {
87 $data['slug'] = SlugHelper::generateUniqueFromDatabase(
88 $data['name'],
89 'yatra_trip_categories',
90 'slug'
91 );
92 } elseif (isset($data['slug'])) {
93 // If name is empty but slug is provided, sanitize it
94 $data['slug'] = SlugHelper::generate($data['slug']);
95 }
96
97 // Sanitize description (rich text)
98 if (isset($data['description'])) {
99 $data['description'] = FormatHelper::sanitizeQuillHtml($data['description']);
100 }
101
102 // Sanitize parent_id
103 if (isset($data['parent_id'])) {
104 $data['parent_id'] = !empty($data['parent_id']) ? absint($data['parent_id']) : null;
105 } else {
106 $data['parent_id'] = null;
107 }
108
109 // Sanitize status
110 if (isset($data['status'])) {
111 $allowed_statuses = ['draft', 'publish', 'trash'];
112 $data['status'] = in_array($data['status'], $allowed_statuses, true)
113 ? $data['status']
114 : 'draft';
115 } else {
116 $data['status'] = 'draft';
117 }
118
119 // Set created_by and updated_by to current user
120 $current_user_id = get_current_user_id();
121 $data['created_by'] = absint($current_user_id);
122 $data['updated_by'] = absint($current_user_id);
123
124 // Sanitize and serialize icon if it's an array
125 if (isset($data['icon'])) {
126 if (is_array($data['icon'])) {
127 // Sanitize icon array
128 $icon = [
129 'type' => isset($data['icon']['type']) && in_array($data['icon']['type'], ['icon', 'image'], true)
130 ? $data['icon']['type']
131 : 'icon',
132 'value' => isset($data['icon']['value'])
133 ? sanitize_text_field($data['icon']['value'])
134 : '',
135 ];
136 $data['icon'] = maybe_serialize($icon);
137 } elseif (is_string($data['icon'])) {
138 // If it's already a string, sanitize it
139 $data['icon'] = sanitize_text_field($data['icon']);
140 }
141 }
142
143 return $data;
144 }
145
146 /**
147 * Process before update
148 */
149 protected function processBeforeUpdate(int $id, array $data): array
150 {
151 // Sanitize name
152 if (isset($data['name'])) {
153 $data['name'] = sanitize_text_field($data['name']);
154 }
155
156 // Handle slug: preserve manually edited slug, otherwise auto-generate from name
157 $preserveSlug = isset($data['preserve_slug']) && $data['preserve_slug'] === true;
158 unset($data['preserve_slug']); // Remove flag from data array
159
160 if ($preserveSlug && isset($data['slug']) && !empty($data['slug'])) {
161 // Slug was manually edited - preserve it but ensure uniqueness
162 $data['slug'] = SlugHelper::generateUniqueFromDatabase(
163 $data['slug'],
164 'yatra_trip_categories',
165 'slug',
166 $id // Exclude current record when checking uniqueness
167 );
168 } elseif (!empty($data['name'])) {
169 // Auto-generate slug from name if name is provided and slug not manually edited
170 $data['slug'] = SlugHelper::generateUniqueFromDatabase(
171 $data['name'],
172 'yatra_trip_categories',
173 'slug',
174 $id // Exclude current record when checking uniqueness
175 );
176 } elseif (isset($data['slug'])) {
177 // If name is not provided but slug is, sanitize the slug
178 $data['slug'] = SlugHelper::generate($data['slug']);
179 }
180
181 // Sanitize description (rich text)
182 if (isset($data['description'])) {
183 $data['description'] = FormatHelper::sanitizeQuillHtml($data['description']);
184 }
185
186 // Sanitize parent_id
187 if (isset($data['parent_id'])) {
188 $data['parent_id'] = !empty($data['parent_id']) ? absint($data['parent_id']) : null;
189 }
190
191 // Sanitize status
192 if (isset($data['status'])) {
193 $allowed_statuses = ['draft', 'publish', 'trash'];
194 $data['status'] = in_array($data['status'], $allowed_statuses, true)
195 ? $data['status']
196 : 'draft';
197 }
198
199 // Set updated_by to current user
200 $data['updated_by'] = absint(get_current_user_id());
201
202 // Sanitize and serialize icon if it's an array
203 if (isset($data['icon'])) {
204 if (is_array($data['icon'])) {
205 // Sanitize icon array
206 $icon = [
207 'type' => isset($data['icon']['type']) && in_array($data['icon']['type'], ['icon', 'image'], true)
208 ? $data['icon']['type']
209 : 'icon',
210 'value' => isset($data['icon']['value'])
211 ? sanitize_text_field($data['icon']['value'])
212 : '',
213 ];
214 $data['icon'] = maybe_serialize($icon);
215 } elseif (is_string($data['icon'])) {
216 // If it's already a string, sanitize it
217 $data['icon'] = sanitize_text_field($data['icon']);
218 }
219 }
220
221 return $data;
222 }
223
224 /**
225 * Get all items with search and filters
226 */
227 public function getAll(array $args = []): array
228 {
229 // Sanitize and handle search
230 if (!empty($args['search'])) {
231 $search = sanitize_text_field($args['search']);
232 return $this->repository->search($search, $args);
233 }
234
235 // Sanitize and handle status filter
236 if (!empty($args['status']) && $args['status'] !== 'all') {
237 $allowed_statuses = ['draft', 'publish', 'trash'];
238 $status = in_array($args['status'], $allowed_statuses, true)
239 ? $args['status']
240 : null;
241 if ($status) {
242 $args['where']['status'] = $status;
243 }
244 }
245
246 return $this->repository->all($args);
247 }
248
249 /**
250 * Get hierarchical categories (with subcategories)
251 */
252 public function getHierarchical(array $args = []): array
253 {
254 return $this->repository->getHierarchical($args);
255 }
256
257 /**
258 * Get top-level categories only
259 */
260 public function getTopLevel(array $args = []): array
261 {
262 return $this->repository->getTopLevel($args);
263 }
264
265 /**
266 * Get subcategories by parent ID
267 */
268 public function getSubcategories(int $parentId, array $args = []): array
269 {
270 return $this->repository->getSubcategories($parentId, $args);
271 }
272
273 /**
274 * Get category with subcategories
275 */
276 public function getWithSubcategories(int $id): ?\stdClass
277 {
278 return $this->repository->getWithSubcategories($id);
279 }
280
281 /**
282 * Get published categories
283 */
284 public function getPublished(array $args = []): array
285 {
286 return $this->repository->getPublished($args);
287 }
288
289 /**
290 * Get published categories with trip counts, ratings, and pricing stats
291 */
292 public function getPublishedWithStats(): array
293 {
294 return $this->repository->getPublishedWithTripCounts();
295 }
296
297 /**
298 * Count items
299 */
300 public function count(array $args = []): int
301 {
302 // Sanitize and handle search
303 if (!empty($args['search'])) {
304 $search = sanitize_text_field($args['search']);
305 $items = $this->repository->search($search, $args);
306 return count($items);
307 }
308
309 // Sanitize and handle status filter
310 if (!empty($args['status']) && $args['status'] !== 'all') {
311 $allowed_statuses = ['draft', 'publish', 'trash'];
312 $status = in_array($args['status'], $allowed_statuses, true)
313 ? $args['status']
314 : null;
315 if ($status) {
316 $args['where']['status'] = $status;
317 }
318 }
319
320 return $this->repository->count($args);
321 }
322
323 /**
324 * Delete category (with validation)
325 */
326 public function delete(int $id): bool
327 {
328 // Check if category can be deleted
329 if (!$this->repository->canDelete($id)) {
330 throw new \InvalidArgumentException('Cannot delete category: it has subcategories or is in use');
331 }
332
333 return parent::delete($id);
334 }
335
336 /**
337 * Get trip count for a trip category
338 */
339 public function getTripCount(int $categoryId): int
340 {
341 $tripCategoryRepository = new \Yatra\Repositories\TripCategoryRepository();
342 return $tripCategoryRepository->getTripCount($categoryId);
343 }
344
345 /**
346 * Get trip count for trip category (direct field method)
347 */
348 public function getTripCountDirect(int $categoryId): int
349 {
350 $tripCategoryRepository = new \Yatra\Repositories\TripCategoryRepository();
351 return $tripCategoryRepository->getTripCountDirect($categoryId);
352 }
353 }
354
355