PluginProbe
Yatra – Travel Booking & Tour Operator Software / 3.0.2.9
Yatra – Travel Booking & Tour Operator Software v3.0.2.9
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 2.0.10 All 83 releases
yatra / app / Shortcodes / DestinationShortcode.php

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

480 lines 17.2 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\Services\SettingsService;
8
9 /**
10 * Destination Shortcode
11 *
12 * Displays destination showcase with associated trips using trip-listing-card.php template
13 */
14 class DestinationShortcode extends BaseShortcode
15 {
16 public function __construct()
17 {
18 parent::__construct('yatra_destination', [
19 'order' => 'desc',
20 'per_page' => '10',
21 'columns' => '3',
22 'show_trip_count' => 'yes',
23 'show_description' => 'yes',
24 'show_image' => 'yes',
25 'show_pagination' => 'yes', // Default to show pagination like trip shortcode
26 'destination' => '', // Specific destination slug(s), comma separated
27 'hide_empty' => 'yes',
28 'featured_only' => 'no',
29 'title' => 'Destination Showcase'
30 ]);
31 }
32
33 /**
34 * Render the destination shortcode content
35 */
36 protected function renderContent(array $atts): string
37 {
38 $atts = shortcode_atts($this->default_attributes, $atts, $this->tag);
39
40 // Extract per_page from attributes (only per_page parameter)
41 $per_page = 10; // default
42 if (!empty($atts['per_page']) && is_numeric($atts['per_page'])) {
43 $per_page = (int) $atts['per_page'];
44 }
45 $atts['per_page'] = $per_page;
46
47 // Get destinations using Yatra's service
48 $destinations_data = $this->getDestinations($atts);
49
50 // Prepare data for template
51 $data = [
52 'destinations' => $destinations_data['destinations'] ?? [],
53 'atts' => $atts,
54 'current_page' => $destinations_data['current_page'] ?? 1,
55 'max_pages' => $destinations_data['max_pages'] ?? 1,
56 'total_found' => $destinations_data['total_found'] ?? 0,
57 'per_page' => $per_page
58 ];
59
60 // Enqueue shortcode-specific CSS
61 wp_enqueue_style(
62 'yatra-destination-shortcode',
63 YATRA_PLUGIN_URL . 'assets/css/shortcodes/destination-shortcode.css',
64 [],
65 YATRA_VERSION
66 );
67
68 // Enqueue shortcode-specific JavaScript
69 wp_enqueue_script(
70 'yatra-destination-shortcode',
71 YATRA_PLUGIN_URL . 'assets/js/destination-shortcode.js',
72 ['jquery'],
73 YATRA_VERSION,
74 true
75 );
76
77 // Pass data to JavaScript
78 wp_localize_script('yatra-destination-shortcode', 'yatraDestinationShortcode', [
79 'ajaxurl' => admin_url('admin-ajax.php'),
80 'nonce' => wp_create_nonce('yatra_destination_shortcode_nonce')
81 ]);
82
83 return $this->loadTemplate('shortcodes/destination.php', $data);
84 }
85
86 /**
87 * Get destinations using Yatra's service
88 */
89 public function getDestinations(array $atts): array
90 {
91 try {
92 $destinationService = new \Yatra\Services\DestinationService();
93
94 // Get current page from query string or attributes (for AJAX)
95 $current_page = isset($atts['current_page']) ? (int) $atts['current_page'] : (isset($_GET['destination_page']) ? (int) $_GET['destination_page'] : 1);
96 // Use per_page parameter only
97 $per_page = 10; // Default fallback
98 if (!empty($atts['per_page'])) {
99 $per_page = (int) $atts['per_page'];
100
101 } else {
102
103 }
104
105 // Validate per_page to prevent issues
106 $per_page = max(1, min($per_page, 100)); // Between 1 and 100 items
107 $offset = ($current_page - 1) * $per_page;
108
109 // Start with very basic arguments to ensure we get destinations
110 $args = [
111 'limit' => $per_page,
112 'offset' => $offset,
113 'order_by' => 'name',
114 'order' => $atts['order'] === 'asc' ? 'ASC' : 'DESC'
115 ];
116
117 // Filter by specific destinations if provided
118 if (!empty($atts['destination'])) {
119 $args['where']['slug'] = explode(',', $atts['destination']);
120 }
121
122 // Get total count for pagination
123 $count_args = $args;
124 unset($count_args['limit']);
125 unset($count_args['offset']);
126 $total_destinations = $destinationService->count($count_args);
127
128 // Try using the base repository method to bypass status filtering
129 $result = $destinationService->getAll($args);
130
131
132 $destinations = [];
133
134 foreach ($result as $destinationData) {
135 // Get real trip data for this destination using classification tables
136 global $wpdb;
137
138 $tripClassificationsTable = \Yatra\Database\Tables\TripClassificationsTable::getTableName();
139 $tripsTable = \Yatra\Database\Tables\TripsTable::getTableName();
140
141 // Get trip IDs for this destination
142 $trip_ids = $wpdb->get_col($wpdb->prepare(
143 "SELECT tc.trip_id
144 FROM {$tripClassificationsTable} tc
145 INNER JOIN {$tripsTable} t ON tc.trip_id = t.id
146 WHERE tc.classification_id = %d
147 AND tc.classification_type = 'destination'
148 AND t.status = 'publish'",
149 $destinationData->id
150 ));
151
152 $trip_count = count($trip_ids);
153
154 // Get actual trips
155 $trips = [];
156 if (!empty($trip_ids)) {
157 $placeholders = implode(',', array_fill(0, count($trip_ids), '%d'));
158 $trips = $wpdb->get_results($wpdb->prepare(
159 "SELECT * FROM {$tripsTable}
160 WHERE id IN ({$placeholders})
161 AND status = 'publish'
162 ORDER BY created_at DESC
163 LIMIT 6",
164 ...$trip_ids
165 ));
166 }
167
168 // Calculate pricing from real trips
169 $min_price = null;
170 $max_price = null;
171 $durations = [];
172 $group_sizes = [];
173 $best_seasons = [];
174
175 // Calculate rating from reviews table directly
176 $total_rating_sum = 0;
177 $total_review_count = 0;
178
179 if (!empty($trip_ids)) {
180 $reviewsTable = \Yatra\Database\Tables\ReviewsTable::getTableName();
181 $placeholders = implode(',', array_fill(0, count($trip_ids), '%d'));
182
183 $reviews = $wpdb->get_results($wpdb->prepare(
184 "SELECT rating, COUNT(*) as review_count
185 FROM {$reviewsTable}
186 WHERE trip_id IN ({$placeholders})
187 AND status = 'approved'",
188 ...$trip_ids
189 ));
190
191
192
193 foreach ($reviews as $review) {
194 $total_rating_sum += $review->rating * $review->review_count;
195 $total_review_count += $review->review_count;
196
197
198 }
199 }
200
201 // Calculate average rating only for trips that actually have reviews
202 // Trips with no reviews are excluded from the average (not treated as 0 rating)
203 $avg_rating = $total_review_count > 0 ? $total_rating_sum / $total_review_count : 0;
204
205 foreach ($trips as $trip) {
206 // Debug: Log all trip data to see what fields exist
207
208 // Get pricing via centralized TripPricingService
209 $effective = \Yatra\Services\TripPricingService::getEffectivePrice($trip);
210 if ($effective > 0) {
211 if ($min_price === null || $effective < $min_price) {
212 $min_price = $effective;
213 }
214 if ($max_price === null || $effective > $max_price) {
215 $max_price = $effective;
216 }
217 }
218
219 // Get duration
220 if (!empty($trip->duration)) {
221 $durations[] = $trip->duration;
222 }
223
224 // Get group size
225 if (!empty($trip->max_group_size)) {
226 $group_sizes[] = $trip->max_group_size;
227 }
228
229 // Get best season
230 if (!empty($trip->best_season)) {
231 $best_seasons[] = $trip->best_season;
232 }
233 }
234
235 // Calculate averages
236 $final_avg_rating = $avg_rating; // Already calculated correctly above
237 $avg_duration = !empty($durations) ? array_sum($durations) / count($durations) : 0;
238 $avg_group_size = !empty($group_sizes) ? round(array_sum($group_sizes) / count($group_sizes)) : 0;
239 $best_season = !empty($best_seasons) ? $this->getMostCommonSeason($best_seasons) : 'Summer';
240
241 if (defined('WP_DEBUG') && WP_DEBUG) {
242
243
244
245
246
247
248
249 }
250
251 $destinations[] = [
252 'term' => $destinationData,
253 'trips' => $trips,
254 'trip_count' => $trip_count,
255 'description' => $destinationData->description ?? '',
256 'image' => $this->getDestinationImage($destinationData, $trips),
257 'link' => $this->getDestinationLink($destinationData),
258 'country' => $destinationData->country ?? '',
259 'region' => $destinationData->region ?? '',
260 'min_price' => $min_price,
261 'max_price' => $max_price,
262 'avg_rating' => $final_avg_rating,
263 'rating_count' => $total_review_count,
264 'avg_duration' => $avg_duration,
265 'avg_group_size' => $avg_group_size,
266 'best_season' => $best_season
267 ];
268 }
269
270 // Filter out empty destinations if requested
271 if ($atts['hide_empty'] === 'yes') {
272 $destinations = array_filter($destinations, function($destination) {
273 return !empty($destination['term']->name) && !empty($destination['term']->slug);
274 });
275 }
276
277 // Filter to show only featured destinations if requested
278 if ($atts['featured_only'] === 'yes') {
279 $destinations = array_filter($destinations, function($destination) {
280 // Check if destination is marked as featured
281 return isset($destination['term']->featured) && $destination['term']->featured == 1;
282 });
283 }
284
285 // Calculate pagination data
286 $max_pages = $per_page > 0 ? ceil($total_destinations / $per_page) : 1;
287
288 return [
289 'destinations' => $destinations,
290 'current_page' => $current_page,
291 'max_pages' => $max_pages,
292 'total_found' => $total_destinations,
293 'per_page' => $per_page
294 ];
295
296 } catch (\Exception $e) {
297 if (defined('WP_DEBUG') && WP_DEBUG) {
298
299 }
300 return [];
301 }
302 }
303
304 /**
305 * Get the most common season from an array of seasons
306 */
307 private function getMostCommonSeason(array $seasons): string
308 {
309 if (empty($seasons)) {
310 return 'Summer';
311 }
312
313 $counts = array_count_values($seasons);
314 arsort($counts);
315 return array_key_first($counts);
316 }
317
318
319 /**
320 * Resolve image URL from destination `icon` (same shape as admin: type image|icon, value = attachment ID or URL).
321 */
322 private function getImageUrlFromDestinationIcon($icon): string
323 {
324 if ($icon === null || $icon === '') {
325 return '';
326 }
327
328 if (is_string($icon)) {
329 $decoded = json_decode($icon, true);
330 if (is_array($decoded)) {
331 $icon = $decoded;
332 } else {
333 $icon = maybe_unserialize($icon);
334 }
335 }
336
337 if (!is_array($icon)) {
338 return '';
339 }
340
341 $type = $icon['type'] ?? $icon[0] ?? '';
342 $value = $icon['value'] ?? $icon[1] ?? '';
343
344 if ($type !== 'image' || $value === '' || $value === null) {
345 return '';
346 }
347
348 if (is_numeric($value)) {
349 $url = wp_get_attachment_image_url((int) $value, 'large');
350
351 return $url ?: '';
352 }
353
354 if (is_string($value) && filter_var($value, FILTER_VALIDATE_URL)) {
355 return $value;
356 }
357
358 return '';
359 }
360
361 /**
362 * Decode metadata column to array (JSON or PHP serialized).
363 *
364 * @return array<string, mixed>
365 */
366 private function decodeDestinationMetadata($raw): array
367 {
368 if ($raw === null || $raw === '') {
369 return [];
370 }
371
372 if (is_array($raw)) {
373 return $raw;
374 }
375
376 if (!is_string($raw)) {
377 return [];
378 }
379
380 $decoded = json_decode($raw, true);
381 if (is_array($decoded)) {
382 return $decoded;
383 }
384
385 $unserialized = maybe_unserialize($raw);
386
387 return is_array($unserialized) ? $unserialized : [];
388 }
389
390 /**
391 * Get destination image URL for shortcode cards.
392 *
393 * @param object $destination Classification row from DB
394 * @param array<int, object> $trips Associated trips (newest first), used for featured_image fallback
395 */
396 private function getDestinationImage($destination, array $trips = []): string
397 {
398 // Check for destination image in metadata
399 if (isset($destination->image) && !empty($destination->image)) {
400 return $destination->image;
401 }
402
403 // Check for destination banner in metadata
404 if (isset($destination->banner) && !empty($destination->banner)) {
405 return $destination->banner;
406 }
407
408 // Check for destination thumbnail/featured image
409 if (isset($destination->thumbnail) && !empty($destination->thumbnail)) {
410 return is_numeric($destination->thumbnail) ? (string) wp_get_attachment_url((int) $destination->thumbnail) : $destination->thumbnail;
411 }
412
413 // Check for destination cover image
414 if (isset($destination->cover_image) && !empty($destination->cover_image)) {
415 return $destination->cover_image;
416 }
417
418 // Check for destination hero image
419 if (isset($destination->hero_image) && !empty($destination->hero_image)) {
420 return $destination->hero_image;
421 }
422
423 $fromIcon = $this->getImageUrlFromDestinationIcon($destination->icon ?? null);
424 if ($fromIcon !== '') {
425 return $fromIcon;
426 }
427
428 // Check for metadata with image (JSON or serialized)
429 if (isset($destination->metadata) && $destination->metadata !== '') {
430 $metadata = $this->decodeDestinationMetadata($destination->metadata);
431 if ($metadata !== []) {
432 $image_fields = ['image', 'thumbnail', 'banner', 'featured_image', 'cover_image', 'hero_image', 'image_id'];
433 foreach ($image_fields as $field) {
434 if (!empty($metadata[$field])) {
435 $val = $metadata[$field];
436
437 return is_numeric($val) ? (string) wp_get_attachment_url((int) $val) : (string) $val;
438 }
439 }
440 }
441 }
442
443 foreach ($trips as $trip) {
444 if (!empty($trip->featured_image) && is_numeric($trip->featured_image)) {
445 $url = wp_get_attachment_image_url((int) $trip->featured_image, 'large');
446 if ($url !== false && $url !== '') {
447 return $url;
448 }
449 }
450 }
451
452 // Fallback to placeholder
453 $fallback_url = YATRA_PLUGIN_URL . 'assets/images/placeholder.png';
454
455 if (defined('WP_DEBUG') && WP_DEBUG) {
456
457 }
458
459 return $fallback_url;
460 }
461
462 /**
463 * Get destination link
464 */
465 private function getDestinationLink($destination): string
466 {
467 if (isset($destination->slug)) {
468 // Use permalink helper so global base + plain permalinks are respected
469 if (function_exists('yatra_get_destination_permalink')) {
470 return yatra_get_destination_permalink($destination);
471 }
472
473 $base = SettingsService::getString('destination_base', 'destination');
474 return home_url('/' . $base . '/' . $destination->slug . '/');
475 }
476
477 return '#'; // Fallback
478 }
479 }
480