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/BlockDataService.php +262 -75 3.0.2.6 → 3.0.16 View file →
@@ -3,10 +3,13 @@
3 3 declare(strict_types=1);
4 4
5 5 namespace Yatra\Services;
6 6
7 +use Yatra\Helpers\TripListingFilterBuilder;
8 +use Yatra\Repositories\TripRepository;
7 9 use Yatra\Shortcodes\ActivityShortcode;
8 10 use Yatra\Shortcodes\DestinationShortcode;
11 +use Yatra\Shortcodes\TripCategoryShortcode;
9 12
10 13 /**
11 14 * Block Data Service
12 15 *
@@ -30,16 +33,55 @@
30 33 {
31 34 return [
32 35 'order' => 'desc',
33 36 'featured' => false,
37 + 'featured_priority' => '',
38 + 'featuredPriority' => '',
34 39 'per_page' => 10,
35 40 'columns' => 3,
36 41 'title' => 'Our Trips',
37 42 'show_pagination' => true,
43 + // Per-instance card layout. 'inherit' = use the site-wide setting.
44 + 'card_layout' => 'inherit',
45 + 'cardLayout' => '',
46 + 'destinationIds' => [],
47 + 'activityIds' => [],
48 + 'categoryIds' => [],
49 + 'difficultyIds' => [],
50 + 'destination' => '',
51 + 'activity' => '',
52 + 'category' => '',
53 + 'destination_ids' => '',
54 + 'activity_ids' => '',
55 + 'category_ids' => '',
56 + 'difficulty' => '',
57 + 'price_min' => '',
58 + 'price_max' => '',
59 + 'duration_min' => '',
60 + 'duration_max' => '',
61 + 'search' => '',
38 62 ];
39 63 }
40 64
65 + /** Allowed values for the trip "Featured Priority" filter (matches admin TripForm). */
66 + private const FEATURED_PRIORITY_VALUES = ['featured', 'new', 'limited'];
67 +
41 68 /**
69 + * Trip listing for shortcodes, AJAX pagination, and programmatic use.
70 + *
71 + * @param array<string, mixed> $rawAtts
72 + *
73 + * @return array{trips: \Yatra\Models\Trip[], max_pages: int, current_page: int, total_found: int}
74 + */
75 + public static function getTripListingForShortcode(array $rawAtts): array
76 + {
77 + $atts = wp_parse_args(is_array($rawAtts) ? $rawAtts : [], self::defaultTourBlockAttributes());
78 + self::normalizeTripShortcodeAttributes($atts);
79 +
80 + return self::queryTripsForAtts($atts);
81 + }
82 +
83 + /**
42 84 * @return array<string, mixed>
43 85 */
44 86 private static function defaultActivityBlockAttributes(): array
45 87 {
@@ -48,8 +90,15 @@
48 90 'columns' => 3,
49 91 'per_page' => 10,
50 92 'title' => 'Activity Listings',
51 93 'show_pagination' => true,
94 + 'activityIds' => [],
95 + 'activity' => '',
96 + 'activity_ids' => '',
97 + 'show_trip_count' => true,
98 + 'show_description' => true,
99 + 'show_image' => true,
100 + 'hide_empty' => false,
52 101 ];
53 102 }
54 103
55 104 /**
@@ -62,12 +111,42 @@
62 111 'columns' => 3,
63 112 'per_page' => 10,
64 113 'title' => 'Destination Showcase',
65 114 'show_pagination' => true,
115 + 'destinationIds' => [],
116 + 'destination' => '',
117 + 'destination_ids' => '',
118 + 'show_trip_count' => true,
119 + 'show_description' => true,
120 + 'show_image' => true,
121 + 'hide_empty' => false,
122 + 'featured_only' => false,
66 123 ];
67 124 }
68 125
69 126 /**
127 + * @return array<string, mixed>
128 + */
129 + private static function defaultTripCategoryBlockAttributes(): array
130 + {
131 + return [
132 + 'order' => 'desc',
133 + 'columns' => 3,
134 + 'per_page' => 10,
135 + 'title' => 'Trip Categories',
136 + 'show_pagination' => true,
137 + 'category' => '',
138 + 'show_trip_count' => true,
139 + 'show_description' => true,
140 + 'show_image' => true,
141 + 'hide_empty' => false,
142 + 'featured_only' => false,
143 + 'categoryIds' => [],
144 + 'category_ids' => '',
145 + ];
146 + }
147 +
148 + /**
70 149 * Coerce REST/block booleans and legacy string values.
71 150 *
72 151 * @param mixed $value
73 152 */
@@ -110,8 +189,39 @@
110 189
111 190 $atts['title'] = sanitize_text_field((string) ($atts['title'] ?? 'Our Trips'));
112 191
113 192 $atts['show_pagination'] = self::coerceToBool($atts['show_pagination'] ?? true, true) ? 'yes' : 'no';
193 +
194 + // Card layout override. Accept camelCase (Gutenberg block: cardLayout)
195 + // and snake_case (shortcode: card_layout). Unknown/empty/"inherit" keeps
196 + // 'inherit' so the template falls back to the site-wide Design setting.
197 + $cardLayoutRaw = '';
198 + if (isset($atts['cardLayout']) && is_string($atts['cardLayout']) && $atts['cardLayout'] !== '') {
199 + $cardLayoutRaw = $atts['cardLayout'];
200 + } elseif (isset($atts['card_layout']) && is_string($atts['card_layout']) && $atts['card_layout'] !== '') {
201 + $cardLayoutRaw = $atts['card_layout'];
202 + }
203 + $cardLayoutRaw = strtolower(trim($cardLayoutRaw));
204 + $atts['card_layout'] = in_array($cardLayoutRaw, ['standard', 'compact_mobile', 'compact_all'], true)
205 + ? $cardLayoutRaw
206 + : 'inherit';
207 +
208 + // Featured Priority (matches admin form: featured | new | limited; "none"/empty = no filter).
209 + // Accept both snake_case (shortcode) and camelCase (Gutenberg block attribute).
210 + $featuredPriorityRaw = '';
211 + if (isset($atts['featured_priority']) && is_string($atts['featured_priority']) && $atts['featured_priority'] !== '') {
212 + $featuredPriorityRaw = $atts['featured_priority'];
213 + } elseif (isset($atts['featuredPriority']) && is_string($atts['featuredPriority']) && $atts['featuredPriority'] !== '') {
214 + $featuredPriorityRaw = $atts['featuredPriority'];
215 + }
216 + $featuredPriority = strtolower(trim((string) $featuredPriorityRaw));
217 + if ($featuredPriority === 'none') {
218 + $featuredPriority = '';
219 + }
220 + if ($featuredPriority !== '' && !in_array($featuredPriority, self::FEATURED_PRIORITY_VALUES, true)) {
221 + $featuredPriority = '';
222 + }
223 + $atts['featured_priority'] = $featuredPriority;
114 224 }
115 225
116 226 /**
117 227 * Render trip/tour - Shared method for both blocks and shortcodes
@@ -125,9 +235,9 @@
125 235 $atts = wp_parse_args(is_array($attributes) ? $attributes : [], self::defaultTourBlockAttributes());
126 236 self::normalizeTripShortcodeAttributes($atts);
127 237
128 238 // Get trips using Yatra's service
129 - $trips_data = self::getTrips($atts);
239 + $trips_data = self::queryTripsForAtts($atts);
130 240
131 241 $per_page = max(1, (int) ($atts['per_page'] ?? 10));
132 242 $atts['per_page'] = $per_page;
133 243
@@ -151,29 +261,16 @@
151 261 self::enqueueTripAssets();
152 262
153 263 // Load template
154 264 $result = self::loadTemplate('shortcodes/trip.php', $data);
265 +
155 266
156 - if (defined('WP_DEBUG') && WP_DEBUG) {
157 - error_log('Yatra BlockDataService: Render successful, result length: ' . strlen($result));
158 - }
159 -
160 267 return $result;
161 268 } catch (\Exception $e) {
162 - // Return error message for debugging
163 - if (defined('WP_DEBUG') && WP_DEBUG) {
164 - error_log('Yatra BlockDataService Exception: ' . $e->getMessage());
165 - error_log('Yatra BlockDataService Exception Trace: ' . $e->getTraceAsString());
166 - return '<div class="yatra-error">Trip rendering error: ' . esc_html($e->getMessage()) . ' in ' . esc_html($e->getFile()) . ':' . esc_html($e->getLine()) . '</div>';
167 - }
269 +
168 270 return '<div class="yatra-error">Trip rendering failed</div>';
169 271 } catch (\Error $e) {
170 - // Catch fatal errors too
171 - if (defined('WP_DEBUG') && WP_DEBUG) {
172 - error_log('Yatra BlockDataService Error: ' . $e->getMessage());
173 - error_log('Yatra BlockDataService Error Trace: ' . $e->getTraceAsString());
174 - return '<div class="yatra-error">Trip rendering error: ' . esc_html($e->getMessage()) . ' in ' . esc_html($e->getFile()) . ':' . esc_html($e->getLine()) . '</div>';
175 - }
272 +
176 273 return '<div class="yatra-error">Trip rendering failed</div>';
177 274 }
178 275 }
179 276
@@ -192,10 +289,18 @@
192 289 * Enqueue trip assets
193 290 */
194 291 private static function enqueueTripAssets(): void
195 292 {
196 - wp_enqueue_style('yatra-trip-shortcode', \YATRA_PLUGIN_URL . 'assets/css/shortcodes/trip-shortcode.css', array(), '3.0.2.4');
197 - wp_enqueue_script('yatra-trip-shortcode', \YATRA_PLUGIN_URL . 'assets/js/trip-shortcode.js', array('jquery'), '3.0.2.4', true);
293 + \Yatra\Providers\FrontendAssetsProvider::registerCoreFrontendStylesheets();
294 + $cssPath = \YATRA_PLUGIN_PATH . 'assets/css/shortcodes/trip-shortcode.css';
295 + $cssVer = is_readable($cssPath) ? \YATRA_VERSION . '.' . filemtime($cssPath) : \YATRA_VERSION;
296 + wp_enqueue_style(
297 + 'yatra-trip-shortcode',
298 + \YATRA_PLUGIN_URL . 'assets/css/shortcodes/trip-shortcode.css',
299 + \Yatra\Providers\FrontendAssetsProvider::shortcodeStyleDependencies(),
300 + $cssVer
301 + );
302 + wp_enqueue_script('yatra-trip-shortcode', \YATRA_PLUGIN_URL . 'assets/js/trip-shortcode.js', array('jquery'), \YATRA_VERSION, true);
198 303
199 304 // Localize script for AJAX
200 305 wp_localize_script('yatra-trip-shortcode', 'yatraTripShortcode', [
201 306 'ajaxurl' => admin_url('admin-ajax.php'),
@@ -230,85 +335,58 @@
230 335 return ob_get_clean();
231 336 }
232 337
233 338 /**
234 - * Get trips data - copied from TripShortcode
339 + * @param array<string, mixed> $atts
340 + *
341 + * @return array{trips: \Yatra\Models\Trip[], max_pages: int, current_page: int, total_found: int}
235 342 */
236 - private static function getTrips(array $atts): array
343 + private static function queryTripsForAtts(array $atts): array
237 344 {
238 345 try {
239 - $tripService = new \Yatra\Services\TripService();
240 -
241 - // Get current page from query string or attributes (for AJAX)
242 - $current_page = isset($atts['current_page']) ? (int) $atts['current_page'] : (isset($_GET['trip_page']) ? (int) $_GET['trip_page'] : 1);
243 - $per_page = max(1, (int) ($atts['per_page'] ?? 10));
244 - $offset = ($current_page - 1) * $per_page;
346 + $tripRepository = new TripRepository();
245 347
246 - $order = strtolower((string) ($atts['order'] ?? 'desc'));
348 + $current_page = isset($atts['current_page'])
349 + ? (int) $atts['current_page']
350 + : (isset($_GET['trip_page']) ? (int) $_GET['trip_page'] : 1);
351 + $current_page = max(1, $current_page);
247 352
248 - // Start with very basic arguments to ensure we get trips
249 - $args = [
250 - 'limit' => $per_page,
251 - 'offset' => $offset,
252 - 'order_by' => 'created_at',
253 - 'order' => $order === 'asc' ? 'ASC' : 'DESC',
254 - ];
353 + $per_page = max(1, (int) ($atts['per_page'] ?? 10));
255 354
256 - // Add featured filter if requested
257 - $featured = (string) ($atts['featured'] ?? '0');
258 - if ($featured === '1') {
259 - $args['where']['is_featured'] = 1;
260 - }
355 + $filters = TripListingFilterBuilder::buildFindWithFiltersArray($atts);
356 + $result = $tripRepository->findWithFilters($filters, $current_page, $per_page);
261 357
262 - // Get total count for pagination
263 - $count_args = $args;
264 - unset($count_args['limit']);
265 - unset($count_args['offset']);
266 - $total_trips = $tripService->count($count_args);
267 -
268 - // Get trips using the service
269 - $trips_data = $tripService->getActiveTrips($args);
270 -
271 -
272 -
273 358 $trips = [];
274 - foreach ($trips_data as $tripData) {
275 - // Convert to Trip model
359 + foreach (($result['trips'] ?? []) as $tripData) {
276 360 $trip = \Yatra\Models\Trip::fromStdClass($tripData);
277 -
278 - // Add basic data needed for the card
279 - // Note: bookings_count and reviews will be added later
280 - // For now, we'll set default values to ensure basic functionality
281 - $trip->bookings_count = 0;
361 + if (isset($tripData->booking_count)) {
362 + $trip->bookings_count = (int) $tripData->booking_count;
363 + }
364 + if (isset($tripData->review_count)) {
365 + $trip->reviews_count = (int) $tripData->review_count;
366 + }
367 + if (isset($tripData->average_rating)) {
368 + $ar = (float) $tripData->average_rating;
369 + $trip->average_rating = $ar;
370 + $trip->avg_rating = $ar;
371 + }
282 372 $trip->reviews = [];
283 -
373 +
284 374 $trips[] = $trip;
285 375 }
286 -
287 - // Calculate pagination data
288 - $max_pages = $per_page > 0 ? ceil($total_trips / $per_page) : 1;
289 376
290 377 return [
291 378 'trips' => $trips,
292 - 'max_pages' => $max_pages,
293 - 'current_page' => $current_page,
294 - 'total_found' => $total_trips,
295 - 'debug_info' => [
296 - 'args_used' => $args,
297 - 'raw_count' => count($trips_data)
298 - ]
379 + 'max_pages' => max(1, (int) ($result['pages'] ?? 1)),
380 + 'current_page' => max(1, (int) ($result['page'] ?? $current_page)),
381 + 'total_found' => (int) ($result['total'] ?? 0),
299 382 ];
300 -
301 383 } catch (\Exception $e) {
302 - if (defined('WP_DEBUG') && WP_DEBUG) {
303 - error_log('Yatra BlockDataService Trip Error: ' . $e->getMessage());
304 - }
305 -
306 384 return [
307 385 'trips' => [],
308 386 'max_pages' => 1,
309 387 'current_page' => 1,
310 - 'total_found' => 0
388 + 'total_found' => 0,
311 389 ];
312 390 }
313 391 }
314 392
@@ -346,8 +424,35 @@
346 424 return $shortcode->render(self::mapDestinationAttributes($merged));
347 425 }
348 426
349 427 /**
428 + * Render trip category block (same card UI as destinations).
429 + *
430 + * @param array<string, mixed> $attributes
431 + */
432 + public static function renderTripCategoryBlock(array $attributes): string
433 + {
434 + $shortcode = new TripCategoryShortcode();
435 + $merged = wp_parse_args(is_array($attributes) ? $attributes : [], self::defaultTripCategoryBlockAttributes());
436 +
437 + return $shortcode->render(self::mapTripCategoryAttributes($merged));
438 + }
439 +
440 + /**
441 + * @param array<string, mixed> $attributes
442 + * @param string ...$legacyCsvKeys
443 + */
444 + private static function classificationIdsCsvForShortcode(
445 + array $attributes,
446 + string $arrayKey,
447 + string ...$legacyCsvKeys
448 + ): string {
449 + $ids = TripListingFilterBuilder::positiveIntIdsFromAtts($attributes, $arrayKey, ...$legacyCsvKeys);
450 +
451 + return $ids === [] ? '' : implode(',', $ids);
452 + }
453 +
454 + /**
350 455 * Map activity block attributes to shortcode format (full set for shortcode_atts merge).
351 456 *
352 457 * @param array<string, mixed> $attributes Merged with defaults
353 458 * @return array<string, string>
@@ -365,8 +470,17 @@
365 470
366 471 $cols = max(1, min(self::LISTING_COLUMNS_MAX, (int) ($attributes['columns'] ?? 3)));
367 472
368 473 $showPag = self::coerceToBool($attributes['show_pagination'] ?? true, true);
474 + // Forward the same visibility / hide-empty toggles the
475 + // trip-category block has exposed since v3.0 so all three
476 + // taxonomy blocks share one user-facing surface. hide_empty
477 + // is what actually skips activities with zero published
478 + // trips (see ActivityShortcode::getActivities).
479 + $showTripCount = self::coerceToBool($attributes['show_trip_count'] ?? true, true);
480 + $showDescription = self::coerceToBool($attributes['show_description'] ?? true, true);
481 + $showImage = self::coerceToBool($attributes['show_image'] ?? true, true);
482 + $hideEmpty = self::coerceToBool($attributes['hide_empty'] ?? true, true);
369 483
370 484 return [
371 485 'order' => $order,
372 486 'per_page' => (string) $perPage,
@@ -372,8 +486,18 @@
372 486 'per_page' => (string) $perPage,
373 487 'columns' => (string) $cols,
374 488 'title' => sanitize_text_field((string) ($attributes['title'] ?? 'Activity Listings')),
375 489 'show_pagination' => $showPag ? 'yes' : 'no',
490 + 'activity' => self::classificationIdsCsvForShortcode(
491 + $attributes,
492 + 'activityIds',
493 + 'activity_ids',
494 + 'activity'
495 + ),
496 + 'show_trip_count' => $showTripCount ? 'yes' : 'no',
497 + 'show_description' => $showDescription ? 'yes' : 'no',
498 + 'show_image' => $showImage ? 'yes' : 'no',
499 + 'hide_empty' => $hideEmpty ? 'yes' : 'no',
376 500 ];
377 501 }
378 502
379 503 /**
@@ -393,8 +517,16 @@
393 517
394 518 $cols = max(1, min(self::LISTING_COLUMNS_MAX, (int) ($attributes['columns'] ?? 3)));
395 519
396 520 $showPag = self::coerceToBool($attributes['show_pagination'] ?? true, true);
521 + // Forward the visibility + hide_empty + featured_only toggles
522 + // so the destination block exposes the same controls the
523 + // [yatra_destination] shortcode already accepts.
524 + $showTripCount = self::coerceToBool($attributes['show_trip_count'] ?? true, true);
525 + $showDescription = self::coerceToBool($attributes['show_description'] ?? true, true);
526 + $showImage = self::coerceToBool($attributes['show_image'] ?? true, true);
527 + $hideEmpty = self::coerceToBool($attributes['hide_empty'] ?? true, true);
528 + $featuredOnly = self::coerceToBool($attributes['featured_only'] ?? false, false);
397 529
398 530 return [
399 531 'order' => $order,
400 532 'per_page' => (string) $perPage,
@@ -400,7 +532,62 @@
400 532 'per_page' => (string) $perPage,
401 533 'columns' => (string) $cols,
402 534 'title' => sanitize_text_field((string) ($attributes['title'] ?? 'Destination Showcase')),
403 535 'show_pagination' => $showPag ? 'yes' : 'no',
536 + 'destination' => self::classificationIdsCsvForShortcode(
537 + $attributes,
538 + 'destinationIds',
539 + 'destination_ids',
540 + 'destination'
541 + ),
542 + 'show_trip_count' => $showTripCount ? 'yes' : 'no',
543 + 'show_description' => $showDescription ? 'yes' : 'no',
544 + 'show_image' => $showImage ? 'yes' : 'no',
545 + 'hide_empty' => $hideEmpty ? 'yes' : 'no',
546 + 'featured_only' => $featuredOnly ? 'yes' : 'no',
547 + ];
548 + }
549 +
550 + /**
551 + * @param array<string, mixed> $attributes
552 + * @return array<string, string>
553 + */
554 + private static function mapTripCategoryAttributes(array $attributes): array
555 + {
556 + $order = strtolower((string) ($attributes['order'] ?? 'desc'));
557 + $order = in_array($order, ['asc', 'desc'], true) ? $order : 'desc';
558 +
559 + $perPage = (int) ($attributes['per_page'] ?? 10);
560 + if ($perPage === -1) {
561 + $perPage = 10;
562 + }
563 + $perPage = max(1, min(100, $perPage));
564 +
565 + $cols = max(1, min(self::LISTING_COLUMNS_MAX, (int) ($attributes['columns'] ?? 3)));
566 +
567 + $showPag = self::coerceToBool($attributes['show_pagination'] ?? true, true);
568 + $showTripCount = self::coerceToBool($attributes['show_trip_count'] ?? true, true);
569 + $showDescription = self::coerceToBool($attributes['show_description'] ?? true, true);
570 + $showImage = self::coerceToBool($attributes['show_image'] ?? true, true);
571 + $hideEmpty = self::coerceToBool($attributes['hide_empty'] ?? true, true);
572 + $featuredOnly = self::coerceToBool($attributes['featured_only'] ?? false, false);
573 +
574 + return [
575 + 'order' => $order,
576 + 'per_page' => (string) $perPage,
577 + 'columns' => (string) $cols,
578 + 'title' => sanitize_text_field((string) ($attributes['title'] ?? 'Trip Categories')),
579 + 'show_pagination' => $showPag ? 'yes' : 'no',
580 + 'category' => self::classificationIdsCsvForShortcode(
581 + $attributes,
582 + 'categoryIds',
583 + 'category_ids',
584 + 'category'
585 + ),
586 + 'show_trip_count' => $showTripCount ? 'yes' : 'no',
587 + 'show_description' => $showDescription ? 'yes' : 'no',
588 + 'show_image' => $showImage ? 'yes' : 'no',
589 + 'hide_empty' => $hideEmpty ? 'yes' : 'no',
590 + 'featured_only' => $featuredOnly ? 'yes' : 'no',
404 591 ];
405 592 }
406 593 }