PluginProbe
Yatra – Travel Booking & Tour Operator Software / 3.0.5
Yatra – Travel Booking & Tour Operator Software v3.0.5
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 / Repositories / CategoryRepository.php

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

362 lines 11.4 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\Database\Tables\ClassificationsTable;
9 use Yatra\Database\Tables\TripClassificationsTable;
10 use Yatra\Database\Tables\TripsTable;
11 use Yatra\Database\Tables\ReviewsTable;
12
13 /**
14 * Category Repository
15 * Handles database operations for categories using ClassificationsTable
16 */
17 class CategoryRepository extends BaseRepository
18 {
19 /**
20 * Rich text fields specific to categories
21 */
22 protected array $richTextFields = ['description'];
23
24 /**
25 * Integer fields specific to categories
26 */
27 protected array $integerFields = ['parent_id', 'level', 'sorting', 'is_featured'];
28
29 /**
30 * JSON fields specific to categories
31 */
32 protected array $jsonFields = ['metadata'];
33
34 /**
35 * Constructor
36 */
37 public function __construct()
38 {
39 parent::__construct(ClassificationsTable::getTableName());
40 }
41
42 /**
43 * Get table name
44 */
45 protected function getTableName(): string
46 {
47 return ClassificationsTable::getTableName();
48 }
49
50 /**
51 * Find by slug
52 */
53 public function findBySlug(string $slug): ?\stdClass
54 {
55 $table = esc_sql($this->table);
56 $result = $this->wpdb->get_row(
57 $this->wpdb->prepare(
58 "SELECT * FROM `{$table}` WHERE type = %s AND slug = %s",
59 ClassificationTypes::CATEGORY,
60 $slug
61 )
62 );
63 return $result ?: null;
64 }
65
66 /**
67 * Override base all() method to filter by type = 'category'
68 */
69 public function all(array $args = []): array
70 {
71 // IMPORTANT: Always filter by type = 'category' for categories
72 $args['where']['type'] = ClassificationTypes::CATEGORY;
73 return parent::all($args);
74 }
75
76 /**
77 * Override base count() method to filter by type = 'category'
78 */
79 public function count(array $args = []): int
80 {
81 // IMPORTANT: Always filter by type = 'category' for categories
82 $args['where']['type'] = ClassificationTypes::CATEGORY;
83 return parent::count($args);
84 }
85
86 /**
87 * Override base find() method to filter by type = 'category'
88 */
89 public function find(int $id, bool $includeDeleted = false): ?\stdClass
90 {
91 $table = esc_sql($this->table);
92 $query = "SELECT * FROM `{$table}` WHERE type = %s AND id = %d";
93
94 if (!$includeDeleted && $this->hasSoftDelete()) {
95 $query .= " AND (deleted_at IS NULL OR deleted_at = '0000-00-00 00:00:00')";
96 }
97
98 $result = $this->wpdb->get_row($this->wpdb->prepare($query, ClassificationTypes::CATEGORY, $id));
99 return $result ?: null;
100 }
101
102 /**
103 * Search categories
104 */
105 public function search(string $search, array $args = []): array
106 {
107 $table = esc_sql($this->table);
108 $search = sanitize_text_field($search);
109
110 $where = ["type = %s"];
111 $where[] = "(name LIKE %s OR slug LIKE %s OR description LIKE %s)";
112 $searchTerm = '%' . $wpdb->esc_like($search) . '%';
113
114 // Add additional where conditions
115 if (isset($args['where']) && is_array($args['where'])) {
116 foreach ($args['where'] as $field => $value) {
117 if (is_array($value)) {
118 $placeholders = implode(',', array_fill(0, count($value), '%s'));
119 $where[] = "{$field} IN ({$placeholders})";
120 $params = array_merge($params, $value);
121 } else {
122 $where[] = "{$field} = %s";
123 $params[] = $value;
124 }
125 }
126 }
127
128 $whereClause = implode(' AND ', $where);
129 $order = isset($args['order']) ? $args['order'] : 'name ASC';
130 $limit = isset($args['limit']) ? "LIMIT {$args['limit']}" : '';
131
132 $query = "SELECT * FROM `{$table}` WHERE {$whereClause} ORDER BY {$order} {$limit}";
133
134 $params = [ClassificationTypes::CATEGORY, $searchTerm, $searchTerm, $searchTerm];
135 if (isset($args['where']) && is_array($args['where'])) {
136 foreach ($args['where'] as $field => $value) {
137 if (is_array($value)) {
138 $params = array_merge($params, $value);
139 } else {
140 $params[] = $value;
141 }
142 }
143 }
144
145 $results = $this->wpdb->get_results($this->wpdb->prepare($query, $params));
146 return $results ?: [];
147 }
148
149 /**
150 * Get published categories with trip counts
151 */
152 public function getPublishedWithTripCounts(): array
153 {
154 global $wpdb;
155
156 $catTable = esc_sql($this->table);
157 $relTable = TripClassificationsTable::getTableName();
158 $tripsTable = TripsTable::getTableName();
159 $reviewsTable = ReviewsTable::getTableName();
160
161 // COUNT(DISTINCT tc.trip_id) gives real number of trips per category.
162 // avg_rating is computed from approved reviews across all those trips.
163 // starting_price is computed in PHP using both regular trip prices and
164 // traveler-based pricing from recurring availability rules.
165 $sql = "SELECT c.*,
166 COUNT(DISTINCT tc.trip_id) AS trips_count,
167 COALESCE(AVG(r.rating), 0) AS avg_rating,
168 GROUP_CONCAT(DISTINCT tc.trip_id) AS trip_ids
169 FROM `{$catTable}` c
170 LEFT JOIN `{$relTable}` tc
171 ON tc.classification_id = c.id
172 AND tc.classification_type = %s
173 LEFT JOIN `{$tripsTable}` t
174 ON t.id = tc.trip_id
175 LEFT JOIN `{$reviewsTable}` r
176 ON r.trip_id = t.id AND r.status = 'approved'
177 WHERE c.type = %s AND c.status = 'publish'
178 GROUP BY c.id";
179
180 $rows = $this->wpdb->get_results($this->wpdb->prepare($sql, ClassificationTypes::CATEGORY, ClassificationTypes::CATEGORY)) ?: [];
181
182 // Compute starting prices for the trip IDs found.
183 $tripIds = [];
184 foreach ($rows as $row) {
185 if (!empty($row->trip_ids)) {
186 $tripIds = array_merge($tripIds, explode(',', $row->trip_ids));
187 }
188 }
189
190 $pricesByTrip = [];
191 if (!empty($tripIds)) {
192 $pricesByTrip = $this->computeStartingPriceForTripIds(array_unique($tripIds));
193 }
194
195 // Attach starting_price to each category row.
196 foreach ($rows as $row) {
197 $row->starting_price = 0;
198 if (!empty($row->trip_ids)) {
199 $tripIdsForCategory = explode(',', $row->trip_ids);
200 $pricesForCategory = array_intersect_key($pricesByTrip, array_flip($tripIdsForCategory));
201 $row->starting_price = !empty($pricesForCategory) ? min($pricesForCategory) : 0;
202 }
203 }
204
205 return $rows;
206 }
207
208 /**
209 * Compute starting prices for given trip IDs.
210 */
211 private function computeStartingPriceForTripIds(array $tripIds): array
212 {
213 global $wpdb;
214 if (empty($tripIds)) {
215 return [];
216 }
217
218 $tripsTable = TripsTable::getTableName();
219 $placeholders = implode(',', array_fill(0, count($tripIds), '%d'));
220
221 $prices = $wpdb->get_results($wpdb->prepare(
222 "SELECT id, original_price FROM `{$tripsTable}`
223 WHERE id IN ({$placeholders}) AND original_price > 0",
224 ...$tripIds
225 ));
226
227 $pricesByTrip = [];
228 foreach ($prices as $price) {
229 $pricesByTrip[$price->id] = (float) $price->original_price;
230 }
231
232 return $pricesByTrip;
233 }
234
235 /**
236 * Get status counts for categories
237 */
238 public function getStatusCounts(array $args = []): array
239 {
240 $table = esc_sql($this->table);
241
242 $sql = "SELECT status, COUNT(*) as count
243 FROM `{$table}`
244 WHERE type = %s
245 GROUP BY status";
246
247 $results = $this->wpdb->get_results($this->wpdb->prepare($sql, ClassificationTypes::CATEGORY)) ?: [];
248
249 $counts = [
250 'publish' => 0,
251 'draft' => 0,
252 'trash' => 0,
253 'total' => 0
254 ];
255
256 foreach ($results as $row) {
257 $status = $row->status;
258 $count = (int) $row->count;
259
260 // Map old status values to new ones if needed
261 if ($status === 'active') {
262 $status = 'publish';
263 } elseif ($status === 'inactive') {
264 $status = 'trash';
265 }
266
267 if (isset($counts[$status])) {
268 $counts[$status] += $count;
269 $counts['total'] += $count;
270 } else {
271 // Handle any unexpected statuses
272 $counts['total'] += $count;
273 }
274 }
275
276 // Ensure all status keys are present
277 $counts['publish'] = $counts['publish'] ?? 0;
278 $counts['draft'] = $counts['draft'] ?? 0;
279 $counts['trash'] = $counts['trash'] ?? 0;
280
281 return $counts;
282 }
283
284 /**
285 * Get subcategories by parent ID
286 */
287 public function getSubcategories(int $parentId, array $args = []): array
288 {
289 $args['where']['parent_id'] = $parentId;
290 return $this->all($args);
291 }
292
293 /**
294 * Get all categories with subcategories (hierarchical)
295 */
296 public function getHierarchical(array $args = []): array
297 {
298 // Get all top-level categories
299 $topLevelArgs = $args;
300 $topLevelArgs['where']['parent_id'] = null;
301 $categories = $this->all($topLevelArgs);
302
303 // For each category, get its subcategories
304 foreach ($categories as $category) {
305 $subArgs = $args;
306 unset($subArgs['where']['parent_id']); // Remove parent_id filter for subcategories
307 $category->subcategories = $this->getSubcategories((int) $category->id, $subArgs);
308 }
309
310 return $categories;
311 }
312
313 /**
314 * Get trip count for a category
315 *
316 * @param int $categoryId Category ID
317 * @return int Number of trips with this category
318 */
319 public function getTripCount(int $categoryId): int
320 {
321 global $wpdb;
322 $tripRepository = new \Yatra\Repositories\TripRepository();
323 $tripsTable = $tripRepository->getTableName();
324
325 // Use TripClassificationsTable for trip-category relationships
326 $tripClassificationsTable = TripClassificationsTable::getTableName();
327
328 return (int) $wpdb->get_var($wpdb->prepare(
329 "SELECT COUNT(DISTINCT t.id)
330 FROM `{$tripsTable}` t
331 INNER JOIN `{$tripClassificationsTable}` tc ON tc.trip_id = t.id
332 WHERE tc.classification_id = %d
333 AND tc.classification_type = %s
334 AND t.status != 'trash'",
335 $categoryId,
336 ClassificationTypes::CATEGORY
337 ));
338 }
339
340 /**
341 * Get trip count for category (direct field method)
342 *
343 * @param int $categoryId Category ID
344 * @return int Number of trips with this category
345 */
346 public function getTripCountDirect(int $categoryId): int
347 {
348 global $wpdb;
349 $tripRepository = new \Yatra\Repositories\TripRepository();
350 $tripTable = $tripRepository->getTableName();
351
352 return (int) $wpdb->get_var($wpdb->prepare(
353 "SELECT COUNT(*)
354 FROM `{$tripTable}` t
355 WHERE t.category_id = %d
356 AND t.status != 'trash'",
357 $categoryId
358 ));
359 }
360 }
361
362