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 +815 -100 2.7.10 → 2.15.2 View file →
@@ -4,11 +4,13 @@
4 4
5 5 use ImportWP\Common\Exporter\ExporterManager;
6 6 use ImportWP\Common\Exporter\State\ExporterState;
7 7 use ImportWP\Common\Filesystem\Filesystem;
8 +use ImportWP\Common\Filesystem\ZipArchive;
8 9 use ImportWP\Common\Http\Http;
9 10 use ImportWP\Common\Importer\ImporterManager;
10 11 use ImportWP\Common\Importer\Preview\CSVPreview;
12 +use ImportWP\Common\Importer\Preview\JSONPreview;
11 13 use ImportWP\Common\Importer\Preview\XMLPreview;
12 14 use ImportWP\Common\Importer\State\ImporterState;
13 15 use ImportWP\Common\Importer\Template\Template;
14 16 use ImportWP\Common\Importer\Template\TemplateManager;
@@ -235,8 +237,21 @@
235 237 'permission_callback' => array($this, 'get_permission')
236 238 ),
237 239 ));
238 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 +
239 254 register_rest_route($namespace, '/import-export', array(
240 255 array(
241 256 'methods' => \WP_REST_Server::CREATABLE,
242 257 'callback' => array($this, 'import_exporters'),
@@ -264,8 +279,16 @@
264 279 'permission_callback' => array($this, 'get_permission')
265 280 )
266 281 ));
267 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 +
268 291 // Exporter
269 292
270 293 register_rest_route($namespace, '/exporter', array(
271 294 array(
@@ -308,8 +331,24 @@
308 331 'permission_callback' => array($this, 'get_permission')
309 332 )
310 333 ));
311 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 +
312 351 register_rest_route($namespace, '/exporter/(?P<id>\d+)/run', array(
313 352 array(
314 353 'methods' => \WP_REST_Server::CREATABLE,
315 354 'callback' => array($this, 'run_exporter'),
@@ -329,9 +368,9 @@
329 368 public function get_permission()
330 369 {
331 370
332 371 if (!current_user_can('manage_options')) {
333 - 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));
334 373 }
335 374
336 375 return true;
337 376 }
@@ -347,8 +386,13 @@
347 386
348 387 return $data;
349 388 }
350 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 +
351 395 $full_path = implode('.', array_filter($path));
352 396 if (!is_null($full_path)) {
353 397
354 398 if (preg_match('/^map\.\S+/', $full_path) !== false) {
@@ -365,8 +409,70 @@
365 409
366 410 return sanitize_text_field($data);
367 411 }
368 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 +
369 475 public function check_rest_status(\WP_REST_Request $request)
370 476 {
371 477 $result = [];
372 478 // find required php ext: https://github.com/llaville/php-compat-info
@@ -376,9 +482,9 @@
376 482
377 483 $result = [
378 484 'tmp_writable' => [
379 485 'status' => $tmp_writable ? 'yes' : 'no',
380 - '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()),
381 487 ],
382 488 'rest_enabled' => [
383 489 'status' => 'yes',
384 490 'message' => '',
@@ -398,8 +504,11 @@
398 504 'php_version' => [
399 505 'status' => version_compare(phpversion(), '5.5.0') ? 'yes' : 'no',
400 506 'message' => '',
401 507 ],
508 + 'zip_archive' => [
509 + 'status' => ZipArchive::has_requirements_met() ? 'yes' : 'no'
510 + ]
402 511 ];
403 512 return $this->http->end_rest_success($result);
404 513 }
405 514
@@ -407,9 +516,9 @@
407 516 {
408 517 $migration = new Migrations();
409 518 $result = $migration->migrate();
410 519
411 - return $this->http->end_rest_success("Migration Complete.");
520 + return $this->http->end_rest_success(__("Migration Complete.", 'jc-importer'));
412 521 }
413 522
414 523 public function get_importers(\WP_REST_Request $request)
415 524 {
@@ -436,9 +545,9 @@
436 545 public function save_importer(\WP_REST_Request $request)
437 546 {
438 547 Logger::setRequestType('save_importer');
439 548
440 - $post_data = $request->get_body_params();
549 + $post_data = $this->get_request_data($request);
441 550 $id = isset($post_data['id']) ? intval($post_data['id']) : null;
442 551
443 552 $temp_post_data = [
444 553 'create' => [],
@@ -446,10 +555,19 @@
446 555 ];
447 556
448 557 if (isset($post_data['permissions'])) {
449 558 // TODO: update sanitize method to keep track of hierarchy
450 - $temp_post_data['create'] = explode("\n", $post_data['permissions']['create']['fields']);
451 - $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 + }
452 570 }
453 571
454 572 $post_data = $this->sanitize($post_data);
455 573
@@ -464,12 +582,9 @@
464 582 }
465 583 }
466 584 }
467 585
468 -
469 -
470 586 $importer = new ImporterModel($id, $this->importer_manager->is_debug());
471 -
472 587 if (isset($post_data['datasource'])) {
473 588 $importer->setDatasource($post_data['datasource']);
474 589 }
475 590
@@ -477,9 +592,9 @@
477 592 $importer->setDatasourceSetting('remote_url', $post_data['remote_url']);
478 593 }
479 594
480 595 if (isset($post_data['local_url'])) {
481 - $importer->setDatasourceSetting('local_url', $post_data['local_url']);
596 + $importer->setDatasourceSetting('local_url', wp_normalize_path($post_data['local_url']));
482 597 }
483 598
484 599 if (isset($post_data['existing_id'])) {
485 600 $importer->setFileId(intval($post_data['existing_id']));
@@ -500,8 +615,14 @@
500 615 if (isset($post_data['template_options'])) {
501 616 $template_options = array_merge($template_options, $post_data['template_options']);
502 617 }
503 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 +
504 625 foreach ($template_options as $key => $val) {
505 626 $importer->setSetting($key, $val);
506 627 }
507 628 }
@@ -509,8 +630,220 @@
509 630 if (isset($post_data['name'])) {
510 631 $importer->setName($post_data['name']);
511 632 }
512 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 +
513 846 if (isset($post_data['setting_start_row'])) {
514 847 $importer->setStartRow($post_data['setting_start_row']);
515 848 }
516 849
@@ -526,8 +859,13 @@
526 859 $importer->setSetting($matches['key'], $value);
527 860 }
528 861 }
529 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 +
530 868 $parser = $importer->getParser();
531 869
532 870 if (isset($post_data['file_settings_encoding'])) {
533 871 $importer->setFileSetting('file_encoding', $post_data['file_settings_encoding']);
@@ -550,16 +888,26 @@
550 888 $clear_config = true;
551 889 }
552 890 $importer->setFileSetting('enclosure', $post_data['file_settings_enclosure']);
553 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 +
554 902 if (isset($post_data['file_settings_show_headings'])) {
555 - $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']));
556 904 }
557 905
558 906 if ($clear_config) {
559 907 $this->importer_manager->clear_config_files($importer->getId(), true);
560 908 }
561 - } elseif ($parser === 'xml') {
909 + } elseif ($parser === 'xml' || $parser === 'json') {
562 910 if (isset($post_data['file_settings_base_path'])) {
563 911 $importer->setFileSetting('base_path', $post_data['file_settings_base_path']);
564 912 }
565 913 }
@@ -564,25 +912,37 @@
564 912 }
565 913 }
566 914
567 915 if (isset($post_data['file_settings_setup'])) {
568 - $importer->setFileSetting('setup', $post_data['file_settings_setup'] === 'true' ? true : false);
916 + $importer->setFileSetting('setup', $this->is_truthy($post_data['file_settings_setup']));
569 917 }
570 918
571 919 if (isset($post_data['map']) && is_array($post_data['map'])) {
920 + $map = [];
572 921 foreach ($post_data['map'] as $key => $value) {
922 + if ($value === null) {
923 + continue;
924 + }
573 925
574 926 if (1 === preg_match('/\._index$/', $key)) {
575 927 $value = intval($value);
576 928 }
577 929
578 - $importer->setMap($key, $value);
930 + $map[$key] = $value;
579 931 }
932 + $importer->replaceMap($map);
580 933 }
581 934
582 935 if (isset($post_data['enabled']) && is_array($post_data['enabled'])) {
583 936 foreach ($post_data['enabled'] as $key => $value) {
584 - 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)) {
585 945 $importer->setEnabled($key);
586 946 } else {
587 947 $importer->setEnabled($key, false);
588 948 }
@@ -594,9 +954,9 @@
594 954 // create
595 955 if (isset($post_data['permissions']['create'])) {
596 956 $create = $post_data['permissions']['create'];
597 957 $importer->setPermission('create', [
598 - 'enabled' => isset($create['enabled']) && $create['enabled'] === 'true' ? true : false,
958 + 'enabled' => isset($create['enabled']) && $this->is_truthy($create['enabled']) ? true : false,
599 959 'type' => isset($create['type']) && in_array($create['type'], ['include', 'exclude'], true) ? $create['type'] : null,
600 960 'fields' => isset($create['fields']) ? $create['fields'] : [],
601 961 ]);
602 962 }
@@ -604,9 +964,9 @@
604 964 // update
605 965 if (isset($post_data['permissions']['update'])) {
606 966 $update = $post_data['permissions']['update'];
607 967 $importer->setPermission('update', [
608 - 'enabled' => isset($update['enabled']) && $update['enabled'] === 'true' ? true : false,
968 + 'enabled' => isset($update['enabled']) && $this->is_truthy($update['enabled']) ? true : false,
609 969 'type' => isset($update['type']) && in_array($update['type'], ['include', 'exclude'], true) ? $update['type'] : null,
610 970 'fields' => isset($update['fields']) ? $update['fields'] : [],
611 971 ]);
612 972 }
@@ -613,10 +973,11 @@
613 973
614 974 // remove
615 975 if (isset($post_data['permissions']['remove'])) {
616 976 $importer->setPermission('remove', [
617 - 'enabled' => $post_data['permissions']['remove']['enabled'] === 'true',
618 - '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']),
619 980 ]);
620 981 }
621 982 }
622 983
@@ -626,8 +987,88 @@
626 987 }
627 988 return $this->http->end_rest_success($importer->data());
628 989 }
629 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 +
630 1071 private function sanitize_setting($value)
631 1072 {
632 1073 if (is_array($value)) {
633 1074 foreach ($value as &$tmp) {
@@ -670,14 +1111,25 @@
670 1111
671 1112 foreach ($query->posts as $importer_id) {
672 1113
673 1114 $importer_model = $this->importer_manager->get_importer($importer_id);
1115 + $config = $this->importer_manager->get_config($importer_model);
674 1116
675 1117 $output = ImporterState::get_state($importer_id);
676 - $output['version'] = 2;
677 - $output['message'] = $this->generate_status_message($output);
678 - $output['importer'] = $importer_id;
1118 + if (!$output) {
1119 + $output = [
1120 + 'version' => 2,
1121 + 'message' => 'Not setup',
1122 + 'importer' => $importer_id
1123 + ];
1124 + } else {
679 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 +
680 1132 $result[] = $this->event_handler->run('iwp/importer/status/output', [$output, $importer_model]);
681 1133 }
682 1134
683 1135 echo json_encode($result) . "\n";
@@ -698,12 +1150,12 @@
698 1150
699 1151 if (isset($data['status'])) {
700 1152 switch ($data['status']) {
701 1153 case 'cancelled':
702 - $output .= 'Import cancelled';
1154 + $output .= __('Import cancelled', 'jc-importer');
703 1155 break;
704 1156 case 'complete':
705 - $output .= 'Import complete';
1157 + $output .= __('Import complete', 'jc-importer');
706 1158 break;
707 1159 case 'error':
708 1160 $output = $data['message'];
709 1161 return $output;
@@ -710,15 +1162,14 @@
710 1162 break;
711 1163 }
712 1164 }
713 1165
714 - 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'])) {
715 1167 switch ($data['section']) {
716 1168 case 'import':
717 1169 case 'delete':
718 - $output = $data['section'] === 'import' ? 'Importing: ' : 'Deleting: ';
719 - $output .= $data['progress'][$data['section']]['current_row'] . ' / ' . ($data['progress'][$data['section']]['end'] - $data['progress'][$data['section']]['start']);
720 -
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);
721 1172 break;
722 1173 }
723 1174 }
724 1175
@@ -723,21 +1174,21 @@
723 1174 }
724 1175
725 1176 if (isset($data['stats'])) {
726 1177 if ($data['stats']['inserts'] > 0) {
727 - $output .= sprintf(', Inserts: %d', $data['stats']['inserts']);
1178 + $output .= sprintf(__(', Inserts: %d', 'jc-importer'), $data['stats']['inserts']);
728 1179 }
729 1180 if ($data['stats']['updates'] > 0) {
730 - $output .= sprintf(', Updates: %d', $data['stats']['updates']);
1181 + $output .= sprintf(__(', Updates: %d', 'jc-importer'), $data['stats']['updates']);
731 1182 }
732 1183 if ($data['stats']['deletes'] > 0) {
733 - $output .= sprintf(', Deletes: %d', $data['stats']['deletes']);
1184 + $output .= sprintf(__(', Deletes: %d', 'jc-importer'), $data['stats']['deletes']);
734 1185 }
735 1186 if ($data['stats']['skips'] > 0) {
736 - $output .= sprintf(', Skips: %d', $data['stats']['skips']);
1187 + $output .= sprintf(__(', Skips: %d', 'jc-importer'), $data['stats']['skips']);
737 1188 }
738 1189 if ($data['stats']['errors'] > 0) {
739 - $output .= sprintf(', Errors: %d', $data['stats']['errors']);
1190 + $output .= sprintf(__(', Errors: %d', 'jc-importer'), $data['stats']['errors']);
740 1191 }
741 1192 }
742 1193
743 1194 if (isset($data['timestamp'], $data['updated'])) {
@@ -744,15 +1195,15 @@
744 1195 /**
745 1196 * @var Util $util
746 1197 */
747 1198 $util = Container::getInstance()->get('util');
748 - $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']));
749 1200 } elseif (isset($data['duration'])) {
750 1201 /**
751 1202 * @var Util $util
752 1203 */
753 1204 $util = Container::getInstance()->get('util');
754 - $output .= ', Run Time: ' . $util->format_time($data['duration']);
1205 + $output .= sprintf(__(', Run Time: %s', 'jc-importer'), $util->format_time($data['duration']));
755 1206 }
756 1207
757 1208 return $output;
758 1209 }
@@ -776,8 +1227,10 @@
776 1227 $importer = $this->importer_manager->get_importer($id);
777 1228
778 1229 // TODO: Remove duplicate code, found in cron manager
779 1230
1231 + add_filter('upload_dir', [$this->importer_manager, 'set_custom_upload_path']);
1232 +
780 1233 switch ($action) {
781 1234 case 'file_upload':
782 1235 $attachment_id = $this->importer_manager->upload_file($importer, $_FILES['file']);
783 1236 break;
@@ -796,15 +1249,23 @@
796 1249 case 'file_local':
797 1250 $raw_source = $post_data['local_url'];
798 1251 $source = apply_filters('iwp/importer/datasource', $raw_source, $raw_source, $importer);
799 1252 $source = apply_filters('iwp/importer/datasource/local', $source, $raw_source, $importer);
800 - $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);
801 1260 break;
802 1261 default:
803 - $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'));
804 1263 break;
805 1264 }
806 1265
1266 + remove_filter('upload_dir', [$this->importer_manager, 'set_custom_upload_path']);
1267 +
807 1268 if (is_wp_error($attachment_id)) {
808 1269 return $this->http->end_rest_error($attachment_id->get_error_message());
809 1270 }
810 1271
@@ -818,9 +1279,9 @@
818 1279 $post_data = $this->sanitize($request->get_body_params());
819 1280
820 1281 $importer = $this->importer_manager->get_importer($id);
821 1282 if (!$importer) {
822 - return $this->http->end_rest_error('Invalid importer');
1283 + return $this->http->end_rest_error(__('Invalid importer', 'jc-importer'));
823 1284 }
824 1285
825 1286 $this->importer_manager->clear_config_files($id, true);
826 1287 $parser = $importer->getParser();
@@ -828,20 +1289,29 @@
828 1289 if ('xml' === $parser) {
829 1290
830 1291 $nodes = $this->importer_manager->process_xml_file($id, true);
831 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);
832 1297 } elseif ('csv' === $parser) {
833 1298
834 1299 $delimiter = isset($post_data['delimiter']) ? $post_data['delimiter'] : null;
835 1300 if (is_null($delimiter)) {
836 - $delimiter = ',';
1301 + $delimiter = $importer->getFileSetting('delimiter', ',');
837 1302 }
838 1303
839 1304 $enclosure = isset($post_data['enclosure']) ? $post_data['enclosure'] : null;
840 1305 if (is_null($enclosure)) {
841 - $enclosure = '"';
1306 + $enclosure = $importer->getFileSetting('enclosure', '"');
842 1307 }
843 1308
1309 + $escape = isset($post_data['escape']) ? $post_data['escape'] : null;
1310 + if (is_null($escape)) {
1311 + $escape = $importer->getFileSetting('escape', '\\');
1312 + }
1313 +
844 1314 $count = $this->importer_manager->process_csv_file($id, $delimiter, $enclosure, true);
845 1315
846 1316 // Need fresh importer, due to process_csv_file importer modifications might get overwritten.
847 1317 $importer = $this->importer_manager->get_importer($id);
@@ -868,9 +1338,9 @@
868 1338 $importer = $this->importer_manager->get_importer($id);
869 1339
870 1340 $import_file_exists = $this->filesystem->file_exists($importer->getFile());
871 1341 if (is_wp_error($import_file_exists)) {
872 - 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()));
873 1343 }
874 1344
875 1345 $record_index = isset($post_data['record']) ? $post_data['record'] : null;
876 1346 if (is_null($record_index)) {
@@ -881,11 +1351,21 @@
881 1351 if (isset($post_data['file_encoding'])) {
882 1352 $importer->setFileSetting('file_encoding', $post_data['file_encoding']);
883 1353 }
884 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 +
885 1366 if ($importer->getParser() === 'xml') {
886 1367
887 - $config = $this->importer_manager->get_config($importer, true);
888 1368 $file = $this->importer_manager->get_xml_file($importer, $config);
889 1369
890 1370 $base_path = $post_data['base_path'];
891 1371 if (!is_null($base_path)) {
@@ -891,18 +1371,55 @@
891 1371 if (!is_null($base_path)) {
892 1372 $file->setRecordPath($base_path);
893 1373 }
894 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 +
895 1382 $preview = new XMLPreview($file, $base_path);
896 - $result = $preview->data();
1383 + $result = $preview->data($record_index);
897 1384 if (is_wp_error($result)) {
898 1385 return $this->http->end_rest_error($result);
899 1386 }
900 1387
901 - 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 + ]);
902 1420 } elseif ($importer->getParser() === 'csv') {
903 1421
904 - $config = $this->importer_manager->get_config($importer, true);
905 1422 $file = $this->importer_manager->get_csv_file($importer, $config);
906 1423
907 1424 $clear_config = false;
908 1425 if (!is_null($post_data['delimiter']) && $post_data['delimiter'] !== $file->getDelimiter()) {
@@ -908,13 +1425,16 @@
908 1425 if (!is_null($post_data['delimiter']) && $post_data['delimiter'] !== $file->getDelimiter()) {
909 1426 $clear_config = true;
910 1427 } elseif (!is_null($post_data['enclosure']) && $post_data['enclosure'] !== $file->getEnclosure()) {
911 1428 $clear_config = true;
1429 + } elseif (!is_null($post_data['escape']) && $post_data['escape'] !== $file->getEscape()) {
1430 + $clear_config = true;
912 1431 }
913 1432
914 1433 if ($clear_config) {
915 1434 $this->importer_manager->clear_config_files($importer->getId(), true);
916 1435 $config = $this->importer_manager->get_config($importer, true);
1436 + $config->set('preview_full_index', true);
917 1437 $file = $this->importer_manager->get_csv_file($importer, $config);
918 1438 }
919 1439
920 1440 $delimiter = $post_data['delimiter'];
@@ -926,8 +1446,13 @@
926 1446 if (!is_null($enclosure)) {
927 1447 $file->setEnclosure($enclosure);
928 1448 }
929 1449
1450 + $escape = $post_data['escape'];
1451 + if (!is_null($escape)) {
1452 + $file->setEscape($escape);
1453 + }
1454 +
930 1455 // parse bool param from string
931 1456 $show_headings = $post_data['show_headings'];
932 1457 if (in_array($show_headings, ['true', 'false'])) {
933 1458 $show_headings = $show_headings === 'true' ? true : false;
@@ -945,9 +1470,9 @@
945 1470
946 1471 return $this->http->end_rest_success($result);
947 1472 } else {
948 1473
949 - $result = apply_filters('iwp/file-preview/' . $importer->getParser(), null, $importer);
1474 + $result = apply_filters('iwp/file-preview/' . $importer->getParser(), null, $importer, $record_index);
950 1475 if (is_wp_error($result)) {
951 1476 return $this->http->end_rest_error($result);
952 1477 }
953 1478
@@ -965,24 +1490,31 @@
965 1490 $id = intval($request->get_param('id'));
966 1491 $importer = $this->importer_manager->get_importer($id);
967 1492 $import_file_exists = $this->filesystem->file_exists($importer->getFile());
968 1493 if (is_wp_error($import_file_exists)) {
969 - 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()));
970 1495 }
971 1496
972 1497 $parser = $importer->getParser();
973 1498
974 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 + }
975 1505 if (is_null($fields) || empty($fields)) {
976 1506 $fields = $importer->getMap();
977 1507 }
978 1508
979 1509 if ('xml' === $parser) {
980 - $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);
981 1513 } elseif ('csv' === $parser) {
982 - $row = 0;
1514 + $row = $record_index;
983 1515 if ($importer->getFileSetting('show_headings') === true) {
984 - $row = 1;
1516 + $row = $record_index + 1;
985 1517 }
986 1518 $result = $this->importer_manager->preview_csv_file($importer, $fields, $row);
987 1519 } else {
988 1520
@@ -1022,8 +1554,13 @@
1022 1554 // This is used for storing version on imported records
1023 1555 $session_id = md5($importer_data->getId() . time());
1024 1556 update_post_meta($importer_data->getId(), '_iwp_session', $session_id);
1025 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 +
1026 1563 Logger::clearRequestType();
1027 1564
1028 1565 return $this->http->end_rest_success([
1029 1566 'session' => $session_id
@@ -1056,25 +1593,10 @@
1056 1593 {
1057 1594 $id = intval($request->get_param('id'));
1058 1595 $paused = $this->sanitize($request->get_param('paused'), 'paused');
1059 1596
1060 - $importer_data = $this->importer_manager->get_importer($id);
1061 - $user = uniqid('iwp', true);
1597 + $state = $this->importer_manager->pause_import($id, $paused);
1062 1598
1063 - $state = ImporterState::wait_for_lock($importer_data->getId(), $user, function () use ($importer_data, $paused) {
1064 - $state = ImporterState::get_state($importer_data->getId());
1065 -
1066 -
1067 - if ($paused === 'no') {
1068 - $state['status'] = 'running';
1069 - } else {
1070 - $state['status'] = 'paused';
1071 - }
1072 -
1073 - ImporterState::set_state($importer_data->getId(), $state);
1074 - return $state;
1075 - });
1076 -
1077 1599 return $this->http->end_rest_success($state);
1078 1600 }
1079 1601
1080 1602 public function stop_import(\WP_REST_Request $request)
@@ -1080,19 +1602,10 @@
1080 1602 public function stop_import(\WP_REST_Request $request)
1081 1603 {
1082 1604 $id = intval($request->get_param('id'));
1083 1605
1084 - $importer_data = $this->importer_manager->get_importer($id);
1606 + $state = $this->importer_manager->stop_import($id);
1085 1607
1086 - $user = uniqid('iwp');
1087 -
1088 - $state = ImporterState::wait_for_lock($importer_data->getId(), $user, function () use ($importer_data) {
1089 - $state = ImporterState::get_state($importer_data->getId());
1090 - $state['status'] = 'cancelled';
1091 - ImporterState::set_state($importer_data->getId(), $state);
1092 - return $state;
1093 - });
1094 -
1095 1608 return $this->http->end_rest_success($state);
1096 1609 }
1097 1610
1098 1611 /**
@@ -1102,15 +1615,20 @@
1102 1615 * @return void
1103 1616 */
1104 1617 public function get_template_field_options(\WP_REST_Request $request)
1105 1618 {
1106 - $id = intval($request->get_param('id'));
1107 - $field = $this->sanitize($request->get_param('field'), 'field');
1619 + try {
1108 1620
1109 - $importer_data = $this->importer_manager->get_importer($id);
1110 - $template = $this->importer_manager->get_importer_template($id);
1621 + $id = intval($request->get_param('id'));
1622 + $field = $this->sanitize($request->get_param('field'), 'field');
1111 1623
1112 - 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 + }
1113 1631 }
1114 1632
1115 1633 public function get_logs(\WP_REST_Request $request)
1116 1634 {
@@ -1143,9 +1661,19 @@
1143 1661 }
1144 1662 $importer_data = $this->importer_manager->get_importer($id);
1145 1663 $log = $this->importer_manager->get_importer_debug_log($importer_data, $page, 100);
1146 1664
1147 - $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 +
1148 1676 return $this->http->end_rest_success(['log' => $log, 'download' => $download]);
1149 1677 }
1150 1678
1151 1679 public function save_settings(\WP_REST_Request $request)
@@ -1177,9 +1705,9 @@
1177 1705 $output[$setting] = $default['value'];
1178 1706 }
1179 1707 }
1180 1708
1181 - update_site_option('iwp_settings', $output);
1709 + update_option('iwp_settings', $output);
1182 1710
1183 1711 return $this->http->end_rest_success($this->properties->get_settings());
1184 1712 }
1185 1713
@@ -1199,9 +1727,9 @@
1199 1727
1200 1728 $templates = $this->importer_manager->get_templates();
1201 1729
1202 1730 if (!isset($templates[$importer_template])) {
1203 - 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));
1204 1732 }
1205 1733
1206 1734 $template_id = $templates[$importer_template];
1207 1735 $template_class = $this->template_manager->load_template($template_id);
@@ -1211,8 +1739,9 @@
1211 1739 'label' => $template_class->get_name(),
1212 1740 'map' => $template_class->get_fields($importer_model),
1213 1741 'settings' => $template_class->register_settings(),
1214 1742 'options' => $template_class->register_options(),
1743 + 'permission_fields' => apply_filters('iwp/template/permission_fields', $template_class->get_permission_fields($importer_model), $template_class)
1215 1744 ];
1216 1745 return $this->http->end_rest_success($output);
1217 1746 }
1218 1747
@@ -1267,8 +1796,143 @@
1267 1796 echo $json;
1268 1797 die();
1269 1798 }
1270 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 +
1271 1935 public function import_exporters(\WP_REST_Request $request = null)
1272 1936 {
1273 1937 Logger::setRequestType('import_exporters');
1274 1938
@@ -1290,9 +1954,9 @@
1290 1954 'post_type' => IWP_POST_TYPE,
1291 1955 'post_title' => $importer['name'],
1292 1956 'post_status' => 'publish',
1293 1957 'post_author' => get_current_user_id(),
1294 - 'post_content' => serialize($importer['data'])
1958 + 'post_content' => wp_slash(serialize($importer['data']))
1295 1959 ], true);
1296 1960
1297 1961 if (is_wp_error($result)) {
1298 1962 $errors[] = $result->get_error_message();
@@ -1303,12 +1967,12 @@
1303 1967 }
1304 1968 }
1305 1969
1306 1970 if (empty($counter)) {
1307 - return $this->http->end_rest_error('No Importers found.');
1971 + return $this->http->end_rest_error(__('No Importers found.', 'jc-importer'));
1308 1972 }
1309 1973
1310 - 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));
1311 1975 }
1312 1976
1313 1977 public function save_exporter(\WP_REST_Request $request = null)
1314 1978 {
@@ -1331,8 +1995,12 @@
1331 1995 if (isset($post_data['file_type'])) {
1332 1996 $exporter->setFileType($post_data['file_type']);
1333 1997 }
1334 1998
1999 + if (isset($post_data['file_settings'])) {
2000 + $exporter->setFileSettings($post_data['file_settings']);
2001 + }
2002 +
1335 2003 if (isset($post_data['fields']) || $id > 0) {
1336 2004 $exporter->setFields(isset($post_data['fields']) ? $post_data['fields'] : []);
1337 2005 }
1338 2006
@@ -1478,24 +2146,33 @@
1478 2146 $status = ExporterState::get_state($post_id);
1479 2147 $status['exporter'] = $post_id;
1480 2148 $status['version'] = 2;
1481 2149
1482 - $current = $status['progress']['export']['current_row'];
1483 - $end = $status['progress']['export']['end'] - $status['progress']['export']['start'];
1484 - $status['progress'] = ($current / $end) * 100;
2150 + if (isset($status['progress'])) {
2151 + $current = $status['progress']['export']['current_row'];
2152 + $end = $status['progress']['export']['end'] - $status['progress']['export']['start'];
2153 + $status['progress'] = $end > 0 ? ($current / $end) * 100 : 0;
2154 + } else {
2155 + $status['progress'] = 0;
2156 + }
1485 2157
2158 +
1486 2159 $output = '';
1487 2160
1488 - switch ($status['status']) {
1489 - case 'cancelled':
1490 - $output .= 'Export cancelled';
1491 - break;
1492 - case 'complete':
1493 - $output .= 'Export complete';
1494 - break;
1495 - case 'running':
1496 - $output .= 'Exporting: ' . $current . '/' . $end;
1497 - break;
2161 + if (isset($status['status'])) {
2162 + switch ($status['status']) {
2163 + case 'cancelled':
2164 + $output .= 'Export cancelled';
2165 + break;
2166 + case 'complete':
2167 + $output .= 'Export complete';
2168 + break;
2169 + case 'processing':
2170 + case 'timeout':
2171 + case 'running':
2172 + $output .= 'Exporting: ' . $current . '/' . $end;
2173 + break;
2174 + }
1498 2175 }
1499 2176
1500 2177 $status['message'] = $output;
1501 2178
@@ -1504,6 +2181,44 @@
1504 2181 }
1505 2182
1506 2183 echo json_encode($result) . "\n";
1507 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)]);
1508 2223 }
1509 2224 }