PluginProbe
Import WP – CSV & XML Import Export for WordPress / 2.15.1
Import WP – CSV & XML Import Export for WordPress v2.15.1
2.15.1 2.15.0 2.14.24 2.14.23 2.7.0 2.7.1 2.7.10 2.7.11 2.7.12 2.7.13 2.7.14 2.7.2 2.7.3 2.7.4 2.7.5 2.7.6 2.7.7 2.7.8 2.7.9 2.8.0 2.8.1 2.8.2 2.8.3 2.9.0 2.9.1 All 144 releases
jc-importer / class / Common / Migration / Migrations.php

Migrations.php in Import WP – CSV & XML Import Export for WordPress 2.15.1, at class/Common/Migration/Migrations.php

1,077 lines 42.1 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2
3 namespace ImportWP\Common\Migration;
4
5 use ImportWP\Common\Filesystem\Filesystem;
6 use ImportWP\Common\Importer\ImporterManager;
7 use ImportWP\Common\Model\ImporterModel;
8 use ImportWP\Common\Util\Logger;
9 use ImportWP\Container;
10
11 class Migrations
12 {
13 private $_version = 0;
14 private $_migrations = array();
15
16 public function __construct()
17 {
18
19 $starting_version = intval(get_option('iwp_db_version', 0));
20 if ($starting_version <= 3 && $starting_version > 0) {
21 // v1 migrations
22 $this->_migrations[] = array($this, 'migration_01');
23 $this->_migrations[] = array($this, 'migration_02');
24 $this->_migrations[] = array($this, 'migration_03');
25 $this->_migrations[] = array($this, 'migration_04_migrate_v1_to_v2_data');
26 } else {
27 // skip v1 migrations
28 $this->_migrations[] = null;
29 $this->_migrations[] = null;
30 $this->_migrations[] = null;
31 $this->_migrations[] = null;
32 }
33
34 // v2 migrations
35 $this->_migrations[] = array($this, 'migration_05_multiple_crons');
36 $this->_migrations[] = array($this, 'migration_06_cron_update');
37 $this->_migrations[] = array($this, 'migration_07_add_session_table');
38 $this->_migrations[] = array($this, 'migration_08_migrate_taxonomy_settings');
39 $this->_migrations[] = array($this, 'migration_09_migrate_attachment_settings');
40 $this->_migrations[] = array($this, 'migration_10_relative_importer_file_paths');
41 $this->_migrations[] = array($this, 'migration_11_prefix_custom_methods');
42
43 $this->_version = count($this->_migrations);
44 }
45
46 public function isSetup()
47 {
48 $version = intval(get_option('iwp_db_version', get_option('jci_db_version', 0)));
49 if ($version < count($this->_migrations)) {
50 return false;
51 }
52 return true;
53 }
54
55 public function install()
56 {
57
58 //run through schema migrations only
59 $this->migrate(false);
60 }
61
62 public function uninstall()
63 {
64 global $wpdb;
65 $wpdb->query("DROP TABLE IF EXISTS `" . $wpdb->prefix . "importer_log`;");
66 $wpdb->query("DROP TABLE IF EXISTS `" . $wpdb->prefix . "importer_files`;");
67 delete_option('iwp_db_version');
68 delete_option('jci_db_version');
69 delete_option('iwp_is_migrating');
70 }
71
72 public function migrate($migrate_data = true)
73 {
74
75 $verion_key = 'iwp_db_version';
76 $version = intval(get_option('iwp_db_version', get_option('jci_db_version', 0)));
77 $migrating = get_option('iwp_is_migrating', 'no');
78 if ('yes' === $migrating) {
79 return;
80 }
81
82 if ($version < count($this->_migrations)) {
83
84 require_once(ABSPATH . 'wp-admin/includes/upgrade.php');
85
86 for ($i = 0; $i < count($this->_migrations); $i++) {
87
88 $migration_version = $i + 1;
89 if ($version < $migration_version) {
90
91 update_option('iwp_is_migrating', 'yes');
92
93 set_time_limit(0);
94
95 // Run migration
96 if (!is_null($this->_migrations[$i])) {
97 call_user_func($this->_migrations[$i], $migrate_data);
98 }
99
100 // Flag as migrated
101 update_option($verion_key, $migration_version);
102 update_option('iwp_is_migrating', 'no');
103 }
104 }
105 }
106
107 // update_option('iwp_is_setup', 'yes');
108 }
109
110 public function get_charset()
111 {
112
113 global $wpdb;
114 $charset_collate = "";
115
116 if (!empty($wpdb->charset)) {
117 $charset_collate = "DEFAULT CHARACTER SET $wpdb->charset";
118 }
119 if (!empty($wpdb->collate)) {
120 $charset_collate .= " COLLATE $wpdb->collate";
121 }
122 return $charset_collate;
123 }
124
125 public function migration_01($migrate_data = true)
126 {
127
128 global $wpdb;
129 $charset_collate = $this->get_charset();
130
131 $sql = "CREATE TABLE `" . $wpdb->prefix . "importer_log` (
132 `id` int(11) unsigned NOT NULL AUTO_INCREMENT,
133 `importer_name` varchar(255) DEFAULT NULL,
134 `object_id` int(11) DEFAULT NULL,
135 `template` varchar(255) DEFAULT NULL,
136 `type` varchar(255) DEFAULT NULL,
137 `file` varchar(255) DEFAULT NULL,
138 `version` int(11) DEFAULT NULL,
139 `row` int(11) DEFAULT NULL,
140 `src` text,
141 `value` text,
142 `created` datetime DEFAULT NULL,
143 `import_settings` TEXT NULL,
144 `mapped_fields` TEXT NULL,
145 `attachments` TEXT NULL,
146 `taxonomies` TEXT NULL,
147 `parser_settings` TEXT NULL,
148 `template_settings` TEXT NULL,
149 PRIMARY KEY (`id`)
150 ) $charset_collate; ";
151
152 dbDelta($sql);
153
154 $sql = "CREATE TABLE `" . $wpdb->prefix . "importer_files`(
155 `id` int(11) unsigned NOT NULL AUTO_INCREMENT,
156 `importer_id` INT(11),
157 `author_id` INT(11),
158 `mime_type` VARCHAR(255),
159 `name` VARCHAR(255),
160 `src` VARCHAR(255),
161 `created` DATETIME,
162 PRIMARY KEY (`id`)
163 ) $charset_collate;";
164 dbDelta($sql);
165 }
166
167 public function migration_02($migrate_data = true)
168 {
169
170 if (!$migrate_data) {
171 return;
172 }
173
174 global $wpdb;
175
176 // return list of importer file links
177 $importer_file_ids = $this->migration_02_get_importer_file_ids();
178
179 // get ids of all importers and fetch all their import files
180 $importers = $wpdb->get_col("SELECT id FROM " . $wpdb->posts . " WHERE post_type = 'jc-imports'");
181
182 //
183 $or_query = '';
184 if (!empty($importers)) {
185 $or_query = "
186 OR (
187 post_type = 'attachment'
188 AND post_parent IN ( " . implode(',', $importers) . ")
189 )
190 ";
191 }
192
193 $results = $wpdb->get_results("
194 SELECT ID, guid, post_parent, post_author, post_mime_type, post_name, post_date
195 FROM " . $wpdb->posts . "
196 WHERE
197 (post_type = 'jc-import-files')
198 " . $or_query . "
199 ");
200
201
202 if (!empty($results)) {
203
204 // print_r($importer_attachments);
205 $upload_dir = wp_upload_dir();
206 $baseurl = $upload_dir['baseurl'];
207 $records = array();
208
209 foreach ($results as $importer) {
210
211 $src = $importer->guid;
212 if (strpos($src, $baseurl) === 0) {
213 $src = substr($src, strlen($baseurl));
214 }
215 // $record = array(
216 $importer_id = $importer->post_parent;
217 $author_id = $importer->post_author;
218 $mime = $importer->post_mime_type;
219 $name = $importer->post_name;
220 $attachment_src = $src;
221 $created = $importer->post_date;
222 // );
223
224 $query_result = $wpdb->query($wpdb->prepare("INSERT INTO `" . $wpdb->prefix . "importer_files`(importer_id, author_id, mime_type, name, src, created) VALUES(%d, %d, %s, %s, %s, %s)", $importer_id, $author_id, $mime, $name, $attachment_src, $created));
225
226 // check if importer_file_id exists in importer settings array
227 if (is_array($importer_file_ids) && array_key_exists($importer->ID, $importer_file_ids)) {
228 $importer_file_ids[$importer->ID] = $wpdb->insert_id;
229 set_transient('jci_db_import_file_ids', $importer_file_ids);
230 }
231
232 if ($query_result) {
233 wp_delete_post($importer->ID, true);
234 }
235 }
236 }
237
238 // loop through all importer_file meta data
239 $importer_settings = $wpdb->get_results("SELECT * FROM `" . $wpdb->prefix . "postmeta` WHERE meta_key='_import_settings'");
240 if ($importer_settings) {
241
242 foreach ($importer_settings as $settings) {
243
244 $post_id = $settings->post_id;
245 $value = maybe_unserialize($settings->meta_value);
246
247 if (is_array($importer_file_ids) && array_key_exists($value['import_file'], $importer_file_ids)) {
248 $value['import_file'] = $importer_file_ids[$value['import_file']];
249 update_post_meta($post_id, '_import_settings', $value);
250 continue;
251 }
252 }
253 }
254 }
255
256 /**
257 * Fetch list of current importer_file id's
258 * @return array
259 */
260 private function migration_02_get_importer_file_ids()
261 {
262 global $wpdb;
263
264 $transient = get_transient('jci_db_import_file_ids');
265 if (get_transient('jci_db_import_file_ids') === false) {
266
267 $importer_files = array();
268
269 $importer_settings = $wpdb->get_results("SELECT * FROM `" . $wpdb->prefix . "postmeta` WHERE meta_key='_import_settings'");
270 if ($importer_settings) {
271
272 foreach ($importer_settings as $settings) {
273 $value = maybe_unserialize($settings->meta_value);
274 $importer_files[$value['import_file']] = null;
275 }
276 }
277 set_transient('jci_db_import_file_ids', $importer_files);
278
279 return $importer_files;
280 } else {
281 return $transient;
282 }
283 }
284
285 /**
286 * Migration 03
287 * Refactor logs table, so duplication of data.
288 *
289 * @since 1.1.0
290 */
291 public function migration_03($migrate_data = true)
292 {
293
294 global $wpdb;
295 $wpdb->query("ALTER TABLE `" . $wpdb->prefix . "importer_log`
296 DROP COLUMN importer_name,
297 DROP COLUMN src,
298 DROP COLUMN template,
299 DROP COLUMN type,
300 DROP COLUMN import_settings,
301 DROP COLUMN mapped_fields,
302 DROP COLUMN attachments,
303 DROP COLUMN taxonomies,
304 DROP COLUMN parser_settings,
305 DROP COLUMN template_settings");
306 }
307
308 public function migration_04_migrate_v1_to_v2_data($migrate_data = true)
309 {
310 global $wpdb;
311 // remove log table
312 // $wpdb->query("DROP TABLE `" . $wpdb->prefix . "importer_log`");
313
314 $query = new \WP_Query([
315 'post_type' => 'jc-imports',
316 'posts_per_page' => -1
317 ]);
318
319 /**
320 * @var ImporterManager $importer_manager
321 */
322 $importer_manager = Container::getInstance()->get('importer_manager');
323
324 if ($query->have_posts()) {
325 foreach ($query->posts as $post) {
326 $v2_id = get_post_meta($post->ID, '_iwp_v2_importer', true);
327 $import_settings = get_post_meta($post->ID, '_import_settings', true);
328 $mapped_fields = get_post_meta($post->ID, '_mapped_fields', true);
329 $attachments = get_post_meta($post->ID, '_attachments', true);
330 $taxonomies = get_post_meta($post->ID, '_taxonomies', true);
331 $parser_settings = get_post_meta($post->ID, '_parser_settings', true);
332 $field_permissions = get_post_meta($post->ID, 'field_permissions', true);
333
334 $import_file_id = $import_settings['import_file'];
335
336
337 $file_result = $wpdb->get_var($wpdb->prepare("SELECT src FROM {$wpdb->prefix}importer_files WHERE id=%d", [$import_file_id]));
338 $filepath = false;
339 if ($file_result) {
340 $wp_upload_dir = wp_upload_dir();
341 $filepath = $wp_upload_dir['basedir'] . $file_result;
342 }
343
344 $post_type = '';
345 $template = $import_settings['template'];
346 if ($template === 'taxonomy') {
347 // Skip taxonomy template as this is now term (same but without the taxonomy column)
348 continue;
349 }
350
351 if ($template === 'page') {
352 $post_type = 'page';
353 } elseif ($template === 'post') {
354 $post_type = 'post';
355 } elseif ($template === 'custom-post-type') {
356 $post_type = $import_settings['general']['custom_post_type'];
357 }
358
359 $settings = [
360 'post_type' => $post_type,
361 'max_row' => intval($import_settings['row_count']) > 0 ? intval($import_settings['row_count']) : '',
362 'start_row' => intval($import_settings['start_line']) > 1 ? intval($import_settings['start_line']) : '',
363 ];
364
365 $converted_mapped_fields = [];
366 $enabled = [];
367 foreach ($mapped_fields as $group => $field_data) {
368 foreach ($field_data as $k => $v) {
369
370 $group_id = $group;
371 if ($group === 'page') {
372 $group_id = 'post';
373 }
374
375 switch ($k) {
376
377 // TODO: Migrate user fields
378 case 'generate_pass':
379 $settings['generate_pass'] = boolval($v) === true ? true : false;
380 break;
381 case 'notify_reg':
382 $settings['notify_users'] = boolval($v) === true ? true : false;
383 break;
384 case 'enable_user_nicename':
385 $enabled[$group_id . '.user_nicename'] = boolval($v) === true ? true : false;
386 break;
387 case 'enable_display_name':
388 $enabled[$group_id . '.display_name'] = boolval($v) === true ? true : false;
389 break;
390 case 'enable_description':
391 $enabled[$group_id . '.description'] = boolval($v) === true ? true : false;
392 break;
393 case 'enable_role':
394 $enabled[$group_id . '.role'] = boolval($v) === true ? true : false;
395 break;
396 case 'enable_pass':
397 $enabled[$group_id . '.user_pass'] = boolval($v) === true ? true : false;
398 break;
399 case 'user_url':
400 if (!empty($v)) {
401 $enabled[$group_id . '.user_url'] = true;
402 $converted_mapped_fields[$group_id . '.' . $k] = $v;
403 }
404 break;
405
406 // post_type
407 case 'post_author':
408 $converted_mapped_fields[$group_id . '._author.post_author'] = $v;
409 break;
410 case 'post_author_field_type':
411 $converted_mapped_fields[$group_id . '._author._author_type'] = $v;
412 break;
413 case 'post_parent':
414 $converted_mapped_fields[$group_id . '._parent.parent'] = $v;
415 break;
416 case 'post_parent_field_type':
417 $converted_mapped_fields[$group_id . '._parent._parent_type'] = $v;
418 break;
419 case 'post_parent_ref':
420 $converted_mapped_fields[$group_id . '._parent._parent_ref'] = $v;
421 break;
422 case 'post_excerpt':
423 $converted_mapped_fields[$group_id . '.' . $k] = $v;
424 if (!empty($v)) {
425 $enabled[$group_id . '.post_excerpt'] = true;
426 }
427 break;
428 case 'post_name':
429 $converted_mapped_fields[$group_id . '.' . $k] = $v;
430 if (!empty($v)) {
431 $enabled[$group_id . '.post_name'] = true;
432 }
433 break;
434 // Enable Fields
435 case 'enable_post_parent':
436 $enabled[$group_id . '._parent'] = boolval($v) === true ? true : false;
437 break;
438 case 'enable_post_status':
439 $enabled[$group_id . '.post_status'] = boolval($v) === true ? true : false;
440 break;
441 case 'enable_post_author':
442 $enabled[$group_id . '._author'] = boolval($v) === true ? true : false;
443 break;
444 case 'enable_menu_order':
445 $enabled[$group_id . '.menu_order'] = boolval($v) === true ? true : false;
446 break;
447 case 'enable_post_password':
448 $enabled[$group_id . '.post_password'] = boolval($v) === true ? true : false;
449 break;
450 case 'enable_post_date':
451 $enabled[$group_id . '.post_date'] = boolval($v) === true ? true : false;
452 break;
453 case 'enable_comment_status':
454 $enabled[$group_id . '.comment_status'] = boolval($v) === true ? true : false;
455 break;
456 case 'enable_ping_status':
457 $enabled[$group_id . '.ping_status'] = boolval($v) === true ? true : false;
458 break;
459 case 'enable_page_template':
460 $enabled[$group_id . '._wp_page_template'] = boolval($v) === true ? true : false;
461 break;
462 default:
463 $converted_mapped_fields[$group_id . '.' . $k] = $v;
464 break;
465 }
466 }
467 }
468
469 if ($taxonomies) {
470 foreach ($taxonomies as $group => $field_data) {
471 foreach ($field_data['tax'] as $row_id => $tax) {
472
473 $term = $field_data['term'][$row_id];
474
475 // TODO: what do we do with permissions
476 $permissions = $field_data['permissions'][$row_id];
477
478 $converted_mapped_fields['taxonomies.' . $row_id . '.tax'] = $tax;
479 $converted_mapped_fields['taxonomies.' . $row_id . '.term'] = $term;
480 }
481 }
482
483 $converted_mapped_fields['taxonomies._index'] = count($taxonomies);
484 }
485
486 if ($attachments) {
487 foreach ($attachments as $group => $field_data) {
488
489 // attachments used to be gloablly set ftp|remote|local
490 $type = $field_data['type'];
491 $ftp_server = $field_data['ftp']['server'];
492 $ftp_user = $field_data['ftp']['user'];
493 $ftp_pass = $field_data['ftp']['pass'];
494 $local_base_path = $field_data['local']['base_path'];
495
496 if ($type === 'url') {
497 $type = 'remote';
498 }
499
500 foreach ($field_data['location'] as $row_id => $location) {
501 $alt = $field_data['alt'][$row_id];
502 $title = $field_data['title'][$row_id];
503 $caption = $field_data['caption'][$row_id];
504 $description = $field_data['description'][$row_id];
505 $permissions = $field_data['permissions'][$row_id];
506 $featured_image = $field_data['featured_image'][$row_id];
507
508 $converted_mapped_fields['attachments.' . $row_id . '.location'] = $location;
509 $converted_mapped_fields['attachments.' . $row_id . '._featured'] = $featured_image == 1 ? 'yes' : 'no';
510 $converted_mapped_fields['attachments.' . $row_id . '._download'] = $type;
511 $converted_mapped_fields['attachments.' . $row_id . '._ftp_host'] = $ftp_server;
512 $converted_mapped_fields['attachments.' . $row_id . '._ftp_user'] = $ftp_user;
513 $converted_mapped_fields['attachments.' . $row_id . '._ftp_pass'] = $ftp_pass;
514 $converted_mapped_fields['attachments.' . $row_id . '._ftp_path'] = '';
515 $converted_mapped_fields['attachments.' . $row_id . '._remote_url'] = '';
516 $converted_mapped_fields['attachments.' . $row_id . '._local_url'] = $local_base_path;
517
518 $converted_mapped_fields['attachments.' . $row_id . '._meta._enabled'] = 'yes';
519 $converted_mapped_fields['attachments.' . $row_id . '._meta._alt'] = $alt;
520 $converted_mapped_fields['attachments.' . $row_id . '._meta._title'] = $title;
521 $converted_mapped_fields['attachments.' . $row_id . '._meta._caption'] = $caption;
522 $converted_mapped_fields['attachments.' . $row_id . '._meta._description'] = $description;
523 }
524 }
525
526 $converted_mapped_fields['attachments._index'] = count($attachments);
527 }
528
529 // custom fields
530 $custom_fields = isset($import_settings['_custom_fields'], $import_settings['_custom_fields'][$template]) ? $import_settings['_custom_fields'][$template] : [];
531 if (!empty($custom_fields)) {
532 $row_id = 0;
533 foreach ($custom_fields as $custom_field) {
534
535 $cf_prefix = 'custom_fields.' . $row_id . '.';
536 $converted_mapped_fields[$cf_prefix . 'key'] = $custom_field['key'];
537 $converted_mapped_fields[$cf_prefix . 'value'] = $custom_field['value'];
538 $converted_mapped_fields[$cf_prefix . '_field_type'] = $custom_field['type'];
539
540 if ('attachment' === $custom_field['type']) {
541 $converted_mapped_fields[$cf_prefix . '_return'] = $custom_field['settings']['attachment_return'];
542
543 $converted_mapped_fields[$cf_prefix . '_ftp_host'] = $custom_field['settings']['attachment_ftp_server'];
544 $converted_mapped_fields[$cf_prefix . '_ftp_user'] = $custom_field['settings']['attachment_ftp_user'];
545 $converted_mapped_fields[$cf_prefix . '_ftp_pass'] = $custom_field['settings']['attachment_ftp_pass'];
546
547
548 $converted_mapped_fields[$cf_prefix . '_ftp_path'] = '';
549 $converted_mapped_fields[$cf_prefix . '_remote_url'] = '';
550 $converted_mapped_fields[$cf_prefix . '_local_url'] = '';
551
552 switch ($custom_field['settings']['attachment_download']) {
553 case 'ftp':
554 $converted_mapped_fields[$cf_prefix . '_download'] = 'ftp';
555 $converted_mapped_fields[$cf_prefix . '_ftp_path'] = $custom_field['settings']['attachment_base_url'];
556 break;
557 case 'url':
558 $converted_mapped_fields[$cf_prefix . '_download'] = 'remote';
559 $converted_mapped_fields[$cf_prefix . '_remote_url'] = $custom_field['settings']['attachment_base_url'];
560 break;
561 case 'local':
562 $converted_mapped_fields[$cf_prefix . '_download'] = 'local';
563 $converted_mapped_fields[$cf_prefix . '_local_url'] = $custom_field['settings']['attachment_base_url'];
564 break;
565 }
566
567 $converted_mapped_fields[$cf_prefix . '_enabled'] = 'no';
568 $converted_mapped_fields[$cf_prefix . '_alt'] = '';
569 $converted_mapped_fields[$cf_prefix . '_title'] = '';
570 $converted_mapped_fields[$cf_prefix . '_caption'] = '';
571 $converted_mapped_fields[$cf_prefix . '_description'] = '';
572 }
573 $row_id++;
574 }
575
576 $converted_mapped_fields['custom_fields._index'] = $row_id;
577 }
578
579 // TODO: Migrate cron
580 // _jci_cron_enabled: yes
581 // _jci_cron_minutes: 60
582 // _jci_cron_last_ran: 1579960020
583 // _cron_last_updated: 1579960020
584
585 $cron_enabled = get_post_meta($post->ID, '_jci_cron_enabled', true);
586 if ($cron_enabled === 'yes') {
587 $settings['import_method'] = 'schedule';
588 $settings['cron_day'] = 0;
589 $settings['cron_hour'] = 0;
590 $settings['cron_minute'] = 0;
591
592 $minutes = intval(get_post_meta($post->ID, '_jci_cron_minutes', true));
593 if ($minutes < 60) {
594 // Hourly
595 $settings['cron_schedule'] = 'hour';
596 } elseif ($minutes < 60 * 24) {
597 $settings['cron_schedule'] = 'day';
598 } elseif ($minutes < 60 * 24 * 7) {
599 $settings['cron_schedule'] = 'week';
600 } else {
601 $settings['cron_schedule'] = 'month';
602 }
603 } else {
604 $settings['import_method'] = 'run';
605 }
606
607
608
609 $importer_model_data = [
610 'name' => $post->post_title,
611 'template' => $import_settings['template'],
612 'template_type' => '',
613 'parser' => $import_settings['template_type'],
614 'permissions' => [
615 'create' => [
616 'enabled' => $import_settings['permissions']['create'] === 1 ? true : false,
617 'type' => $field_permissions['create_type'],
618 'fields' => $field_permissions['create_fields']
619 ],
620 'update' => [
621 'enabled' => $import_settings['permissions']['update'] === 1 ? true : false,
622 'type' => $field_permissions['update_type'],
623 'fields' => $field_permissions['update_fields']
624 ],
625 'remove' => [
626 'enabled' => $import_settings['permissions']['delete'] === 1 ? true : false
627 ],
628 ],
629 'datasource' => [
630 'type' => $import_settings['import_type'],
631 'settings' => [
632 'remoute_url' => '',
633 'local_url' => ''
634 ]
635 ],
636 'settings' => $settings,
637 'file' => [
638 'id' => null,
639 'settings' => [
640 'count' => 0,
641 'processed' => false,
642 'setup' => false,
643 // csv
644 'delimiter' => stripslashes($parser_settings['csv_delimiter']),
645 'enclosure' => stripslashes($parser_settings['csv_enclosure']),
646 'show_headings' => false,
647 // xml
648 'base_path' => $parser_settings['import_base'],
649 'nodes' => [],
650 ]
651 ],
652 'map' => $converted_mapped_fields,
653 'enabled' => $enabled
654 ];
655
656 if (intval($v2_id) > 0) {
657 $importer_model_data['id'] = intval($v2_id);
658 }
659
660 $importer_model = new ImporterModel($importer_model_data);
661 $result = $importer_model->save();
662
663 if (!is_wp_error($result)) {
664
665 // clear existing importer files
666 $query = "DELETE FROM {$wpdb->postmeta} WHERE post_id={$result} AND meta_key LIKE '_importer_file%'";
667 $wpdb->query($query);
668
669 if ($filepath && file_exists($filepath)) {
670 $file_id = $importer_manager->link_importer_file($result, $filepath);
671 $importer_model->setFileId($file_id);
672 $importer_model->save();
673 }
674
675 update_post_meta($result, '_iwp_v1_importer', $post->ID);
676 update_post_meta($post->ID, '_iwp_v2_importer', $result);
677 }
678 }
679 }
680 }
681
682 public function migration_05_multiple_crons()
683 {
684
685 /**
686 * @var \wpdb $wpdb
687 */
688 global $wpdb;
689
690 // TODO: loop through serialsed post_content, switching from single cron to array
691 $importers = $wpdb->get_results("SELECT * FROM {$wpdb->posts} WHERE post_type='" . IWP_POST_TYPE . "'", ARRAY_A);
692 foreach ($importers as $importer) {
693 $id = $importer['ID'];
694 $data = unserialize($importer['post_content']);
695 if ($data['settings']['import_method'] !== 'schedule') {
696 continue;
697 }
698
699 $cron = [[
700 'setting_cron_schedule' => $data['settings']['cron_schedule'],
701 'setting_cron_day' => $data['settings']['cron_day'],
702 'setting_cron_hour' => $data['settings']['cron_hour'],
703 'setting_cron_minute' => $data['settings']['cron_minute'],
704 'setting_cron_disabled' => $data['settings']['cron_disabled'],
705 ]];
706
707 unset($data['settings']['cron_schedule']);
708 unset($data['settings']['cron_day']);
709 unset($data['settings']['cron_hour']);
710 unset($data['settings']['cron_minute']);
711 unset($data['settings']['cron_disabled']);
712
713 $data['settings']['cron'] = $cron;
714
715 $this->update_importer_post_content($id, $data);
716 }
717 }
718
719 public function migration_06_cron_update()
720 {
721 wp_unschedule_hook('iwp_runner');
722
723 /**
724 * @var \wpdb $wpdb
725 */
726 global $wpdb;
727
728 // TODO: loop through serialsed post_content, switching from single cron to array
729 $importers = $wpdb->get_results("SELECT * FROM {$wpdb->posts} WHERE post_type='" . IWP_POST_TYPE . "'", ARRAY_A);
730 foreach ($importers as $importer) {
731
732 $id = $importer['ID'];
733 $data = unserialize($importer['post_content']);
734 if ($data['settings']['import_method'] !== 'schedule') {
735 continue;
736 }
737
738 delete_post_meta($id, '_iwp_session');
739 delete_post_meta($id, '_iwp_cron_updated');
740 delete_post_meta($id, '_iwp_cron_status');
741 delete_post_meta($id, '_iwp_cron_version');
742 wp_update_post(['ID' => $id, 'post_excerpt' => '']);
743
744 Logger::write(__CLASS__ . '::migration_06_cron_update -rest', $id);
745 }
746 }
747
748 public function migration_07_add_session_table($migrate_data = true)
749 {
750 /**
751 * @var \WPDB $wpdb
752 */
753 global $wpdb;
754 $charset_collate = $this->get_charset();
755
756 $sql = "CREATE TABLE `" . $wpdb->prefix . "iwp_sessions` (
757 `id` int(11) unsigned NOT NULL AUTO_INCREMENT,
758 `site_id` int(11) DEFAULT NULL,
759 `importer_id` int(11) DEFAULT NULL,
760 `item_id` int(11) DEFAULT NULL,
761 `item_type` varchar(255) DEFAULT NULL,
762 `session` varchar(255) DEFAULT NULL,
763 PRIMARY KEY (`id`)
764 ) $charset_collate; ";
765
766 dbDelta($sql);
767
768 if (!$migrate_data) {
769 return;
770 }
771
772 // Migrate post sessions
773 $posts = $wpdb->get_results("SELECT {$wpdb->postmeta}.*, {$wpdb->posts}.post_type FROM {$wpdb->postmeta} INNER JOIN {$wpdb->posts} ON {$wpdb->postmeta}.post_id = {$wpdb->posts}.ID WHERE meta_key LIKE '\_iwp\_session\_%'", ARRAY_A);
774 if (!empty($posts)) {
775 foreach ($posts as $post) {
776
777 if (preg_match('/_iwp_session_(\d+)/', $post['meta_key'], $matches) !== 1) {
778 continue;
779 }
780
781 $data = [
782 'importer_id' => $matches[1],
783 'item_id' => $post['post_id'],
784 'item_type' => 'pt-' . $post['post_type'],
785 'session' => $post['meta_value']
786 ];
787 $format = ['%d', '%d', '%s', '%s'];
788
789 // TODO: is this needed as blog_id is more relevant since there is a sessions table per site?
790 if (is_multisite()) {
791 $data['site_id'] = $wpdb->siteid;
792 $format[] = '%d';
793 }
794
795 $wpdb->insert($wpdb->prefix . 'iwp_sessions', $data, $format);
796 }
797 }
798
799 // Migrate term sessions
800 $terms = $wpdb->get_results("SELECT {$wpdb->termmeta}.*, {$wpdb->term_taxonomy}.taxonomy FROM {$wpdb->termmeta} INNER JOIN {$wpdb->term_taxonomy} ON {$wpdb->termmeta}.term_id = {$wpdb->term_taxonomy}.term_id WHERE meta_key LIKE '\_iwp\_session\_%'", ARRAY_A);
801 if (!empty($terms)) {
802 foreach ($terms as $post) {
803
804 if (preg_match('/_iwp_session_(\d+)/', $post['meta_key'], $matches) !== 1) {
805 continue;
806 }
807
808 $data = [
809 'importer_id' => $matches[1],
810 'item_id' => $post['term_id'],
811 'item_type' => 't-' . $post['taxonomy'],
812 'session' => $post['meta_value']
813 ];
814 $format = ['%d', '%d', '%s', '%s'];
815
816 // TODO: is this needed as blog_id is more relevant since there is a sessions table per site?
817 if (is_multisite()) {
818 $data['site_id'] = $wpdb->siteid;
819 $format[] = '%d';
820 }
821
822 $wpdb->insert($wpdb->prefix . 'iwp_sessions', $data, $format);
823 }
824 }
825
826 // Migrate user sessions
827 $users = $wpdb->get_results("SELECT * FROM {$wpdb->usermeta} WHERE meta_key LIKE '\_iwp\_session\_%'", ARRAY_A);
828 if (!empty($users)) {
829 foreach ($users as $post) {
830
831 if (preg_match('/_iwp_session_(\d+)/', $post['meta_key'], $matches) !== 1) {
832 continue;
833 }
834
835 $data = [
836 'importer_id' => $matches[1],
837 'item_id' => $post['user_id'],
838 'item_type' => 'user',
839 'session' => $post['meta_value']
840 ];
841 $format = ['%d', '%d', '%s', '%s'];
842
843 // TODO: is this needed as blog_id is more relevant since there is a sessions table per site?
844 if (is_multisite()) {
845 $data['site_id'] = $wpdb->siteid;
846 $format[] = '%d';
847 }
848
849 $wpdb->insert($wpdb->prefix . 'iwp_sessions', $data, $format);
850 }
851 }
852 }
853
854 public function migration_08_migrate_taxonomy_settings($migrate_data = true)
855 {
856 /**
857 * @var \wpdb $wpdb
858 */
859 global $wpdb;
860
861 // TODO: loop through serialsed post_content, switching from single cron to array
862 $importers = $wpdb->get_results("SELECT * FROM {$wpdb->posts} WHERE post_type='" . IWP_POST_TYPE . "'", ARRAY_A);
863
864 foreach ($importers as $importer) {
865
866 $data = maybe_unserialize($importer['post_content']);
867 $modified = false;
868
869 $tmp = [];
870 foreach ($data['map'] as $field_id => $field_value) {
871 $count = 0;
872 $tmp[preg_replace('/^(taxonomies\.\d+\.)(_.*?)$/', '$1settings.$2', $field_id, -1, $count)] = $field_value;
873 if ($count > 0) {
874 $modified = true;
875 }
876 }
877
878 if (!$modified) {
879 continue;
880 }
881
882 $data['map'] = $tmp;
883
884 $this->update_importer_post_content($importer['ID'], $data);
885 }
886 }
887
888 // TODO: do we need this? can we get around this with manipulating the data if it exists?
889 public function migration_09_migrate_attachment_settings($migrate_data = true)
890 {
891 /**
892 * @var \wpdb $wpdb
893 */
894 global $wpdb;
895
896 $importers = $wpdb->get_results("SELECT * FROM {$wpdb->posts} WHERE post_type='" . IWP_POST_TYPE . "'", ARRAY_A);
897
898 foreach ($importers as $importer) {
899
900 $data = maybe_unserialize($importer['post_content']);
901 $modified = false;
902
903 $tmp = [];
904 foreach ($data['map'] as $field_id => $field_value) {
905 $count = 0;
906
907 $ends_with = [
908 '_download',
909 '_enable_image_hash',
910 '_featured',
911 '_ftp_host',
912 '_ftp_pass',
913 '_ftp_path',
914 '_ftp_user',
915 '_local_url',
916 '_meta\._alt',
917 '_meta\._caption',
918 '_meta\._description',
919 '_meta\._enabled',
920 '_meta\._title',
921 '_remote_url',
922 '_return'
923 ];
924
925 $tmp[preg_replace('/^(.+)(?<!\.settings)\.(' . implode('|', $ends_with) . ')$/', '$1.settings.$2', $field_id, -1, $count)] = $field_value;
926
927 if ($count > 0) {
928 $modified = true;
929 }
930 }
931
932 if (!$modified) {
933 continue;
934 }
935
936 $data['map'] = $tmp;
937
938 $this->update_importer_post_content($importer['ID'], $data);
939 }
940 }
941
942 /**
943 * Convert stored absolute importer file paths to uploads-relative paths.
944 *
945 * Prevents open_basedir warnings after hosting path / WordPress root changes.
946 *
947 * @param bool $migrate_data
948 * @return void
949 */
950 public function migration_10_relative_importer_file_paths($migrate_data = true)
951 {
952 if (!$migrate_data) {
953 return;
954 }
955
956 /**
957 * @var \wpdb $wpdb
958 */
959 global $wpdb;
960
961 $results = $wpdb->get_results(
962 "SELECT meta_id, post_id, meta_key, meta_value
963 FROM {$wpdb->postmeta}
964 WHERE meta_key LIKE '\\_importer\\_file\\_%'",
965 ARRAY_A
966 );
967
968 if (empty($results)) {
969 return;
970 }
971
972 foreach ($results as $row) {
973 $stored = $row['meta_value'];
974 if (!Filesystem::is_absolute_path($stored)) {
975 continue;
976 }
977
978 $resolved = Filesystem::resolve_importer_file_path($stored);
979 if (!$resolved) {
980 continue;
981 }
982
983 $relative = Filesystem::to_uploads_relative_path($resolved);
984 if ($relative === '' || $relative === $stored) {
985 continue;
986 }
987
988 update_post_meta((int) $row['post_id'], $row['meta_key'], $relative);
989 }
990 }
991
992 /**
993 * Prefix custom method calls with [iwp:...] so they do not collide with
994 * shortcodes or Gutenberg content that uses [name(...)].
995 *
996 * Rewrites [strtoupper("x")] → [iwp:strtoupper("x")] in importer maps,
997 * filters, and other stored string settings. Already-prefixed calls are left alone.
998 *
999 * @param bool $migrate_data
1000 * @return void
1001 */
1002 public function migration_11_prefix_custom_methods($migrate_data = true)
1003 {
1004 if (!$migrate_data) {
1005 return;
1006 }
1007
1008 /**
1009 * @var \wpdb $wpdb
1010 */
1011 global $wpdb;
1012
1013 $importers = $wpdb->get_results("SELECT * FROM {$wpdb->posts} WHERE post_type='" . IWP_POST_TYPE . "'", ARRAY_A);
1014 if (empty($importers)) {
1015 return;
1016 }
1017
1018 foreach ($importers as $importer) {
1019 $data = maybe_unserialize($importer['post_content']);
1020 if (!is_array($data)) {
1021 continue;
1022 }
1023
1024 $migrated = $this->migration_11_prefix_custom_methods_in_value($data);
1025 if ($migrated === $data) {
1026 continue;
1027 }
1028
1029 $this->update_importer_post_content($importer['ID'], $migrated);
1030 }
1031 }
1032
1033 /**
1034 * Persist serialized importer settings without corrupting backslashes.
1035 *
1036 * wp_update_post() only slashes the existing DB row, then merges in the
1037 * caller-supplied fields. Those fields must already be slashed or
1038 * wp_insert_post() will stripslashes() the payload and break serialize()
1039 * strings such as the CSV escape character "\".
1040 *
1041 * @param int $id
1042 * @param array $data
1043 * @return void
1044 */
1045 private function update_importer_post_content($id, array $data)
1046 {
1047 remove_filter('content_save_pre', 'wp_filter_post_kses');
1048 wp_update_post([
1049 'ID' => $id,
1050 'post_content' => wp_slash(serialize($data)),
1051 ]);
1052 add_filter('content_save_pre', 'wp_filter_post_kses');
1053 }
1054
1055 /**
1056 * Recursively rewrite [method( → [iwp:method( in strings.
1057 *
1058 * @param mixed $value
1059 * @return mixed
1060 */
1061 private function migration_11_prefix_custom_methods_in_value($value)
1062 {
1063 if (is_array($value)) {
1064 foreach ($value as $key => $item) {
1065 $value[$key] = $this->migration_11_prefix_custom_methods_in_value($item);
1066 }
1067 return $value;
1068 }
1069
1070 if (!is_string($value) || $value === '' || strpos($value, '[') === false) {
1071 return $value;
1072 }
1073
1074 return preg_replace('/\[(?!iwp:)(\w+)\(/', '[iwp:$1(', $value);
1075 }
1076 }
1077