PluginProbe
Yatra – Travel Booking & Tour Operator Software / 3.0.15
Yatra – Travel Booking & Tour Operator Software v3.0.15
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 / Repositories / TripCategoryRepository.php

TripCategoryRepository.php in Yatra – Travel Booking & Tour Operator Software 3.0.15, at app/Repositories/TripCategoryRepository.php

379 lines 11.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\Repositories;
6
7 use Yatra\Constants\ClassificationTypes;
8 use Yatra\Utils\Cache;
9
10 /**
11 * Trip Category Repository
12 * Handles database operations for trip categories
13 */
14 class TripCategoryRepository extends BaseRepository
15 {
16 /**
17 * Rich text fields
18 */
19 protected array $richTextFields = ['description'];
20
21 /**
22 * Integer fields
23 */
24 protected array $integerFields = ['id', 'parent_id', 'created_by', 'updated_by'];
25
26 /**
27 * Get table name
28 */
29 protected function getTableName(): string
30 {
31 // Use ClassificationsTable for categories (type = 'category')
32 return \Yatra\Database\Tables\ClassificationsTable::getTableName();
33 }
34
35 /**
36 * Build where clause (override to filter by type='category')
37 */
38 protected function buildWhereClause(array $args): string
39 {
40 $where = parent::buildWhereClause($args);
41
42 // Always filter by type = 'category' for this repository
43 if ($where) {
44 $where .= sprintf(" AND type = '%s'", ClassificationTypes::CATEGORY);
45 } else {
46 $where = sprintf("WHERE type = '%s'", ClassificationTypes::CATEGORY);
47 }
48
49 return $where;
50 }
51
52 /**
53 * Find by slug
54 */
55 public function findBySlug(string $slug): ?\stdClass
56 {
57 $table = esc_sql($this->table);
58 $result = $this->wpdb->get_row(
59 $this->wpdb->prepare(
60 "SELECT * FROM `{$table}` WHERE slug = %s AND type = %s",
61 $slug,
62 ClassificationTypes::CATEGORY
63 )
64 );
65
66 return $result ?: null;
67 }
68
69 /**
70 * Get published categories
71 */
72 public function getPublished(array $args = []): array
73 {
74 $args['where']['status'] = 'publish';
75 return $this->all($args);
76 }
77
78 /**
79 * Get categories by status
80 */
81 public function getByStatus(string $status, array $args = []): array
82 {
83 $args['where']['status'] = $status;
84 return $this->all($args);
85 }
86
87 /**
88 * Get top-level categories (no parent)
89 */
90 public function getTopLevel(array $args = []): array
91 {
92 $args['where']['parent_id'] = null;
93 return $this->all($args);
94 }
95
96 /**
97 * Get subcategories by parent ID
98 */
99 public function getSubcategories(int $parentId, array $args = []): array
100 {
101 $args['where']['parent_id'] = $parentId;
102 return $this->all($args);
103 }
104
105 /**
106 * Get category with subcategories
107 */
108 public function getWithSubcategories(int $id): ?\stdClass
109 {
110 $category = $this->find($id);
111 if (!$category) {
112 return null;
113 }
114
115 $subcategories = $this->getSubcategories($id);
116 $category->subcategories = $subcategories;
117
118 return $category;
119 }
120
121 /**
122 * Get all categories with subcategories (hierarchical)
123 */
124 public function getHierarchical(array $args = []): array
125 {
126 // Get all top-level categories
127 $topLevelArgs = $args;
128 $topLevelArgs['where']['parent_id'] = null;
129 $categories = $this->all($topLevelArgs);
130
131 // For each category, get its subcategories
132 foreach ($categories as $category) {
133 $subArgs = $args;
134 unset($subArgs['where']['parent_id']); // Remove parent_id filter for subcategories
135 $category->subcategories = $this->getSubcategories((int) $category->id, $subArgs);
136 }
137
138 return $categories;
139 }
140
141 /**
142 * Check if category has subcategories
143 */
144 public function hasSubcategories(int $id): bool
145 {
146 $table = esc_sql($this->table);
147 $count = $this->wpdb->get_var(
148 $this->wpdb->prepare(
149 "SELECT COUNT(*) FROM `{$table}` WHERE parent_id = %d",
150 $id
151 )
152 );
153
154 return (int) $count > 0;
155 }
156
157 /**
158 * Check if category can be deleted (no subcategories and not used by trips)
159 */
160 public function canDelete(int $id): bool
161 {
162 // Check if has subcategories
163 if ($this->hasSubcategories($id)) {
164 return false;
165 }
166
167 // Check if used by trips (you may want to add this check later)
168 // For now, we'll allow deletion if no subcategories
169
170 return true;
171 }
172
173 /**
174 * Get published categories with trip counts, ratings, and pricing stats
175 */
176 /**
177 * Wipe listing caches when a trip-category row is written so
178 * {@see self::getPublishedWithTripCounts()} doesn't serve stale
179 * aggregates. {@see \Yatra\Hooks\CacheHooks} only watches trips /
180 * activities / destinations / bookings — category writes have no
181 * corresponding domain action.
182 */
183 protected function afterWrite(string $operation, int $id, array $context = []): void
184 {
185 Cache::invalidateListingCaches();
186 }
187
188 public function getPublishedWithTripCounts(): array
189 {
190 // Cache the full payload. /trip-categories used to run the
191 // GROUP BY aggregate plus one MIN-price SELECT per category on
192 // every request. Cache key uses the `trip_listing_` prefix that
193 // {@see \Yatra\Utils\Cache::invalidateListingCaches()} clears on
194 // every trip / category write (the latter via this class's
195 // afterWrite override), so visitors always see current data
196 // after admin edits.
197 return $this->cacheQueryResult(
198 'trip_listing_categories_with_counts_v2',
199 function (): array {
200 return $this->fetchPublishedWithTripCounts();
201 },
202 Cache::DURATION_LISTINGS
203 );
204 }
205
206 /**
207 * Uncached worker for {@see self::getPublishedWithTripCounts()}.
208 *
209 * @return array<int, \stdClass>
210 */
211 private function fetchPublishedWithTripCounts(): array
212 {
213 $table = esc_sql($this->table);
214
215 // Use TripRepository for trips table
216 $tripRepository = new \Yatra\Repositories\TripRepository();
217 $trip_table = esc_sql($tripRepository->getTableName());
218
219 // Use TripClassificationsTable for trip-category relationships
220 $trip_cat_table = esc_sql(\Yatra\Database\Tables\TripClassificationsTable::getTableName());
221 $reviews_table = esc_sql(\Yatra\Database\Tables\ReviewsTable::getTableName());
222
223 // TripClassificationsTable uses classification_id + classification_type (not category_id).
224 // ClassificationsTable holds all taxonomy rows — restrict to type = category.
225 $query = $this->wpdb->prepare(
226 "SELECT c.*,
227 COUNT(DISTINCT tc.trip_id) AS trips_count,
228 COALESCE(AVG(r.rating), 0) AS avg_rating,
229 GROUP_CONCAT(DISTINCT tc.trip_id) AS trip_ids
230 FROM `{$table}` c
231 LEFT JOIN `{$trip_cat_table}` tc
232 ON tc.classification_id = c.id
233 AND tc.classification_type = %s
234 LEFT JOIN `{$trip_table}` t ON t.id = tc.trip_id AND t.status = 'publish'
235 LEFT JOIN `{$reviews_table}` r ON r.trip_id = t.id AND r.status = 'approved'
236 WHERE c.type = %s AND c.status = 'publish'
237 GROUP BY c.id
238 ORDER BY c.name ASC",
239 ClassificationTypes::CATEGORY,
240 ClassificationTypes::CATEGORY
241 );
242
243 $categories = $this->wpdb->get_results($query) ?: [];
244
245 // Calculate starting prices for each category
246 foreach ($categories as $category) {
247 $category->starting_price = 0;
248 if (!empty($category->trip_ids)) {
249 $trip_ids = explode(',', $category->trip_ids);
250 $category->starting_price = $this->computeStartingPriceForTripIds($trip_ids);
251 }
252 }
253
254 return $categories;
255 }
256
257 /**
258 * Compute starting price for given trip IDs
259 */
260 private function computeStartingPriceForTripIds(array $trip_ids): float
261 {
262 if (empty($trip_ids)) {
263 return 0;
264 }
265
266 // Use TripRepository for trips table
267 $tripRepository = new \Yatra\Repositories\TripRepository();
268 $trip_table = esc_sql($tripRepository->getTableName());
269 $placeholders = implode(',', array_fill(0, count($trip_ids), '%d'));
270
271 $query = "SELECT MIN(CAST(original_price AS DECIMAL(10,2))) as min_price
272 FROM `{$trip_table}`
273 WHERE id IN ({$placeholders}) AND original_price > 0";
274
275 $min_price = $this->wpdb->get_var(
276 $this->wpdb->prepare($query, ...$trip_ids)
277 );
278
279 return $min_price ? (float) $min_price : 0;
280 }
281
282 /**
283 * Search categories
284 */
285 public function search(string $search, array $args = []): array
286 {
287 $table = esc_sql($this->table);
288 $where = $this->buildWhereClause($args);
289 $order = $this->buildOrderClause($args);
290 $limit = $this->buildLimitClause($args);
291
292 $search_where = $this->wpdb->prepare(
293 "WHERE (name LIKE %s OR slug LIKE %s OR description LIKE %s)",
294 '%' . $this->wpdb->esc_like($search) . '%',
295 '%' . $this->wpdb->esc_like($search) . '%',
296 '%' . $this->wpdb->esc_like($search) . '%'
297 );
298
299 if ($where) {
300 $search_where .= ' AND ' . str_replace('WHERE ', '', $where);
301 }
302
303 $query = "SELECT * FROM `{$table}` {$search_where} {$order} {$limit}";
304
305 return $this->wpdb->get_results($query) ?: [];
306 }
307
308 /**
309 * Get trip count for a trip category
310 *
311 * @param int $categoryId Category ID
312 * @return int Number of trips with this category
313 */
314 public function getTripCount(int $categoryId): int
315 {
316 global $wpdb;
317 $tripRepository = new \Yatra\Repositories\TripRepository();
318 $tripsTable = $tripRepository->getTableName();
319
320 // Use TripClassificationsTable for trip-category relationships
321 $tripClassificationsTable = \Yatra\Database\Tables\TripClassificationsTable::getTableName();
322
323 return (int) $wpdb->get_var($wpdb->prepare(
324 "SELECT COUNT(DISTINCT t.id)
325 FROM `{$tripsTable}` t
326 INNER JOIN `{$tripClassificationsTable}` tc ON tc.trip_id = t.id
327 WHERE tc.classification_id = %d
328 AND tc.classification_type = %s
329 AND t.status != 'trash'",
330 $categoryId,
331 ClassificationTypes::CATEGORY
332 ));
333 }
334
335 /**
336 * Get trip count for trip category (direct field method)
337 *
338 * @param int $categoryId Category ID
339 * @return int Number of trips with this category
340 */
341 public function getTripCountDirect(int $categoryId): int
342 {
343 global $wpdb;
344 $tripRepository = new \Yatra\Repositories\TripRepository();
345 $tripTable = $tripRepository->getTableName();
346
347 // Use TripClassificationsTable for trip-category relationships
348 $tripCatTable = \Yatra\Database\Tables\TripClassificationsTable::getTableName();
349
350 return (int) $wpdb->get_var($wpdb->prepare(
351 "SELECT COUNT(DISTINCT t.id)
352 FROM `{$tripTable}` t
353 INNER JOIN `{$tripCatTable}` tc ON tc.trip_id = t.id
354 WHERE tc.classification_id = %d
355 AND tc.classification_type = %s
356 AND t.status != 'trash'",
357 $categoryId,
358 ClassificationTypes::CATEGORY
359 ));
360 }
361
362 /**
363 * Get published categories for templates
364 */
365 public function getPublishedCategories(): array
366 {
367 return $this->getPublished();
368 }
369
370 /**
371 * Get difficulty levels for templates (published rows from classifications).
372 */
373 public function getDifficultyLevels(): array
374 {
375 return (new \Yatra\Repositories\DifficultyLevelRepository())->getPublished();
376 }
377 }
378
379