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 / Services / SampleDataService.php

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

389 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 namespace Yatra\Services;
4
5 use Yatra\Repositories\SampleDataRepository;
6 use Yatra\Database\Tables\TripsTable;
7 use Yatra\Database\Tables\ClassificationsTable;
8 use Yatra\Database\Tables\DiscountsTable;
9
10 /**
11 * Sample Data Service
12 *
13 * Handles the business logic for importing and managing sample data
14 */
15 class SampleDataService
16 {
17 /**
18 * @var SampleDataRepository
19 */
20 private $repository;
21
22 /**
23 * Constructor
24 */
25 public function __construct()
26 {
27 $this->repository = new SampleDataRepository();
28 }
29
30 /**
31 * Generate dynamic future dates for sample data.
32 *
33 * Availability rows are grouped per trip, sorted by original departure, then assigned
34 * spaced future departures while preserving each row's original trip length (arrival − departure).
35 * Rules are shifted so start is at least 7 days from "today" in the site timezone, keeping
36 * the original rule window length (capped at 2 years).
37 *
38 * @param array $base_data Original sample data
39 * @return array Modified data with future dates
40 */
41 private function generate_dynamic_dates(array $base_data): array
42 {
43 $tz = function_exists('wp_timezone') ? wp_timezone() : new \DateTimeZone('UTC');
44 $today = new \DateTimeImmutable('today', $tz);
45 $min_departure = $today->modify('+14 days');
46 $rule_min_start = $today->modify('+7 days');
47 $first_slot_anchor = $today->modify('+21 days');
48 $slot_gap_days = 14;
49
50 if (!empty($base_data['availability_dates'])) {
51 $dates = $base_data['availability_dates'];
52 $by_trip = [];
53 foreach ($dates as $i => $row) {
54 $slug = $row['trip_slug'] ?? '';
55 if ($slug === '') {
56 continue;
57 }
58 $by_trip[$slug][] = $i;
59 }
60
61 foreach ($by_trip as $indices) {
62 usort($indices, function ($a, $b) use ($dates) {
63 $da = $dates[$a]['departure_date'] ?? '';
64 $db = $dates[$b]['departure_date'] ?? '';
65
66 return strcmp((string) $da, (string) $db);
67 });
68
69 $cursor = $first_slot_anchor;
70 foreach ($indices as $idx) {
71 $dep_s = $dates[$idx]['departure_date'] ?? '';
72 $arr_s = $dates[$idx]['arrival_date'] ?? $dep_s;
73 $orig_dep = \DateTimeImmutable::createFromFormat('Y-m-d', (string) $dep_s, $tz);
74 $orig_arr = \DateTimeImmutable::createFromFormat('Y-m-d', (string) $arr_s, $tz);
75 if (!$orig_dep) {
76 continue;
77 }
78 if (!$orig_arr) {
79 $orig_arr = $orig_dep;
80 }
81 $duration_days = max(0, (int) $orig_dep->diff($orig_arr)->format('%a'));
82
83 if ($cursor < $min_departure) {
84 $cursor = $min_departure;
85 }
86
87 $new_dep = $cursor;
88 $new_arr = $new_dep->modify('+' . $duration_days . ' days');
89 $dates[$idx]['departure_date'] = $new_dep->format('Y-m-d');
90 $dates[$idx]['arrival_date'] = $new_arr->format('Y-m-d');
91 // DB + frontend expect return_date; sample JSON only had arrival_date
92 $dates[$idx]['return_date'] = $new_arr->format('Y-m-d');
93
94 $cursor = $new_dep->modify('+' . $slot_gap_days . ' days');
95 }
96 }
97
98 $base_data['availability_dates'] = $dates;
99 }
100
101 if (!empty($base_data['availability_dates'])) {
102 $base_data['availability_dates'] = $this->alignSampleAvailabilitySeatConsistency(
103 $base_data['availability_dates'],
104 $base_data['trips'] ?? []
105 );
106 }
107
108 if (!empty($base_data['availability_rules'])) {
109 foreach ($base_data['availability_rules'] as $i => $rule) {
110 $orig_start = \DateTimeImmutable::createFromFormat(
111 'Y-m-d',
112 (string) ($rule['start_date'] ?? ''),
113 $tz
114 );
115 $orig_end = \DateTimeImmutable::createFromFormat(
116 'Y-m-d',
117 (string) ($rule['end_date'] ?? ''),
118 $tz
119 );
120 if (!$orig_start || !$orig_end) {
121 continue;
122 }
123
124 $span_days = max(1, (int) $orig_start->diff($orig_end)->format('%a'));
125
126 $new_start = $rule_min_start;
127 $new_end = $new_start->modify('+' . $span_days . ' days');
128
129 $cap = $new_start->modify('+2 years');
130 if ($new_end > $cap) {
131 $new_end = $cap;
132 }
133
134 $base_data['availability_rules'][$i]['start_date'] = $new_start->format('Y-m-d');
135 $base_data['availability_rules'][$i]['end_date'] = $new_end->format('Y-m-d');
136 }
137 }
138
139 return $base_data;
140 }
141
142 /**
143 * Keep seats_total / seats_available / seats_reserved consistent and align status
144 * (available | limited | sold_out) with seat counts so demo data matches the UI.
145 */
146 private function alignSampleAvailabilitySeatConsistency(array $dates, array $trips): array
147 {
148 $tripCap = [];
149 foreach ($trips as $t) {
150 if (!empty($t['slug'])) {
151 $tripCap[(string) $t['slug']] = max(1, (int) ($t['max_travelers'] ?? 20));
152 }
153 }
154
155 foreach ($dates as $i => $row) {
156 $slug = (string) ($row['trip_slug'] ?? '');
157 $defaultCap = $tripCap[$slug] ?? 20;
158
159 $total = (int) ($row['seats_total'] ?? 0);
160 if ($total <= 0) {
161 $total = $defaultCap;
162 }
163
164 $avail = (int) ($row['seats_available'] ?? 0);
165 $reserved = (int) ($row['seats_reserved'] ?? 0);
166
167 if ($avail < 0) {
168 $avail = 0;
169 }
170 if ($reserved < 0) {
171 $reserved = 0;
172 }
173 if ($avail > $total) {
174 $avail = $total;
175 }
176 if ($avail + $reserved !== $total) {
177 $reserved = max(0, min($total, $total - $avail));
178 $avail = max(0, $total - $reserved);
179 }
180
181 $status = 'available';
182 if ($avail <= 0) {
183 $status = 'sold_out';
184 } elseif ($total > 0 && $avail <= max(1, (int) ceil($total * 0.2))) {
185 $status = 'limited';
186 }
187
188 if (in_array($row['status'] ?? '', ['blocked', 'closed', 'cancelled', 'unavailable'], true)) {
189 $status = $row['status'];
190 }
191
192 $dates[$i]['seats_total'] = $total;
193 $dates[$i]['seats_available'] = $avail;
194 $dates[$i]['seats_reserved'] = $reserved;
195 $dates[$i]['status'] = $status;
196 }
197
198 return $dates;
199 }
200
201 /**
202 * Import all sample data from JSON files
203 *
204 * Order matters:
205 * 1. Classifications (categories, activities, destinations, etc.) - tracked by slug=>id
206 * 2. Items (type='item') - needs item_type IDs from step 1
207 * 3. Discounts
208 * 4. Trips - tracked by slug=>id
209 * 5. Trip-Classification pivot - needs both classification IDs and trip IDs
210 * 6. Availability dates/rules - needs trip IDs (with dynamic dates)
211 * 7. Itinerary days - needs trip IDs, tracked by composite key=>id
212 * 8. Itinerary entries - needs day IDs, item_type IDs, and item IDs
213 */
214 public function import_sample_data()
215 {
216 $results = [];
217
218 try {
219 $sample_data = $this->repository->get_all_sample_data();
220
221 // Generate dynamic future dates
222 $sample_data = $this->generate_dynamic_dates($sample_data);
223
224 // 1. Import Classifications (all types except items)
225 $classifications = array_merge(
226 $sample_data['categories'] ?? [],
227 $sample_data['activities'] ?? [],
228 $sample_data['destinations'] ?? [],
229 $sample_data['difficulty_levels'] ?? [],
230 $sample_data['attributes'] ?? [],
231 $sample_data['item_types'] ?? [],
232 $sample_data['traveler_categories'] ?? []
233 );
234 $classifications_count = $this->repository->insert_classifications($classifications);
235 $results['classifications'] = $classifications_count;
236
237 // 2. Import Items (type='item', needs item_type parent IDs from step 1)
238 $items_count = 0;
239 if (!empty($sample_data['items'])) {
240 $items_count = $this->repository->insert_items($sample_data['items']);
241 }
242 $results['items'] = $items_count;
243
244 // 3. Import Discounts
245 $discounts_count = $this->repository->insert_discounts($sample_data['discounts'] ?? []);
246 $results['discounts'] = $discounts_count;
247
248 // 4. Import Trips (returns slug => id mapping)
249 $trip_ids = $this->repository->insert_trips($sample_data['trips'] ?? []);
250 $results['trips'] = count($trip_ids);
251
252 // 5. Import Trip-Classification pivot records (links trips to categories/activities/etc.)
253 $trip_cls_count = 0;
254 if (!empty($sample_data['trip_classifications'])) {
255 $trip_cls_count = $this->repository->insert_trip_classifications(
256 $sample_data['trip_classifications'],
257 $trip_ids
258 );
259 }
260 $results['trip_classifications'] = $trip_cls_count;
261
262 // 6. Import Availability Dates
263 $results['availability_dates'] = $this->repository->insert_availability_dates(
264 $sample_data['availability_dates'] ?? [],
265 $trip_ids
266 );
267
268 // 7. Import Availability Rules
269 $results['availability_rules'] = $this->repository->insert_availability_rules(
270 $sample_data['availability_rules'] ?? [],
271 $trip_ids
272 );
273
274 // 8. Import Itinerary Days
275 $day_ids = $this->repository->insert_itinerary_days(
276 $sample_data['itinerary_days'] ?? [],
277 $trip_ids
278 );
279 $results['itinerary_days'] = count($day_ids);
280
281 // 9. Import Itinerary Entries (resolves item_type_id and item_id from classifications)
282 $results['itinerary_entries'] = $this->repository->insert_itinerary_entries(
283 $sample_data['itinerary_entries'] ?? [],
284 $trip_ids,
285 $day_ids
286 );
287
288 return [
289 'success' => true,
290 'message' => __('Sample data imported successfully!', 'yatra'),
291 'data' => $results
292 ];
293
294 } catch (\Exception $e) {
295 return [
296 'success' => false,
297 'message' => sprintf(
298 /* translators: %s: error message. */
299 __('Error importing sample data: %s', 'yatra'),
300 $e->getMessage()
301 ),
302 'data' => $results
303 ];
304 }
305 }
306
307 /**
308 * Cleanup sample data
309 */
310 public function cleanup_sample_data()
311 {
312 $results = $this->repository->cleanup_sample_data();
313
314 return [
315 'success' => true,
316 'message' => __('Sample data cleaned up successfully!', 'yatra'),
317 'data' => $results
318 ];
319 }
320
321 /**
322 * Get import status
323 */
324 public function get_import_status()
325 {
326 global $wpdb;
327
328 $trips_table = TripsTable::getTableName();
329 $classifications_table = ClassificationsTable::getTableName();
330 $discounts_table = DiscountsTable::getTableName();
331
332 // Sample trip slugs from trips.json
333 $trip_slugs = [
334 'swiss-alps-mountain-trek', 'maldives-beach-escape', 'kyoto-cultural-journey',
335 'serengeti-wildlife-safari', 'paris-city-explorer', 'bali-island-adventure',
336 'iceland-northern-lights', 'new-zealand-adventure', 'peru-machu-picchu-trek',
337 'norway-fjords-cruise', 'grand-canyon-day-tour', 'paris-city-highlights-tour',
338 'nyc-helicopter-liberty-tour', 'tokyo-cultural-food-tour'
339 ];
340 $trip_slugs_sql = "'" . implode("','", $trip_slugs) . "'";
341
342 $trips_count = (int) $wpdb->get_var(
343 "SELECT COUNT(*) FROM `{$trips_table}` WHERE slug IN ({$trip_slugs_sql})"
344 );
345
346 // Sample classification slugs from all classification JSON files
347 $cls_slugs = [
348 'adventure-tours', 'beach-island', 'cultural-tours', 'wildlife-safari',
349 'city-tours', 'trekking-hiking', 'cruise-tours', 'food-wine',
350 'hiking', 'snorkeling', 'city-walking-tour', 'wildlife-viewing',
351 'kayaking', 'photography', 'camping', 'rock-climbing', 'cycling', 'cooking-class',
352 'swiss-alps', 'maldives', 'kyoto-japan', 'serengeti-tanzania', 'paris-france',
353 'bali-indonesia', 'iceland', 'new-zealand', 'peru', 'norway',
354 'easy', 'moderate', 'challenging', 'difficult', 'extreme', 'expert',
355 'group-size', 'age-restriction', 'fitness-level', 'accommodation-type',
356 'meal-plan', 'transportation', 'guide-language', 'season',
357 'accommodation', 'activity', 'meal', 'sightseeing', 'free-time',
358 'adult', 'child', 'infant', 'student', 'senior', 'group-leader', 'family', 'solo-traveler'
359 ];
360 $cls_slugs_sql = "'" . implode("','", $cls_slugs) . "'";
361
362 $classifications_count = (int) $wpdb->get_var(
363 "SELECT COUNT(*) FROM `{$classifications_table}` WHERE slug IN ({$cls_slugs_sql})"
364 );
365
366 // Sample discount codes from discounts.json
367 $discount_codes = ['SUMMER2024', 'EARLYBIRD', 'GROUP10', 'WELCOME50', 'FAMILY20', 'LASTMINUTE', 'LOYAL100', 'WINTER2024'];
368 $codes_sql = "'" . implode("','", $discount_codes) . "'";
369
370 $discounts_count = (int) $wpdb->get_var(
371 "SELECT COUNT(*) FROM `{$discounts_table}` WHERE code IN ({$codes_sql})"
372 );
373
374 $is_imported = get_option('yatra_sample_data_imported', false);
375 $import_date = get_option('yatra_sample_data_import_date', '');
376
377 return [
378 'is_imported' => $is_imported,
379 'import_date' => $import_date,
380 'counts' => [
381 'trips' => $trips_count,
382 'classifications' => $classifications_count,
383 'discounts' => $discounts_count,
384 ],
385 'has_data' => ($trips_count > 0 || $classifications_count > 0 || $discounts_count > 0)
386 ];
387 }
388 }
389