PluginProbe
Yatra – Travel Booking & Tour Operator Software / 3.0.7
Yatra – Travel Booking & Tour Operator Software v3.0.7
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 2.0.11 All 82 releases
yatra / app / Shortcodes / TripCategoryShortcode.php

TripCategoryShortcode.php in Yatra – Travel Booking & Tour Operator Software 3.0.7, at app/Shortcodes/TripCategoryShortcode.php

419 lines 15.1 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\Shortcodes;
6
7 use Yatra\Helpers\TripListingFilterBuilder;
8 use Yatra\Services\SettingsService;
9
10 /**
11 * Trip category listing shortcode — same card UI as {@see DestinationShortcode}.
12 */
13 class TripCategoryShortcode extends BaseShortcode
14 {
15 public function __construct()
16 {
17 parent::__construct('yatra_trip_category', [
18 'order' => 'desc',
19 'per_page' => '10',
20 'columns' => '3',
21 'show_trip_count' => 'yes',
22 'show_description' => 'yes',
23 'show_image' => 'yes',
24 'show_pagination' => 'yes',
25 'category' => '', // Classification IDs, comma-separated
26 // hide_empty defaults to 'no' to preserve the historical
27 // behavior (show every category, even ones with zero
28 // trips). Operators that prefer the empty-archive defense
29 // opt in with hide_empty="yes".
30 'hide_empty' => 'no',
31 'featured_only' => 'no',
32 'title' => 'Trip Categories',
33 ]);
34 }
35
36 protected function renderContent(array $atts): string
37 {
38 $atts = shortcode_atts($this->default_attributes, $atts, $this->tag);
39
40 $per_page = 10;
41 if (!empty($atts['per_page']) && is_numeric($atts['per_page'])) {
42 $per_page = (int) $atts['per_page'];
43 }
44 $atts['per_page'] = $per_page;
45
46 $categories_data = $this->getCategories($atts);
47
48 $data = [
49 'categories' => $categories_data['categories'] ?? [],
50 'atts' => $atts,
51 'current_page' => $categories_data['current_page'] ?? 1,
52 'max_pages' => $categories_data['max_pages'] ?? 1,
53 'total_found' => $categories_data['total_found'] ?? 0,
54 'per_page' => $per_page,
55 ];
56
57 $destinationCssPath = YATRA_PLUGIN_PATH . 'assets/css/shortcodes/destination-shortcode.css';
58 $destinationCssVer = is_readable($destinationCssPath) ? YATRA_VERSION . '.' . filemtime($destinationCssPath) : YATRA_VERSION;
59 wp_enqueue_style(
60 'yatra-destination-shortcode',
61 YATRA_PLUGIN_URL . 'assets/css/shortcodes/destination-shortcode.css',
62 \Yatra\Providers\FrontendAssetsProvider::shortcodeStyleDependencies(),
63 $destinationCssVer
64 );
65
66 wp_enqueue_script(
67 'yatra-trip-category-shortcode',
68 YATRA_PLUGIN_URL . 'assets/js/trip-category-shortcode.js',
69 ['jquery'],
70 YATRA_VERSION,
71 true
72 );
73
74 wp_localize_script('yatra-trip-category-shortcode', 'yatraTripCategoryShortcode', [
75 'ajaxurl' => admin_url('admin-ajax.php'),
76 'nonce' => wp_create_nonce('yatra_trip_category_shortcode_nonce'),
77 ]);
78
79 return $this->loadTemplate('shortcodes/trip-category.php', $data);
80 }
81
82 /**
83 * @return array{categories: array, current_page: int, max_pages: int, total_found: int, per_page: int}
84 */
85 public function getCategories(array $atts): array
86 {
87 try {
88 $categoryService = new \Yatra\Services\TripCategoryService();
89
90 $current_page = isset($atts['current_page'])
91 ? (int) $atts['current_page']
92 : (isset($_GET['trip_category_page']) ? (int) $_GET['trip_category_page'] : 1);
93
94 $per_page = !empty($atts['per_page']) ? (int) $atts['per_page'] : 10;
95 $per_page = max(1, min($per_page, 100));
96 $offset = ($current_page - 1) * $per_page;
97
98 $args = [
99 'limit' => $per_page,
100 'offset' => $offset,
101 'order_by' => 'name',
102 'order' => ($atts['order'] ?? 'desc') === 'asc' ? 'ASC' : 'DESC',
103 'where' => [
104 'status' => 'publish',
105 ],
106 ];
107
108 TripListingFilterBuilder::applyTaxonomyWhere(
109 $args['where'],
110 $atts,
111 'categoryIds',
112 'category_ids',
113 'category'
114 );
115
116 $count_args = $args;
117 unset($count_args['limit'], $count_args['offset']);
118 $total_categories = $categoryService->count($count_args);
119
120 $result = $categoryService->getAll($args);
121
122 $categories = [];
123
124 foreach ($result as $categoryData) {
125 global $wpdb;
126
127 $tripClassificationsTable = \Yatra\Database\Tables\TripClassificationsTable::getTableName();
128 $tripsTable = \Yatra\Database\Tables\TripsTable::getTableName();
129
130 $trip_ids = $wpdb->get_col($wpdb->prepare(
131 "SELECT tc.trip_id
132 FROM {$tripClassificationsTable} tc
133 INNER JOIN {$tripsTable} t ON tc.trip_id = t.id
134 WHERE tc.classification_id = %d
135 AND tc.classification_type = 'category'
136 AND t.status = 'publish'",
137 $categoryData->id
138 ));
139
140 $trip_count = count($trip_ids);
141
142 $trips = [];
143 if (!empty($trip_ids)) {
144 $placeholders = implode(',', array_fill(0, count($trip_ids), '%d'));
145 $trips = $wpdb->get_results($wpdb->prepare(
146 "SELECT * FROM {$tripsTable}
147 WHERE id IN ({$placeholders})
148 AND status = 'publish'
149 ORDER BY created_at DESC
150 LIMIT 6",
151 ...$trip_ids
152 ));
153 }
154
155 $min_price = null;
156 $max_price = null;
157 $durations = [];
158 $group_sizes = [];
159 $best_seasons = [];
160
161 $total_rating_sum = 0;
162 $total_review_count = 0;
163
164 if (!empty($trip_ids)) {
165 $reviewsTable = \Yatra\Database\Tables\ReviewsTable::getTableName();
166 $placeholders = implode(',', array_fill(0, count($trip_ids), '%d'));
167
168 $reviews = $wpdb->get_results($wpdb->prepare(
169 "SELECT rating, COUNT(*) as review_count
170 FROM {$reviewsTable}
171 WHERE trip_id IN ({$placeholders})
172 AND status = 'approved'",
173 ...$trip_ids
174 ));
175
176 foreach ($reviews as $review) {
177 $total_rating_sum += $review->rating * $review->review_count;
178 $total_review_count += $review->review_count;
179 }
180 }
181
182 $avg_rating = $total_review_count > 0 ? $total_rating_sum / $total_review_count : 0;
183
184 foreach ($trips as $trip) {
185 $effective = \Yatra\Services\TripPricingService::getEffectivePrice($trip);
186 if ($effective > 0) {
187 if ($min_price === null || $effective < $min_price) {
188 $min_price = $effective;
189 }
190 if ($max_price === null || $effective > $max_price) {
191 $max_price = $effective;
192 }
193 }
194 if (!empty($trip->duration)) {
195 $durations[] = $trip->duration;
196 }
197 if (!empty($trip->max_group_size)) {
198 $group_sizes[] = $trip->max_group_size;
199 }
200 if (!empty($trip->best_season)) {
201 $best_seasons[] = $trip->best_season;
202 }
203 }
204
205 $final_avg_rating = $avg_rating;
206 $avg_duration = !empty($durations) ? array_sum($durations) / count($durations) : 0;
207 $avg_group_size = !empty($group_sizes) ? round(array_sum($group_sizes) / count($group_sizes)) : 0;
208 $best_season = !empty($best_seasons) ? $this->getMostCommonSeason($best_seasons) : 'Summer';
209
210 $categories[] = [
211 'term' => $categoryData,
212 'trips' => $trips,
213 'trip_count' => $trip_count,
214 'description' => $categoryData->description ?? '',
215 'image' => $this->getCategoryImage($categoryData, $trips),
216 'link' => $this->getCategoryLink($categoryData),
217 'min_price' => $min_price,
218 'max_price' => $max_price,
219 'avg_rating' => $final_avg_rating,
220 'rating_count' => $total_review_count,
221 'avg_duration' => $avg_duration,
222 'avg_group_size' => $avg_group_size,
223 'best_season' => $best_season,
224 ];
225 }
226
227 // Filter out trip categories that have no published trips.
228 //
229 // See ActivityShortcode for the full rationale — the
230 // prior implementation only filtered on term-metadata
231 // emptiness (which never actually fires), so empty
232 // categories were rendered with "0 trips" badges and
233 // broken archive links. We now drop any category whose
234 // trip_count (computed above from
235 // TripClassificationsTable JOIN TripsTable WHERE
236 // status=publish) is zero, plus the original sanity
237 // check on name/slug.
238 if (($atts['hide_empty'] ?? 'yes') === 'yes') {
239 $categories = array_filter($categories, static function ($row) {
240 return (int) ($row['trip_count'] ?? 0) > 0
241 && !empty($row['term']->name)
242 && !empty($row['term']->slug);
243 });
244 }
245
246 if (($atts['featured_only'] ?? 'no') === 'yes') {
247 $categories = array_filter($categories, static function ($row) {
248 $t = $row['term'];
249
250 return (isset($t->is_featured) && (int) $t->is_featured === 1)
251 || (isset($t->featured) && (int) $t->featured === 1);
252 });
253 }
254
255 $max_pages = $per_page > 0 ? (int) ceil($total_categories / $per_page) : 1;
256
257 return [
258 'categories' => $categories,
259 'current_page' => $current_page,
260 'max_pages' => max(1, $max_pages),
261 'total_found' => $total_categories,
262 'per_page' => $per_page,
263 ];
264 } catch (\Exception $e) {
265 if (defined('WP_DEBUG') && WP_DEBUG) {
266 }
267
268 return [
269 'categories' => [],
270 'current_page' => 1,
271 'max_pages' => 1,
272 'total_found' => 0,
273 'per_page' => (int) ($atts['per_page'] ?? 10),
274 ];
275 }
276 }
277
278 private function getMostCommonSeason(array $seasons): string
279 {
280 if (empty($seasons)) {
281 return 'Summer';
282 }
283 $counts = array_count_values($seasons);
284 arsort($counts);
285
286 return array_key_first($counts);
287 }
288
289 private function getImageUrlFromCategoryIcon($icon): string
290 {
291 if ($icon === null || $icon === '') {
292 return '';
293 }
294
295 if (is_string($icon)) {
296 $decoded = json_decode($icon, true);
297 if (is_array($decoded)) {
298 $icon = $decoded;
299 } else {
300 $icon = maybe_unserialize($icon);
301 }
302 }
303
304 if (!is_array($icon)) {
305 return '';
306 }
307
308 $type = $icon['type'] ?? $icon[0] ?? '';
309 $value = $icon['value'] ?? $icon[1] ?? '';
310
311 if ($type !== 'image' || $value === '' || $value === null) {
312 return '';
313 }
314
315 if (is_numeric($value)) {
316 $url = wp_get_attachment_image_url((int) $value, 'large');
317
318 return $url ?: '';
319 }
320
321 if (is_string($value) && filter_var($value, FILTER_VALIDATE_URL)) {
322 return $value;
323 }
324
325 return '';
326 }
327
328 /**
329 * @return array<string, mixed>
330 */
331 private function decodeCategoryMetadata($raw): array
332 {
333 if ($raw === null || $raw === '') {
334 return [];
335 }
336 if (is_array($raw)) {
337 return $raw;
338 }
339 if (!is_string($raw)) {
340 return [];
341 }
342 $decoded = json_decode($raw, true);
343 if (is_array($decoded)) {
344 return $decoded;
345 }
346 $unserialized = maybe_unserialize($raw);
347
348 return is_array($unserialized) ? $unserialized : [];
349 }
350
351 /**
352 * @param object $category Row from classifications (type category)
353 * @param array<int, object> $trips
354 */
355 private function getCategoryImage($category, array $trips = []): string
356 {
357 if (isset($category->image) && !empty($category->image)) {
358 return $category->image;
359 }
360 if (isset($category->banner) && !empty($category->banner)) {
361 return $category->banner;
362 }
363 if (isset($category->thumbnail) && !empty($category->thumbnail)) {
364 return is_numeric($category->thumbnail)
365 ? (string) wp_get_attachment_url((int) $category->thumbnail)
366 : $category->thumbnail;
367 }
368 if (isset($category->cover_image) && !empty($category->cover_image)) {
369 return $category->cover_image;
370 }
371 if (isset($category->hero_image) && !empty($category->hero_image)) {
372 return $category->hero_image;
373 }
374
375 $fromIcon = $this->getImageUrlFromCategoryIcon($category->icon ?? null);
376 if ($fromIcon !== '') {
377 return $fromIcon;
378 }
379
380 if (isset($category->metadata) && $category->metadata !== '') {
381 $metadata = $this->decodeCategoryMetadata($category->metadata);
382 if ($metadata !== []) {
383 $image_fields = ['image', 'thumbnail', 'banner', 'featured_image', 'cover_image', 'hero_image', 'image_id'];
384 foreach ($image_fields as $field) {
385 if (!empty($metadata[$field])) {
386 $val = $metadata[$field];
387
388 return is_numeric($val) ? (string) wp_get_attachment_url((int) $val) : (string) $val;
389 }
390 }
391 }
392 }
393
394 foreach ($trips as $trip) {
395 if (!empty($trip->featured_image) && is_numeric($trip->featured_image)) {
396 $url = wp_get_attachment_image_url((int) $trip->featured_image, 'large');
397 if ($url !== false && $url !== '') {
398 return $url;
399 }
400 }
401 }
402
403 return YATRA_PLUGIN_URL . 'assets/images/placeholder.png';
404 }
405
406 private function getCategoryLink($category): string
407 {
408 if (isset($category->slug) && function_exists('yatra_get_category_permalink')) {
409 $url = yatra_get_category_permalink($category);
410
411 return $url !== '' ? $url : '#';
412 }
413
414 $base = SettingsService::getTripCategoryBase();
415
416 return isset($category->slug) ? home_url('/' . $base . '/' . $category->slug . '/') : '#';
417 }
418 }
419