PluginProbe
Yatra – Travel Booking & Tour Operator Software / 3.0.14.2
Yatra – Travel Booking & Tour Operator Software v3.0.14.2
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 2.0.10 All 83 releases
yatra / app / Services / TripCategoryService.php

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

339 lines 10.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\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 $data['icon'] = yatra_normalize_icon_picker_for_storage($data['icon']);
128 $data['icon'] = maybe_serialize($data['icon']);
129 } elseif (is_string($data['icon'])) {
130 // If it's already a string, sanitize it
131 $data['icon'] = sanitize_text_field($data['icon']);
132 }
133 }
134
135 return $data;
136 }
137
138 /**
139 * Process before update
140 */
141 protected function processBeforeUpdate(int $id, array $data): array
142 {
143 // Sanitize name
144 if (isset($data['name'])) {
145 $data['name'] = sanitize_text_field($data['name']);
146 }
147
148 // Handle slug: preserve manually edited slug, otherwise auto-generate from name
149 $preserveSlug = isset($data['preserve_slug']) && $data['preserve_slug'] === true;
150 unset($data['preserve_slug']); // Remove flag from data array
151
152 if ($preserveSlug && isset($data['slug']) && !empty($data['slug'])) {
153 // Slug was manually edited - preserve it but ensure uniqueness
154 $data['slug'] = SlugHelper::generateUniqueFromDatabase(
155 $data['slug'],
156 'yatra_trip_categories',
157 'slug',
158 $id // Exclude current record when checking uniqueness
159 );
160 } elseif (!empty($data['name'])) {
161 // Auto-generate slug from name if name is provided and slug not manually edited
162 $data['slug'] = SlugHelper::generateUniqueFromDatabase(
163 $data['name'],
164 'yatra_trip_categories',
165 'slug',
166 $id // Exclude current record when checking uniqueness
167 );
168 } elseif (isset($data['slug'])) {
169 // If name is not provided but slug is, sanitize the slug
170 $data['slug'] = SlugHelper::generate($data['slug']);
171 }
172
173 // Sanitize description (rich text)
174 if (isset($data['description'])) {
175 $data['description'] = FormatHelper::sanitizeQuillHtml($data['description']);
176 }
177
178 // Sanitize parent_id
179 if (isset($data['parent_id'])) {
180 $data['parent_id'] = !empty($data['parent_id']) ? absint($data['parent_id']) : null;
181 }
182
183 // Sanitize status
184 if (isset($data['status'])) {
185 $allowed_statuses = ['draft', 'publish', 'trash'];
186 $data['status'] = in_array($data['status'], $allowed_statuses, true)
187 ? $data['status']
188 : 'draft';
189 }
190
191 // Set updated_by to current user
192 $data['updated_by'] = absint(get_current_user_id());
193
194 // Sanitize and serialize icon if it's an array
195 if (isset($data['icon'])) {
196 if (is_array($data['icon'])) {
197 $data['icon'] = yatra_normalize_icon_picker_for_storage($data['icon']);
198 $data['icon'] = maybe_serialize($data['icon']);
199 } elseif (is_string($data['icon'])) {
200 // If it's already a string, sanitize it
201 $data['icon'] = sanitize_text_field($data['icon']);
202 }
203 }
204
205 return $data;
206 }
207
208 /**
209 * Get all items with search and filters
210 */
211 public function getAll(array $args = []): array
212 {
213 // Sanitize and handle search
214 if (!empty($args['search'])) {
215 $search = sanitize_text_field($args['search']);
216 return $this->repository->search($search, $args);
217 }
218
219 // Sanitize and handle status filter
220 if (!empty($args['status']) && $args['status'] !== 'all') {
221 $allowed_statuses = ['draft', 'publish', 'trash'];
222 $status = in_array($args['status'], $allowed_statuses, true)
223 ? $args['status']
224 : null;
225 if ($status) {
226 $args['where']['status'] = $status;
227 }
228 }
229
230 return $this->repository->all($args);
231 }
232
233 /**
234 * Get hierarchical categories (with subcategories)
235 */
236 public function getHierarchical(array $args = []): array
237 {
238 return $this->repository->getHierarchical($args);
239 }
240
241 /**
242 * Get top-level categories only
243 */
244 public function getTopLevel(array $args = []): array
245 {
246 return $this->repository->getTopLevel($args);
247 }
248
249 /**
250 * Get subcategories by parent ID
251 */
252 public function getSubcategories(int $parentId, array $args = []): array
253 {
254 return $this->repository->getSubcategories($parentId, $args);
255 }
256
257 /**
258 * Get category with subcategories
259 */
260 public function getWithSubcategories(int $id): ?\stdClass
261 {
262 return $this->repository->getWithSubcategories($id);
263 }
264
265 /**
266 * Get published categories
267 */
268 public function getPublished(array $args = []): array
269 {
270 return $this->repository->getPublished($args);
271 }
272
273 /**
274 * Get published categories with trip counts, ratings, and pricing stats
275 */
276 public function getPublishedWithStats(): array
277 {
278 return $this->repository->getPublishedWithTripCounts();
279 }
280
281 /**
282 * Count items
283 */
284 public function count(array $args = []): int
285 {
286 // Sanitize and handle search
287 if (!empty($args['search'])) {
288 $search = sanitize_text_field($args['search']);
289 $items = $this->repository->search($search, $args);
290 return count($items);
291 }
292
293 // Sanitize and handle status filter
294 if (!empty($args['status']) && $args['status'] !== 'all') {
295 $allowed_statuses = ['draft', 'publish', 'trash'];
296 $status = in_array($args['status'], $allowed_statuses, true)
297 ? $args['status']
298 : null;
299 if ($status) {
300 $args['where']['status'] = $status;
301 }
302 }
303
304 return $this->repository->count($args);
305 }
306
307 /**
308 * Delete category (with validation)
309 */
310 public function delete(int $id): bool
311 {
312 // Check if category can be deleted
313 if (!$this->repository->canDelete($id)) {
314 throw new \InvalidArgumentException('Cannot delete category: it has subcategories or is in use');
315 }
316
317 return parent::delete($id);
318 }
319
320 /**
321 * Get trip count for a trip category
322 */
323 public function getTripCount(int $categoryId): int
324 {
325 $tripCategoryRepository = new \Yatra\Repositories\TripCategoryRepository();
326 return $tripCategoryRepository->getTripCount($categoryId);
327 }
328
329 /**
330 * Get trip count for trip category (direct field method)
331 */
332 public function getTripCountDirect(int $categoryId): int
333 {
334 $tripCategoryRepository = new \Yatra\Repositories\TripCategoryRepository();
335 return $tripCategoryRepository->getTripCountDirect($categoryId);
336 }
337 }
338
339