PluginProbe
Yatra – Travel Booking & Tour Operator Software / 3.0.2.8
Yatra – Travel Booking & Tour Operator Software v3.0.2.8
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 / ActivityRepository.php

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

368 lines 12.2 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 use Yatra\Utils\Cache;
13 use Yatra\Utils\QueryCache;
14
15 /**
16 * Activity Repository
17 * Handles database operations for activities using the new ClassificationsTable
18 */
19 class ActivityRepository extends BaseRepository
20 {
21 /**
22 * Rich text fields specific to activities
23 */
24 protected array $richTextFields = ['description'];
25
26 /**
27 * Integer fields specific to activities
28 */
29 protected array $integerFields = ['id', 'created_by', 'updated_by'];
30
31 /**
32 * JSON fields specific to activities
33 */
34 protected array $jsonFields = ['metadata'];
35
36 /**
37 * Get table name - using the new ClassificationsTable
38 */
39 protected function getTableName(): string
40 {
41 return ClassificationsTable::getTableName();
42 }
43
44
45 /**
46 * Find by slug - for activities
47 */
48 public function findBySlug(string $slug): ?\stdClass
49 {
50 $table = esc_sql($this->table);
51 $result = $this->wpdb->get_row(
52 $this->wpdb->prepare(
53 "SELECT * FROM `{$table}` WHERE type = %s AND slug = %s",
54 ClassificationTypes::ACTIVITY,
55 $slug
56 )
57 );
58
59 return $result ?: null;
60 }
61
62 /**
63 * Get published activities (visible in listings and search).
64 *
65 * Accepts both `active` and `publish` status — some sites/data use either.
66 */
67 public function getPublished(array $args = []): array
68 {
69 $statuses = apply_filters('yatra_activity_published_statuses', ['active', 'publish']);
70 if (!is_array($statuses) || $statuses === []) {
71 $statuses = ['active', 'publish'];
72 }
73 $args['where']['type'] = ClassificationTypes::ACTIVITY;
74 $args['where']['status'] = $statuses;
75
76 return $this->all($args);
77 }
78
79 /**
80 * Get activities by status
81 */
82 public function getByStatus(string $status, array $args = []): array
83 {
84 $args['where']['type'] = ClassificationTypes::ACTIVITY;
85 $args['where']['status'] = $status;
86 return $this->all($args);
87 }
88
89 /**
90 * Search activities
91 */
92 public function search(string $search, array $args = []): array
93 {
94 $table = esc_sql($this->table);
95 $where = $this->buildWhereClause($args);
96 $order = $this->buildOrderClause($args);
97 $limit = $this->buildLimitClause($args);
98
99 $search_where = $this->wpdb->prepare(
100 "WHERE type = %s AND (name LIKE %s OR slug LIKE %s OR description LIKE %s)",
101 ClassificationTypes::ACTIVITY,
102 '%' . $this->wpdb->esc_like($search) . '%',
103 '%' . $this->wpdb->esc_like($search) . '%',
104 '%' . $this->wpdb->esc_like($search) . '%'
105 );
106
107 if ($where) {
108 $search_where .= ' AND ' . str_replace('WHERE ', '', $where);
109 }
110
111 $query = "SELECT * FROM `{$table}` {$search_where} {$order} {$limit}";
112
113 return $this->wpdb->get_results($query) ?: [];
114 }
115
116 /**
117 * Bulk update status for multiple activities
118 */
119 public function bulkUpdateStatus(array $ids, string $status): bool
120 {
121 if (empty($ids)) {
122 return false;
123 }
124
125 $table = esc_sql($this->table);
126 $placeholders = implode(',', array_fill(0, count($ids), '%d'));
127 $sql = "UPDATE `{$table}` SET status = %s, updated_at = %s WHERE type = %s AND id IN ({$placeholders})";
128 $params = array_merge([$status, current_time('mysql'), ClassificationTypes::ACTIVITY], $ids);
129
130 $prepared = $this->wpdb->prepare($sql, $params);
131 return $this->wpdb->query($prepared) !== false;
132 }
133
134 /**
135 * Permanently delete multiple activities
136 */
137 public function bulkDelete(array $ids): bool
138 {
139 if (empty($ids)) {
140 return false;
141 }
142
143 $table = esc_sql($this->table);
144 $placeholders = implode(',', array_fill(0, count($ids), '%d'));
145 $sql = "DELETE FROM `{$table}` WHERE type = %s AND id IN ({$placeholders})";
146
147 $prepared = $this->wpdb->prepare($sql, array_merge([ClassificationTypes::ACTIVITY], $ids));
148 return $this->wpdb->query($prepared) !== false;
149 }
150
151 /**
152 * Get published activities with trip counts and stats
153 * Uses the new ClassificationsTable and TripClassificationsTable
154 */
155 public function getPublishedWithTripCounts(): array
156 {
157 global $wpdb;
158
159 $actTable = esc_sql($this->table);
160 $relTable = TripClassificationsTable::getTableName();
161 $tripsTable = TripsTable::getTableName();
162 $reviewsTable = ReviewsTable::getTableName();
163
164 // COUNT(DISTINCT tc.trip_id) gives real number of trips per activity.
165 // avg_rating is computed from approved reviews across all those trips.
166 // starting_price is computed in PHP using both regular trip prices and
167 // traveler-based pricing from recurring availability rules.
168 // Status must match {@see getPublished()} — many sites use `publish`, not only `active`.
169 $statuses = apply_filters('yatra_activity_published_statuses', ['active', 'publish']);
170 if (!is_array($statuses) || $statuses === []) {
171 $statuses = ['active', 'publish'];
172 }
173 $statuses = array_values(array_intersect($statuses, ['active', 'publish']));
174 if ($statuses === []) {
175 $statuses = ['active', 'publish'];
176 }
177 $statusIn = implode(',', array_fill(0, count($statuses), '%s'));
178
179 $sql = "SELECT a.*,
180 COUNT(DISTINCT tc.trip_id) AS trips_count,
181 COALESCE(AVG(r.rating), 0) AS avg_rating,
182 GROUP_CONCAT(DISTINCT tc.trip_id) AS trip_ids
183 FROM `{$actTable}` a
184 LEFT JOIN `{$relTable}` tc
185 ON tc.classification_id = a.id
186 AND tc.classification_type = %s
187 LEFT JOIN `{$tripsTable}` t
188 ON t.id = tc.trip_id
189 LEFT JOIN `{$reviewsTable}` r
190 ON r.trip_id = t.id AND r.status = 'approved'
191 WHERE a.type = %s AND a.status IN ({$statusIn})
192 GROUP BY a.id";
193
194 $params = array_merge(
195 [ClassificationTypes::ACTIVITY, ClassificationTypes::ACTIVITY],
196 $statuses
197 );
198 $rows = $this->wpdb->get_results($this->wpdb->prepare($sql, ...$params)) ?: [];
199
200 if (empty($rows)) {
201 return [];
202 }
203
204 foreach ($rows as $row) {
205 $row->starting_price = $this->computeStartingPriceForTripIds($row->trip_ids ?? '');
206 }
207
208 return $rows;
209 }
210
211 /**
212 * Compute starting price for given trip IDs
213 * This method calculates the lowest price from all trips associated with an activity
214 */
215 private function computeStartingPriceForTripIds(string $tripIds): float
216 {
217 if (empty($tripIds)) {
218 return 0.0;
219 }
220
221 global $wpdb;
222 $tripIdsArray = array_filter(array_map('intval', explode(',', $tripIds)));
223
224 if (empty($tripIdsArray)) {
225 return 0.0;
226 }
227
228 $placeholders = implode(',', array_fill(0, count($tripIdsArray), '%d'));
229 $tripsTable = TripsTable::getTableName();
230
231 // Get the minimum original price from trips
232 $sql = "SELECT MIN(CAST(original_price AS DECIMAL(10,2))) as min_price
233 FROM `{$tripsTable}`
234 WHERE id IN ({$placeholders}) AND original_price > 0";
235
236 $prepared = $this->wpdb->prepare($sql, $tripIdsArray);
237 $result = $this->wpdb->get_var($prepared);
238
239 return $result ? (float) $result : 0.0;
240 }
241
242 /**
243 * Get status counts for activities
244 */
245 public function getStatusCounts(array $args = []): array
246 {
247 $table = esc_sql($this->table);
248
249 $debug_sql = "SELECT id, type, status, name FROM `{$table}` ORDER BY id";
250 $debug_results = $this->wpdb->get_results($debug_sql);
251
252 // Get counts for each status - only for activities
253 $sql = "SELECT status, COUNT(*) as count
254 FROM `{$table}`
255 WHERE type = %s
256 GROUP BY status";
257
258 $results = $this->wpdb->get_results($this->wpdb->prepare($sql, ClassificationTypes::ACTIVITY));
259 $counts = [
260 'publish' => 0,
261 'draft' => 0,
262 'trash' => 0,
263 'total' => 0
264 ];
265
266 foreach ($results as $row) {
267 $status = $row->status;
268 $count = (int) $row->count;
269
270 // Map old status values to new ones if needed
271 if ($status === 'active') {
272 $status = 'publish';
273 } elseif ($status === 'inactive') {
274 $status = 'trash';
275 }
276
277 if (isset($counts[$status])) {
278 $counts[$status] += $count;
279 $counts['total'] += $count;
280 } else {
281 // Handle any unexpected statuses
282 $counts['total'] += $count;
283 }
284 }
285
286
287 return $counts;
288 }
289
290 /**
291 * Override base all() method to ensure type filtering
292 */
293 public function all(array $args = []): array
294 {
295 // IMPORTANT: Always filter by type = 'activity' for activities
296 $args['where']['type'] = ClassificationTypes::ACTIVITY;
297 return parent::all($args);
298 }
299
300 /**
301 * Override base count() method to ensure type filtering
302 */
303 public function count(array $args = []): int
304 {
305 // IMPORTANT: Always filter by type = 'activity' for activities
306 $args['where']['type'] = ClassificationTypes::ACTIVITY;
307 return parent::count($args);
308 }
309
310 /**
311 * Get trip count for an activity
312 *
313 * @param int $activityId Activity ID
314 * @return int Number of trips with this activity
315 */
316 public function getTripCount(int $activityId): int
317 {
318 // Use QueryCache for caching activity trip counts
319 $cacheKey = Cache::KEY_ACTIVITY_TRIP_COUNT . '_' . $activityId;
320
321 // Cache backends often return strings for scalars; force int on read + write.
322 return (int) $this->cacheQueryResult($cacheKey, function () use ($activityId): int {
323 global $wpdb;
324 $tripRepository = new \Yatra\Repositories\TripRepository();
325 $tripsTable = $tripRepository->getTableName();
326
327 // Using hardcoded table name since there's no dedicated repository for trip activities
328 $tripActivitiesTable = TripClassificationsTable::getTableName();
329
330 return (int) ($wpdb->get_var($wpdb->prepare(
331 "SELECT COUNT(DISTINCT t.id)
332 FROM `{$tripsTable}` t
333 INNER JOIN `{$tripActivitiesTable}` ta ON ta.trip_id = t.id
334 WHERE ta.classification_id = %d AND ta.classification_type = %s
335 AND t.status != 'trash'",
336 $activityId,
337 ClassificationTypes::ACTIVITY
338 )) ?? 0);
339 }, Cache::DURATION_COUNTS); // Cache for 30 minutes
340 }
341
342 /**
343 * Get trip count for activity (direct field method)
344 *
345 * @param int $activityId Activity ID
346 * @return int Number of trips with this activity
347 */
348 public function getTripCountDirect(int $activityId): int
349 {
350 // Use QueryCache for caching activity trip counts
351 $cacheKey = Cache::KEY_ACTIVITY_TRIP_COUNT_DIRECT . '_' . $activityId;
352
353 // Cache backends often return strings for scalars; force int on read + write.
354 return (int) $this->cacheQueryResult($cacheKey, function () use ($activityId): int {
355 global $wpdb;
356 $tripRepository = new \Yatra\Repositories\TripRepository();
357 $tripTable = $tripRepository->getTableName();
358
359 return (int) ($wpdb->get_var($wpdb->prepare(
360 "SELECT COUNT(*)
361 FROM `{$tripTable}` t
362 WHERE t.activity_id = %d",
363 $activityId
364 )) ?? 0);
365 }, Cache::DURATION_COUNTS); // Cache for 30 minutes
366 }
367 }
368