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 +178 -72 3.0.3 → 3.0.16 View file →
@@ -3,8 +3,10 @@
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;
9 11 use Yatra\Shortcodes\TripCategoryShortcode;
10 12
@@ -31,16 +33,55 @@
31 33 {
32 34 return [
33 35 'order' => 'desc',
34 36 'featured' => false,
37 + 'featured_priority' => '',
38 + 'featuredPriority' => '',
35 39 'per_page' => 10,
36 40 'columns' => 3,
37 41 'title' => 'Our Trips',
38 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' => '',
39 62 ];
40 63 }
41 64
65 + /** Allowed values for the trip "Featured Priority" filter (matches admin TripForm). */
66 + private const FEATURED_PRIORITY_VALUES = ['featured', 'new', 'limited'];
67 +
42 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 + /**
43 84 * @return array<string, mixed>
44 85 */
45 86 private static function defaultActivityBlockAttributes(): array
46 87 {
@@ -49,8 +90,15 @@
49 90 'columns' => 3,
50 91 'per_page' => 10,
51 92 'title' => 'Activity Listings',
52 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,
53 101 ];
54 102 }
55 103
56 104 /**
@@ -63,8 +111,16 @@
63 111 'columns' => 3,
64 112 'per_page' => 10,
65 113 'title' => 'Destination Showcase',
66 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,
67 123 ];
68 124 }
69 125
70 126 /**
@@ -81,10 +137,12 @@
81 137 'category' => '',
82 138 'show_trip_count' => true,
83 139 'show_description' => true,
84 140 'show_image' => true,
85 - 'hide_empty' => true,
141 + 'hide_empty' => false,
86 142 'featured_only' => false,
143 + 'categoryIds' => [],
144 + 'category_ids' => '',
87 145 ];
88 146 }
89 147
90 148 /**
@@ -131,8 +189,39 @@
131 189
132 190 $atts['title'] = sanitize_text_field((string) ($atts['title'] ?? 'Our Trips'));
133 191
134 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;
135 224 }
136 225
137 226 /**
138 227 * Render trip/tour - Shared method for both blocks and shortcodes
@@ -146,9 +235,9 @@
146 235 $atts = wp_parse_args(is_array($attributes) ? $attributes : [], self::defaultTourBlockAttributes());
147 236 self::normalizeTripShortcodeAttributes($atts);
148 237
149 238 // Get trips using Yatra's service
150 - $trips_data = self::getTrips($atts);
239 + $trips_data = self::queryTripsForAtts($atts);
151 240
152 241 $per_page = max(1, (int) ($atts['per_page'] ?? 10));
153 242 $atts['per_page'] = $per_page;
154 243
@@ -246,96 +335,58 @@
246 335 return ob_get_clean();
247 336 }
248 337
249 338 /**
250 - * 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}
251 342 */
252 - private static function getTrips(array $atts): array
343 + private static function queryTripsForAtts(array $atts): array
253 344 {
254 345 try {
255 - $tripService = new \Yatra\Services\TripService();
256 -
257 - // Get current page from query string or attributes (for AJAX)
258 - $current_page = isset($atts['current_page']) ? (int) $atts['current_page'] : (isset($_GET['trip_page']) ? (int) $_GET['trip_page'] : 1);
259 - $per_page = max(1, (int) ($atts['per_page'] ?? 10));
260 - $offset = ($current_page - 1) * $per_page;
346 + $tripRepository = new TripRepository();
261 347
262 - $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);
263 352
264 - // Start with very basic arguments to ensure we get trips
265 - $args = [
266 - 'limit' => $per_page,
267 - 'offset' => $offset,
268 - 'order_by' => 'created_at',
269 - 'order' => $order === 'asc' ? 'ASC' : 'DESC',
270 - ];
353 + $per_page = max(1, (int) ($atts['per_page'] ?? 10));
271 354
272 - // Add featured filter if requested
273 - $featured = (string) ($atts['featured'] ?? '0');
274 - if ($featured === '1') {
275 - $args['where']['is_featured'] = 1;
276 - }
355 + $filters = TripListingFilterBuilder::buildFindWithFiltersArray($atts);
356 + $result = $tripRepository->findWithFilters($filters, $current_page, $per_page);
277 357
278 - // Get total count for pagination
279 - $count_args = $args;
280 - unset($count_args['limit']);
281 - unset($count_args['offset']);
282 - $total_trips = $tripService->count($count_args);
283 -
284 - // Get trips using the service
285 - $trips_data = $tripService->getActiveTrips($args);
286 -
287 -
288 -
289 358 $trips = [];
290 - foreach ($trips_data as $tripData) {
291 - // Convert to Trip model
359 + foreach (($result['trips'] ?? []) as $tripData) {
292 360 $trip = \Yatra\Models\Trip::fromStdClass($tripData);
293 -
294 - // Add basic data needed for the card
295 - // 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 + }
296 372 $trip->reviews = [];
297 -
373 +
298 374 $trips[] = $trip;
299 375 }
300 376
301 - // Attach bookings_count (computed from bookings table) for just these trips
302 - $tripIds = array_map(static function ($t) {
303 - return isset($t->id) ? (int) $t->id : 0;
304 - }, $trips);
305 - $tripIds = array_values(array_filter($tripIds));
306 - if (!empty($tripIds)) {
307 - $bookingsCountMap = $tripService->getBookingsCountMap($tripIds);
308 - foreach ($trips as $t) {
309 - $tId = isset($t->id) ? (int) $t->id : 0;
310 - if ($tId > 0) {
311 - $t->bookings_count = (int) ($bookingsCountMap[$tId] ?? 0);
312 - }
313 - }
314 - }
315 -
316 - // Calculate pagination data
317 - $max_pages = $per_page > 0 ? ceil($total_trips / $per_page) : 1;
318 -
319 377 return [
320 378 'trips' => $trips,
321 - 'max_pages' => $max_pages,
322 - 'current_page' => $current_page,
323 - 'total_found' => $total_trips,
324 - 'debug_info' => [
325 - 'args_used' => $args,
326 - 'raw_count' => count($trips_data)
327 - ]
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),
328 382 ];
329 -
330 383 } catch (\Exception $e) {
331 -
332 -
333 384 return [
334 385 'trips' => [],
335 386 'max_pages' => 1,
336 387 'current_page' => 1,
337 - 'total_found' => 0
388 + 'total_found' => 0,
338 389 ];
339 390 }
340 391 }
341 392
@@ -386,8 +437,22 @@
386 437 return $shortcode->render(self::mapTripCategoryAttributes($merged));
387 438 }
388 439
389 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 + /**
390 455 * Map activity block attributes to shortcode format (full set for shortcode_atts merge).
391 456 *
392 457 * @param array<string, mixed> $attributes Merged with defaults
393 458 * @return array<string, string>
@@ -405,8 +470,17 @@
405 470
406 471 $cols = max(1, min(self::LISTING_COLUMNS_MAX, (int) ($attributes['columns'] ?? 3)));
407 472
408 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);
409 483
410 484 return [
411 485 'order' => $order,
412 486 'per_page' => (string) $perPage,
@@ -412,8 +486,18 @@
412 486 'per_page' => (string) $perPage,
413 487 'columns' => (string) $cols,
414 488 'title' => sanitize_text_field((string) ($attributes['title'] ?? 'Activity Listings')),
415 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',
416 500 ];
417 501 }
418 502
419 503 /**
@@ -433,8 +517,16 @@
433 517
434 518 $cols = max(1, min(self::LISTING_COLUMNS_MAX, (int) ($attributes['columns'] ?? 3)));
435 519
436 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);
437 529
438 530 return [
439 531 'order' => $order,
440 532 'per_page' => (string) $perPage,
@@ -440,8 +532,19 @@
440 532 'per_page' => (string) $perPage,
441 533 'columns' => (string) $cols,
442 534 'title' => sanitize_text_field((string) ($attributes['title'] ?? 'Destination Showcase')),
443 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',
444 547 ];
445 548 }
446 549
447 550 /**
@@ -467,10 +570,8 @@
467 570 $showImage = self::coerceToBool($attributes['show_image'] ?? true, true);
468 571 $hideEmpty = self::coerceToBool($attributes['hide_empty'] ?? true, true);
469 572 $featuredOnly = self::coerceToBool($attributes['featured_only'] ?? false, false);
470 573
471 - $category = isset($attributes['category']) ? sanitize_text_field((string) $attributes['category']) : '';
472 -
473 574 return [
474 575 'order' => $order,
475 576 'per_page' => (string) $perPage,
476 577 'columns' => (string) $cols,
@@ -475,9 +576,14 @@
475 576 'per_page' => (string) $perPage,
476 577 'columns' => (string) $cols,
477 578 'title' => sanitize_text_field((string) ($attributes['title'] ?? 'Trip Categories')),
478 579 'show_pagination' => $showPag ? 'yes' : 'no',
479 - 'category' => $category,
580 + 'category' => self::classificationIdsCsvForShortcode(
581 + $attributes,
582 + 'categoryIds',
583 + 'category_ids',
584 + 'category'
585 + ),
480 586 'show_trip_count' => $showTripCount ? 'yes' : 'no',
481 587 'show_description' => $showDescription ? 'yes' : 'no',
482 588 'show_image' => $showImage ? 'yes' : 'no',
483 589 'hide_empty' => $hideEmpty ? 'yes' : 'no',