PluginProbe
Yatra – Travel Booking & Tour Operator Software / trunk
Yatra – Travel Booking & Tour Operator Software vtrunk
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 2.0.11 All 82 releases
yatra / app / Services / CategoryService.php

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

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