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 -88 3.0.2.8 → 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,98 +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: 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 + }
280 372 $trip->reviews = [];
281 -
373 +
282 374 $trips[] = $trip;
283 375 }
284 376
285 - // Attach bookings_count (computed from bookings table) for just these trips
286 - $tripIds = array_map(static function ($t) {
287 - return isset($t->id) ? (int) $t->id : 0;
288 - }, $trips);
289 - $tripIds = array_values(array_filter($tripIds));
290 - if (!empty($tripIds)) {
291 - $bookingsCountMap = $tripService->getBookingsCountMap($tripIds);
292 - foreach ($trips as $t) {
293 - $tId = isset($t->id) ? (int) $t->id : 0;
294 - if ($tId > 0) {
295 - $t->bookings_count = (int) ($bookingsCountMap[$tId] ?? 0);
296 - }
297 - }
298 - }
299 -
300 - // Calculate pagination data
301 - $max_pages = $per_page > 0 ? ceil($total_trips / $per_page) : 1;
302 -
303 377 return [
304 378 'trips' => $trips,
305 - 'max_pages' => $max_pages,
306 - 'current_page' => $current_page,
307 - 'total_found' => $total_trips,
308 - 'debug_info' => [
309 - 'args_used' => $args,
310 - 'raw_count' => count($trips_data)
311 - ]
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),
312 382 ];
313 -
314 383 } catch (\Exception $e) {
315 - if (defined('WP_DEBUG') && WP_DEBUG) {
316 - error_log('Yatra BlockDataService Trip Error: ' . $e->getMessage());
317 - }
318 -
319 384 return [
320 385 'trips' => [],
321 386 'max_pages' => 1,
322 387 'current_page' => 1,
323 - 'total_found' => 0
388 + 'total_found' => 0,
324 389 ];
325 390 }
326 391 }
327 392
@@ -359,8 +424,35 @@
359 424 return $shortcode->render(self::mapDestinationAttributes($merged));
360 425 }
361 426
362 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 + /**
363 455 * Map activity block attributes to shortcode format (full set for shortcode_atts merge).
364 456 *
365 457 * @param array<string, mixed> $attributes Merged with defaults
366 458 * @return array<string, string>
@@ -378,8 +470,17 @@
378 470
379 471 $cols = max(1, min(self::LISTING_COLUMNS_MAX, (int) ($attributes['columns'] ?? 3)));
380 472
381 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);
382 483
383 484 return [
384 485 'order' => $order,
385 486 'per_page' => (string) $perPage,
@@ -385,8 +486,18 @@
385 486 'per_page' => (string) $perPage,
386 487 'columns' => (string) $cols,
387 488 'title' => sanitize_text_field((string) ($attributes['title'] ?? 'Activity Listings')),
388 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',
389 500 ];
390 501 }
391 502
392 503 /**
@@ -406,8 +517,16 @@
406 517
407 518 $cols = max(1, min(self::LISTING_COLUMNS_MAX, (int) ($attributes['columns'] ?? 3)));
408 519
409 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);
410 529
411 530 return [
412 531 'order' => $order,
413 532 'per_page' => (string) $perPage,
@@ -413,7 +532,62 @@
413 532 'per_page' => (string) $perPage,
414 533 'columns' => (string) $cols,
415 534 'title' => sanitize_text_field((string) ($attributes['title'] ?? 'Destination Showcase')),
416 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',
417 591 ];
418 592 }
419 593 }