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/Models/Trip.php +113 -34 3.0.2.9 → 3.0.16 View file →
@@ -233,8 +233,17 @@
233 233 public array $tags = [];
234 234 public array $included_items = [];
235 235 public array $excluded_items = [];
236 236 public array $gallery_images = [];
237 + // Media arrays computed in SingleTripController::prepareTrip(). These MUST be
238 + // declared so Trip::fromStdClass() (which only copies declared properties via
239 + // property_exists) carries them onto the model — otherwise the single-trip
240 + // hero media switcher reads empty arrays and the Video / 360° / Documents
241 + // buttons never render even when video_url / virtual_tour_url are set.
242 + public array $videos = [];
243 + public array $youtube_videos = [];
244 + public array $virtual_tours = [];
245 + public array $documents = [];
237 246 public array $price_types = [];
238 247 public array $itinerary_days = [];
239 248 public array $faqs = [];
240 249 public array $frontend_tabs = [];
@@ -1017,80 +1026,127 @@
1017 1026 ];
1018 1027 }
1019 1028
1020 1029 /**
1021 - * Get difficulty information
1030 + * Resolve difficulty label + icon for display (shared by single trip, similar trips, etc.).
1031 + *
1032 + * @param string|null $difficultyLevel Raw trip.difficulty_level (usually classification row id).
1033 + * @param string|null $difficultyName Optional joined name from list queries.
1034 + * @param mixed|null $difficultyIcon Optional serialized icon payload from list queries.
1035 + * @return array{level: string, icon: string, icon_picker: array<string, mixed>|null, has_difficulty: bool}
1022 1036 */
1023 - public function getDifficulty(): array
1024 - {
1037 + public static function resolveDifficultyDisplay(
1038 + ?string $difficultyLevel,
1039 + ?string $difficultyName = null,
1040 + $difficultyIcon = null
1041 + ): array {
1025 1042 $difficulty = '';
1026 1043 $difficulty_icon = '';
1027 -
1028 - // Always prioritize fetching from difficulty_levels table if we have a difficulty_level ID
1029 - // Check if difficulty_level is set and is a valid positive integer (not 0, null, or empty)
1030 - if (!empty($this->difficulty_level) && is_numeric($this->difficulty_level) && (int) $this->difficulty_level > 0) {
1044 + /** @var array<string, mixed>|null $icon_picker */
1045 + $icon_picker = null;
1046 +
1047 + if (!empty($difficultyLevel) && is_numeric($difficultyLevel) && (int) $difficultyLevel > 0) {
1031 1048 global $wpdb;
1032 -
1049 +
1033 1050 $difficulty_data = $wpdb->get_row($wpdb->prepare(
1034 - "SELECT * FROM " . ClassificationsTable::getTableName() . " WHERE id = %d AND type = %s",
1035 - (int) $this->difficulty_level,
1051 + 'SELECT * FROM ' . ClassificationsTable::getTableName() . ' WHERE id = %d AND type = %s',
1052 + (int) $difficultyLevel,
1036 1053 ClassificationTypes::DIFFICULTY
1037 1054 ));
1038 -
1055 +
1039 1056 if ($difficulty_data) {
1040 - $difficulty = $difficulty_data->name;
1057 + $difficulty = (string) $difficulty_data->name;
1041 1058 if (!empty($difficulty_data->icon)) {
1042 1059 $icon_data = maybe_unserialize($difficulty_data->icon);
1043 - if (is_array($icon_data) && isset($icon_data['type']) && $icon_data['type'] === 'icon' && !empty($icon_data['value'])) {
1044 - $difficulty_icon = $icon_data['value'];
1060 + if (is_array($icon_data) && isset($icon_data['type'])) {
1061 + $icon_picker = $icon_data;
1062 + if ($icon_data['type'] === 'icon' && !empty($icon_data['value'])) {
1063 + $difficulty_icon = (string) $icon_data['value'];
1064 + }
1045 1065 } elseif (is_string($difficulty_data->icon)) {
1046 1066 $difficulty_icon = $difficulty_data->icon;
1067 + $icon_picker = [
1068 + 'type' => 'icon',
1069 + 'value' => $difficulty_icon,
1070 + 'provider' => 'yatra',
1071 + ];
1047 1072 }
1048 1073 }
1049 1074 }
1050 1075 }
1051 -
1052 - // Fallback: Use pre-populated difficulty_name from main query (AppServiceProvider)
1053 - if (empty($difficulty) && !empty($this->difficulty_name)) {
1054 - $difficulty = $this->difficulty_name;
1076 +
1077 + if ($difficulty === '' && !empty($difficultyName)) {
1078 + $difficulty = $difficultyName;
1055 1079 }
1056 -
1057 - // Fallback: Use pre-populated difficulty_icon from main query (AppServiceProvider)
1058 - if (empty($difficulty_icon) && !empty($this->difficulty_icon)) {
1059 - $icon_data = maybe_unserialize($this->difficulty_icon);
1060 - if (is_array($icon_data) && isset($icon_data['type']) && $icon_data['type'] === 'icon' && !empty($icon_data['value'])) {
1061 - $difficulty_icon = $icon_data['value'];
1062 - } elseif (is_string($this->difficulty_icon)) {
1063 - $difficulty_icon = $this->difficulty_icon;
1080 +
1081 + // Legacy / non-id values stored in difficulty_level (e.g. slug text).
1082 + if ($difficulty === '' && $difficultyLevel !== null && $difficultyLevel !== '' && !is_numeric($difficultyLevel)) {
1083 + $difficulty = ucfirst($difficultyLevel);
1084 + }
1085 +
1086 + if ($icon_picker === null && $difficultyIcon !== null && $difficultyIcon !== '') {
1087 + $icon_data = is_string($difficultyIcon) ? maybe_unserialize($difficultyIcon) : $difficultyIcon;
1088 + if (is_array($icon_data) && isset($icon_data['type'])) {
1089 + $icon_picker = $icon_data;
1090 + if ($icon_data['type'] === 'icon' && !empty($icon_data['value'])) {
1091 + $difficulty_icon = (string) $icon_data['value'];
1092 + }
1093 + } elseif (is_string($difficultyIcon)) {
1094 + $difficulty_icon = $difficultyIcon;
1095 + $icon_picker = [
1096 + 'type' => 'icon',
1097 + 'value' => $difficulty_icon,
1098 + 'provider' => 'yatra',
1099 + ];
1064 1100 }
1065 1101 }
1066 -
1067 - // If we have difficulty but no icon, provide a default icon
1068 - if (!empty($difficulty) && empty($difficulty_icon)) {
1102 +
1103 + if ($difficulty !== '' && $icon_picker === null && $difficulty_icon === '') {
1069 1104 $difficulty_icon = 'mountain';
1105 + $icon_picker = [
1106 + 'type' => 'icon',
1107 + 'value' => 'mountain',
1108 + 'provider' => 'yatra',
1109 + ];
1070 1110 }
1071 -
1111 +
1072 1112 return [
1073 1113 'level' => $difficulty,
1074 1114 'icon' => $difficulty_icon,
1075 - 'has_difficulty' => !empty($difficulty)
1115 + 'icon_picker' => $icon_picker,
1116 + 'has_difficulty' => $difficulty !== '',
1076 1117 ];
1077 1118 }
1078 1119
1079 1120 /**
1121 + * Get difficulty information
1122 + */
1123 + public function getDifficulty(): array
1124 + {
1125 + return self::resolveDifficultyDisplay(
1126 + $this->difficulty_level,
1127 + $this->difficulty_name,
1128 + $this->difficulty_icon
1129 + );
1130 + }
1131 +
1132 + /**
1080 1133 * Get trip duration information
1081 1134 */
1082 1135 public function getDuration(): array
1083 1136 {
1084 1137 $duration = '';
1085 - if (!empty($this->duration_days)) {
1138 + if ($this->isHoursBased()) {
1139 + $duration = yatra_format_duration(0, null, $this->getDurationHours());
1140 + } elseif (!empty($this->duration_days)) {
1086 1141 $duration = yatra_format_duration($this->duration_days, $this->duration_nights ?? null);
1087 1142 }
1088 -
1143 +
1089 1144 return [
1090 1145 'formatted' => $duration,
1091 1146 'days' => $this->duration_days ?? 0,
1092 1147 'nights' => $this->duration_nights ?? 0,
1148 + 'hours' => $this->getDurationHours(),
1093 1149 'has_duration' => !empty($duration)
1094 1150 ];
1095 1151 }
1096 1152
@@ -1510,9 +1566,32 @@
1510 1566 return $this->duration_nights ?? 0;
1511 1567 }
1512 1568
1513 1569 /**
1514 - * Get difficulty level
1570 + * Duration in hours for hour-based (single-day) tours. 0 when the tour is
1571 + * day-based (the default) — see {@see self::isHoursBased()}.
1572 + */
1573 + public function getDurationHours(): int
1574 + {
1575 + return (int) ($this->duration_hours ?? 0);
1576 + }
1577 +
1578 + /**
1579 + * Whether this tour's duration is expressed in hours rather than days.
1580 + *
1581 + * True only when a positive `duration_hours` is set — so every existing
1582 + * (day-based) trip returns false and keeps its current behaviour. Used by
1583 + * the front-end duration display and the Google Calendar module to build a
1584 + * same-day timed event instead of a multi-day span.
1585 + */
1586 + public function isHoursBased(): bool
1587 + {
1588 + return $this->getDurationHours() > 0;
1589 + }
1590 +
1591 + /**
1592 + * Raw difficulty classification ID on the trip row (not a human-readable label).
1593 + * For display, use {@see self::getDifficulty()} which resolves the name from the classifications table.
1515 1594 */
1516 1595 public function getDifficultyLevel(): string
1517 1596 {
1518 1597 return $this->difficulty_level ?? '';