PluginProbe
Yatra – Travel Booking & Tour Operator Software / 3.0.2.9
Yatra – Travel Booking & Tour Operator Software v3.0.2.9
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 / ActivityService.php

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

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