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 / AttributeMigration.php

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

299 lines 12.1 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2
3 namespace Yatra\Migration;
4
5 use Yatra\Constants\ClassificationTypes;
6 use Yatra\Database\Tables\ClassificationsTable;
7 use Yatra\Database\Tables\TripClassificationsTable;
8 use Yatra\Utils\Logger;
9 use Yatra\Migration\MigrationProgress;
10
11 /**
12 * Attribute Migration - Migrates trip attributes from old Yatra system
13 *
14 * Old System: Attributes stored as WordPress taxonomy 'attributes' terms,
15 * and per-trip values in post meta 'tour_meta_custom_attributes' (serialized array).
16 * New System: Unified ClassificationsTable (type='attribute') + TripClassificationsTable
17 *
18 * All queries use raw SQL because old taxonomies/CPTs are NOT registered in the new plugin.
19 */
20 class AttributeMigration extends BaseMigration
21 {
22 public function __construct(MigrationProgress $service)
23 {
24 parent::__construct($service);
25 }
26
27 public function run(): array
28 {
29 $migrated = 0;
30 $skipped = 0;
31 $failed = 0;
32
33 Logger::info("Starting attribute migration.", ['source' => 'migration']);
34
35 // Step 1: Migrate attribute definitions (taxonomy terms → ClassificationsTable)
36 $defResult = $this->migrateAttributeDefinitions();
37 $migrated += $defResult['migrated'];
38 $skipped += $defResult['skipped'];
39 $failed += $defResult['failed'];
40
41 // Step 2: Migrate trip-attribute relationships (post meta → TripClassificationsTable)
42 $relResult = $this->migrateTripAttributeRelationships();
43 $migrated += $relResult['migrated'];
44 $skipped += $relResult['skipped'];
45 $failed += $relResult['failed'];
46
47 Logger::info("Attribute migration completed", [
48 'source' => 'migration',
49 'migrated' => $migrated,
50 'skipped' => $skipped,
51 'failed' => $failed
52 ]);
53
54 return compact('migrated', 'skipped', 'failed');
55 }
56
57 /**
58 * Migrate attribute definitions from old taxonomy 'attributes' into ClassificationsTable (type=attribute).
59 */
60 private function migrateAttributeDefinitions(): array
61 {
62 $migrated = 0;
63 $skipped = 0;
64 $failed = 0;
65
66 $classificationsTable = ClassificationsTable::getTableName();
67 $metaKey = '_yatra_migrated_attributes_id';
68
69 // Raw SQL: old 'attributes' taxonomy terms
70 $terms = $this->wpdb->get_results(
71 "SELECT t.term_id, t.name, t.slug, tt.description
72 FROM {$this->wpdb->terms} t
73 INNER JOIN {$this->wpdb->term_taxonomy} tt ON t.term_id = tt.term_id
74 WHERE tt.taxonomy = 'attributes'"
75 );
76
77 if (empty($terms)) {
78 Logger::info("No 'attributes' taxonomy terms found", ['source' => 'migration']);
79 return compact('migrated', 'skipped', 'failed');
80 }
81
82 $total = count($terms);
83 Logger::info("Found {$total} attribute terms", ['source' => 'migration', 'count' => $total]);
84 $this->updateProgress('attributes', 'running', 0, 0, 0, $total, null, null);
85
86 foreach ($terms as $term) {
87 try {
88 $termId = (int) $term->term_id;
89
90 // Check if already migrated via raw term meta
91 $existingMappedId = $this->getRawTermMeta($termId, $metaKey);
92 if (!$this->isForceMigration() && $existingMappedId) {
93 $skipped++;
94 $this->updateProgress('attributes', 'running', $migrated, $skipped, $failed, $total, null, null);
95 continue;
96 }
97
98 $baseSlug = !empty($term->slug) ? $term->slug : sanitize_title($term->name ?: uniqid('attr-'));
99
100 // Check if already exists in ClassificationsTable by type+slug
101 $existingId = $this->wpdb->get_var($this->wpdb->prepare(
102 "SELECT id FROM {$classificationsTable} WHERE type = %s AND slug = %s",
103 ClassificationTypes::ATTRIBUTE,
104 $baseSlug
105 ));
106
107 $slug = $baseSlug;
108
109 if ($existingId && !$this->isForceMigration()) {
110 // Skip if already exists and not force migration
111 $skipped++;
112 $this->updateProgress('attributes', 'running', $migrated, $skipped, $failed, $total, null, null);
113 continue;
114 }
115
116 if (!$existingId) {
117 // Generate unique slug for new insert
118 $counter = 1;
119 $uniqueSlug = $slug;
120 while ($this->wpdb->get_var($this->wpdb->prepare(
121 "SELECT id FROM {$classificationsTable} WHERE type = %s AND slug = %s",
122 ClassificationTypes::ATTRIBUTE,
123 $uniqueSlug
124 ))) {
125 $uniqueSlug = $slug . '-' . $counter++;
126 }
127 $slug = $uniqueSlug;
128 }
129
130 // Get icon from raw term meta
131 $icon = $this->getRawTermMeta($termId, 'icon');
132
133 // Build metadata JSON
134 $metadata = [];
135 $fieldType = $this->getRawTermMeta($termId, 'attribute_field_type');
136 if ($fieldType) {
137 $metadata['field_type'] = $fieldType;
138 }
139 $fieldOptions = $this->getRawTermMeta($termId, 'field_options');
140 if ($fieldOptions) {
141 $metadata['field_options'] = maybe_unserialize($fieldOptions);
142 }
143
144 $data = [
145 'type' => ClassificationTypes::ATTRIBUTE,
146 'name' => $term->name,
147 'slug' => $slug,
148 'description' => $term->description ?? '',
149 'icon' => $icon ?: null,
150 'metadata' => !empty($metadata) ? json_encode($metadata) : null,
151 'status' => 'publish',
152 'updated_at' => current_time('mysql'),
153 ];
154
155 if ($existingId && $this->isForceMigration()) {
156 // Update existing during force migration
157 $this->wpdb->update($classificationsTable, $data, ['id' => $existingId]);
158 $newId = (int) $existingId;
159 } else {
160 // Insert new
161 $data['created_at'] = current_time('mysql');
162 $inserted = $this->wpdb->insert($classificationsTable, $data);
163 if (!$inserted) {
164 $errorMsg = $this->wpdb->last_error;
165 // Check if it's a duplicate key error and handle gracefully
166 if (strpos($errorMsg, 'Duplicate entry') !== false) {
167 $skipped++;
168 $this->updateProgress('attributes', 'running', $migrated, $skipped, $failed, $total, null, null);
169 continue;
170 }
171 $failed++;
172 Logger::error("Failed to insert attribute '{$term->name}': {$errorMsg}", ['source' => 'migration']);
173 $this->updateProgress('attributes', 'running', $migrated, $skipped, $failed, $total, null, null);
174 continue;
175 }
176 $newId = (int) $this->wpdb->insert_id;
177 }
178
179 // Store mapping in raw term meta
180 $this->setRawTermMeta($termId, $metaKey, (string) $newId);
181 $migrated++;
182 $this->updateProgress('attributes', 'running', $migrated, $skipped, $failed, $total, null, null);
183
184 } catch (\Exception $e) {
185 $failed++;
186 Logger::error("Exception migrating attribute: {$term->name}{$e->getMessage()}", ['source' => 'migration']);
187 $this->updateProgress('attributes', 'running', $migrated, $skipped, $failed, $total, null, null);
188 }
189 }
190
191 return compact('migrated', 'skipped', 'failed');
192 }
193
194 /**
195 * Migrate trip-attribute relationships.
196 *
197 * Old system: post meta 'tour_meta_custom_attributes' = serialized array keyed by term_id.
198 * New system: TripClassificationsTable rows with classification_type='attribute' and metadata containing value.
199 */
200 private function migrateTripAttributeRelationships(): array
201 {
202 $migrated = 0;
203 $skipped = 0;
204 $failed = 0;
205
206 $tripClassificationsTable = TripClassificationsTable::getTableName();
207 $metaKey = '_yatra_migrated_attributes_id';
208
209 // Raw SQL: get all tour posts with custom attributes meta
210 $tripsWithAttributes = $this->wpdb->get_results(
211 "SELECT post_id, meta_value
212 FROM {$this->wpdb->postmeta}
213 WHERE meta_key = 'tour_meta_custom_attributes'
214 AND meta_value IS NOT NULL
215 AND meta_value != ''"
216 );
217
218 if (empty($tripsWithAttributes)) {
219 Logger::info("No trips with custom attributes found", ['source' => 'migration']);
220 return compact('migrated', 'skipped', 'failed');
221 }
222
223 $total = count($tripsWithAttributes);
224 Logger::info("Found {$total} trips with custom attributes", ['source' => 'migration']);
225
226 foreach ($tripsWithAttributes as $tripMeta) {
227 try {
228 $oldTripId = (int) $tripMeta->post_id;
229 $newTripId = $this->getMigratedTripId($oldTripId);
230
231 if (!$newTripId) {
232 $skipped++;
233 continue;
234 }
235
236 $attributes = maybe_unserialize($tripMeta->meta_value);
237 if (!is_array($attributes)) {
238 $skipped++;
239 continue;
240 }
241
242 foreach ($attributes as $oldTermId => $attributeData) {
243 // Look up the new classification ID via raw term meta
244 $newAttrId = $this->getRawTermMeta((int) $oldTermId, $metaKey);
245 if (!$newAttrId) {
246 continue;
247 }
248
249 $value = $attributeData['content'] ?? $attributeData['value'] ?? '';
250 if (empty($value)) {
251 continue;
252 }
253
254 // Check if relationship already exists
255 $exists = $this->wpdb->get_var($this->wpdb->prepare(
256 "SELECT id FROM {$tripClassificationsTable}
257 WHERE trip_id = %d AND classification_id = %d AND classification_type = %s",
258 $newTripId,
259 (int) $newAttrId,
260 ClassificationTypes::ATTRIBUTE
261 ));
262
263 $relMetadata = json_encode(['value' => $value]);
264
265 if ($exists) {
266 if ($this->isForceMigration()) {
267 $this->wpdb->update(
268 $tripClassificationsTable,
269 ['metadata' => $relMetadata, 'updated_at' => current_time('mysql')],
270 ['id' => $exists]
271 );
272 $migrated++;
273 }
274 } else {
275 $this->wpdb->insert($tripClassificationsTable, [
276 'trip_id' => $newTripId,
277 'classification_id' => (int) $newAttrId,
278 'classification_type' => ClassificationTypes::ATTRIBUTE,
279 'relationship_type' => 'primary',
280 'metadata' => $relMetadata,
281 'sort_order' => 0,
282 'is_active' => 1,
283 'created_at' => current_time('mysql'),
284 'updated_at' => current_time('mysql'),
285 ]);
286 $migrated++;
287 }
288 }
289
290 } catch (\Exception $e) {
291 $failed++;
292 Logger::error("Exception migrating attributes for trip {$tripMeta->post_id}: {$e->getMessage()}", ['source' => 'migration']);
293 }
294 }
295
296 return compact('migrated', 'skipped', 'failed');
297 }
298 }
299