PluginProbe
Yatra – Travel Booking & Tour Operator Software / trunk
Yatra – Travel Booking & Tour Operator Software vtrunk
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 2.0.11 All 82 releases
yatra / app / Shortcodes / ActivityShortcode.php

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

388 lines 14.9 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\Helpers\TripListingFilterBuilder;
8 use Yatra\Services\SettingsService;
9
10 /**
11 * Activity Shortcode
12 *
13 * Displays activity listings with associated trips using trip-listing-card.php template
14 */
15 class ActivityShortcode extends BaseShortcode
16 {
17 public function __construct()
18 {
19 parent::__construct('yatra_activity', [
20 'order' => 'desc',
21 'per_page' => '10',
22 'columns' => '3',
23 'show_trip_count' => 'yes',
24 'show_description' => 'yes',
25 'show_image' => 'yes',
26 'show_pagination' => 'yes', // Default to show pagination like trip shortcode
27 'activity' => '', // Classification IDs, comma-separated
28 // hide_empty defaults to 'no' to preserve the historical
29 // behavior (show every activity, even ones with zero
30 // trips). Operators that prefer the empty-archive defense
31 // opt in with hide_empty="yes".
32 'hide_empty' => 'no',
33 'title' => 'Activity Listings'
34 ]);
35 }
36
37 /**
38 * Render the activity shortcode content
39 */
40 protected function renderContent(array $atts): string
41 {
42 $atts = shortcode_atts($this->default_attributes, $atts, $this->tag);
43
44 // Extract per_page from attributes (only per_page parameter)
45 $per_page = 10; // default
46 if (!empty($atts['per_page']) && is_numeric($atts['per_page'])) {
47 $per_page = (int) $atts['per_page'];
48 }
49 $atts['per_page'] = $per_page;
50
51 // Get activities using Yatra's service
52 $activities_data = $this->getActivities($atts);
53
54 // Prepare data for template
55 $data = [
56 'activities' => $activities_data['activities'] ?? [],
57 'atts' => $atts,
58 'current_page' => $activities_data['current_page'] ?? 1,
59 'max_pages' => $activities_data['max_pages'] ?? 1,
60 'total_found' => $activities_data['total_found'] ?? 0,
61 'per_page' => $per_page
62 ];
63
64 $activityCssPath = YATRA_PLUGIN_PATH . 'assets/css/shortcodes/activity-shortcode.css';
65 $activityCssVer = is_readable($activityCssPath) ? YATRA_VERSION . '.' . filemtime($activityCssPath) : YATRA_VERSION;
66 wp_enqueue_style(
67 'yatra-activity-shortcode',
68 YATRA_PLUGIN_URL . 'assets/css/shortcodes/activity-shortcode.css',
69 \Yatra\Providers\FrontendAssetsProvider::shortcodeStyleDependencies(),
70 $activityCssVer
71 );
72
73 // Enqueue shortcode-specific JavaScript
74 wp_enqueue_script(
75 'yatra-activity-shortcode',
76 YATRA_PLUGIN_URL . 'assets/js/activity-shortcode.js',
77 ['jquery'],
78 YATRA_VERSION,
79 true
80 );
81
82 // Pass data to JavaScript
83 wp_localize_script('yatra-activity-shortcode', 'yatraActivityShortcode', [
84 'ajaxurl' => admin_url('admin-ajax.php'),
85 'nonce' => wp_create_nonce('yatra_activity_shortcode_nonce')
86 ]);
87
88 return $this->loadTemplate('shortcodes/activity.php', $data);
89 }
90
91 /**
92 * Get activities using Yatra's service
93 */
94 public function getActivities(array $atts): array
95 {
96 try {
97 $activityService = new \Yatra\Services\ActivityService();
98
99 // Get current page from query string or attributes (for AJAX)
100 $current_page = isset($atts['current_page']) ? (int) $atts['current_page'] : (isset($_GET['activity_page']) ? (int) $_GET['activity_page'] : 1);
101 // Enhanced per_page handling with validation and debugging
102
103 $per_page = (int) $atts['per_page'];
104
105
106 // Validate per_page to prevent issues
107 $per_page = max(1, min($per_page, 100)); // Between 1 and 100 items
108 $offset = ($current_page - 1) * $per_page;
109
110 // Start with very basic arguments to ensure we get activities
111 $args = [
112 'limit' => $per_page,
113 'offset' => $offset,
114 'order_by' => 'name',
115 'order' => $atts['order'] === 'asc' ? 'ASC' : 'DESC'
116 ];
117
118 $args['where'] = $args['where'] ?? [];
119 TripListingFilterBuilder::applyTaxonomyWhere(
120 $args['where'],
121 $atts,
122 'activityIds',
123 'activity_ids',
124 'activity'
125 );
126
127 // Get total count for pagination
128 $count_args = $args;
129 unset($count_args['limit']);
130 unset($count_args['offset']);
131 $total_activities = $activityService->count($count_args);
132
133 // Try using the base repository method to bypass status filtering
134 $result = $activityService->getAll($args);
135
136
137
138 $activities = [];
139
140 foreach ($result as $activityData) {
141
142
143 // Get real trip data for this activity using classification tables
144 global $wpdb;
145
146 $tripClassificationsTable = \Yatra\Database\Tables\TripClassificationsTable::getTableName();
147 $tripsTable = \Yatra\Database\Tables\TripsTable::getTableName();
148
149 // Get trip IDs for this activity
150 $trip_ids = $wpdb->get_col($wpdb->prepare(
151 "SELECT tc.trip_id
152 FROM {$tripClassificationsTable} tc
153 INNER JOIN {$tripsTable} t ON tc.trip_id = t.id
154 WHERE tc.classification_id = %d
155 AND tc.classification_type = 'activity'
156 AND t.status = 'publish'",
157 $activityData->id
158 ));
159
160 $trip_count = count($trip_ids);
161
162 // Get actual trips
163 $trips = [];
164 if (!empty($trip_ids)) {
165 $placeholders = implode(',', array_fill(0, count($trip_ids), '%d'));
166 $trips = $wpdb->get_results($wpdb->prepare(
167 "SELECT * FROM {$tripsTable}
168 WHERE id IN ({$placeholders})
169 AND status = 'publish'
170 ORDER BY created_at DESC
171 LIMIT 6",
172 ...$trip_ids
173 ));
174 }
175
176 // Calculate pricing from real trips
177 $min_price = null;
178 $max_price = null;
179 $durations = [];
180 $group_sizes = [];
181 $difficulties = [];
182
183 // Calculate rating from reviews table directly
184 $total_rating_sum = 0;
185 $total_review_count = 0;
186
187 if (!empty($trip_ids)) {
188 $reviewsTable = \Yatra\Database\Tables\ReviewsTable::getTableName();
189 $placeholders = implode(',', array_fill(0, count($trip_ids), '%d'));
190
191 $reviews = $wpdb->get_results($wpdb->prepare(
192 "SELECT rating, COUNT(*) as review_count
193 FROM {$reviewsTable}
194 WHERE trip_id IN ({$placeholders})
195 AND status = 'approved'",
196 ...$trip_ids
197 ));
198
199
200 foreach ($reviews as $review) {
201 $total_rating_sum += $review->rating * $review->review_count;
202 $total_review_count += $review->review_count;
203
204 }
205 }
206
207 // Calculate average rating only for trips that actually have reviews
208 // Trips with no reviews are excluded from the average (not treated as 0 rating)
209 $avg_rating = $total_review_count > 0 ? $total_rating_sum / $total_review_count : 0;
210
211 foreach ($trips as $trip) {
212 // Debug: Log all trip data to see what fields exist
213
214
215 // Get pricing via centralized TripPricingService
216 $effective = \Yatra\Services\TripPricingService::getEffectivePrice($trip);
217 if ($effective > 0) {
218 if ($min_price === null || $effective < $min_price) {
219 $min_price = $effective;
220 }
221 if ($max_price === null || $effective > $max_price) {
222 $max_price = $effective;
223 }
224 }
225
226 // Get duration
227 if (!empty($trip->duration)) {
228 $durations[] = $trip->duration;
229 }
230
231 // Get group size
232 if (!empty($trip->max_group_size)) {
233 $group_sizes[] = $trip->max_group_size;
234 }
235
236 // Get difficulty
237 if (!empty($trip->difficulty)) {
238 $difficulties[] = $trip->difficulty;
239 }
240 }
241
242 // Calculate averages
243 $final_avg_rating = $avg_rating; // Already calculated correctly above
244 $avg_duration = !empty($durations) ? array_sum($durations) / count($durations) : 0;
245 $avg_group_size = !empty($group_sizes) ? round(array_sum($group_sizes) / count($group_sizes)) : 0;
246
247
248
249 $activities[] = [
250 'term' => $activityData,
251 'trips' => $trips,
252 'trip_count' => $trip_count,
253 'description' => $activityData->description ?? '',
254 'image' => $this->getActivityImage($activityData),
255 'link' => $this->getActivityLink($activityData),
256 'min_price' => $min_price,
257 'max_price' => $max_price,
258 'avg_rating' => $final_avg_rating,
259 'rating_count' => $total_review_count,
260 'avg_duration' => $avg_duration,
261 'avg_group_size' => $avg_group_size,
262 'difficulty' => !empty($difficulties) ? $this->getMostCommonDifficulty($difficulties) : null
263 ];
264 }
265
266 // Filter out activities that have no published trips.
267 //
268 // Previously this only checked term name/slug presence —
269 // which never actually fires because all valid terms have
270 // both. As a result the shortcode would render activity
271 // cards with "0 trips" badges, leading users to click
272 // into empty archive pages. With hide_empty=yes we now
273 // drop any activity whose trip_count (computed above
274 // from TripClassificationsTable JOIN TripsTable WHERE
275 // status=publish) is zero. Term-metadata sanity is also
276 // preserved as a secondary safety check so we don't
277 // render orphan terms.
278 if ($atts['hide_empty'] === 'yes') {
279 $activities = array_filter($activities, static function ($activity) {
280 return (int) ($activity['trip_count'] ?? 0) > 0
281 && !empty($activity['term']->name)
282 && !empty($activity['term']->slug);
283 });
284 }
285
286 // Calculate pagination data
287 $max_pages = $per_page > 0 ? ceil($total_activities / $per_page) : 1;
288
289
290
291 return [
292 'activities' => $activities,
293 'current_page' => $current_page,
294 'max_pages' => $max_pages,
295 'total_found' => $total_activities,
296 'per_page' => $per_page
297 ];
298
299 } catch (\Exception $e) {
300
301 return [];
302 }
303 }
304
305 /**
306 * Get the most common difficulty from an array of difficulties
307 */
308 private function getMostCommonDifficulty(array $difficulties): string
309 {
310 if (empty($difficulties)) {
311 return 'Moderate';
312 }
313
314 $counts = array_count_values($difficulties);
315 arsort($counts);
316 return array_key_first($counts);
317 }
318
319
320 /**
321 * Get activity image
322 */
323 private function getActivityImage($activity): string
324 {
325 // Check for activity image in metadata
326 if (isset($activity->image) && !empty($activity->image)) {
327 return $activity->image;
328 }
329
330 // Check for activity icon in metadata
331 if (isset($activity->icon) && !empty($activity->icon)) {
332 $icon_data = maybe_unserialize($activity->icon);
333 if (is_array($icon_data) && isset($icon_data['type']) && $icon_data['type'] === 'image') {
334 return is_numeric($icon_data['value']) ? wp_get_attachment_url($icon_data['value']) : $icon_data['value'];
335 }
336 }
337
338 // Check for activity thumbnail/featured image
339 if (isset($activity->thumbnail) && !empty($activity->thumbnail)) {
340 return is_numeric($activity->thumbnail) ? wp_get_attachment_url($activity->thumbnail) : $activity->thumbnail;
341 }
342
343 // Check for activity banner
344 if (isset($activity->banner) && !empty($activity->banner)) {
345 return $activity->banner;
346 }
347
348 // Check for metadata with image
349 if (isset($activity->metadata) && !empty($activity->metadata)) {
350 $metadata = maybe_unserialize($activity->metadata);
351 if (is_array($metadata)) {
352 // Check for various image fields in metadata
353 $image_fields = ['image', 'thumbnail', 'banner', 'featured_image', 'cover_image'];
354 foreach ($image_fields as $field) {
355 if (isset($metadata[$field]) && !empty($metadata[$field])) {
356 return is_numeric($metadata[$field]) ? wp_get_attachment_url($metadata[$field]) : $metadata[$field];
357 }
358 }
359 }
360 }
361
362 // Fallback to placeholder
363 $fallback_url = YATRA_PLUGIN_URL . 'assets/images/placeholder.png';
364
365
366
367 return $fallback_url;
368 }
369
370 /**
371 * Get activity link
372 */
373 private function getActivityLink($activity): string
374 {
375 if (isset($activity->slug)) {
376 // Use permalink helper so global base + plain permalinks are respected
377 if (function_exists('yatra_get_activity_permalink')) {
378 return yatra_get_activity_permalink($activity);
379 }
380
381 $base = SettingsService::getActivityBase();
382 return home_url('/' . $base . '/' . $activity->slug . '/');
383 }
384
385 return '#'; // Fallback
386 }
387 }
388