PluginProbe
Yatra – Travel Booking & Tour Operator Software / 3.0.17
Yatra – Travel Booking & Tour Operator Software v3.0.17
3.0.17 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 All 85 releases
← All changes | app/Repositories/TripRepository.php +107 -13 3.0.9 → 3.0.17 View file →
@@ -18,8 +18,9 @@
18 18 use Yatra\Utils\Cache;
19 19 use Yatra\Utils\QueryCache;
20 20 use Yatra\Database\Tables\TripAvailabilityDatesTable;
21 21 use Yatra\Database\Tables\TripAvailabilityRulesTable;
22 +use Yatra\Database\Tables\DeparturesTable;
22 23 use Yatra\Constants\ClassificationTypes;
23 24
24 25 /**
25 26 * Trip Repository
@@ -270,8 +271,9 @@
270 271 * Create trip row; {@see afterWrite} handles cache; fires action `yatra_trip_created` once per insert.
271 272 */
272 273 public function create(array $data): int
273 274 {
275 + $data = $this->dropUnsupportedDurationHours($data);
274 276 $id = parent::create($data);
275 277 do_action('yatra_trip_created', $id);
276 278
277 279 return $id;
@@ -277,12 +279,32 @@
277 279 return $id;
278 280 }
279 281
280 282 /**
283 + * Defensive: if the `duration_hours` column is somehow missing (a failed or
284 + * pending column-add migration on a restrictive host), drop it from the
285 + * write set so a normal trip save still succeeds instead of erroring on an
286 + * unknown column. The hour-based feature simply stays off until the column
287 + * exists — it never breaks saving.
288 + *
289 + * @param array<string, mixed> $data
290 + * @return array<string, mixed>
291 + */
292 + protected function dropUnsupportedDurationHours(array $data): array
293 + {
294 + if (array_key_exists('duration_hours', $data) && !$this->tripTableHasColumn('duration_hours')) {
295 + unset($data['duration_hours']);
296 + }
297 +
298 + return $data;
299 + }
300 +
301 + /**
281 302 * Override update method to provide proper field formats
282 303 */
283 304 public function update(int $id, array $data): bool
284 305 {
306 + $data = $this->dropUnsupportedDurationHours($data);
285 307 $data = $this->sanitizeData($data);
286 308 $data['updated_at'] = current_time('mysql');
287 309
288 310 // Build format array based on field types
@@ -400,8 +422,21 @@
400 422 return self::$tripColumnExistsCache[$column];
401 423 }
402 424
403 425 /**
426 + * Public, cached column-existence check for the trips table.
427 + *
428 + * Lets raw SELECTs outside this repository (booking confirmation join,
429 + * similar-trips query) add columns introduced by later upgrades — e.g.
430 + * `duration_hours` — without failing on an install whose ALTER has not
431 + * run yet (see InstallerService::maybeAddTripDurationHoursColumn()).
432 + */
433 + public function hasTripColumn(string $column): bool
434 + {
435 + return $this->tripTableHasColumn($column);
436 + }
437 +
438 + /**
404 439 * SQL expression for trip "current" list price (matches TripPricingService::resolveRegularCurrentPrice).
405 440 */
406 441 protected function sqlTripEffectiveListPrice(): string
407 442 {
@@ -656,20 +691,21 @@
656 691 foreach (array_unique($filters['age_suitability']) as $age) {
657 692 if (!is_string($age)) {
658 693 continue;
659 694 }
695 + $ageLimits = self::ageSuitabilityThresholds();
660 696 switch ($age) {
661 697 case 'family-friendly':
662 - $ageParts[] = '(t.age_min IS NULL OR t.age_min <= 5)';
698 + $ageParts[] = '(t.age_min IS NULL OR t.age_min <= ' . (int) $ageLimits['family_max'] . ')';
663 699 break;
664 700 case 'kids-friendly':
665 - $ageParts[] = '(t.age_min IS NULL OR t.age_min <= 12)';
701 + $ageParts[] = '(t.age_min IS NULL OR t.age_min <= ' . (int) $ageLimits['kids_max'] . ')';
666 702 break;
667 703 case 'senior-friendly':
668 - $ageParts[] = '(t.age_max IS NULL OR t.age_max >= 65)';
704 + $ageParts[] = '(t.age_max IS NULL OR t.age_max >= ' . (int) $ageLimits['senior_min'] . ')';
669 705 break;
670 706 case 'adults-only':
671 - $ageParts[] = 't.age_min >= 18';
707 + $ageParts[] = 't.age_min >= ' . (int) $ageLimits['adults_min'];
672 708 break;
673 709 }
674 710 }
675 711 if ($ageParts !== []) {
@@ -810,9 +846,29 @@
810 846 if (!empty($filters['duration_max']) && $filters['duration_max'] > 0) {
811 847 $wheres[] = "CAST(t.duration_days AS UNSIGNED) <= %d";
812 848 $params[] = $filters['duration_max'];
813 849 }
814 -
850 +
851 + // Availability date filter ("available from this date onward"): keep only
852 + // trips that have a departure starting ON OR AFTER the selected date, so
853 + // a customer who picks a date sees every trip they could still travel on
854 + // from then. A departure matches when its effective start date (the
855 + // explicit start_date, else the single `date`) is >= the chosen day and
856 + // it has not been cancelled — the date bound alone excludes past dates
857 + // when the picker's min=today is respected, so no reliance on a possibly
858 + // stale 'past' status. Clause + param are appended together so the WHERE
859 + // ordering stays in sync with $params (see the count/main queries).
860 + if (!empty($filters['available_date'])) {
861 + $departures_table = DeparturesTable::getTableName();
862 + $wheres[] = "EXISTS (
863 + SELECT 1 FROM {$departures_table} yd
864 + WHERE yd.trip_id = t.id
865 + AND COALESCE(NULLIF(yd.start_date, '0000-00-00'), yd.date) >= %s
866 + AND yd.status <> 'cancelled'
867 + )";
868 + $params[] = $filters['available_date'];
869 + }
870 +
815 871 // Rating filter
816 872 if (!empty($filters['rating_min']) && $filters['rating_min'] > 0) {
817 873 $having_clauses[] = "AVG(r.rating) >= %f";
818 874 $rating_params[] = $filters['rating_min'];
@@ -3675,16 +3731,48 @@
3675 3731 return 0;
3676 3732 }
3677 3733
3678 3734 /**
3735 + * Age thresholds behind the suitability filters.
3736 + *
3737 + * These were repeated as literals in six places — the filter query and the
3738 + * count query for each band — so an operator whose "kids" means under 18
3739 + * rather than under 12 had no way to say so, and changing it meant editing
3740 + * the same number in six spots and hoping none were missed.
3741 + *
3742 + * @return array{family_max:int, kids_max:int, senior_min:int, adults_min:int}
3743 + */
3744 + public static function ageSuitabilityThresholds(): array
3745 + {
3746 + $defaults = [
3747 + 'family_max' => 5,
3748 + 'kids_max' => 12,
3749 + 'senior_min' => 65,
3750 + 'adults_min' => 18,
3751 + ];
3752 +
3753 + $filtered = (array) apply_filters('yatra_age_suitability_thresholds', $defaults);
3754 +
3755 + foreach ($defaults as $key => $fallback) {
3756 + $filtered[$key] = isset($filtered[$key]) && is_numeric($filtered[$key])
3757 + ? (int) $filtered[$key]
3758 + : $fallback;
3759 + }
3760 +
3761 + return $filtered;
3762 + }
3763 +
3764 + /**
3679 3765 * Count family friendly trips
3680 3766 */
3681 3767 public function countByFamilyFriendly(): int
3682 3768 {
3683 3769 $table = $this->getTableName();
3770 + $limit = (int) self::ageSuitabilityThresholds()['family_max'];
3771 +
3684 3772 return (int) $this->wpdb->get_var(
3685 - "SELECT COUNT(*) FROM {$table}
3686 - WHERE status = 'publish' AND (age_min IS NULL OR age_min <= 5)"
3773 + "SELECT COUNT(*) FROM {$table}
3774 + WHERE status = 'publish' AND (age_min IS NULL OR age_min <= {$limit})"
3687 3775 );
3688 3776 }
3689 3777
3690 3778 /**
@@ -3692,11 +3780,13 @@
3692 3780 */
3693 3781 public function countByKidsFriendly(): int
3694 3782 {
3695 3783 $table = $this->getTableName();
3784 + $limit = (int) self::ageSuitabilityThresholds()['kids_max'];
3785 +
3696 3786 return (int) $this->wpdb->get_var(
3697 - "SELECT COUNT(*) FROM {$table}
3698 - WHERE status = 'publish' AND (age_min IS NULL OR age_min <= 12)"
3787 + "SELECT COUNT(*) FROM {$table}
3788 + WHERE status = 'publish' AND (age_min IS NULL OR age_min <= {$limit})"
3699 3789 );
3700 3790 }
3701 3791
3702 3792 /**
@@ -3704,11 +3794,13 @@
3704 3794 */
3705 3795 public function countBySeniorFriendly(): int
3706 3796 {
3707 3797 $table = $this->getTableName();
3798 + $limit = (int) self::ageSuitabilityThresholds()['senior_min'];
3799 +
3708 3800 return (int) $this->wpdb->get_var(
3709 - "SELECT COUNT(*) FROM {$table}
3710 - WHERE status = 'publish' AND (age_max IS NULL OR age_max >= 65)"
3801 + "SELECT COUNT(*) FROM {$table}
3802 + WHERE status = 'publish' AND (age_max IS NULL OR age_max >= {$limit})"
3711 3803 );
3712 3804 }
3713 3805
3714 3806 /**
@@ -3716,11 +3808,13 @@
3716 3808 */
3717 3809 public function countByAdultsOnly(): int
3718 3810 {
3719 3811 $table = $this->getTableName();
3812 + $limit = (int) self::ageSuitabilityThresholds()['adults_min'];
3813 +
3720 3814 return (int) $this->wpdb->get_var(
3721 - "SELECT COUNT(*) FROM {$table}
3722 - WHERE status = 'publish' AND age_min >= 18"
3815 + "SELECT COUNT(*) FROM {$table}
3816 + WHERE status = 'publish' AND age_min >= {$limit}"
3723 3817 );
3724 3818 }
3725 3819
3726 3820 /**