PluginProbe
Yatra – Travel Booking & Tour Operator Software / 3.0.2.7
Yatra – Travel Booking & Tour Operator Software v3.0.2.7
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 / SavedTripRepository.php

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

390 lines 14.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\Repositories;
6
7 /**
8 * Saved Trip Repository (Wishlist)
9 *
10 * Handles all database operations for saved trips/wishlist using WordPress user meta.
11 *
12 * @package Yatra\Repositories
13 */
14 class SavedTripRepository extends BaseRepository
15 {
16 /**
17 * User meta key for saved trips
18 */
19 private const META_KEY = 'yatra_saved_trips';
20
21 /**
22 * Normalize stored meta to a list of trip IDs (handles legacy rows with trip_id keys).
23 *
24 * @param mixed $savedData
25 * @return list<int>
26 */
27 private function normalizeSavedTripIdsFromMeta($savedData): array
28 {
29 if (!is_array($savedData)) {
30 return [];
31 }
32
33 $ids = [];
34 foreach ($savedData as $item) {
35 if (is_array($item) && isset($item['trip_id'])) {
36 $ids[] = (int) $item['trip_id'];
37 } elseif (is_numeric($item)) {
38 $ids[] = (int) $item;
39 }
40 }
41
42 return array_values(array_unique($ids));
43 }
44
45 /**
46 * Get full table name with prefix (not used, but required by BaseRepository)
47 */
48 protected function getTableName(): string
49 {
50 // Not used since we're using user meta, but required by BaseRepository
51 return $this->wpdb->prefix . 'yatra_saved_trips';
52 }
53
54 /**
55 * Check if trip is saved by user
56 *
57 * @param int $userId User ID
58 * @param int $tripId Trip ID
59 * @return bool
60 */
61 public function isSaved(int $userId, int $tripId): bool
62 {
63 $raw = get_user_meta($userId, self::META_KEY, true);
64
65 return in_array($tripId, $this->normalizeSavedTripIdsFromMeta($raw), true);
66 }
67
68 /**
69 * Save trip for user
70 *
71 * @param int $userId User ID
72 * @param int $tripId Trip ID
73 * @return bool
74 */
75 public function saveTrip(int $userId, int $tripId): bool
76 {
77 $tripId = (int) $tripId;
78 if ($tripId <= 0) {
79 return false;
80 }
81
82 $savedData = get_user_meta($userId, self::META_KEY, true);
83 $savedTripIds = $this->normalizeSavedTripIdsFromMeta(is_array($savedData) ? $savedData : []);
84
85 if (in_array($tripId, $savedTripIds, true)) {
86 return true;
87 }
88
89 $tripRepository = new TripRepository();
90 $trip = $tripRepository->find($tripId);
91
92 if (!$trip) {
93 return false;
94 }
95
96 $savedTripIds[] = $tripId;
97 $savedTripIds = array_values(array_unique($savedTripIds));
98
99 $updated = update_user_meta($userId, self::META_KEY, $savedTripIds);
100 if ($updated !== false) {
101 return true;
102 }
103
104 // update_user_meta() returns false when the value is unchanged; treat as success if the trip is stored.
105 return $this->isSaved($userId, $tripId);
106 }
107
108 /**
109 * Remove saved trip
110 *
111 * @param int $userId User ID
112 * @param int $tripId Trip ID
113 * @return bool
114 */
115 public function removeTrip(int $userId, int $tripId): bool
116 {
117 $tripId = (int) $tripId;
118 if ($tripId <= 0 || !$this->isSaved($userId, $tripId)) {
119 return false;
120 }
121
122 $savedData = get_user_meta($userId, self::META_KEY, true);
123 if (!is_array($savedData) || $savedData === []) {
124 return false;
125 }
126
127 $before = $this->normalizeSavedTripIdsFromMeta($savedData);
128 $savedTripIds = array_values(array_filter(
129 $before,
130 static fn (int $id): bool => $id !== $tripId
131 ));
132
133 if (count($savedTripIds) === count($before)) {
134 return false;
135 }
136
137 $updated = update_user_meta($userId, self::META_KEY, $savedTripIds);
138 if ($updated !== false) {
139 return true;
140 }
141
142 return !$this->isSaved($userId, $tripId);
143 }
144
145 /**
146 * Get user's saved trips (fetches fresh data from database)
147 *
148 * @param int $userId User ID
149 * @param int $limit Limit results (not used with meta, but kept for compatibility)
150 * @return array
151 */
152 public function getUserSavedTrips(int $userId, int $limit = 100): array
153 {
154 // Get saved trip IDs from user meta
155 $savedData = get_user_meta($userId, self::META_KEY, true);
156
157 // Debug: log what we retrieved
158 // Handle empty or invalid data
159 if (empty($savedData)) {
160 return [];
161 }
162
163 // WordPress unserializes automatically, but ensure we have an array
164 if (!is_array($savedData)) {
165 // If it's a string, it might be serialized (shouldn't happen, but handle it)
166 if (is_string($savedData)) {
167 $unserialized = @unserialize($savedData);
168 if ($unserialized !== false && is_array($unserialized)) {
169 $savedData = $unserialized;
170 } else {
171 return [];
172 }
173 } else {
174 return [];
175 }
176 }
177
178 // Handle both old format (array of trip objects) and new format (array of IDs)
179 $savedTripIds = [];
180 foreach ($savedData as $key => $item) {
181 if (is_array($item) && isset($item['trip_id'])) {
182 // Old format: array with trip_id key
183 $savedTripIds[] = (int) $item['trip_id'];
184 } elseif (is_int($item)) {
185 // Direct integer
186 $savedTripIds[] = $item;
187 } elseif (is_numeric($item)) {
188 // Numeric string
189 $savedTripIds[] = (int) $item;
190 } else {
191 }
192 }
193
194 // Remove duplicates and re-index
195 $savedTripIds = array_values(array_unique($savedTripIds));
196
197 if (empty($savedTripIds)) {
198 return [];
199 }
200
201
202
203 // Fetch fresh trip data from database for each saved trip ID
204 $tripRepository = new TripRepository();
205 $validTrips = [];
206
207 foreach ($savedTripIds as $tripId) {
208 if ($tripId <= 0) {
209 continue;
210 }
211
212 $tripObj = $tripRepository->findWithRelations($tripId);
213
214 // Include trip if it exists
215 if (!$tripObj) {
216 continue; // Trip not found, skip it
217 }
218
219 $tripStatus = $tripObj->status ?? '';
220 // Only include published trips (accept both 'publish' and 'published')
221 if (!in_array($tripStatus, ['publish', 'published'], true)) {
222 continue; // Trip not published, skip it
223 }
224
225 // Calculate price using the SAME logic as single-trip.php
226 $hasAvailability = !empty($tripObj->availability_dates) && is_array($tripObj->availability_dates) && count($tripObj->availability_dates) > 0;
227 $pricingType = $tripObj->pricing_type ?? 'regular';
228 $hasTravelerPricing = ($pricingType === 'traveler_based' && !empty($tripObj->price_types));
229
230 // Centralized pricing via TripPricingService (single source of truth)
231 $availDates = $hasAvailability && !empty($tripObj->availability_dates)
232 ? array_map(function($a) { return (object) $a; }, $tripObj->availability_dates)
233 : null;
234 $resolvedPricing = \Yatra\Services\TripPricingService::resolveDisplayPricing($tripObj, $availDates);
235 $originalPrice = $resolvedPricing['original_price'];
236 $displayPrice = $resolvedPricing['current_price'];
237 $hasDiscount = $resolvedPricing['has_discount'];
238 $discountPercent = $resolvedPricing['discount_percentage'];
239 $salePrice = (float) ($tripObj->sale_price ?? 0);
240 $discountedPrice = (float) ($tripObj->discounted_price ?? 0);
241
242 // Get destinations for location
243 $destinations = $tripRepository->getDestinations($tripId);
244 $location = !empty($destinations) ? $destinations[0]->destination_name ?? '' : '';
245
246 // Get difficulty level
247 $difficulty = $tripObj->difficulty_level ?? '';
248
249 // Format duration using helper function
250 $durationDays = !empty($tripObj->duration_days) ? (int) $tripObj->duration_days : null;
251 $durationNights = !empty($tripObj->duration_nights) ? (int) $tripObj->duration_nights : null;
252 $duration = '';
253 if (!empty($durationDays)) {
254 if (function_exists('yatra_format_duration')) {
255 $duration = yatra_format_duration($durationDays, $durationNights);
256 } else {
257 $duration = sprintf(__('%d Days', 'yatra'), $durationDays);
258 if (!empty($durationNights)) {
259 $duration .= ' / ' . sprintf(__('%d Nights', 'yatra'), $durationNights);
260 }
261 }
262 } else {
263 $duration = __('Flexible', 'yatra');
264 }
265
266 // Get highlights (matching listing page logic)
267 $highlights = [];
268
269 // Group size highlights
270 if (!empty($tripObj->max_travelers)) {
271 if ($tripObj->max_travelers <= 2) {
272 $highlights[] = ['text' => __('Private Tour', 'yatra'), 'link' => null];
273 } elseif ($tripObj->max_travelers <= 8) {
274 $highlights[] = ['text' => __('Small Group', 'yatra'), 'link' => null];
275 }
276 }
277
278 // Category highlights (with link)
279 $tripCategories = $tripRepository->getTripCategories($tripId);
280 if (!empty($tripCategories) && is_array($tripCategories)) {
281 $firstCategory = $tripCategories[0];
282 if (!empty($firstCategory->name)) {
283 $catLink = !empty($firstCategory->slug) ? (function_exists('yatra_get_category_permalink') ? yatra_get_category_permalink($firstCategory) : null) : null;
284 $highlights[] = ['text' => $firstCategory->name, 'link' => $catLink];
285 }
286 }
287
288 // Activity highlights (with link)
289 $activities = $tripRepository->getActivities($tripId);
290 if (!empty($activities) && is_array($activities)) {
291 $firstActivity = $activities[0];
292 if (!empty($firstActivity->name)) {
293 $actLink = !empty($firstActivity->slug) ? (function_exists('yatra_get_activity_permalink') ? yatra_get_activity_permalink($firstActivity) : null) : null;
294 $highlights[] = ['text' => $firstActivity->name, 'link' => $actLink];
295 }
296 }
297
298 // Feature highlights
299 if (!empty($tripObj->meals_included) && $tripObj->meals_included === 'all') {
300 $highlights[] = ['text' => __('All Meals Included', 'yatra'), 'link' => null];
301 }
302 if (!empty($tripObj->guide_included) && $tripObj->guide_included) {
303 $highlights[] = ['text' => __('Expert Guide', 'yatra'), 'link' => null];
304 }
305
306 // Limit to 3 highlights
307 $highlights = array_slice($highlights, 0, 3);
308
309 // Get rating and reviews
310 $avgRating = (float) ($tripObj->avg_rating ?? $tripObj->average_rating ?? 0);
311 $reviewsCount = (int) ($tripObj->reviews_count ?? $tripObj->review_count ?? 0);
312
313 // If rating is 0, try to fetch from ReviewRepository
314 if ($avgRating == 0) {
315 if (class_exists('\Yatra\Repositories\ReviewRepository')) {
316 $reviewRepository = new \Yatra\Repositories\ReviewRepository();
317 $avgRating = $reviewRepository->getAverageRating($tripId);
318 $reviewsCount = $reviewRepository->getReviewCount($tripId);
319 }
320 }
321
322 // Get featured image URL
323 $imageUrl = '';
324 if (!empty($tripObj->featured_image)) {
325 $imageUrl = wp_get_attachment_url($tripObj->featured_image);
326 }
327
328 // Get permalink
329 $permalink = '';
330 if (function_exists('yatra_get_trip_permalink')) {
331 $permalink = yatra_get_trip_permalink($tripObj);
332 } else {
333 $tripBase = \Yatra\Services\SettingsService::getTripBase();
334 $permalink = home_url('/' . $tripBase . '/' . ($tripObj->slug ?? ''));
335 }
336
337 // Build trip data array
338 $validTrips[] = [
339 'id' => $tripId,
340 'trip_id' => $tripId,
341 'trip_title' => $tripObj->title ?? '',
342 'trip_slug' => $tripObj->slug ?? '',
343 'trip_image' => $imageUrl,
344 'price' => $displayPrice,
345 'original_price' => $originalPrice > 0 ? $originalPrice : null,
346 'sale_price' => $salePrice > 0 ? $salePrice : null,
347 'discounted_price' => $discountedPrice > 0 ? $discountedPrice : null,
348 'discount_percent' => ($discountPercent > 0 && !$hasTravelerPricing) ? $discountPercent : null,
349 'pricing_type' => $pricingType,
350 'is_traveler_based' => $hasTravelerPricing,
351 'currency' => $tripObj->currency ?? 'USD',
352 'location' => $location,
353 'duration' => $duration,
354 'duration_days' => $durationDays,
355 'difficulty' => $difficulty,
356 'highlights' => $highlights,
357 'rating' => $avgRating,
358 'average_rating' => $avgRating,
359 'reviews' => $reviewsCount, // Frontend expects 'reviews' field
360 'reviews_count' => $reviewsCount,
361 'review_count' => $reviewsCount,
362 'permalink' => $permalink,
363 ];
364 // If trip not found or not published, skip it (don't show in saved trips)
365 }
366
367 // Don't update user meta here - only remove trips when user explicitly removes them
368 // This prevents clearing saved trips if they're temporarily unpublished
369
370 // Apply limit
371 if ($limit > 0 && count($validTrips) > $limit) {
372 $validTrips = array_slice($validTrips, 0, $limit);
373 }
374
375 return $validTrips;
376 }
377
378 /**
379 * Get count of saved trips for user
380 *
381 * @param int $userId User ID
382 * @return int
383 */
384 public function getCount(int $userId): int
385 {
386 return count($this->getUserSavedTrips($userId));
387 }
388 }
389
390