PluginProbe
Yatra – Travel Booking & Tour Operator Software / 3.0.4
Yatra – Travel Booking & Tour Operator Software v3.0.4
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 3.0.4, at app/Shortcodes/ActivityShortcode.php

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