PluginProbe
Easy Invoice – Invoice Generator, PDF Quotes & Payments / 2.3.8
Easy Invoice – Invoice Generator, PDF Quotes & Payments v2.3.8
2.4.0 2.4.1 2.3.8 2.3.7 2.3.6 2.3.5 2.3.4 2.3.3 2.3.2 2.3.1 2.2.0 2.1.21 2.1.20 2.1.19 2.1.18 2.1.0 2.1.1 2.1.10 2.1.11 2.1.12 2.1.13 2.1.14 2.1.15 2.1.16 2.1.2 All 57 releases
easy-invoice / includes / Migration / Src / MetaMigration.php

MetaMigration.php in Easy Invoice – Invoice Generator, PDF Quotes & Payments 2.3.8, at includes/Migration/Src/MetaMigration.php

720 lines 30.1 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 /**
3 * Meta Migration for Easy Invoice
4 *
5 * @package EasyInvoice
6 * @subpackage Migration
7 * @since 2.0.0
8 */
9
10 namespace EasyInvoice\Migration\Src;
11
12 /**
13 * Meta migration class.
14 *
15 * Handles migration of meta data from old structure to new structure.
16 *
17 * @since 2.0.0
18 */
19 class MetaMigration extends AbstractMigration {
20
21 /**
22 * Migration description.
23 *
24 * @since 2.0.0
25 * @var string
26 */
27 protected $description = 'Migrate meta data to new structure';
28
29 /**
30 * Meta key mappings.
31 *
32 * @since 2.0.0
33 * @var array
34 */
35 private $meta_mappings = [
36 'easy-invoice' => [
37 // Basic info
38 'invoice_number' => '_easy_invoice_number',
39 'created_date' => '_easy_invoice_date',
40 'due_date' => '_easy_invoice_due_date',
41 'status' => '_easy_invoice_status',
42 'invoice_status' => '_easy_invoice_status', // Old plugin specific status field
43 'currency' => '_easy_invoice_currency_code',
44 'description' => '_easy_invoice_description',
45 'notes' => '_easy_invoice_notes',
46 'terms_and_conditions' => '_easy_invoice_terms',
47
48 // Tax and discount
49 'tax_type' => '_easy_invoice_prices_include_tax',
50 'tax_rate' => '_easy_invoice_tax_rate',
51 'discount' => '_easy_invoice_discount_value',
52 'discount_type' => '_easy_invoice_discount_type',
53 'discount_calculation_method' => '_easy_invoice_discount_calculation_method',
54
55 // Items
56 'easy_invoice_line_items' => '_easy_invoice_items',
57
58 // Client info (these are used to find/create WordPress user)
59 'client_name' => '_easy_invoice_client_name',
60 'client_email' => '_easy_invoice_client_email',
61 'client_company' => '_easy_invoice_client_company',
62 'client_address' => '_easy_invoice_client_address',
63 'client_phone' => '_easy_invoice_client_phone',
64 'client_vat' => '_easy_invoice_client_vat',
65
66 // Template settings
67 'template' => '_easy_invoice_template'
68 ],
69 'easy-invoice-quotes' => [
70 // Basic info
71 'quote_number' => '_easy_invoice_quote_number',
72 'created_date' => '_easy_invoice_quote_date',
73 'valid_until' => '_easy_invoice_quote_expiry_date',
74 'status' => '_easy_invoice_quote_status',
75 'currency' => '_easy_invoice_quote_currency',
76 'description' => '_easy_invoice_quote_description',
77 'notes' => '_easy_invoice_quote_notes',
78
79 // Tax and discount
80 'tax_type' => '_easy_invoice_quote_prices_include_tax',
81 'tax_rate' => '_easy_invoice_quote_tax_rate',
82 'discount' => '_easy_invoice_quote_discount_value',
83 'discount_type' => '_easy_invoice_quote_discount_type',
84 'discount_calculation_method' => '_easy_invoice_quote_discount_calculation_method',
85
86 // Items
87 'easy_invoice_quote_line_items' => '_easy_invoice_quote_items',
88
89 // Client info (these are used to find/create WordPress user)
90 'client_name' => '_easy_invoice_quote_client_name',
91 'client_email' => '_easy_invoice_quote_client_email',
92 'client_company' => '_easy_invoice_quote_client_company',
93 'client_address' => '_easy_invoice_quote_client_address',
94 'client_phone' => '_easy_invoice_quote_client_phone',
95 'client_vat' => '_easy_invoice_quote_client_vat',
96
97 // Template settings
98 'template' => '_easy_invoice_quote_template'
99 ],
100 'easy-invoice-payment' => [
101 // Basic info
102 'payment_number' => '_easy_payment_number',
103 'payment_date' => '_easy_payment_date',
104 'status' => '_easy_payment_status',
105 'payment_method' => '_easy_payment_method',
106 'transaction_id' => '_easy_payment_transaction_id',
107 'notes' => '_easy_payment_notes',
108
109 // Invoice info
110 'invoice_id' => '_easy_payment_invoice_id',
111 'invoice_number' => '_easy_payment_invoice_number',
112
113 // Amount info
114 'amount' => '_easy_payment_amount',
115 'currency' => '_easy_payment_currency_code'
116 ]
117 ];
118
119 // ... (keep the rest of the existing code until transform_items_meta method)
120
121 /**
122 * Transform items meta.
123 *
124 * @since 2.0.0
125 * @param mixed $items Items array
126 * @return array
127 */
128 /**
129 * Migrate post meta from old post to new post.
130 *
131 * @since 2.0.0
132 * @param int $old_post_id Old post ID
133 * @param int $new_post_id New post ID
134 * @param string $post_type New post type
135 * @return array
136 */
137 public function migrate_post_meta(int $old_post_id, int $new_post_id, string $post_type): array {
138 try {
139 global $wpdb;
140
141 // Get all meta for the old post directly from database to ensure we get everything
142 $meta_values = $wpdb->get_results($wpdb->prepare(
143 "SELECT meta_key, meta_value FROM {$wpdb->postmeta} WHERE post_id = %d",
144 $old_post_id
145 ), ARRAY_A);
146
147 if (empty($meta_values)) {
148 return [
149 'success' => true,
150 'message' => 'No meta to migrate',
151 'migrated_count' => 0
152 ];
153 }
154
155 // Convert to format similar to get_post_custom
156 $meta_data = [];
157 foreach ($meta_values as $row) {
158 $meta_data[$row['meta_key']][] = $row['meta_value'];
159 }
160
161 // Get the old post type
162 $old_post_type = get_post_type($old_post_id);
163 if (empty($old_post_type)) {
164 $old_post_type = $wpdb->get_var($wpdb->prepare(
165 "SELECT post_type FROM {$wpdb->posts} WHERE ID = %d",
166 $old_post_id
167 ));
168 }
169
170 if (empty($old_post_type)) {
171 return [
172 'success' => false,
173 'message' => 'Could not determine old post type'
174 ];
175 }
176
177 $migrated_count = 0;
178 $errors = [];
179
180 // Handle items first if they exist
181 $possible_item_keys = [];
182 $new_items_key = '';
183
184 if ($post_type === 'easy_invoice') {
185 $possible_item_keys = [
186 'easy_invoice_line_items',
187 '_easy_invoice_line_items',
188 'invoice_items',
189 '_invoice_items',
190 'line_items',
191 '_line_items'
192 ];
193 $new_items_key = '_easy_invoice_items';
194 } elseif ($post_type === 'easy_invoice_quote') {
195 $possible_item_keys = [
196 'easy_invoice_quote_line_items',
197 '_easy_invoice_quote_line_items',
198 'quote_items',
199 '_quote_items',
200 'line_items',
201 '_line_items'
202 ];
203 $new_items_key = '_easy_invoice_quote_items';
204 }
205
206 // Try to find items using the correct old plugin meta keys
207 $items = null;
208 if ($post_type === 'easy_invoice') {
209 // For invoices, look for the correct old meta key
210 if (isset($meta_data['easy_invoice_line_items'])) {
211 $items = $meta_data['easy_invoice_line_items'][0];
212 $this->log(sprintf('Found invoice items using key: easy_invoice_line_items for post %d', $old_post_id));
213 }
214 } elseif ($post_type === 'easy_invoice_quote') {
215 // For quotes, look for the correct old meta key
216 if (isset($meta_data['easy_invoice_quote_line_items'])) {
217 $items = $meta_data['easy_invoice_quote_line_items'][0];
218 $this->log(sprintf('Found quote items using key: easy_invoice_quote_line_items for post %d', $old_post_id));
219 }
220 }
221
222 if ($items !== null) {
223 $items = $this->transform_items_meta($items);
224 if (!empty($items)) {
225 delete_post_meta($new_post_id, $new_items_key);
226 update_post_meta($new_post_id, $new_items_key, $items);
227 $migrated_count++;
228 $this->log(sprintf('Migrated %d items for post %d to %d', count($items), $old_post_id, $new_post_id));
229 }
230 }
231
232 // Log available meta keys for debugging
233 $this->log(sprintf('Available meta keys for post %d (type: %s): %s',
234 $old_post_id,
235 $old_post_type,
236 implode(', ', array_keys($meta_data))
237 ));
238
239 // Get meta mappings for this post type
240 $mappings = $this->meta_mappings[$old_post_type] ?? [];
241 if (empty($mappings)) {
242 return [
243 'success' => false,
244 'message' => sprintf('No meta mappings found for post type: %s', $old_post_type)
245 ];
246 }
247
248 // Migrate each meta value
249 foreach ($meta_data as $old_key => $values) {
250 // Skip items keys as we've already handled them
251 if (in_array($old_key, $possible_item_keys)) {
252 continue;
253 }
254
255 // Find the new key in mappings
256 $new_key = $mappings[$old_key] ?? '';
257 if (empty($new_key)) {
258 // Try without underscore prefix
259 if (strpos($old_key, '_') === 0) {
260 $new_key = $mappings[substr($old_key, 1)] ?? '';
261 }
262 // Try with underscore prefix
263 if (empty($new_key) && strpos($old_key, '_') !== 0) {
264 $new_key = $mappings['_' . $old_key] ?? '';
265 }
266 if (empty($new_key)) {
267 continue; // Skip if no mapping found
268 }
269 }
270
271 // Get the first value since WordPress stores meta as arrays
272 $value = $values[0] ?? null;
273 if ($value === null) {
274 continue;
275 }
276
277 // Delete any existing meta with the new key
278 delete_post_meta($new_post_id, $new_key);
279
280 // Skip invoice_number as it will be generated
281 if ($old_key === 'invoice_number') {
282 continue;
283 }
284
285 // Transform and update the meta value
286 $value = maybe_unserialize($value);
287
288 // Special handling for tax_type
289 if ($old_key === 'tax_type') {
290 $value = $value === 'inclusive' ? 'yes' : 'no';
291 }
292
293 // Special handling for status fields
294 if ($old_key === 'status') {
295 $value = $this->transform_status_value($value, $post_type);
296 $this->log(sprintf('Transformed status from %s to %s for post type %s', $meta_data[$old_key][0], $value, $post_type));
297 }
298
299 // Special handling for dates
300 if (in_array($old_key, ['created_date', 'due_date', 'valid_until'])) {
301 // Convert from "August 11, 2025" to "2025-08-11"
302 if ($timestamp = strtotime($value)) {
303 $value = date('Y-m-d', $timestamp);
304 $this->log(sprintf('Converted date %s to %s for key %s', $meta_data[$old_key][0], $value, $old_key));
305 } else {
306 $this->log(sprintf('Failed to parse date: %s for key %s', $value, $old_key), 'warning');
307 }
308 }
309
310 // Update meta
311 $result = update_post_meta($new_post_id, $new_key, $value);
312 if ($result) {
313 $migrated_count++;
314 $this->log(sprintf('Migrated meta %s to %s for post %d', $old_key, $new_key, $new_post_id));
315 } else {
316 $errors[] = sprintf('Failed to migrate meta: %s', $old_key);
317 $this->log(sprintf('Failed to migrate meta %s to %s for post %d', $old_key, $new_key, $new_post_id), 'error');
318 }
319 }
320
321 // After migrating all meta, handle client ID assignment
322 if ($post_type === 'easy_invoice' || $post_type === 'easy_invoice_quote') {
323 // Get client email from meta
324 $client_email = '';
325 foreach ($meta_data as $key => $values) {
326 if (in_array($key, ['client_email', '_client_email', 'easy_invoice_client_email', 'easy_invoice_quote_client_email'])) {
327 $client_email = $values[0] ?? '';
328 break;
329 }
330 }
331
332 if (!empty($client_email)) {
333 // Try to find existing user by email first
334 $user = get_user_by('email', $client_email);
335
336 if ($user) {
337 $user_id = $user->ID;
338 } else {
339 // Use ClientMigration to get/create user if not found
340 $client_migration = new ClientMigration();
341
342 // Get client name from meta with all possible keys
343 $client_name = '';
344 $name_keys = [
345 'client_name',
346 '_client_name',
347 'easy_invoice_client_name',
348 'easy_invoice_quote_client_name',
349 'name',
350 '_name',
351 'customer_name',
352 '_customer_name'
353 ];
354 foreach ($name_keys as $key) {
355 if (isset($meta_data[$key][0]) && !empty($meta_data[$key][0])) {
356 $client_name = $meta_data[$key][0];
357 break;
358 }
359 }
360
361 // Create client data array
362 $client_data = [
363 'email' => $client_email,
364 'name' => $client_name,
365 'company' => $meta_data['client_company'][0] ?? $meta_data['_client_company'][0] ?? $meta_data['company'][0] ?? $meta_data['_company'][0] ?? '',
366 'address' => $meta_data['client_address'][0] ?? $meta_data['_client_address'][0] ?? $meta_data['address'][0] ?? $meta_data['_address'][0] ?? '',
367 'phone' => $meta_data['client_phone'][0] ?? $meta_data['_client_phone'][0] ?? $meta_data['phone'][0] ?? $meta_data['_phone'][0] ?? '',
368 'vat' => $meta_data['client_vat'][0] ?? $meta_data['_client_vat'][0] ?? $meta_data['vat'][0] ?? $meta_data['_vat'][0] ?? ''
369 ];
370
371 // Get user ID from ClientMigration
372 $user_id = $client_migration->get_or_create_user($client_data);
373 }
374
375 if ($user_id > 0) {
376 // Set client ID in post meta
377 $client_id_meta_key = $post_type === 'easy_invoice' ? '_easy_invoice_client_id' : '_easy_invoice_quote_client_id';
378 update_post_meta($new_post_id, $client_id_meta_key, $user_id);
379 $this->log(sprintf('Assigned client ID %d to %s %d', $user_id, $post_type, $new_post_id));
380 }
381 }
382 }
383
384 // Generate new invoice/quote number
385 if ($post_type === 'easy_invoice') {
386 if (class_exists('\\EasyInvoice\\Services\\InvoiceNumberService')) {
387 $invoice_service = new \EasyInvoice\Services\InvoiceNumberService();
388 $new_invoice_number = $invoice_service->generateUniqueNumber();
389 update_post_meta($new_post_id, '_easy_invoice_number', $new_invoice_number);
390 $this->log(sprintf('Generated new invoice number: %s for post %d', $new_invoice_number, $new_post_id));
391 $migrated_count++;
392 }
393 } elseif ($post_type === 'easy_invoice_quote') {
394 if (class_exists('\\EasyInvoice\\Services\\QuoteNumberService')) {
395 $quote_service = new \EasyInvoice\Services\QuoteNumberService();
396 $new_quote_number = $quote_service->generateUniqueNumber();
397 update_post_meta($new_post_id, '_easy_invoice_quote_number', $new_quote_number);
398 $this->log(sprintf('Generated new quote number: %s for post %d', $new_quote_number, $new_post_id));
399 $migrated_count++;
400 }
401 }
402
403 if (empty($errors)) {
404 return [
405 'success' => true,
406 'message' => sprintf('Successfully migrated %d meta entries', $migrated_count),
407 'migrated_count' => $migrated_count
408 ];
409 } else {
410 return [
411 'success' => false,
412 'message' => 'Meta migration completed with errors: ' . implode(', ', $errors),
413 'migrated_count' => $migrated_count
414 ];
415 }
416
417 } catch (\Exception $e) {
418 $this->log(sprintf('Failed to migrate meta for post %d: %s', $old_post_id, $e->getMessage()), 'error');
419 return [
420 'success' => false,
421 'message' => $e->getMessage()
422 ];
423 }
424 }
425
426 private function transform_items_meta($items): array {
427 if (!is_array($items)) {
428 $items = maybe_unserialize($items);
429 }
430
431 if (!is_array($items)) {
432 return [];
433 }
434
435 $transformed = [];
436 foreach ($items as $item) {
437 if (!is_array($item)) {
438 $item = maybe_unserialize($item);
439 }
440
441 if (!is_array($item)) {
442 continue;
443 }
444
445 // The old format has these keys:
446 // quantity, item_title, adjust, rate, description, taxable
447
448 $title = $item['item_title'] ?? '';
449 $description = $item['description'] ?? '';
450
451 // Handle quantity (always a string in old format)
452 $quantity = isset($item['quantity']) ? floatval($item['quantity']) : 1;
453 // Allow quantity 0 - don't force minimum of 1
454
455 // Handle price (called 'rate' in old format)
456 $price = isset($item['rate']) ? floatval($item['rate']) : 0;
457
458 // Handle adjustment (called 'adjust' in old format)
459 // The old system stored adjust as a percentage value (e.g., 10 for 10%)
460 $adjust_percentage = 0;
461 if (isset($item['adjust']) && !empty($item['adjust'])) {
462 $adjust_percentage = floatval($item['adjust']);
463 // Ensure it's a valid percentage
464 if ($adjust_percentage < -100) {
465 $adjust_percentage = -100; // Cap at -100%
466 }
467 }
468
469 // Skip if no title
470 if (empty($title)) {
471 continue;
472 }
473
474 // Calculate total based on old system logic
475 // Old system: total = quantity * rate * (1 + adjust_percentage/100)
476 $total = $quantity * $price;
477 if ($adjust_percentage != 0) {
478 $total = $total * (1 + $adjust_percentage / 100);
479 }
480
481 // Handle taxable field - this is critical for tax calculations
482 // The old plugin logic: isset($line_item['taxable']) && (boolean)$line_item['taxable']
483 // This means: if field doesn't exist OR is falsy → NOT taxable
484 $taxable = '0'; // Default to NOT taxable (following old plugin logic)
485 $original_taxable = $item['taxable'] ?? 'NOT_SET';
486
487 if (isset($item['taxable'])) {
488 // The old system used (boolean)$item['taxable']
489 // Convert various formats to '1' or '0'
490 $tax_value = $item['taxable'];
491
492 // Log the original taxable value and type for debugging
493 $this->log(sprintf('Processing taxable field: value=%s, type=%s', var_export($tax_value, true), gettype($tax_value)));
494
495 if (is_bool($tax_value)) {
496 $taxable = $tax_value ? '1' : '0';
497 $this->log(sprintf('Boolean taxable: %s -> %s', var_export($tax_value, true), $taxable));
498 } elseif (is_string($tax_value)) {
499 $tax_value = strtolower(trim($tax_value));
500 if (in_array($tax_value, ['0', 'false', 'no', 'n', 'off'], true)) {
501 $taxable = '0';
502 $this->log(sprintf('String taxable (false): %s -> %s', $tax_value, $taxable));
503 } else {
504 $taxable = '1'; // Only set to taxable if explicitly truthy
505 $this->log(sprintf('String taxable (true): %s -> %s', $tax_value, $taxable));
506 }
507 } elseif (is_numeric($tax_value)) {
508 $taxable = $tax_value > 0 ? '1' : '0';
509 $this->log(sprintf('Numeric taxable: %s -> %s', $tax_value, $taxable));
510 } else {
511 $this->log(sprintf('Unknown taxable type: %s, value: %s', gettype($tax_value), var_export($tax_value, true)));
512 }
513 } elseif (isset($item['tax_exempt'])) {
514 // Some versions used tax_exempt instead
515 $exempt_value = $item['tax_exempt'];
516 $this->log(sprintf('Using tax_exempt field: %s', var_export($exempt_value, true)));
517
518 if (is_bool($exempt_value)) {
519 $taxable = $exempt_value ? '0' : '1'; // tax_exempt = true means NOT taxable
520 $this->log(sprintf('Boolean tax_exempt: %s -> taxable: %s', var_export($exempt_value, true), $taxable));
521 } elseif (is_string($exempt_value)) {
522 $exempt_value = strtolower(trim($exempt_value));
523 if (in_array($exempt_value, ['1', 'true', 'yes', 'y', 'on'], true)) {
524 $taxable = '0'; // tax_exempt = true means NOT taxable
525 $this->log(sprintf('String tax_exempt (true): %s -> taxable: %s', $exempt_value, $taxable));
526 } else {
527 $this->log(sprintf('String tax_exempt (false): %s -> taxable: %s', $exempt_value, $taxable));
528 }
529 }
530 } elseif (isset($item['is_taxable'])) {
531 // Some versions used is_taxable
532 $is_taxable = $item['is_taxable'];
533 $this->log(sprintf('Using is_taxable field: %s', var_export($is_taxable, true)));
534
535 if (is_bool($is_taxable)) {
536 $taxable = $is_taxable ? '1' : '0';
537 $this->log(sprintf('Boolean is_taxable: %s -> taxable: %s', var_export($is_taxable, true), $taxable));
538 } elseif (is_string($is_taxable)) {
539 $is_taxable = strtolower(trim($is_taxable));
540 if (in_array($is_taxable, ['0', 'false', 'no', 'n', 'off'], true)) {
541 $taxable = '0';
542 $this->log(sprintf('String is_taxable (false): %s -> taxable: %s', $is_taxable, $taxable));
543 } else {
544 $this->log(sprintf('String is_taxable (true): %s -> taxable: %s', $is_taxable, $taxable));
545 }
546 }
547 } else {
548 $this->log(sprintf('No taxable field found, using default: %s', $taxable));
549 }
550
551 // Log the transformation for debugging
552 $this->log(sprintf(
553 'Transformed item: title="%s", quantity=%f, price=%f, adjust_percentage=%f, taxable=%s, total=%f (original taxable: %s, original adjust: %s)',
554 $title, $quantity, $price, $adjust_percentage, $taxable, $total,
555 var_export($item['taxable'] ?? 'NOT_SET', true),
556 var_export($item['adjust'] ?? 'NOT_SET', true)
557 ));
558
559 $transformed[] = [
560 'title' => $title,
561 'description' => $description,
562 'quantity' => $quantity,
563 'price' => $price,
564 'adjust_percentage' => $adjust_percentage,
565 'total' => $total,
566 'taxable' => $taxable,
567 'id' => 0 // Required by new format
568 ];
569 }
570
571 return $transformed;
572 }
573
574 /**
575 * Check if migration is needed.
576 *
577 * @since 2.0.0
578 * @return bool
579 */
580 public function is_needed(): bool {
581 global $wpdb;
582
583 // Check if any old post types have meta that needs migration
584 $old_post_types = ['easy-invoice', 'easy-invoice-quotes', 'easy-invoice-payment'];
585
586 foreach ($old_post_types as $post_type) {
587 $count = $wpdb->get_var($wpdb->prepare(
588 "SELECT COUNT(*) FROM {$wpdb->postmeta} pm
589 JOIN {$wpdb->posts} p ON p.ID = pm.post_id
590 WHERE p.post_type = %s AND p.post_status NOT IN ('auto-draft', 'trash', 'inherit')",
591 $post_type
592 ));
593
594 if ($count > 0) {
595 $this->log(sprintf('Found %d meta entries for post type %s that need migration', $count, $post_type));
596 return true;
597 }
598 }
599
600 return false;
601 }
602
603 /**
604 * Run the migration.
605 *
606 * @since 2.0.0
607 * @return array
608 */
609 public function migrate(): array {
610 try {
611 global $wpdb;
612
613 $migrated_count = 0;
614 $errors = [];
615
616 // Get all posts from old post types
617 $old_post_types = ['easy-invoice', 'easy-invoice-quotes', 'easy-invoice-payment'];
618 $new_post_types = [
619 'easy-invoice' => 'easy_invoice',
620 'easy-invoice-quotes' => 'easy_invoice_quote',
621 'easy-invoice-payment' => 'easy_payment'
622 ];
623
624 foreach ($old_post_types as $old_post_type) {
625 $posts = $wpdb->get_results($wpdb->prepare(
626 "SELECT ID FROM {$wpdb->posts} WHERE post_type = %s AND post_status NOT IN ('auto-draft', 'trash', 'inherit')",
627 $old_post_type
628 ));
629
630 if (empty($posts)) {
631 continue;
632 }
633
634 foreach ($posts as $post) {
635 // Get the new post ID from the mapping
636 $new_post_id = get_option('easy_invoice_migration_post_mapping_' . $post->ID, 0);
637 if ($new_post_id <= 0) {
638 $errors[] = sprintf('No mapping found for old post ID: %d', $post->ID);
639 continue;
640 }
641
642 // Get the new post type
643 $new_post_type = $new_post_types[$old_post_type] ?? '';
644 if (empty($new_post_type)) {
645 $errors[] = sprintf('No post type mapping found for: %s', $old_post_type);
646 continue;
647 }
648
649 // Migrate meta
650 $result = $this->migrate_post_meta($post->ID, $new_post_id, $new_post_type);
651 if ($result['success']) {
652 $migrated_count += $result['migrated_count'];
653 } else {
654 $errors[] = $result['message'];
655 }
656 }
657 }
658
659 if (empty($errors)) {
660 return [
661 'success' => true,
662 'message' => sprintf('Successfully migrated %d meta entries', $migrated_count),
663 'migrated_count' => $migrated_count
664 ];
665 } else {
666 return [
667 'success' => false,
668 'message' => 'Meta migration completed with errors: ' . implode(', ', $errors),
669 'migrated_count' => $migrated_count
670 ];
671 }
672
673 } catch (\Exception $e) {
674 $this->log('Meta migration failed: ' . $e->getMessage(), 'error');
675 return [
676 'success' => false,
677 'message' => $e->getMessage()
678 ];
679 }
680 }
681
682 /**
683 * Transform status value from old plugin format to new plugin format.
684 *
685 * @since 2.0.0
686 * @param string $old_status Old status value
687 * @param string $post_type Post type (easy_invoice or easy_invoice_quote)
688 * @return string New status value
689 */
690 private function transform_status_value(string $old_status, string $post_type): string {
691 $old_status = strtolower(trim($old_status));
692
693 if ($post_type === 'easy_invoice') {
694 // Invoice status mapping
695 $status_mapping = [
696 'available' => 'unpaid', // Old 'available' becomes 'unpaid' in new plugin
697 'draft' => 'draft', // 'draft' stays the same
698 'paid' => 'paid', // 'paid' stays the same
699 'cancelled' => 'cancelled' // 'cancelled' stays the same
700 ];
701 } elseif ($post_type === 'easy_invoice_quote') {
702 // Quote status mapping
703 $status_mapping = [
704 'available' => 'available', // 'available' stays the same
705 'draft' => 'draft', // 'draft' stays the same
706 'sent' => 'sent', // 'sent' stays the same
707 'declined' => 'declined', // 'declined' stays the same
708 'cancelled' => 'cancelled' // 'cancelled' stays the same
709 ];
710 } else {
711 // Unknown post type, return original status
712 return $old_status;
713 }
714
715 // Return mapped status or original if no mapping found
716 $this->log(sprintf('Status transformation: %s -> %s for post type %s', $old_status, $status_mapping[$old_status] ?? $old_status, $post_type));
717 return $status_mapping[$old_status] ?? $old_status;
718 }
719 }
720