PluginProbe
Import WP – CSV & XML Import Export for WordPress / 2.15.2
Import WP – CSV & XML Import Export for WordPress v2.15.2
2.15.2 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 All 145 releases
← All changes | class/Common/Rest/RestManager.php +789 -87 2.8.3 → 2.15.2 View file →
@@ -8,8 +8,9 @@
8 8 use ImportWP\Common\Filesystem\ZipArchive;
9 9 use ImportWP\Common\Http\Http;
10 10 use ImportWP\Common\Importer\ImporterManager;
11 11 use ImportWP\Common\Importer\Preview\CSVPreview;
12 +use ImportWP\Common\Importer\Preview\JSONPreview;
12 13 use ImportWP\Common\Importer\Preview\XMLPreview;
13 14 use ImportWP\Common\Importer\State\ImporterState;
14 15 use ImportWP\Common\Importer\Template\Template;
15 16 use ImportWP\Common\Importer\Template\TemplateManager;
@@ -236,8 +237,21 @@
236 237 'permission_callback' => array($this, 'get_permission')
237 238 ),
238 239 ));
239 240
241 + register_rest_route($namespace, '/compatibility', array(
242 + array(
243 + 'methods' => \WP_REST_Server::CREATABLE,
244 + 'callback' => array($this, 'save_compatibility'),
245 + 'permission_callback' => array($this, 'get_permission')
246 + ),
247 + array(
248 + 'methods' => \WP_REST_Server::READABLE,
249 + 'callback' => array($this, 'get_compatibility'),
250 + 'permission_callback' => array($this, 'get_permission')
251 + ),
252 + ));
253 +
240 254 register_rest_route($namespace, '/import-export', array(
241 255 array(
242 256 'methods' => \WP_REST_Server::CREATABLE,
243 257 'callback' => array($this, 'import_exporters'),
@@ -265,8 +279,16 @@
265 279 'permission_callback' => array($this, 'get_permission')
266 280 )
267 281 ));
268 282
283 + register_rest_route($namespace, '/importer/(?P<id>\d+)/template_unique_identifiers', array(
284 + array(
285 + 'methods' => \WP_REST_Server::READABLE,
286 + 'callback' => array($this, 'get_importer_unique_identifier_options'),
287 + 'permission_callback' => array($this, 'get_permission')
288 + )
289 + ));
290 +
269 291 // Exporter
270 292
271 293 register_rest_route($namespace, '/exporter', array(
272 294 array(
@@ -309,8 +331,24 @@
309 331 'permission_callback' => array($this, 'get_permission')
310 332 )
311 333 ));
312 334
335 + register_rest_route($namespace, '/exporter/(?P<id>\d+)/download-config', array(
336 + array(
337 + 'methods' => \WP_REST_Server::READABLE,
338 + 'callback' => array($this, 'download_exporter_config'),
339 + 'permission_callback' => array($this, 'get_permission')
340 + )
341 + ));
342 +
343 + register_rest_route($namespace, '/importer/read-config', array(
344 + array(
345 + 'methods' => \WP_REST_Server::CREATABLE,
346 + 'callback' => array($this, 'read_exporter_config'),
347 + 'permission_callback' => array($this, 'get_permission')
348 + )
349 + ));
350 +
313 351 register_rest_route($namespace, '/exporter/(?P<id>\d+)/run', array(
314 352 array(
315 353 'methods' => \WP_REST_Server::CREATABLE,
316 354 'callback' => array($this, 'run_exporter'),
@@ -330,9 +368,9 @@
330 368 public function get_permission()
331 369 {
332 370
333 371 if (!current_user_can('manage_options')) {
334 - return new \WP_Error('rest_forbidden', esc_html__('You do not have permissions.', $this->properties->plugin_domain), array('status' => 401));
372 + return new \WP_Error('rest_forbidden', __('You do not have permissions.', 'jc-importer'), array('status' => 401));
335 373 }
336 374
337 375 return true;
338 376 }
@@ -348,8 +386,13 @@
348 386
349 387 return $data;
350 388 }
351 389
390 + // Preserve booleans/numbers from JSON request bodies
391 + if (is_bool($data) || is_int($data) || is_float($data) || $data === null) {
392 + return $data;
393 + }
394 +
352 395 $full_path = implode('.', array_filter($path));
353 396 if (!is_null($full_path)) {
354 397
355 398 if (preg_match('/^map\.\S+/', $full_path) !== false) {
@@ -366,8 +409,70 @@
366 409
367 410 return sanitize_text_field($data);
368 411 }
369 412
413 + /**
414 + * Read request body from form-urlencoded or JSON.
415 + *
416 + * @param \WP_REST_Request $request
417 + * @return array
418 + */
419 + protected function get_request_data(\WP_REST_Request $request)
420 + {
421 + $post_data = $request->get_body_params();
422 + if (!is_array($post_data)) {
423 + $post_data = [];
424 + }
425 +
426 + $json_params = $request->get_json_params();
427 + if (is_array($json_params) && !empty($json_params)) {
428 + $post_data = array_replace_recursive($post_data, $json_params);
429 + }
430 +
431 + return $this->decode_packed_fields($post_data);
432 + }
433 +
434 + /**
435 + * Decode map/enabled when sent as a single JSON string (avoids max_input_vars).
436 + *
437 + * Do not wp_unslash() first: REST body params are already unslashed, and a
438 + * second pass strips JSON escapes inside quoted modifier args.
439 + *
440 + * @param array $post_data
441 + * @return array
442 + */
443 + protected function decode_packed_fields($post_data)
444 + {
445 + foreach (['map', 'enabled'] as $field) {
446 + if (!isset($post_data[$field]) || !is_string($post_data[$field])) {
447 + continue;
448 + }
449 +
450 + $raw = $post_data[$field];
451 + $decoded = json_decode($raw, true);
452 +
453 + // Fallback for callers that still pass slashed form data.
454 + if (json_last_error() !== JSON_ERROR_NONE || !is_array($decoded)) {
455 + $decoded = json_decode(wp_unslash($raw), true);
456 + }
457 +
458 + if (json_last_error() === JSON_ERROR_NONE && is_array($decoded)) {
459 + $post_data[$field] = $decoded;
460 + }
461 + }
462 +
463 + return $post_data;
464 + }
465 +
466 + /**
467 + * @param mixed $value
468 + * @return bool
469 + */
470 + protected function is_truthy($value)
471 + {
472 + return $value === true || $value === 1 || $value === '1' || $value === 'true';
473 + }
474 +
370 475 public function check_rest_status(\WP_REST_Request $request)
371 476 {
372 477 $result = [];
373 478 // find required php ext: https://github.com/llaville/php-compat-info
@@ -377,9 +482,9 @@
377 482
378 483 $result = [
379 484 'tmp_writable' => [
380 485 'status' => $tmp_writable ? 'yes' : 'no',
381 - 'message' => $tmp_writable ? '' : 'WordPress is unable to write to directory: ' . $this->filesystem->get_temp_directory() . ', make sure this directory is writable.',
486 + 'message' => $tmp_writable ? '' : sprintf(__('WordPress is unable to write to directory: %s, make sure this directory is writable.', 'jc-importer'), $this->filesystem->get_temp_directory()),
382 487 ],
383 488 'rest_enabled' => [
384 489 'status' => 'yes',
385 490 'message' => '',
@@ -411,9 +516,9 @@
411 516 {
412 517 $migration = new Migrations();
413 518 $result = $migration->migrate();
414 519
415 - return $this->http->end_rest_success("Migration Complete.");
520 + return $this->http->end_rest_success(__("Migration Complete.", 'jc-importer'));
416 521 }
417 522
418 523 public function get_importers(\WP_REST_Request $request)
419 524 {
@@ -440,9 +545,9 @@
440 545 public function save_importer(\WP_REST_Request $request)
441 546 {
442 547 Logger::setRequestType('save_importer');
443 548
444 - $post_data = $request->get_body_params();
549 + $post_data = $this->get_request_data($request);
445 550 $id = isset($post_data['id']) ? intval($post_data['id']) : null;
446 551
447 552 $temp_post_data = [
448 553 'create' => [],
@@ -450,10 +555,19 @@
450 555 ];
451 556
452 557 if (isset($post_data['permissions'])) {
453 558 // TODO: update sanitize method to keep track of hierarchy
454 - $temp_post_data['create'] = explode("\n", $post_data['permissions']['create']['fields']);
455 - $temp_post_data['update'] = explode("\n", $post_data['permissions']['update']['fields']);
559 + foreach (['create', 'update'] as $permission_method) {
560 + if (!isset($post_data['permissions'][$permission_method]['fields'])) {
561 + continue;
562 + }
563 + $permission_fields = $post_data['permissions'][$permission_method]['fields'];
564 + if (is_array($permission_fields)) {
565 + $temp_post_data[$permission_method] = $permission_fields;
566 + } else {
567 + $temp_post_data[$permission_method] = explode("\n", (string) $permission_fields);
568 + }
569 + }
456 570 }
457 571
458 572 $post_data = $this->sanitize($post_data);
459 573
@@ -468,12 +582,9 @@
468 582 }
469 583 }
470 584 }
471 585
472 -
473 -
474 586 $importer = new ImporterModel($id, $this->importer_manager->is_debug());
475 -
476 587 if (isset($post_data['datasource'])) {
477 588 $importer->setDatasource($post_data['datasource']);
478 589 }
479 590
@@ -504,8 +615,14 @@
504 615 if (isset($post_data['template_options'])) {
505 616 $template_options = array_merge($template_options, $post_data['template_options']);
506 617 }
507 618
619 + // setup default enabled fields
620 + $default_enabled_fields = $template->get_default_enabled_fields();
621 + foreach ($default_enabled_fields as $default_enabled_field) {
622 + $importer->setEnabled($default_enabled_field);
623 + }
624 +
508 625 foreach ($template_options as $key => $val) {
509 626 $importer->setSetting($key, $val);
510 627 }
511 628 }
@@ -513,8 +630,220 @@
513 630 if (isset($post_data['name'])) {
514 631 $importer->setName($post_data['name']);
515 632 }
516 633
634 + if (is_null($id)) {
635 +
636 + // NOTE: New importer, should we generate field map from exporter.
637 + if (isset($post_data['setup_type']) && in_array($post_data['setup_type'], ['upload', 'generate'])) {
638 +
639 + $setup_type = $post_data['setup_type'] === 'upload' ? 'upload' : 'generate';
640 + $file_type = null;
641 + $exporter_unique_identifier = '';
642 +
643 + if ($setup_type === 'upload') {
644 + $config = json_decode($post_data['exporter_config_file'], true);
645 + $file_type = $config['data']['file_type'];
646 + $fields = $config['fields'];
647 + $formatted_fields = $config['formatted_fields'];
648 + $file_settings = $config['data']['file_settings'];
649 + $exporter_unique_identifier = isset($config['data']['unique_identifier']) ? $config['data']['unique_identifier'] : '';
650 + } else {
651 +
652 + /**
653 + * @var \ImportWP\Common\Model\ExporterModel $exporter_data
654 + */
655 + $exporter_data = $this->exporter_manager->get_exporter($post_data['exporter']);
656 + $fields = $this->exporter_manager->get_importer_map_fields($exporter_data);
657 + $mapper = $this->exporter_manager->get_exporter_mapper($exporter_data);
658 + $formatted_fields = $mapper->get_fields();
659 + $file_type = $exporter_data->getFileType();
660 + $file_settings = $exporter_data->getFileSettings();
661 + $exporter_unique_identifier = $exporter_data->getUniqueIdentifier();
662 + }
663 +
664 + if (is_null($file_type) || !in_array($file_type, ['xml', 'csv', 'json'])) {
665 + return $this->http->end_rest_error('Invalid exporter file type');
666 + }
667 +
668 + $importer->setParser($file_type);
669 +
670 + $headings = [];
671 + switch ($file_type) {
672 + case 'csv':
673 + $headings = array_reduce($fields, function ($carry, $item) {
674 + $carry[] = $item['selection'];
675 + return $carry;
676 + }, []);
677 + $headings = array_map('trim', $headings);
678 +
679 + if (isset($file_settings['delimiter']) && !empty($file_settings['delimiter']) && strlen($file_settings['delimiter']) === 1) {
680 + $importer->setFileSetting('delimiter', $file_settings['delimiter']);
681 + }
682 + if (isset($file_settings['enclosure']) && !empty($file_settings['enclosure']) && strlen($file_settings['enclosure']) === 1) {
683 + $importer->setFileSetting('enclosure', $file_settings['enclosure']);
684 + }
685 + if (isset($file_settings['escape']) && !empty($file_settings['escape']) && strlen($file_settings['escape']) === 1) {
686 + $importer->setFileSetting('escape', $file_settings['escape']);
687 + }
688 +
689 + break;
690 + case 'xml':
691 +
692 + $main = false;
693 + foreach ($fields as $field) {
694 + if ($field['selection'] === 'main' && ($field['loop'] === true || $field['loop'] === "true")) {
695 + $main = $field;
696 + break;
697 + }
698 + }
699 +
700 + if (!$main) {
701 + return $this->http->end_rest_error("XML exporter is missing main loop.");
702 + }
703 +
704 + // generate base_path
705 + $post_data['file_settings_base_path'] = '/' . implode('/', array_reverse(array_filter($this->generate_base_path($main, $fields))));
706 +
707 + $allowed = [$main['id']];
708 +
709 + $current_section = null;
710 + $current_section_ancestors = [];
711 + $current_section_map = [];
712 +
713 + foreach ($fields as $field) {
714 +
715 + if (in_array($field['parent'], $allowed)) {
716 +
717 + $field_map = '/' . implode('/', array_reverse(array_filter($this->generate_base_path($field, $fields, [], $main['id']))));
718 + $headings[$field_map] = $field['selection'];
719 +
720 + if ($field['loop'] === true || $field['loop'] === 'true') {
721 +
722 + // we need to convert all sub fields to tax_category.id ....
723 + $current_section = $field['selection'];
724 + $current_section_ancestors = [$field['id']];
725 + } else {
726 +
727 + if (!is_null($current_section) && in_array($field['parent'], $current_section_ancestors)) {
728 +
729 + $current_section_ancestors[] = $field['id'];
730 + $current_section_map[$field_map] = $current_section . '.' . $field['selection'];
731 + } else {
732 + $headings = $this->complete_section_map($headings, $current_section_map, $current_section, $formatted_fields);
733 + $current_section = null;
734 + $current_section_ancestors = [];
735 + $current_section_map = [];
736 + }
737 + }
738 +
739 + if (!in_array($field['id'], $allowed)) {
740 + $allowed[] = $field['id'];
741 + }
742 + }
743 + }
744 +
745 + $headings = $this->complete_section_map($headings, $current_section_map, $current_section, $formatted_fields);
746 +
747 + $tmp = [];
748 + foreach ($headings as $map => $heading) {
749 + $tmp[$map] = $heading;
750 + }
751 +
752 + $headings = $tmp;
753 + break;
754 + case 'json':
755 +
756 + $main = false;
757 + foreach ($fields as $field) {
758 + if ($field['selection'] === 'main' && ($field['loop'] === true || $field['loop'] === "true")) {
759 + $main = $field;
760 + break;
761 + }
762 + }
763 +
764 + if (!$main) {
765 + return $this->http->end_rest_error("JSON exporter is missing main loop.");
766 + }
767 +
768 + // generate base_path from nested wrappers + main loop label
769 + $post_data['file_settings_base_path'] = implode('/', array_reverse(array_filter($this->generate_base_path($main, $fields))));
770 +
771 + $allowed = [$main['id']];
772 +
773 + $current_section = null;
774 + $current_section_ancestors = [];
775 + $current_section_map = [];
776 +
777 + foreach ($fields as $field) {
778 +
779 + if (in_array($field['parent'], $allowed)) {
780 +
781 + $field_map = '/' . implode('/', array_reverse(array_filter($this->generate_base_path($field, $fields, [], $main['id']))));
782 + $headings[$field_map] = $field['selection'];
783 +
784 + if ($field['loop'] === true || $field['loop'] === 'true') {
785 +
786 + $current_section = $field['selection'];
787 + $current_section_ancestors = [$field['id']];
788 + } else {
789 +
790 + if (!is_null($current_section) && in_array($field['parent'], $current_section_ancestors)) {
791 +
792 + $current_section_ancestors[] = $field['id'];
793 + $current_section_map[$field_map] = $current_section . '.' . $field['selection'];
794 + } else {
795 + $headings = $this->complete_section_map($headings, $current_section_map, $current_section, $formatted_fields);
796 + $current_section = null;
797 + $current_section_ancestors = [];
798 + $current_section_map = [];
799 + }
800 + }
801 +
802 + if (!in_array($field['id'], $allowed)) {
803 + $allowed[] = $field['id'];
804 + }
805 + }
806 + }
807 +
808 + $headings = $this->complete_section_map($headings, $current_section_map, $current_section, $formatted_fields);
809 +
810 + $tmp = [];
811 + foreach ($headings as $map => $heading) {
812 + $tmp[$map] = $heading;
813 + }
814 +
815 + $headings = $tmp;
816 + break;
817 + }
818 +
819 + if (!empty($headings)) {
820 + $template = $this->importer_manager->get_importer_template($importer);
821 + $field_map = $template->generate_field_map($headings, $importer);
822 + $field_map = apply_filters('iwp/importer/generate_field_map', $field_map, $headings, $importer);
823 +
824 + $post_data['map'] = $field_map['map'];
825 + $post_data['enabled'] = $field_map['enabled'];
826 + }
827 +
828 + // Prefill Permissions unique identifier from the exporter when available.
829 + if (!empty($exporter_unique_identifier)) {
830 + $importer_unique_identifier = apply_filters(
831 + 'iwp/importer/from_exporter/unique_identifier',
832 + $exporter_unique_identifier,
833 + $exporter_unique_identifier,
834 + $importer,
835 + $setup_type === 'upload' ? $config : $exporter_data
836 + );
837 +
838 + if (!empty($importer_unique_identifier)) {
839 + $post_data['setting_unique_identifier_type'] = 'field';
840 + $post_data['setting_unique_identifier'] = $importer_unique_identifier;
841 + }
842 + }
843 + }
844 + }
845 +
517 846 if (isset($post_data['setting_start_row'])) {
518 847 $importer->setStartRow($post_data['setting_start_row']);
519 848 }
520 849
@@ -530,8 +859,13 @@
530 859 $importer->setSetting($matches['key'], $value);
531 860 }
532 861 }
533 862
863 + if (isset($post_data['setting_import_method']) && $post_data['setting_import_method'] === 'background') {
864 + delete_post_meta($importer->getId(), '_iwp_cron_scheduled');
865 + delete_post_meta($importer->getId(), '_iwp_cron_last_ran');
866 + }
867 +
534 868 $parser = $importer->getParser();
535 869
536 870 if (isset($post_data['file_settings_encoding'])) {
537 871 $importer->setFileSetting('file_encoding', $post_data['file_settings_encoding']);
@@ -554,16 +888,26 @@
554 888 $clear_config = true;
555 889 }
556 890 $importer->setFileSetting('enclosure', $post_data['file_settings_enclosure']);
557 891 }
892 +
893 + if (isset($post_data['file_settings_escape'])) {
894 + $old_escape = $importer->getFileSetting('escape');
895 + if ($old_escape !== $post_data['file_settings_escape']) {
896 + $clear_config = true;
897 + }
898 + $importer->setFileSetting('escape', $post_data['file_settings_escape']);
899 + }
900 +
901 +
558 902 if (isset($post_data['file_settings_show_headings'])) {
559 - $importer->setFileSetting('show_headings', $post_data['file_settings_show_headings'] === 'true' || $post_data['file_settings_show_headings'] === true ? true : false);
903 + $importer->setFileSetting('show_headings', $this->is_truthy($post_data['file_settings_show_headings']));
560 904 }
561 905
562 906 if ($clear_config) {
563 907 $this->importer_manager->clear_config_files($importer->getId(), true);
564 908 }
565 - } elseif ($parser === 'xml') {
909 + } elseif ($parser === 'xml' || $parser === 'json') {
566 910 if (isset($post_data['file_settings_base_path'])) {
567 911 $importer->setFileSetting('base_path', $post_data['file_settings_base_path']);
568 912 }
569 913 }
@@ -568,25 +912,37 @@
568 912 }
569 913 }
570 914
571 915 if (isset($post_data['file_settings_setup'])) {
572 - $importer->setFileSetting('setup', $post_data['file_settings_setup'] === 'true' ? true : false);
916 + $importer->setFileSetting('setup', $this->is_truthy($post_data['file_settings_setup']));
573 917 }
574 918
575 919 if (isset($post_data['map']) && is_array($post_data['map'])) {
920 + $map = [];
576 921 foreach ($post_data['map'] as $key => $value) {
922 + if ($value === null) {
923 + continue;
924 + }
577 925
578 926 if (1 === preg_match('/\._index$/', $key)) {
579 927 $value = intval($value);
580 928 }
581 929
582 - $importer->setMap($key, $value);
930 + $map[$key] = $value;
583 931 }
932 + $importer->replaceMap($map);
584 933 }
585 934
586 935 if (isset($post_data['enabled']) && is_array($post_data['enabled'])) {
587 936 foreach ($post_data['enabled'] as $key => $value) {
588 - if ($value === 'true') {
937 + // generate_field_map() returns a list of field ids: [0 => 'billing.first_name', ...]
938 + // The UI saves an object map: ['billing.first_name' => true, ...]
939 + if (is_int($key) && is_string($value) && $value !== '') {
940 + $importer->setEnabled($value);
941 + continue;
942 + }
943 +
944 + if ($this->is_truthy($value)) {
589 945 $importer->setEnabled($key);
590 946 } else {
591 947 $importer->setEnabled($key, false);
592 948 }
@@ -598,9 +954,9 @@
598 954 // create
599 955 if (isset($post_data['permissions']['create'])) {
600 956 $create = $post_data['permissions']['create'];
601 957 $importer->setPermission('create', [
602 - 'enabled' => isset($create['enabled']) && $create['enabled'] === 'true' ? true : false,
958 + 'enabled' => isset($create['enabled']) && $this->is_truthy($create['enabled']) ? true : false,
603 959 'type' => isset($create['type']) && in_array($create['type'], ['include', 'exclude'], true) ? $create['type'] : null,
604 960 'fields' => isset($create['fields']) ? $create['fields'] : [],
605 961 ]);
606 962 }
@@ -608,9 +964,9 @@
608 964 // update
609 965 if (isset($post_data['permissions']['update'])) {
610 966 $update = $post_data['permissions']['update'];
611 967 $importer->setPermission('update', [
612 - 'enabled' => isset($update['enabled']) && $update['enabled'] === 'true' ? true : false,
968 + 'enabled' => isset($update['enabled']) && $this->is_truthy($update['enabled']) ? true : false,
613 969 'type' => isset($update['type']) && in_array($update['type'], ['include', 'exclude'], true) ? $update['type'] : null,
614 970 'fields' => isset($update['fields']) ? $update['fields'] : [],
615 971 ]);
616 972 }
@@ -617,10 +973,11 @@
617 973
618 974 // remove
619 975 if (isset($post_data['permissions']['remove'])) {
620 976 $importer->setPermission('remove', [
621 - 'enabled' => $post_data['permissions']['remove']['enabled'] === 'true',
622 - 'trash' => $post_data['permissions']['remove']['trash'] === 'true',
977 + 'enabled' => $this->is_truthy($post_data['permissions']['remove']['enabled']),
978 + 'trash' => $this->is_truthy($post_data['permissions']['remove']['trash']),
979 + 'media' => $this->is_truthy($post_data['permissions']['remove']['media']),
623 980 ]);
624 981 }
625 982 }
626 983
@@ -630,8 +987,88 @@
630 987 }
631 988 return $this->http->end_rest_success($importer->data());
632 989 }
633 990
991 + public function generate_base_path($field, $fields, $output = [], $stop = 0)
992 + {
993 + $output[] = isset($field['label']) && !empty($field['label']) ? $field['label'] : $field['selection'];
994 +
995 + if ($field['parent'] !== $stop) {
996 +
997 + foreach ($fields as $sub_field) {
998 + if ($sub_field['id'] == $field['parent']) {
999 +
1000 + return $this->generate_base_path($sub_field, $fields, $output, $stop);
1001 + }
1002 + }
1003 + }
1004 + return $output;
1005 + }
1006 +
1007 + public function complete_section_map($headings, $current_section_map, $current_section, $fields)
1008 + {
1009 + if (is_null($current_section)) {
1010 + return $headings;
1011 + }
1012 +
1013 + if ($current_section === 'custom_fields') {
1014 + // $headings['/test/@value'] = 'custom_fields.{/test/@key}';
1015 +
1016 + $meta_key = false;
1017 + $meta_val = false;
1018 +
1019 + if (count($current_section_map) == 2) {
1020 + foreach ($current_section_map as $row_map => $row_value) {
1021 + if ($row_value == 'custom_fields.meta_key') {
1022 + $meta_key = $row_map;
1023 + } elseif ($row_value == 'custom_fields.meta_value') {
1024 + $meta_val = $row_map;
1025 + }
1026 + }
1027 +
1028 + if ($meta_key && $meta_val) {
1029 +
1030 + // Get full list of custom fields, that can be then be used to generate a full list
1031 + // /custom_fields_wrapper/custom_fields[meta_key="_edit_lock"]/meta_key
1032 + // /custom_fields_wrapper/custom_fields[meta_key="_edit_lock"]/meta_value
1033 +
1034 + // Compare the two strings and get the matching parts
1035 + $meta_key_parts = array_values(array_filter(explode('/', $meta_key)));
1036 + $meta_val_parts = array_values(array_filter(explode('/', $meta_val)));
1037 +
1038 + for ($i = 0; $i < count($meta_key_parts); $i++) {
1039 + if ($meta_key_parts[$i] !== $meta_val_parts[$i]) {
1040 + break;
1041 + }
1042 + }
1043 +
1044 + $start = array_slice($meta_key_parts, 0, $i);
1045 + $end_key = array_slice($meta_key_parts, $i);
1046 + $end_val = array_slice($meta_val_parts, $i);
1047 +
1048 + $start_path = implode('/', $start);
1049 + $end_key_path = implode('/', $end_key);
1050 + $end_val_path = implode('/', $end_val);
1051 +
1052 + $custom_field_key_list = $fields['children']['custom_fields']['fields'];
1053 + foreach ($custom_field_key_list as $custom_field_key) {
1054 +
1055 + // $tmp_field_key = '{/' . $start_path . sprintf('[%s="%s"]', $end_key_path, $custom_field_key) . '/' . $end_key_path . '}';
1056 + $tmp_field_val = '/' . $start_path . sprintf('[%s="%s"]', $end_key_path, $custom_field_key) . '/' . $end_val_path;
1057 +
1058 + $headings[$tmp_field_val] = sprintf('custom_fields.%s', $custom_field_key);
1059 + }
1060 +
1061 + return $headings;
1062 + }
1063 + }
1064 +
1065 + return array_merge($headings, $current_section_map);
1066 + }
1067 +
1068 + return array_merge($headings, $current_section_map);
1069 + }
1070 +
634 1071 private function sanitize_setting($value)
635 1072 {
636 1073 if (is_array($value)) {
637 1074 foreach ($value as &$tmp) {
@@ -677,13 +1114,22 @@
677 1114 $importer_model = $this->importer_manager->get_importer($importer_id);
678 1115 $config = $this->importer_manager->get_config($importer_model);
679 1116
680 1117 $output = ImporterState::get_state($importer_id);
681 - $output['version'] = 2;
682 - $output['message'] = $this->generate_status_message($output);
683 - $output['importer'] = $importer_id;
684 - $output['process'] = intval($config->get('process'));
1118 + if (!$output) {
1119 + $output = [
1120 + 'version' => 2,
1121 + 'message' => 'Not setup',
1122 + 'importer' => $importer_id
1123 + ];
1124 + } else {
685 1125
1126 + $output['version'] = 2;
1127 + $output['message'] = $this->generate_status_message($output);
1128 + $output['importer'] = $importer_id;
1129 + $output['process'] = intval($config->get('process'));
1130 + }
1131 +
686 1132 $result[] = $this->event_handler->run('iwp/importer/status/output', [$output, $importer_model]);
687 1133 }
688 1134
689 1135 echo json_encode($result) . "\n";
@@ -704,12 +1150,12 @@
704 1150
705 1151 if (isset($data['status'])) {
706 1152 switch ($data['status']) {
707 1153 case 'cancelled':
708 - $output .= 'Import cancelled';
1154 + $output .= __('Import cancelled', 'jc-importer');
709 1155 break;
710 1156 case 'complete':
711 - $output .= 'Import complete';
1157 + $output .= __('Import complete', 'jc-importer');
712 1158 break;
713 1159 case 'error':
714 1160 $output = $data['message'];
715 1161 return $output;
@@ -716,15 +1162,14 @@
716 1162 break;
717 1163 }
718 1164 }
719 1165
720 - if (isset($data['status']) && $data['status'] === 'running' && isset($data['section'])) {
1166 + if (isset($data['status']) && ($data['status'] === 'running' || $data['status'] === 'processing' || $data['status'] === 'timeout') && isset($data['section'])) {
721 1167 switch ($data['section']) {
722 1168 case 'import':
723 1169 case 'delete':
724 - $output = $data['section'] === 'import' ? 'Importing: ' : 'Deleting: ';
725 - $output .= $data['progress'][$data['section']]['current_row'] . ' / ' . ($data['progress'][$data['section']]['end'] - $data['progress'][$data['section']]['start']);
726 -
1170 + $progress = $data['progress'][$data['section']]['current_row'] . ' / ' . ($data['progress'][$data['section']]['end'] - $data['progress'][$data['section']]['start']);
1171 + $output = sprintf($data['section'] === 'import' ? __('Importing: %s', 'jc-importer') : __('Deleting: %s', 'jc-importer'), $progress);
727 1172 break;
728 1173 }
729 1174 }
730 1175
@@ -729,21 +1174,21 @@
729 1174 }
730 1175
731 1176 if (isset($data['stats'])) {
732 1177 if ($data['stats']['inserts'] > 0) {
733 - $output .= sprintf(', Inserts: %d', $data['stats']['inserts']);
1178 + $output .= sprintf(__(', Inserts: %d', 'jc-importer'), $data['stats']['inserts']);
734 1179 }
735 1180 if ($data['stats']['updates'] > 0) {
736 - $output .= sprintf(', Updates: %d', $data['stats']['updates']);
1181 + $output .= sprintf(__(', Updates: %d', 'jc-importer'), $data['stats']['updates']);
737 1182 }
738 1183 if ($data['stats']['deletes'] > 0) {
739 - $output .= sprintf(', Deletes: %d', $data['stats']['deletes']);
1184 + $output .= sprintf(__(', Deletes: %d', 'jc-importer'), $data['stats']['deletes']);
740 1185 }
741 1186 if ($data['stats']['skips'] > 0) {
742 - $output .= sprintf(', Skips: %d', $data['stats']['skips']);
1187 + $output .= sprintf(__(', Skips: %d', 'jc-importer'), $data['stats']['skips']);
743 1188 }
744 1189 if ($data['stats']['errors'] > 0) {
745 - $output .= sprintf(', Errors: %d', $data['stats']['errors']);
1190 + $output .= sprintf(__(', Errors: %d', 'jc-importer'), $data['stats']['errors']);
746 1191 }
747 1192 }
748 1193
749 1194 if (isset($data['timestamp'], $data['updated'])) {
@@ -750,15 +1195,15 @@
750 1195 /**
751 1196 * @var Util $util
752 1197 */
753 1198 $util = Container::getInstance()->get('util');
754 - $output .= ', Run Time: ' . $util->format_time($data['updated'] - $data['timestamp']);
1199 + $output .= sprintf(__(', Run Time: %s', 'jc-importer'), $util->format_time($data['updated'] - $data['timestamp']));
755 1200 } elseif (isset($data['duration'])) {
756 1201 /**
757 1202 * @var Util $util
758 1203 */
759 1204 $util = Container::getInstance()->get('util');
760 - $output .= ', Run Time: ' . $util->format_time($data['duration']);
1205 + $output .= sprintf(__(', Run Time: %s', 'jc-importer'), $util->format_time($data['duration']));
761 1206 }
762 1207
763 1208 return $output;
764 1209 }
@@ -782,8 +1227,10 @@
782 1227 $importer = $this->importer_manager->get_importer($id);
783 1228
784 1229 // TODO: Remove duplicate code, found in cron manager
785 1230
1231 + add_filter('upload_dir', [$this->importer_manager, 'set_custom_upload_path']);
1232 +
786 1233 switch ($action) {
787 1234 case 'file_upload':
788 1235 $attachment_id = $this->importer_manager->upload_file($importer, $_FILES['file']);
789 1236 break;
@@ -802,15 +1249,23 @@
802 1249 case 'file_local':
803 1250 $raw_source = $post_data['local_url'];
804 1251 $source = apply_filters('iwp/importer/datasource', $raw_source, $raw_source, $importer);
805 1252 $source = apply_filters('iwp/importer/datasource/local', $source, $raw_source, $importer);
806 - $attachment_id = $this->importer_manager->local_file($importer, $source);
1253 +
1254 + $filetype = $importer->getParser();
1255 + if (is_null($filetype)) {
1256 + $filetype = $post_data['filetype'];
1257 + }
1258 +
1259 + $attachment_id = $this->importer_manager->local_file($importer, $source, $filetype);
807 1260 break;
808 1261 default:
809 - $attachment_id = new \WP_Error('IWP_RM_1', 'Invalid form action');
1262 + $attachment_id = new \WP_Error('IWP_RM_1', __('Invalid form action', 'jc-importer'));
810 1263 break;
811 1264 }
812 1265
1266 + remove_filter('upload_dir', [$this->importer_manager, 'set_custom_upload_path']);
1267 +
813 1268 if (is_wp_error($attachment_id)) {
814 1269 return $this->http->end_rest_error($attachment_id->get_error_message());
815 1270 }
816 1271
@@ -824,9 +1279,9 @@
824 1279 $post_data = $this->sanitize($request->get_body_params());
825 1280
826 1281 $importer = $this->importer_manager->get_importer($id);
827 1282 if (!$importer) {
828 - return $this->http->end_rest_error('Invalid importer');
1283 + return $this->http->end_rest_error(__('Invalid importer', 'jc-importer'));
829 1284 }
830 1285
831 1286 $this->importer_manager->clear_config_files($id, true);
832 1287 $parser = $importer->getParser();
@@ -834,20 +1289,29 @@
834 1289 if ('xml' === $parser) {
835 1290
836 1291 $nodes = $this->importer_manager->process_xml_file($id, true);
837 1292 $importer->setFileSetting('nodes', $nodes);
1293 + } elseif ('json' === $parser) {
1294 +
1295 + $nodes = $this->importer_manager->process_json_file($id, true);
1296 + $importer->setFileSetting('nodes', $nodes);
838 1297 } elseif ('csv' === $parser) {
839 1298
840 1299 $delimiter = isset($post_data['delimiter']) ? $post_data['delimiter'] : null;
841 1300 if (is_null($delimiter)) {
842 - $delimiter = ',';
1301 + $delimiter = $importer->getFileSetting('delimiter', ',');
843 1302 }
844 1303
845 1304 $enclosure = isset($post_data['enclosure']) ? $post_data['enclosure'] : null;
846 1305 if (is_null($enclosure)) {
847 - $enclosure = '"';
1306 + $enclosure = $importer->getFileSetting('enclosure', '"');
848 1307 }
849 1308
1309 + $escape = isset($post_data['escape']) ? $post_data['escape'] : null;
1310 + if (is_null($escape)) {
1311 + $escape = $importer->getFileSetting('escape', '\\');
1312 + }
1313 +
850 1314 $count = $this->importer_manager->process_csv_file($id, $delimiter, $enclosure, true);
851 1315
852 1316 // Need fresh importer, due to process_csv_file importer modifications might get overwritten.
853 1317 $importer = $this->importer_manager->get_importer($id);
@@ -874,9 +1338,9 @@
874 1338 $importer = $this->importer_manager->get_importer($id);
875 1339
876 1340 $import_file_exists = $this->filesystem->file_exists($importer->getFile());
877 1341 if (is_wp_error($import_file_exists)) {
878 - throw new \Exception("Unable to load preview, " . $import_file_exists->get_error_message());
1342 + throw new \Exception(sprintf(__("Unable to load preview, %s", 'jc-importer'), $import_file_exists->get_error_message()));
879 1343 }
880 1344
881 1345 $record_index = isset($post_data['record']) ? $post_data['record'] : null;
882 1346 if (is_null($record_index)) {
@@ -887,11 +1351,21 @@
887 1351 if (isset($post_data['file_encoding'])) {
888 1352 $importer->setFileSetting('file_encoding', $post_data['file_encoding']);
889 1353 }
890 1354
1355 + $record_index = intval($record_index);
1356 +
1357 + // Rebuild a complete temp index once for preview navigation.
1358 + // file-process may have stored a partial sample index (e.g. header + 1 row).
1359 + $config = $this->importer_manager->get_config($importer, true);
1360 + if (!$config->get('preview_full_index')) {
1361 + $this->importer_manager->clear_config_files($id, true);
1362 + $config = $this->importer_manager->get_config($importer, true);
1363 + $config->set('preview_full_index', true);
1364 + }
1365 +
891 1366 if ($importer->getParser() === 'xml') {
892 1367
893 - $config = $this->importer_manager->get_config($importer, true);
894 1368 $file = $this->importer_manager->get_xml_file($importer, $config);
895 1369
896 1370 $base_path = $post_data['base_path'];
897 1371 if (!is_null($base_path)) {
@@ -897,18 +1371,55 @@
897 1371 if (!is_null($base_path)) {
898 1372 $file->setRecordPath($base_path);
899 1373 }
900 1374
1375 + $total = $file->getRecordCount();
1376 + if ($total > 0) {
1377 + $record_index = max(0, min($record_index, $total - 1));
1378 + } else {
1379 + $record_index = 0;
1380 + }
1381 +
901 1382 $preview = new XMLPreview($file, $base_path);
902 - $result = $preview->data();
1383 + $result = $preview->data($record_index);
903 1384 if (is_wp_error($result)) {
904 1385 return $this->http->end_rest_error($result);
905 1386 }
906 1387
907 - return $this->http->end_rest_success($result[0]);
1388 + return $this->http->end_rest_success([
1389 + 'data' => $result[0],
1390 + 'record' => $record_index,
1391 + 'total' => $total,
1392 + ]);
1393 + } elseif ($importer->getParser() === 'json') {
1394 +
1395 + $file = $this->importer_manager->get_json_file($importer, $config);
1396 +
1397 + $base_path = isset($post_data['base_path']) ? $post_data['base_path'] : $importer->getFileSetting('base_path');
1398 + if (!is_null($base_path)) {
1399 + $file->setRecordPath($base_path);
1400 + }
1401 +
1402 + $total = $file->getRecordCount();
1403 + if ($total > 0) {
1404 + $record_index = max(0, min($record_index, $total - 1));
1405 + } else {
1406 + $record_index = 0;
1407 + }
1408 +
1409 + $preview = new JSONPreview($file, $base_path);
1410 + $result = $preview->data($record_index);
1411 + if (is_wp_error($result)) {
1412 + return $this->http->end_rest_error($result);
1413 + }
1414 +
1415 + return $this->http->end_rest_success([
1416 + 'data' => $result[0],
1417 + 'record' => $record_index,
1418 + 'total' => $total,
1419 + ]);
908 1420 } elseif ($importer->getParser() === 'csv') {
909 1421
910 - $config = $this->importer_manager->get_config($importer, true);
911 1422 $file = $this->importer_manager->get_csv_file($importer, $config);
912 1423
913 1424 $clear_config = false;
914 1425 if (!is_null($post_data['delimiter']) && $post_data['delimiter'] !== $file->getDelimiter()) {
@@ -914,13 +1425,16 @@
914 1425 if (!is_null($post_data['delimiter']) && $post_data['delimiter'] !== $file->getDelimiter()) {
915 1426 $clear_config = true;
916 1427 } elseif (!is_null($post_data['enclosure']) && $post_data['enclosure'] !== $file->getEnclosure()) {
917 1428 $clear_config = true;
1429 + } elseif (!is_null($post_data['escape']) && $post_data['escape'] !== $file->getEscape()) {
1430 + $clear_config = true;
918 1431 }
919 1432
920 1433 if ($clear_config) {
921 1434 $this->importer_manager->clear_config_files($importer->getId(), true);
922 1435 $config = $this->importer_manager->get_config($importer, true);
1436 + $config->set('preview_full_index', true);
923 1437 $file = $this->importer_manager->get_csv_file($importer, $config);
924 1438 }
925 1439
926 1440 $delimiter = $post_data['delimiter'];
@@ -932,8 +1446,13 @@
932 1446 if (!is_null($enclosure)) {
933 1447 $file->setEnclosure($enclosure);
934 1448 }
935 1449
1450 + $escape = $post_data['escape'];
1451 + if (!is_null($escape)) {
1452 + $file->setEscape($escape);
1453 + }
1454 +
936 1455 // parse bool param from string
937 1456 $show_headings = $post_data['show_headings'];
938 1457 if (in_array($show_headings, ['true', 'false'])) {
939 1458 $show_headings = $show_headings === 'true' ? true : false;
@@ -951,9 +1470,9 @@
951 1470
952 1471 return $this->http->end_rest_success($result);
953 1472 } else {
954 1473
955 - $result = apply_filters('iwp/file-preview/' . $importer->getParser(), null, $importer);
1474 + $result = apply_filters('iwp/file-preview/' . $importer->getParser(), null, $importer, $record_index);
956 1475 if (is_wp_error($result)) {
957 1476 return $this->http->end_rest_error($result);
958 1477 }
959 1478
@@ -971,24 +1490,31 @@
971 1490 $id = intval($request->get_param('id'));
972 1491 $importer = $this->importer_manager->get_importer($id);
973 1492 $import_file_exists = $this->filesystem->file_exists($importer->getFile());
974 1493 if (is_wp_error($import_file_exists)) {
975 - throw new \Exception("Unable to load preview, " . $import_file_exists->get_error_message());
1494 + throw new \Exception(sprintf(__("Unable to load preview, %s", 'jc-importer'), $import_file_exists->get_error_message()));
976 1495 }
977 1496
978 1497 $parser = $importer->getParser();
979 1498
980 1499 $fields = $this->sanitize($request->get_body_params());
1500 + $record_index = 0;
1501 + if (isset($fields['record'])) {
1502 + $record_index = intval($fields['record']);
1503 + unset($fields['record']);
1504 + }
981 1505 if (is_null($fields) || empty($fields)) {
982 1506 $fields = $importer->getMap();
983 1507 }
984 1508
985 1509 if ('xml' === $parser) {
986 - $result = $this->importer_manager->preview_xml_file($importer, $fields);
1510 + $result = $this->importer_manager->preview_xml_file($importer, $fields, $record_index);
1511 + } elseif ('json' === $parser) {
1512 + $result = $this->importer_manager->preview_json_file($importer, $fields, $record_index);
987 1513 } elseif ('csv' === $parser) {
988 - $row = 0;
1514 + $row = $record_index;
989 1515 if ($importer->getFileSetting('show_headings') === true) {
990 - $row = 1;
1516 + $row = $record_index + 1;
991 1517 }
992 1518 $result = $this->importer_manager->preview_csv_file($importer, $fields, $row);
993 1519 } else {
994 1520
@@ -1028,8 +1554,13 @@
1028 1554 // This is used for storing version on imported records
1029 1555 $session_id = md5($importer_data->getId() . time());
1030 1556 update_post_meta($importer_data->getId(), '_iwp_session', $session_id);
1031 1557
1558 + if ('background' === $importer_data->getSetting('import_method')) {
1559 + update_post_meta($importer_data->getId(), '_iwp_cron_scheduled', current_time('timestamp'));
1560 + delete_post_meta($importer_data->getId(), '_iwp_cron_last_ran');
1561 + }
1562 +
1032 1563 Logger::clearRequestType();
1033 1564
1034 1565 return $this->http->end_rest_success([
1035 1566 'session' => $session_id
@@ -1062,25 +1593,10 @@
1062 1593 {
1063 1594 $id = intval($request->get_param('id'));
1064 1595 $paused = $this->sanitize($request->get_param('paused'), 'paused');
1065 1596
1066 - $importer_data = $this->importer_manager->get_importer($id);
1067 - $user = uniqid('iwp', true);
1597 + $state = $this->importer_manager->pause_import($id, $paused);
1068 1598
1069 - $state = ImporterState::wait_for_lock($importer_data->getId(), $user, function () use ($importer_data, $paused) {
1070 - $state = ImporterState::get_state($importer_data->getId());
1071 -
1072 -
1073 - if ($paused === 'no') {
1074 - $state['status'] = 'running';
1075 - } else {
1076 - $state['status'] = 'paused';
1077 - }
1078 -
1079 - ImporterState::set_state($importer_data->getId(), $state);
1080 - return $state;
1081 - });
1082 -
1083 1599 return $this->http->end_rest_success($state);
1084 1600 }
1085 1601
1086 1602 public function stop_import(\WP_REST_Request $request)
@@ -1086,19 +1602,10 @@
1086 1602 public function stop_import(\WP_REST_Request $request)
1087 1603 {
1088 1604 $id = intval($request->get_param('id'));
1089 1605
1090 - $importer_data = $this->importer_manager->get_importer($id);
1606 + $state = $this->importer_manager->stop_import($id);
1091 1607
1092 - $user = uniqid('iwp');
1093 -
1094 - $state = ImporterState::wait_for_lock($importer_data->getId(), $user, function () use ($importer_data) {
1095 - $state = ImporterState::get_state($importer_data->getId());
1096 - $state['status'] = 'cancelled';
1097 - ImporterState::set_state($importer_data->getId(), $state);
1098 - return $state;
1099 - });
1100 -
1101 1608 return $this->http->end_rest_success($state);
1102 1609 }
1103 1610
1104 1611 /**
@@ -1108,15 +1615,20 @@
1108 1615 * @return void
1109 1616 */
1110 1617 public function get_template_field_options(\WP_REST_Request $request)
1111 1618 {
1112 - $id = intval($request->get_param('id'));
1113 - $field = $this->sanitize($request->get_param('field'), 'field');
1619 + try {
1114 1620
1115 - $importer_data = $this->importer_manager->get_importer($id);
1116 - $template = $this->importer_manager->get_importer_template($id);
1621 + $id = intval($request->get_param('id'));
1622 + $field = $this->sanitize($request->get_param('field'), 'field');
1117 1623
1118 - return $this->http->end_rest_success($template->get_field_options($field, $importer_data));
1624 + $importer_data = $this->importer_manager->get_importer($id);
1625 + $template = $this->importer_manager->get_importer_template($id);
1626 +
1627 + return $this->http->end_rest_success($template->get_field_options($field, $importer_data));
1628 + } catch (\Exception $e) {
1629 + return $this->http->end_rest_error($e->getMessage());
1630 + }
1119 1631 }
1120 1632
1121 1633 public function get_logs(\WP_REST_Request $request)
1122 1634 {
@@ -1149,9 +1661,19 @@
1149 1661 }
1150 1662 $importer_data = $this->importer_manager->get_importer($id);
1151 1663 $log = $this->importer_manager->get_importer_debug_log($importer_data, $page, 100);
1152 1664
1153 - $download = Logger::getLogFile($importer_data->getId(), true);
1665 + // Authenticated admin download URL — direct uploads/importwp URLs are blocked by .htaccess.
1666 + $download = add_query_arg(
1667 + array(
1668 + 'page' => 'importwp',
1669 + 'import' => $importer_data->getId(),
1670 + 'download_debug' => 1,
1671 + '_wpnonce' => wp_create_nonce('iwp_debug_log_download'),
1672 + ),
1673 + admin_url('tools.php')
1674 + );
1675 +
1154 1676 return $this->http->end_rest_success(['log' => $log, 'download' => $download]);
1155 1677 }
1156 1678
1157 1679 public function save_settings(\WP_REST_Request $request)
@@ -1183,9 +1705,9 @@
1183 1705 $output[$setting] = $default['value'];
1184 1706 }
1185 1707 }
1186 1708
1187 - update_site_option('iwp_settings', $output);
1709 + update_option('iwp_settings', $output);
1188 1710
1189 1711 return $this->http->end_rest_success($this->properties->get_settings());
1190 1712 }
1191 1713
@@ -1205,9 +1727,9 @@
1205 1727
1206 1728 $templates = $this->importer_manager->get_templates();
1207 1729
1208 1730 if (!isset($templates[$importer_template])) {
1209 - return $this->http->end_rest_error("Invalid template \"" . $importer_template . "\"");
1731 + return $this->http->end_rest_error(sprintf(__("Invalid template \"%s\"", 'jc-importer'), $importer_template));
1210 1732 }
1211 1733
1212 1734 $template_id = $templates[$importer_template];
1213 1735 $template_class = $this->template_manager->load_template($template_id);
@@ -1217,8 +1739,9 @@
1217 1739 'label' => $template_class->get_name(),
1218 1740 'map' => $template_class->get_fields($importer_model),
1219 1741 'settings' => $template_class->register_settings(),
1220 1742 'options' => $template_class->register_options(),
1743 + 'permission_fields' => apply_filters('iwp/template/permission_fields', $template_class->get_permission_fields($importer_model), $template_class)
1221 1744 ];
1222 1745 return $this->http->end_rest_success($output);
1223 1746 }
1224 1747
@@ -1273,8 +1796,143 @@
1273 1796 echo $json;
1274 1797 die();
1275 1798 }
1276 1799
1800 + /**
1801 + * Download exporter config json file.
1802 + *
1803 + * File is used when creating an importer to auto generate field mapping.
1804 + *
1805 + * @param WP_REST_Request|null $request
1806 + * @return never
1807 + */
1808 + public function download_exporter_config(\WP_REST_Request $request = null)
1809 + {
1810 + $exporter_id = $request->get_param('id');
1811 + $exporter_data = $this->exporter_manager->get_exporter($exporter_id);
1812 + $mapper = $this->exporter_manager->get_exporter_mapper($exporter_data);
1813 + $fields = $mapper->get_fields();
1814 +
1815 +
1816 +
1817 + /**
1818 + * @var \WPDB $wpdb
1819 + */
1820 + global $wpdb;
1821 + $result = $wpdb->get_row("SELECT post_title, post_content from {$wpdb->posts} WHERE post_type='" . EWP_POST_TYPE . "' AND ID=" . intval($exporter_id), ARRAY_A);
1822 +
1823 + $output = [
1824 + 'name' => $result['post_title'],
1825 + 'data' => unserialize($result['post_content']),
1826 + 'fields' => $this->exporter_manager->get_importer_map_fields($exporter_data),
1827 + 'formatted_fields' => $fields
1828 + ];
1829 +
1830 + $json = json_encode($output);
1831 +
1832 + header('Content-disposition: attachment; filename="ImportWP-exporter-' . $exporter_id . '-' . date('Y-m-d') . '.json"');
1833 + header('Content-type: "application/json"; charset="utf8"');
1834 + header('Expires: 0');
1835 + header('Cache-Control: must-revalidate');
1836 + header('Pragma: public');
1837 + header('Content-Length: ' . strlen($json));
1838 +
1839 + echo $json;
1840 + die();
1841 + }
1842 +
1843 + public function read_exporter_config(\WP_REST_Request $request = null)
1844 + {
1845 + $upload = $this->filesystem->upload_file($_FILES['file']);
1846 + if (is_wp_error($upload)) {
1847 + return $this->http->end_rest_error($upload->get_error_message());
1848 + }
1849 +
1850 + $json_contents = file_get_contents($upload['dest']);
1851 + @unlink($upload['dest']);
1852 +
1853 + $contents = json_decode($json_contents, true);
1854 +
1855 + if (!isset($contents['name'], $contents['fields'], $contents['data'], $contents['data']['file_type'])) {
1856 + return $this->http->end_rest_error("Invalid exporter config file.");
1857 + }
1858 +
1859 + if (!in_array($contents['data']['file_type'], ['xml', 'csv', 'json'])) {
1860 + return $this->http->end_rest_error("Exporter file type is not supported.");
1861 + }
1862 +
1863 + return $this->http->end_rest_success(['exporter' => $contents]);
1864 + }
1865 +
1866 + public function get_compatibility_settings()
1867 + {
1868 + $output = [];
1869 +
1870 + if (!function_exists('get_plugins')) {
1871 + require_once ABSPATH . 'wp-admin/includes/plugin.php';
1872 + }
1873 +
1874 + $blacklisted = (array)get_option('iwp_compat_blacklist', []);
1875 + $whitelisted = apply_filters('iwp/compat/whitelist', [
1876 + 'jc-importer/jc-importer.php'
1877 + ]);
1878 +
1879 + $all_plugins = get_plugins();
1880 + foreach ($all_plugins as $plugin_id => $plugin_data) {
1881 +
1882 +
1883 +
1884 + if (preg_match('/^importwp-/', $plugin_id, $matches) === 1 || in_array($plugin_id, $whitelisted)) {
1885 + continue;
1886 + }
1887 +
1888 + $output[$plugin_id] = [
1889 + 'id' => $plugin_id,
1890 + 'name' => $plugin_data['Name'],
1891 + 'enabled' => in_array($plugin_id, $blacklisted) ? 'yes' : 'no'
1892 + ];
1893 + }
1894 +
1895 + return $output;
1896 + }
1897 +
1898 + public function get_compatibility(\WP_REST_Request $request = null)
1899 + {
1900 + $output = $this->get_compatibility_settings();
1901 +
1902 + return $this->http->end_rest_success($output);
1903 + }
1904 +
1905 + public function save_compatibility(\WP_REST_Request $request)
1906 + {
1907 + $post_data = $request->get_body_params();
1908 + $post_data = $this->sanitize($post_data);
1909 +
1910 + if (!function_exists('get_plugins')) {
1911 + require_once ABSPATH . 'wp-admin/includes/plugin.php';
1912 + }
1913 + $plugin_keys = array_keys(get_plugins());
1914 +
1915 +
1916 + $tmp = [];
1917 +
1918 + if (isset($post_data['plugins'])) {
1919 +
1920 + foreach ($post_data['plugins'] as $plugin_id) {
1921 + if (!in_array($plugin_id, $plugin_keys)) {
1922 + continue;
1923 + }
1924 +
1925 + $tmp[] = "" . $plugin_id;
1926 + }
1927 + }
1928 +
1929 + update_option('iwp_compat_blacklist', $tmp);
1930 +
1931 + $output = $this->get_compatibility_settings();
1932 + return $this->http->end_rest_success($output);
1933 + }
1934 +
1277 1935 public function import_exporters(\WP_REST_Request $request = null)
1278 1936 {
1279 1937 Logger::setRequestType('import_exporters');
1280 1938
@@ -1296,9 +1954,9 @@
1296 1954 'post_type' => IWP_POST_TYPE,
1297 1955 'post_title' => $importer['name'],
1298 1956 'post_status' => 'publish',
1299 1957 'post_author' => get_current_user_id(),
1300 - 'post_content' => serialize($importer['data'])
1958 + 'post_content' => wp_slash(serialize($importer['data']))
1301 1959 ], true);
1302 1960
1303 1961 if (is_wp_error($result)) {
1304 1962 $errors[] = $result->get_error_message();
@@ -1309,12 +1967,12 @@
1309 1967 }
1310 1968 }
1311 1969
1312 1970 if (empty($counter)) {
1313 - return $this->http->end_rest_error('No Importers found.');
1971 + return $this->http->end_rest_error(__('No Importers found.', 'jc-importer'));
1314 1972 }
1315 1973
1316 - return $this->http->end_rest_success("Import Complete, {$counter} Importer" . ($counter > 1 ? 's' : '') . '.');
1974 + return $this->http->end_rest_success(sprintf(_n("Import Complete, %d Importer", "Import Complete, %d Importers", $counter, 'jc-importer'), $counter));
1317 1975 }
1318 1976
1319 1977 public function save_exporter(\WP_REST_Request $request = null)
1320 1978 {
@@ -1337,8 +1995,12 @@
1337 1995 if (isset($post_data['file_type'])) {
1338 1996 $exporter->setFileType($post_data['file_type']);
1339 1997 }
1340 1998
1999 + if (isset($post_data['file_settings'])) {
2000 + $exporter->setFileSettings($post_data['file_settings']);
2001 + }
2002 +
1341 2003 if (isset($post_data['fields']) || $id > 0) {
1342 2004 $exporter->setFields(isset($post_data['fields']) ? $post_data['fields'] : []);
1343 2005 }
1344 2006
@@ -1503,8 +2165,10 @@
1503 2165 break;
1504 2166 case 'complete':
1505 2167 $output .= 'Export complete';
1506 2168 break;
2169 + case 'processing':
2170 + case 'timeout':
1507 2171 case 'running':
1508 2172 $output .= 'Exporting: ' . $current . '/' . $end;
1509 2173 break;
1510 2174 }
@@ -1517,6 +2181,44 @@
1517 2181 }
1518 2182
1519 2183 echo json_encode($result) . "\n";
1520 2184 die();
2185 + }
2186 +
2187 + public function get_importer_unique_identifier_options(\WP_REST_Request $request)
2188 + {
2189 + $id = intval($request->get_param('id'));
2190 + $importer_model = $this->importer_manager->get_importer($id);
2191 + if (!$importer_model) {
2192 + return $this->http->end_rest_error(['msg' => 'Invalid importer']);
2193 + }
2194 +
2195 + // Get default template options
2196 + $template = $this->importer_manager->get_template($importer_model->getTemplate());
2197 + if (is_wp_error($template)) {
2198 + return $this->http->end_rest_error($template->get_error_message());
2199 + }
2200 +
2201 + $template_class = $this->template_manager->load_template($template);
2202 + $unique_fields = $this->template_manager->get_template_unique_fields($template_class);
2203 + $options = $template_class->get_unique_identifier_options($importer_model, $unique_fields);
2204 +
2205 + // Only add in unqiue fields if they have not been found.
2206 + // Allowing for old templates to continue to list unique identifiers.
2207 + foreach ($unique_fields as $field_id) {
2208 + if (!isset($options[$field_id])) {
2209 + $options[$field_id] = [
2210 + 'value' => $field_id,
2211 + 'label' => $field_id,
2212 + 'uid' => true,
2213 + 'active' => true,
2214 + ];
2215 + }
2216 + }
2217 +
2218 + $options = array_filter($options, function ($item) {
2219 + return $item['active'];
2220 + });
2221 +
2222 + return $this->http->end_rest_success(['options' => array_values($options), 'unique_fields' => array_values($unique_fields)]);
1521 2223 }
1522 2224 }