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 / Migrations / MigrationDetector.php

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

611 lines 19.8 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 /**
3 * Migration Detector - Detects old Yatra data from previous versions
4 *
5 * This class detects data from Yatra versions prior to 3.0.0
6 * All migration-related code is in app/Migrations folder for easy removal
7 *
8 * @package Yatra\Migration
9 * @since 3.0.0
10 */
11
12 namespace Yatra\Migration;
13
14 class MigrationDetector
15 {
16 private $wpdb;
17
18 public function __construct()
19 {
20 global $wpdb;
21 $this->wpdb = $wpdb;
22 }
23
24 /**
25 * Pad semver segments so PHP's version_compare matches human expectations (e.g. 3.0 === 3.0.0).
26 */
27 private static function normalizeSemverForCompare(string $version): string
28 {
29 $version = preg_replace('/^[vV]+/', '', trim($version));
30 $core = preg_split('/[-+]/', $version, 2)[0];
31 $segments = explode('.', $core);
32 $segments = array_pad($segments, 3, '0');
33
34 return implode('.', array_slice(array_map('intval', $segments), 0, 3));
35 }
36
37 /**
38 * Old Yatra 2.x stored its release in wp_options as yatra_plugin_version (not yatra_version).
39 * Fresh Yatra 3.x sites only have yatra_version / installer options — never this legacy key unless
40 * the old plugin ran on this database.
41 */
42 public function isRecordedLegacyYatraInstall(): bool
43 {
44 $legacyVer = get_option('yatra_plugin_version', '');
45 if ($legacyVer === '' || $legacyVer === false) {
46 return false;
47 }
48
49 return version_compare(
50 self::normalizeSemverForCompare((string) $legacyVer),
51 self::normalizeSemverForCompare('3.0.0'),
52 '<'
53 );
54 }
55
56 /**
57 * Yatra 2.x stored enabled gateways as an associative array (slug => yes). Yatra 3.x uses numeric keys.
58 */
59 private function hasLegacyPaymentGatewaysOptionFootprint(): bool
60 {
61 $gw = get_option('yatra_payment_gateways', null);
62 if (!is_array($gw) || $gw === []) {
63 return false;
64 }
65 foreach (array_keys($gw) as $key) {
66 if (is_string($key)) {
67 return true;
68 }
69 }
70
71 return false;
72 }
73
74 /**
75 * Legacy CPTs, taxonomies, and tables from Yatra before 3.0 — excludes options (see countOldSettings).
76 */
77 public function hasStructuralLegacyData(): bool
78 {
79 return $this->hasLegacyPaymentGatewaysOptionFootprint()
80 || $this->countOldTrips() > 0
81 || $this->countOldBookings() > 0
82 || $this->countOldCustomers() > 0
83 || $this->countOldCoupons() > 0
84 || $this->countOldDestinations() > 0
85 || $this->countOldActivities() > 0
86 || $this->countOldTripCategories() > 0
87 || $this->countOldAttributes() > 0
88 || $this->countOldReviews() > 0
89 || $this->countOldEnquiries() > 0
90 || $this->countOldTourDates() > 0
91 || $this->countOldItinerary() > 0
92 || $this->countOldServices() > 0
93 || $this->countOldAvailabilityConditions() > 0
94 || $this->countOldTravelerCategories() > 0
95 // Pro legacy footprints (may exist even when core legacy is already migrated)
96 || $this->countOldProFeatures() > 0
97 || $this->countOldProGatewaySettings() > 0
98 || $this->countOldProGoogleCalendar() > 0
99 || $this->countOldProReviewsCpt() > 0
100 || $this->countOldProDownloads() > 0;
101 }
102
103 /**
104 * Check if old Yatra data exists (for migration UI / notices).
105 *
106 * Do not treat normal Yatra 3.x wp_options as "legacy settings" — that caused false positives
107 * on fresh installs when countOldSettings() counted every yatra_* option.
108 */
109 public function hasOldData(): bool
110 {
111 if ($this->isRecordedLegacyYatraInstall()) {
112 return true;
113 }
114
115 return $this->hasStructuralLegacyData();
116 }
117
118 /**
119 * Detect all old data from previous Yatra versions
120 */
121 public function detectOldData(): array
122 {
123 $prefix = $this->wpdb->prefix;
124
125 return [
126 'trips' => [
127 'label' => 'Trips',
128 'count' => $this->countOldTrips(),
129 'description' => 'Tour packages from old version',
130 'table' => 'posts (post_type=tour)',
131 ],
132 'bookings' => [
133 'label' => 'Bookings',
134 'count' => $this->countOldBookings(),
135 'description' => 'Customer bookings and reservations',
136 'table' => 'posts (post_type=yatra-booking)',
137 ],
138 'customers' => [
139 'label' => 'Customers',
140 'count' => $this->countOldCustomers(),
141 'description' => 'Customer profiles',
142 'table' => 'posts (post_type=yatra-customers)',
143 ],
144 'coupons' => [
145 'label' => 'Coupons',
146 'count' => $this->countOldCoupons(),
147 'description' => 'Discount coupons',
148 'table' => 'posts (post_type=yatra-coupons)',
149 ],
150 'destinations' => [
151 'label' => 'Destinations',
152 'count' => $this->countOldDestinations(),
153 'description' => 'Travel destinations',
154 'table' => 'terms (taxonomy=destination)',
155 ],
156 'activities' => [
157 'label' => 'Activities',
158 'count' => $this->countOldActivities(),
159 'description' => 'Trip activities',
160 'table' => 'terms (taxonomy=activity)',
161 ],
162 'trip_categories' => [
163 'label' => 'Trip categories',
164 'count' => $this->countOldTripCategories(),
165 'description' => 'Trip / tour type categories',
166 'table' => 'terms (taxonomy=trip_category or tour_category)',
167 ],
168 'attributes' => [
169 'label' => 'Attributes',
170 'count' => $this->countOldAttributes(),
171 'description' => 'Trip attributes and characteristics',
172 'table' => 'terms (taxonomy=attributes) or yatra_tour_attributes',
173 ],
174 'reviews' => [
175 'label' => 'Reviews',
176 'count' => $this->countOldReviews(),
177 'description' => 'Trip reviews and ratings',
178 'table' => 'comments (comment_type=yatra_review)',
179 ],
180 'enquiries' => [
181 'label' => 'Enquiries',
182 'count' => $this->countOldEnquiries(),
183 'description' => 'Customer enquiries',
184 'table' => "{$prefix}yatra_tour_enquiries",
185 ],
186 'tour_dates' => [
187 'label' => 'Tour Dates',
188 'count' => $this->countOldTourDates(),
189 'description' => 'Tour availability dates',
190 'table' => "{$prefix}yatra_tour_dates",
191 ],
192 'itinerary' => [
193 'label' => 'Itinerary',
194 'count' => $this->countOldItinerary(),
195 'description' => 'Trip itineraries and schedules',
196 'table' => 'postmeta (tour posts)',
197 ],
198 'settings' => [
199 'label' => 'Settings',
200 'count' => $this->countOldSettings(),
201 'description' => 'Core options plus payment gateways (free flat keys + legacy Pro yatra_pro_* bundles) and Google Calendar token remap when Pro 3.0+ is active',
202 'table' => 'options (yatra_* keys)',
203 ],
204 'services' => [
205 'label' => 'Additional Services',
206 'count' => $this->countOldServices(),
207 'description' => 'Extra services and add-ons (Premium)',
208 'table' => 'term_taxonomy (services)',
209 ],
210 'availability_conditions' => [
211 'label' => 'Availability Conditions',
212 'count' => $this->countOldAvailabilityConditions(),
213 'description' => 'Availability rules and conditions (Premium)',
214 'table' => 'term_taxonomy (availability_conditions)',
215 ],
216 'traveler_categories' => [
217 'label' => 'Traveler Categories',
218 'count' => $this->countOldTravelerCategories(),
219 'description' => 'Multiple pricing / traveler-based pricing options',
220 'table' => 'postmeta (yatra_multiple_pricing on tour posts)',
221 ],
222 'pro_features' => [
223 'label' => 'Pro: Feature Toggles',
224 'count' => $this->countOldProFeatures(),
225 'description' => 'Legacy Pro feature/module toggles (yatra_pro_features option)',
226 'table' => 'options (yatra_pro_features)',
227 ],
228 'pro_reviews_cpt' => [
229 'label' => 'Pro: Reviews (CPT)',
230 'count' => $this->countOldProReviewsCpt(),
231 'description' => 'Legacy Pro reviews stored as yatra-review custom post type',
232 'table' => 'posts (post_type=yatra-review)',
233 ],
234 'pro_downloads' => [
235 'label' => 'Pro: Downloads',
236 'count' => $this->countOldProDownloads(),
237 'description' => 'Legacy downloadable files attached to tours',
238 'table' => 'options + postmeta (downloads_downloadable_files)',
239 ],
240 ];
241 }
242
243 private function countOldProFeatures(): int
244 {
245 $features = get_option('yatra_pro_features', []);
246 if (!is_array($features)) {
247 return 0;
248 }
249 $features = array_filter($features, static fn ($v) => (bool) $v);
250
251 return $features !== [] ? 1 : 0;
252 }
253
254 private function countOldProGatewaySettings(): int
255 {
256 $enabled = get_option('yatra_pro_enabled_payment_gateways', []);
257 if (is_array($enabled) && $enabled !== []) {
258 return 1;
259 }
260
261 $keys = [
262 'yatra_pro_twocheckout_settings',
263 'yatra_pro_square_settings',
264 'yatra_pro_razorpay_settings',
265 'yatra_pro_authorizenet_settings',
266 'yatra_pro_razorpay_refunds',
267 ];
268 foreach ($keys as $k) {
269 $v = get_option($k, null);
270 if ($v !== null && $v !== '' && $v !== []) {
271 return 1;
272 }
273 }
274
275 return 0;
276 }
277
278 private function countOldProGoogleCalendar(): int
279 {
280 $token = get_option('yatra_google_calendar_refresh_token', '');
281 if (is_string($token) && $token !== '') {
282 return 1;
283 }
284 $enabled = get_option('yatra_enable_google_calendar', '');
285 if ($enabled !== '' && $enabled !== null) {
286 return 1;
287 }
288
289 return 0;
290 }
291
292 private function countOldProReviewsCpt(): int
293 {
294 $count = $this->wpdb->get_var(
295 "SELECT COUNT(*) FROM {$this->wpdb->posts} WHERE post_type = 'yatra-review' AND post_status NOT IN ('trash','auto-draft')"
296 );
297
298 return (int) $count;
299 }
300
301 private function countOldProDownloads(): int
302 {
303 $global = get_option('yatra_global_downloadable_files', '');
304 if (is_string($global) && trim($global) !== '') {
305 return 1;
306 }
307 $count = $this->wpdb->get_var(
308 $this->wpdb->prepare(
309 "SELECT COUNT(*) FROM {$this->wpdb->postmeta} pm INNER JOIN {$this->wpdb->posts} p ON pm.post_id = p.ID
310 WHERE p.post_type = %s AND pm.meta_key = %s AND pm.meta_value <> ''",
311 'tour',
312 'downloads_downloadable_files'
313 )
314 );
315
316 return (int) $count;
317 }
318
319 /**
320 * Count old trips from custom post type
321 */
322 private function countOldTrips(): int
323 {
324 $count = $this->wpdb->get_var(
325 "SELECT COUNT(*) FROM {$this->wpdb->posts}
326 WHERE post_type = 'tour' AND post_status != 'auto-draft'"
327 );
328
329 return (int) $count;
330 }
331
332 /**
333 * Count old bookings
334 */
335 private function countOldBookings(): int
336 {
337 $count = $this->wpdb->get_var(
338 "SELECT COUNT(*) FROM {$this->wpdb->posts}
339 WHERE post_type = 'yatra-booking' AND post_status != 'auto-draft'"
340 );
341
342 return (int) $count;
343 }
344
345 /**
346 * Count old customers
347 */
348 private function countOldCustomers(): int
349 {
350 $count = $this->wpdb->get_var(
351 "SELECT COUNT(*) FROM {$this->wpdb->posts}
352 WHERE post_type = 'yatra-customers' AND post_status != 'auto-draft'"
353 );
354
355 return (int) $count;
356 }
357
358 /**
359 * Count old destinations (stored as taxonomy)
360 */
361 private function countOldDestinations(): int
362 {
363 $count = $this->wpdb->get_var(
364 "SELECT COUNT(*) FROM {$this->wpdb->term_taxonomy}
365 WHERE taxonomy = 'destination'"
366 );
367
368 return (int) $count;
369 }
370
371 /**
372 * Count old activities (stored as taxonomy)
373 */
374 private function countOldActivities(): int
375 {
376 $count = $this->wpdb->get_var(
377 "SELECT COUNT(*) FROM {$this->wpdb->term_taxonomy}
378 WHERE taxonomy = 'activity'"
379 );
380
381 return (int) $count;
382 }
383
384 /**
385 * Trip / tour category terms (merged into ClassificationsTable type=category in 3.x).
386 */
387 private function countOldTripCategories(): int
388 {
389 $count = $this->wpdb->get_var(
390 "SELECT COUNT(*) FROM {$this->wpdb->term_taxonomy}
391 WHERE taxonomy IN ('trip_category', 'tour_category')"
392 );
393
394 return (int) $count;
395 }
396
397 /**
398 * Count old attributes (stored as taxonomy or custom table)
399 * Old system uses 'attributes' taxonomy
400 */
401 private function countOldAttributes(): int
402 {
403 // Check for old yatra_tour_attributes table
404 $table = $this->wpdb->prefix . 'yatra_tour_attributes';
405 $tableExists = $this->wpdb->get_var("SHOW TABLES LIKE '{$table}'");
406
407 if ($tableExists) {
408 $count = $this->wpdb->get_var("SELECT COUNT(*) FROM {$table}");
409 return (int) $count;
410 }
411
412 // Check for taxonomy-based attributes (old system uses 'attributes' taxonomy)
413 $count = $this->wpdb->get_var(
414 "SELECT COUNT(*) FROM {$this->wpdb->term_taxonomy}
415 WHERE taxonomy = 'attributes'"
416 );
417
418 return (int) $count;
419 }
420
421 /**
422 * Count old reviews (stored as comments on tour posts)
423 */
424 private function countOldReviews(): int
425 {
426 $count = $this->wpdb->get_var(
427 "SELECT COUNT(*) FROM {$this->wpdb->comments} c
428 INNER JOIN {$this->wpdb->posts} p ON c.comment_post_ID = p.ID
429 WHERE p.post_type = 'tour'
430 AND c.comment_type IN ('', 'comment', 'review', 'yatra_review')
431 AND c.comment_approved != 'trash'"
432 );
433
434 return (int) $count;
435 }
436
437 /**
438 * Count old enquiries
439 */
440 private function countOldEnquiries(): int
441 {
442 $table = $this->wpdb->prefix . 'yatra_tour_enquiries';
443 $tableExists = $this->wpdb->get_var("SHOW TABLES LIKE '{$table}'");
444
445 if (!$tableExists) {
446 return 0;
447 }
448
449 $count = $this->wpdb->get_var("SELECT COUNT(*) FROM {$table}");
450
451 return (int) $count;
452 }
453
454 /**
455 * Count old tour dates
456 */
457 private function countOldTourDates(): int
458 {
459 $table = $this->wpdb->prefix . 'yatra_tour_dates';
460 $tableExists = $this->wpdb->get_var("SHOW TABLES LIKE '{$table}'");
461
462 if (!$tableExists) {
463 return 0;
464 }
465
466 $count = $this->wpdb->get_var("SELECT COUNT(*) FROM {$table}");
467
468 return (int) $count;
469 }
470
471 /**
472 * Count old coupons
473 */
474 private function countOldCoupons(): int
475 {
476 $count = $this->wpdb->get_var(
477 "SELECT COUNT(*) FROM {$this->wpdb->posts}
478 WHERE post_type = 'yatra-coupons' AND post_status != 'auto-draft'"
479 );
480
481 return (int) $count;
482 }
483
484 /**
485 * Count old tours with itinerary data
486 */
487 private function countOldItinerary(): int
488 {
489 // Count tours that have itinerary-related meta data
490 $itineraryKeys = [
491 'itinerary_repeator', // This is the actual key found in the database
492 'itinerary_label',
493 'yatra_tour_itinerary',
494 'yatra_tour_meta_itinerary',
495 'yatra_itinerary',
496 'tour_itinerary',
497 'yatra_tour_days',
498 'yatra_tour_meta_days',
499 'yatra_days',
500 'tour_days',
501 'yatra_tour_schedule',
502 'yatra_tour_meta_schedule',
503 'yatra_schedule',
504 'tour_schedule'
505 ];
506
507 $placeholders = implode(',', array_fill(0, count($itineraryKeys), '%s'));
508
509 $count = $this->wpdb->get_var(
510 $this->wpdb->prepare(
511 "SELECT COUNT(DISTINCT p.ID)
512 FROM {$this->wpdb->posts} p
513 INNER JOIN {$this->wpdb->postmeta} pm ON p.ID = pm.post_id
514 WHERE p.post_type = 'tour'
515 AND p.post_status != 'auto-draft'
516 AND pm.meta_key IN ({$placeholders})",
517 ...$itineraryKeys
518 )
519 );
520
521 return (int) $count;
522 }
523
524 /**
525 * Count old settings from options table
526 */
527 private function countOldSettings(): int
528 {
529 if (
530 !$this->isRecordedLegacyYatraInstall()
531 && !$this->hasStructuralLegacyData()
532 && !$this->hasLegacyPaymentGatewaysOptionFootprint()
533 ) {
534 return 0;
535 }
536
537 // Count old Yatra settings in wp_options
538 $count = $this->wpdb->get_var(
539 "SELECT COUNT(*) FROM {$this->wpdb->options}
540 WHERE option_name LIKE 'yatra_%'
541 AND option_name NOT LIKE 'yatra\\_version%'
542 AND option_name NOT LIKE 'yatra\\_db\\_version%'
543 AND option_name NOT LIKE 'yatra\\_migration%'"
544 );
545
546 return (int) $count;
547 }
548
549 /**
550 * Count old services from taxonomy (checks database directly)
551 */
552 private function countOldServices(): int
553 {
554 // Check database directly, regardless of plugin/module activation
555 $count = $this->wpdb->get_var(
556 "SELECT COUNT(*) FROM {$this->wpdb->term_taxonomy}
557 WHERE taxonomy = 'services'"
558 );
559
560 return (int) $count;
561 }
562
563 /**
564 * Count old availability conditions from taxonomy (checks database directly)
565 */
566 private function countOldAvailabilityConditions(): int
567 {
568 // Check database directly, regardless of plugin/module activation
569 $count = $this->wpdb->get_var(
570 "SELECT COUNT(*) FROM {$this->wpdb->term_taxonomy}
571 WHERE taxonomy = 'availability_conditions'"
572 );
573
574 return (int) $count;
575 }
576
577 /**
578 * Count old traveler categories (tours with multiple pricing in postmeta)
579 */
580 private function countOldTravelerCategories(): int
581 {
582 $count = $this->wpdb->get_var(
583 "SELECT COUNT(DISTINCT pm.post_id)
584 FROM {$this->wpdb->postmeta} pm
585 INNER JOIN {$this->wpdb->posts} p ON pm.post_id = p.ID
586 WHERE pm.meta_key = 'yatra_multiple_pricing'
587 AND p.post_type = 'tour'
588 AND pm.meta_value != ''
589 AND pm.meta_value != 'a:0:{}'
590 AND p.post_status != 'auto-draft'"
591 );
592
593 return (int) $count;
594 }
595
596 /**
597 * Check if a table exists
598 */
599 private function tableExists(string $table): bool
600 {
601 $result = $this->wpdb->get_var(
602 $this->wpdb->prepare(
603 "SHOW TABLES LIKE %s",
604 $table
605 )
606 );
607
608 return $result === $table;
609 }
610 }
611