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 +82 -7 3.0.2.7 → 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 {
@@ -1232,9 +1261,14 @@
1232 1261 * Count departures by date
1233 1262 */
1234 1263 public function countDeparturesByDate(int $tripId, string $date): int
1235 1264 {
1236 - 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);
1237 1271 }
1238 1272
1239 1273 /**
1240 1274 * Get trip with availability
@@ -1251,8 +1285,49 @@
1251 1285 * @return array ['min_price' => float, 'max_price' => float]
1252 1286 */
1253 1287 public function getTripPriceRange(int $tripId): array
1254 1288 {
1255 - $tripPriceTypeRepository = new \Yatra\Repositories\TripPriceTypeRepository();
1256 - 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];
1257 1332 }
1258 1333 }