PluginProbe
Yatra – Travel Booking & Tour Operator Software / 3.0.2.7
Yatra – Travel Booking & Tour Operator Software v3.0.2.7
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 / DestinationService.php

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

429 lines 14.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\DestinationRepository;
8 use Yatra\Helpers\SlugHelper;
9 use Yatra\Helpers\FormatHelper;
10 use Yatra\Database\Tables\ClassificationsTable;
11
12 /**
13 * Destination Service
14 * Contains business logic for destinations
15 */
16 class DestinationService extends BaseService
17 {
18 /**
19 * @var DestinationRepository
20 */
21 private DestinationRepository $repository;
22
23 /**
24 * Constructor
25 */
26 public function __construct()
27 {
28 $this->repository = new DestinationRepository();
29 }
30
31 /**
32 * Get repository
33 */
34 protected function getRepository(): DestinationRepository
35 {
36 return $this->repository;
37 }
38
39 /**
40 * Validate destination data
41 */
42 protected function validate(array $data, ?int $id = null): void
43 {
44 if (empty($data['name'])) {
45 throw new \InvalidArgumentException('Destination name is required');
46 }
47
48 // Slug will be auto-generated from name, so we don't need to validate it here
49 // The SlugHelper will ensure uniqueness
50
51 // Validate status - accept both old and new values for backward compatibility
52 $allowed_statuses = ['draft', 'active', 'inactive', 'publish', 'trash'];
53 if (isset($data['status']) && !in_array($data['status'], $allowed_statuses, true)) {
54 throw new \InvalidArgumentException('Invalid status. Must be one of: ' . implode(', ', $allowed_statuses));
55 }
56 }
57
58 /**
59 * Process before create
60 */
61 protected function processBeforeCreate(array $data): array
62 {
63 // Sanitize name
64 if (isset($data['name'])) {
65 $data['name'] = sanitize_text_field($data['name']);
66 }
67
68 // Always auto-generate slug from name (backend ensures uniqueness)
69 if (!empty($data['name'])) {
70 $data['slug'] = SlugHelper::generateUniqueFromDatabase(
71 $data['name'],
72 ClassificationsTable::getTableName(),
73 'slug'
74 );
75 } elseif (isset($data['slug'])) {
76 // If name is empty but slug is provided, sanitize it
77 $data['slug'] = SlugHelper::generate($data['slug']);
78 }
79
80 // Sanitize Quill HTML description
81 if (isset($data['description'])) {
82 $data['description'] = FormatHelper::sanitizeQuillHtml($data['description']);
83 }
84
85 // Sanitize status
86 if (isset($data['status'])) {
87 // Validate status
88 $allowed_statuses = ['draft', 'publish', 'trash'];
89 $data['status'] = in_array($data['status'], $allowed_statuses, true)
90 ? $data['status']
91 : 'draft';
92 } else {
93 $data['status'] = 'draft';
94 }
95
96 // Set created_by and updated_by to current user
97 $current_user_id = get_current_user_id();
98 $data['created_by'] = absint($current_user_id);
99 $data['updated_by'] = absint($current_user_id);
100
101 // Sanitize and serialize icon if it's an array
102 if (isset($data['icon'])) {
103 if (is_array($data['icon'])) {
104 // Convert URL back to attachment ID if possible
105 if ($data['icon']['type'] === 'image' && !empty($data['icon']['value'])) {
106 $value = $data['icon']['value'];
107
108 // If it's a URL, try to find the attachment ID
109 if (filter_var($value, FILTER_VALIDATE_URL)) {
110 $attachment_id = attachment_url_to_postid($value);
111 if ($attachment_id) {
112 $data['icon']['value'] = $attachment_id;
113 }
114 }
115 // If it's already numeric, keep it as is
116 elseif (is_numeric($value)) {
117 $data['icon']['value'] = (int) $value;
118 }
119 }
120
121 $data['icon'] = maybe_serialize($data['icon']);
122 } elseif (is_string($data['icon'])) {
123 // If it's already a string, sanitize it
124 $data['icon'] = sanitize_text_field($data['icon']);
125 }
126 }
127
128 // IMPORTANT: Force type to 'destination' - this overrides any frontend type
129 $data['type'] = 'destination';
130
131 // Handle SEO metadata
132 if (isset($data['seo_title']) || isset($data['seo_description']) || isset($data['seo_keywords'])) {
133 $existing_metadata = [];
134 if (isset($data['metadata']) && is_array($data['metadata'])) {
135 $existing_metadata = $data['metadata'];
136 } elseif (isset($data['metadata'])) {
137 $existing_metadata = maybe_unserialize($data['metadata']);
138 }
139
140 // Add SEO fields to metadata
141 if (isset($data['seo_title'])) {
142 $existing_metadata['seo_title'] = sanitize_text_field($data['seo_title']);
143 unset($data['seo_title']); // Remove from main data
144 }
145 if (isset($data['seo_description'])) {
146 $existing_metadata['seo_description'] = sanitize_textarea_field($data['seo_description']);
147 unset($data['seo_description']); // Remove from main data
148 }
149 if (isset($data['seo_keywords'])) {
150 $existing_metadata['seo_keywords'] = sanitize_text_field($data['seo_keywords']);
151 unset($data['seo_keywords']); // Remove from main data
152 }
153
154 $data['metadata'] = $existing_metadata;
155 }
156
157 return $data;
158 }
159
160 /**
161 * Process before update
162 */
163 protected function processBeforeUpdate(int $id, array $data): array
164 {
165
166 // Remove preserve_slug flag if sent from frontend (not a database column)
167 unset($data['preserve_slug']);
168
169 // Sanitize name
170 if (isset($data['name'])) {
171 $data['name'] = sanitize_text_field($data['name']);
172 }
173
174 // Handle slug in EDIT mode: Only update if explicitly provided
175 // Do NOT auto-generate from name in edit mode
176 if (isset($data['slug']) && !empty($data['slug'])) {
177 // Slug was explicitly provided - ensure uniqueness
178 $data['slug'] = SlugHelper::generateUniqueFromDatabase(
179 $data['slug'],
180 ClassificationsTable::getTableName(),
181 'slug',
182 $id // Exclude current record when checking uniqueness
183 );
184 }
185 // If slug is not provided, don't modify it (keep existing slug)
186
187 // Sanitize Quill HTML description
188 if (isset($data['description'])) {
189 $data['description'] = FormatHelper::sanitizeQuillHtml($data['description']);
190 }
191
192 // Sanitize status
193 if (isset($data['status'])) {
194 // Validate status
195 $allowed_statuses = ['draft', 'publish', 'trash'];
196 $data['status'] = in_array($data['status'], $allowed_statuses, true)
197 ? $data['status']
198 : 'draft';
199 }
200
201 // Set updated_by to current user
202 $data['updated_by'] = absint(get_current_user_id());
203
204 // Sanitize and serialize icon if it's an array
205 if (isset($data['icon'])) {
206 if (is_array($data['icon'])) {
207 // Convert URL back to attachment ID if possible
208 if ($data['icon']['type'] === 'image' && !empty($data['icon']['value'])) {
209 $value = $data['icon']['value'];
210
211 // If it's a URL, try to find the attachment ID
212 if (filter_var($value, FILTER_VALIDATE_URL)) {
213 $attachment_id = attachment_url_to_postid($value);
214 if ($attachment_id) {
215 $data['icon']['value'] = $attachment_id;
216 }
217 }
218 // If it's already numeric, keep it as is
219 elseif (is_numeric($value)) {
220 $data['icon']['value'] = (int) $value;
221 }
222 }
223
224 $data['icon'] = maybe_serialize($data['icon']);
225 } elseif (is_string($data['icon'])) {
226 // If it's already a string, sanitize it
227 $data['icon'] = sanitize_text_field($data['icon']);
228 }
229 }
230
231 // IMPORTANT: Ensure type remains 'destination' - prevent frontend from changing type
232 $data['type'] = 'destination';
233
234 // Handle SEO metadata
235 if (isset($data['seo_title']) || isset($data['seo_description']) || isset($data['seo_keywords'])) {
236 // Get existing metadata
237 $existing_metadata = [];
238 if (isset($data['metadata']) && is_array($data['metadata'])) {
239 $existing_metadata = $data['metadata'];
240 } elseif (isset($data['metadata'])) {
241 $existing_metadata = maybe_unserialize($data['metadata']);
242 } else {
243 // Get existing metadata from database
244 $existing = $this->repository->find($id);
245 if ($existing && isset($existing->metadata)) {
246 $existing_metadata = maybe_unserialize($existing->metadata);
247 }
248 }
249
250 // Add/update SEO fields in metadata
251 if (isset($data['seo_title'])) {
252 $existing_metadata['seo_title'] = sanitize_text_field($data['seo_title']);
253 unset($data['seo_title']); // Remove from main data
254 }
255 if (isset($data['seo_description'])) {
256 $existing_metadata['seo_description'] = sanitize_textarea_field($data['seo_description']);
257 unset($data['seo_description']); // Remove from main data
258 }
259 if (isset($data['seo_keywords'])) {
260 $existing_metadata['seo_keywords'] = sanitize_text_field($data['seo_keywords']);
261 unset($data['seo_keywords']); // Remove from main data
262 }
263
264 $data['metadata'] = $existing_metadata;
265 }
266
267 return $data;
268 }
269
270 /**
271 * Get status counts for destinations
272 */
273 public function getAll(array $args = []): array
274 {
275 // IMPORTANT: Always filter by type = 'destination' for destinations
276 $args['where']['type'] = 'destination';
277
278 // Sanitize and handle search
279 if (!empty($args['search'])) {
280 $search = sanitize_text_field($args['search']);
281 return $this->repository->search($search, $args);
282 }
283
284 // Sanitize and handle status filter
285 if (!empty($args['status']) && $args['status'] !== 'all') {
286 $allowed_statuses = ['draft', 'publish', 'trash'];
287 $status = in_array($args['status'], $allowed_statuses, true)
288 ? $args['status']
289 : null;
290 if ($status) {
291 $args['where']['status'] = $status;
292 }
293 }
294
295 return $this->repository->all($args);
296 }
297
298 /**
299 * Get published destinations
300 */
301 public function getPublished(array $args = []): array
302 {
303 return $this->repository->getPublished($args);
304 }
305
306 /**
307 * Get published destinations along with aggregated stats such as
308 * trips_count derived from related trip records.
309 */
310 public function getPublishedWithStats(): array
311 {
312 // For now this simply delegates to a repository method that attaches
313 // trips_count using the yatra_trip_destinations relation table.
314 return $this->repository->getPublishedWithTripCounts();
315 }
316
317 /**
318 * Count items
319 */
320 public function count(array $args = []): int
321 {
322 // IMPORTANT: Always filter by type = 'destination' for destinations
323 $args['where']['type'] = 'destination';
324
325 // Sanitize and handle search
326 if (!empty($args['search'])) {
327 $search = sanitize_text_field($args['search']);
328 $items = $this->repository->search($search, $args);
329 return count($items);
330 }
331
332 // Sanitize and handle status filter
333 if (!empty($args['status']) && $args['status'] !== 'all') {
334 $allowed_statuses = ['draft', 'publish', 'trash'];
335 $status = in_array($args['status'], $allowed_statuses, true)
336 ? $args['status']
337 : null;
338 if ($status) {
339 $args['where']['status'] = $status;
340 }
341 }
342
343 return $this->repository->count($args);
344 }
345
346 /**
347 * Bulk update status for multiple destinations
348 */
349 public function bulkUpdateStatus(array $ids, string $status): int
350 {
351 $allowed_statuses = ['draft', 'publish', 'trash'];
352 if (!in_array($status, $allowed_statuses, true)) {
353 throw new \InvalidArgumentException('Invalid status. Must be one of: ' . implode(', ', $allowed_statuses));
354 }
355
356 $updated = 0;
357 foreach ($ids as $id) {
358 $id = absint($id);
359 if ($this->repository->update($id, ['status' => $status])) {
360 $updated++;
361 }
362 }
363
364 return $updated;
365 }
366
367 /**
368 * Get trip count for a destination
369 */
370 public function getTripCount(int $destinationId): int
371 {
372 $destinationRepository = new \Yatra\Repositories\DestinationRepository();
373 return $destinationRepository->getTripCount($destinationId);
374 }
375
376 /**
377 * Get trip count for destination (direct field method)
378 */
379 public function getTripCountDirect(int $destinationId): int
380 {
381 $destinationRepository = new \Yatra\Repositories\DestinationRepository();
382 return $destinationRepository->getTripCountDirect($destinationId);
383 }
384
385 /**
386 * Bulk delete destinations
387 */
388 public function bulkDelete(array $ids): int
389 {
390 $deleted = 0;
391 foreach ($ids as $id) {
392 $id = absint($id);
393 if ($this->repository->delete($id)) {
394 $deleted++;
395 }
396 }
397
398 return $deleted;
399 }
400
401 /**
402 * Get status counts for list views
403 */
404 public function getStatusCounts(): array
405 {
406 $counts = $this->repository->getStatusCounts();
407
408 $publish = $counts['publish'] ?? 0;
409 $draft = $counts['draft'] ?? 0;
410 $trash = $counts['trash'] ?? 0;
411
412 // Calculate total from all statuses, not just the main three
413 $all = $counts['total']??0;
414
415 $result = [
416 'all' => (int) $all,
417 'publish' => (int) $publish,
418 'draft' => (int) $draft,
419 'trash' => (int) $trash,
420 ];
421
422 // Add legacy status keys for backward compatibility
423 $result['active'] = $result['publish'];
424 $result['inactive'] = $result['trash'];
425
426 return $result;
427 }
428 }
429