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 +259 -70 3.0.2.9 → 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
@@ -179,10 +289,18 @@
179 289 * Enqueue trip assets
180 290 */
181 291 private static function enqueueTripAssets(): void
182 292 {
183 - wp_enqueue_style('yatra-trip-shortcode', \YATRA_PLUGIN_URL . 'assets/css/shortcodes/trip-shortcode.css', array(), '3.0.2.4');
184 - 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);
185 303
186 304 // Localize script for AJAX
187 305 wp_localize_script('yatra-trip-shortcode', 'yatraTripShortcode', [
188 306 'ajaxurl' => admin_url('admin-ajax.php'),
@@ -217,96 +335,58 @@
217 335 return ob_get_clean();
218 336 }
219 337
220 338 /**
221 - * 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}
222 342 */
223 - private static function getTrips(array $atts): array
343 + private static function queryTripsForAtts(array $atts): array
224 344 {
225 345 try {
226 - $tripService = new \Yatra\Services\TripService();
227 -
228 - // Get current page from query string or attributes (for AJAX)
229 - $current_page = isset($atts['current_page']) ? (int) $atts['current_page'] : (isset($_GET['trip_page']) ? (int) $_GET['trip_page'] : 1);
230 - $per_page = max(1, (int) ($atts['per_page'] ?? 10));
231 - $offset = ($current_page - 1) * $per_page;
346 + $tripRepository = new TripRepository();
232 347
233 - $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);
234 352
235 - // Start with very basic arguments to ensure we get trips
236 - $args = [
237 - 'limit' => $per_page,
238 - 'offset' => $offset,
239 - 'order_by' => 'created_at',
240 - 'order' => $order === 'asc' ? 'ASC' : 'DESC',
241 - ];
353 + $per_page = max(1, (int) ($atts['per_page'] ?? 10));
242 354
243 - // Add featured filter if requested
244 - $featured = (string) ($atts['featured'] ?? '0');
245 - if ($featured === '1') {
246 - $args['where']['is_featured'] = 1;
247 - }
355 + $filters = TripListingFilterBuilder::buildFindWithFiltersArray($atts);
356 + $result = $tripRepository->findWithFilters($filters, $current_page, $per_page);
248 357
249 - // Get total count for pagination
250 - $count_args = $args;
251 - unset($count_args['limit']);
252 - unset($count_args['offset']);
253 - $total_trips = $tripService->count($count_args);
254 -
255 - // Get trips using the service
256 - $trips_data = $tripService->getActiveTrips($args);
257 -
258 -
259 -
260 358 $trips = [];
261 - foreach ($trips_data as $tripData) {
262 - // Convert to Trip model
359 + foreach (($result['trips'] ?? []) as $tripData) {
263 360 $trip = \Yatra\Models\Trip::fromStdClass($tripData);
264 -
265 - // Add basic data needed for the card
266 - // Note: reviews are loaded elsewhere; bookings_count is attached below
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 + }
267 372 $trip->reviews = [];
268 -
373 +
269 374 $trips[] = $trip;
270 375 }
271 376
272 - // Attach bookings_count (computed from bookings table) for just these trips
273 - $tripIds = array_map(static function ($t) {
274 - return isset($t->id) ? (int) $t->id : 0;
275 - }, $trips);
276 - $tripIds = array_values(array_filter($tripIds));
277 - if (!empty($tripIds)) {
278 - $bookingsCountMap = $tripService->getBookingsCountMap($tripIds);
279 - foreach ($trips as $t) {
280 - $tId = isset($t->id) ? (int) $t->id : 0;
281 - if ($tId > 0) {
282 - $t->bookings_count = (int) ($bookingsCountMap[$tId] ?? 0);
283 - }
284 - }
285 - }
286 -
287 - // Calculate pagination data
288 - $max_pages = $per_page > 0 ? ceil($total_trips / $per_page) : 1;
289 -
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 -
303 -
304 384 return [
305 385 'trips' => [],
306 386 'max_pages' => 1,
307 387 'current_page' => 1,
308 - 'total_found' => 0
388 + 'total_found' => 0,
309 389 ];
310 390 }
311 391 }
312 392
@@ -344,8 +424,35 @@
344 424 return $shortcode->render(self::mapDestinationAttributes($merged));
345 425 }
346 426
347 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 + /**
348 455 * Map activity block attributes to shortcode format (full set for shortcode_atts merge).
349 456 *
350 457 * @param array<string, mixed> $attributes Merged with defaults
351 458 * @return array<string, string>
@@ -363,8 +470,17 @@
363 470
364 471 $cols = max(1, min(self::LISTING_COLUMNS_MAX, (int) ($attributes['columns'] ?? 3)));
365 472
366 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);
367 483
368 484 return [
369 485 'order' => $order,
370 486 'per_page' => (string) $perPage,
@@ -370,8 +486,18 @@
370 486 'per_page' => (string) $perPage,
371 487 'columns' => (string) $cols,
372 488 'title' => sanitize_text_field((string) ($attributes['title'] ?? 'Activity Listings')),
373 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',
374 500 ];
375 501 }
376 502
377 503 /**
@@ -391,8 +517,16 @@
391 517
392 518 $cols = max(1, min(self::LISTING_COLUMNS_MAX, (int) ($attributes['columns'] ?? 3)));
393 519
394 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);
395 529
396 530 return [
397 531 'order' => $order,
398 532 'per_page' => (string) $perPage,
@@ -398,7 +532,62 @@
398 532 'per_page' => (string) $perPage,
399 533 'columns' => (string) $cols,
400 534 'title' => sanitize_text_field((string) ($attributes['title'] ?? 'Destination Showcase')),
401 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',
402 591 ];
403 592 }
404 593 }