PluginProbe
Yatra – Travel Booking & Tour Operator Software / 3.0.16
Yatra – Travel Booking & Tour Operator Software v3.0.16
3.0.16 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 All 84 releases
← All changes | app/Services/TripService.php +94 -7 3.0.2.6 → 3.0.16 View file →
@@ -192,10 +192,10 @@
192 192 if (empty($data['slug'])) {
193 193 throw new \InvalidArgumentException('Trip slug is required');
194 194 }
195 195
196 - if (!preg_match('/^[a-z0-9-]+$/', $data['slug'])) {
197 - throw new \InvalidArgumentException('Trip slug can only contain lowercase letters, numbers, and hyphens');
196 + if (!preg_match('/^[\pL\pN-]+$/u', $data['slug'])) {
197 + throw new \InvalidArgumentException('Trip slug can only contain letters, numbers, and hyphens');
198 198 }
199 199
200 200 // Check if slug is unique (exclude current trip when updating)
201 201 $existing = $this->repository->findBySlug($data['slug']);
@@ -275,10 +275,10 @@
275 275 * Process before create
276 276 */
277 277 protected function processBeforeCreate(array $data): array
278 278 {
279 - if (!preg_match('/^[a-z0-9-]+$/', $data['slug'])) {
280 - throw new \InvalidArgumentException('Trip slug can only contain lowercase letters, numbers, and hyphens');
279 + if (!preg_match('/^[\pL\pN-]+$/u', $data['slug'])) {
280 + throw new \InvalidArgumentException('Trip slug can only contain letters, numbers, and hyphens');
281 281 }
282 282
283 283 if (empty($data['status'])) {
284 284 $data['status'] = 'draft';
@@ -356,8 +356,13 @@
356 356
357 357 // Set updated_by
358 358 $data['updated_by'] = get_current_user_id();
359 359
360 + // Merge custom_fields: keep existing keys that the incoming payload doesn't touch
361 + if (isset($data['custom_fields']) && is_array($data['custom_fields'])) {
362 + $data['custom_fields'] = $this->mergeCustomFields($id, $data['custom_fields']);
363 + }
364 +
360 365 // Process JSON fields
361 366 $data = $this->processJsonFields($data);
362 367
363 368 // Ensure difficulty_level is stored as integer ID
@@ -368,8 +373,32 @@
368 373 return $data;
369 374 }
370 375
371 376 /**
377 + * Merge incoming custom_fields with what is already stored in the DB for a trip.
378 + * Only keys present in the incoming array are changed; all other existing keys are
379 + * kept intact. This is the "append, never override" contract the UI depends on.
380 + */
381 + private function mergeCustomFields(int $id, array $incoming): array
382 + {
383 + $existing = $this->repository->find($id);
384 + if (!$existing || empty($existing->custom_fields)) {
385 + return $incoming;
386 + }
387 +
388 + $storedRaw = $existing->custom_fields;
389 + if (is_string($storedRaw)) {
390 + $decoded = maybe_unserialize($storedRaw);
391 + if (!is_array($decoded)) {
392 + $decoded = json_decode($storedRaw, true);
393 + }
394 + $storedRaw = is_array($decoded) ? $decoded : [];
395 + }
396 +
397 + return array_merge($storedRaw, $incoming);
398 + }
399 +
400 + /**
372 401 * Process JSON fields - serialize arrays
373 402 */
374 403 private function processJsonFields(array $data): array
375 404 {
@@ -826,8 +855,20 @@
826 855 return $this->repository->getActive($args);
827 856 }
828 857
829 858 /**
859 + * Map trip_id => bookings count for list views.
860 + *
861 + * @param int[] $tripIds
862 + * @param string[]|null $excludeStatuses
863 + * @return array<int,int>
864 + */
865 + public function getBookingsCountMap(array $tripIds, ?array $excludeStatuses = null): array
866 + {
867 + return $this->repository->getBookingsCountMap($tripIds, $excludeStatuses);
868 + }
869 +
870 + /**
830 871 * Search trips
831 872 */
832 873 public function search(string $keyword, array $args = []): array
833 874 {
@@ -1220,9 +1261,14 @@
1220 1261 * Count departures by date
1221 1262 */
1222 1263 public function countDeparturesByDate(int $tripId, string $date): int
1223 1264 {
1224 - return $this->repository->countDeparturesByDate($tripId, $date);
1265 + // The December 2025 service/controller refactor moved this query to
1266 + // TripAvailabilityRepository::countAvailableDeparturesByDate() but left
1267 + // this call pointing at TripRepository, where no such method exists —
1268 + // so the storefront's date-pricing request (fired whenever a customer
1269 + // picks a date) has returned a fatal 500 ever since.
1270 + return $this->availabilityRepository->countAvailableDeparturesByDate($tripId, $date);
1225 1271 }
1226 1272
1227 1273 /**
1228 1274 * Get trip with availability
@@ -1239,8 +1285,49 @@
1239 1285 * @return array ['min_price' => float, 'max_price' => float]
1240 1286 */
1241 1287 public function getTripPriceRange(int $tripId): array
1242 1288 {
1243 - $tripPriceTypeRepository = new \Yatra\Repositories\TripPriceTypeRepository();
1244 - return $tripPriceTypeRepository->getPriceRangeByTripId($tripId);
1289 + // Traveler-based pricing stores per-category prices in `trips.price_types` JSON.
1290 + // The old implementation referenced a repository that may not exist in all builds;
1291 + // compute the range directly from the persisted JSON so list endpoints can't fatal.
1292 + $tripRepository = new \Yatra\Repositories\TripRepository();
1293 + $priceTypes = $tripRepository->getPriceTypes($tripId);
1294 +
1295 + $min = PHP_FLOAT_MAX;
1296 + $max = 0.0;
1297 +
1298 + foreach ($priceTypes as $pt) {
1299 + if (!is_array($pt)) {
1300 + continue;
1301 + }
1302 +
1303 + $discounted = isset($pt['discounted_price']) ? (float) $pt['discounted_price'] : 0.0;
1304 + $sale = isset($pt['sale_price']) ? (float) $pt['sale_price'] : 0.0;
1305 + $original = isset($pt['original_price']) ? (float) $pt['original_price'] : 0.0;
1306 + $legacyPrice = isset($pt['price']) ? (float) $pt['price'] : 0.0;
1307 +
1308 + $effective = 0.0;
1309 + if ($discounted > 0) {
1310 + $effective = $discounted;
1311 + } elseif ($sale > 0) {
1312 + $effective = $sale;
1313 + } elseif ($original > 0) {
1314 + $effective = $original;
1315 + } elseif ($legacyPrice > 0) {
1316 + $effective = $legacyPrice;
1317 + }
1318 +
1319 + if ($effective <= 0) {
1320 + continue;
1321 + }
1322 +
1323 + $min = min($min, $effective);
1324 + $max = max($max, $effective);
1325 + }
1326 +
1327 + if ($min === PHP_FLOAT_MAX) {
1328 + return ['min_price' => 0.0, 'max_price' => 0.0];
1329 + }
1330 +
1331 + return ['min_price' => (float) $min, 'max_price' => (float) $max];
1245 1332 }
1246 1333 }