PluginProbe
Import WP – CSV & XML Import Export for WordPress / 2.15.0
Import WP – CSV & XML Import Export for WordPress v2.15.0
2.15.1 2.15.0 2.14.24 2.14.23 2.7.0 2.7.1 2.7.10 2.7.11 2.7.12 2.7.13 2.7.14 2.7.2 2.7.3 2.7.4 2.7.5 2.7.6 2.7.7 2.7.8 2.7.9 2.8.0 2.8.1 2.8.2 2.8.3 2.9.0 2.9.1 All 144 releases
← All changes | class/Common/Rest/RestManager.php +181 -12 2.14.242.15.0 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;
@@ -432,8 +433,11 @@
432 433
433 434 /**
434 435 * Decode map/enabled when sent as a single JSON string (avoids max_input_vars).
435 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 + *
436 440 * @param array $post_data
437 441 * @return array
438 442 */
439 443 protected function decode_packed_fields($post_data)
@@ -442,9 +446,16 @@
442 446 if (!isset($post_data[$field]) || !is_string($post_data[$field])) {
443 447 continue;
444 448 }
445 449
446 - $decoded = json_decode(wp_unslash($post_data[$field]), true);
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 +
447 458 if (json_last_error() === JSON_ERROR_NONE && is_array($decoded)) {
448 459 $post_data[$field] = $decoded;
449 460 }
450 461 }
@@ -626,8 +637,9 @@
626 637 if (isset($post_data['setup_type']) && in_array($post_data['setup_type'], ['upload', 'generate'])) {
627 638
628 639 $setup_type = $post_data['setup_type'] === 'upload' ? 'upload' : 'generate';
629 640 $file_type = null;
641 + $exporter_unique_identifier = '';
630 642
631 643 if ($setup_type === 'upload') {
632 644 $config = json_decode($post_data['exporter_config_file'], true);
633 645 $file_type = $config['data']['file_type'];
@@ -633,8 +645,9 @@
633 645 $file_type = $config['data']['file_type'];
634 646 $fields = $config['fields'];
635 647 $formatted_fields = $config['formatted_fields'];
636 648 $file_settings = $config['data']['file_settings'];
649 + $exporter_unique_identifier = isset($config['data']['unique_identifier']) ? $config['data']['unique_identifier'] : '';
637 650 } else {
638 651
639 652 /**
640 653 * @var \ImportWP\Common\Model\ExporterModel $exporter_data
@@ -644,11 +657,12 @@
644 657 $mapper = $this->exporter_manager->get_exporter_mapper($exporter_data);
645 658 $formatted_fields = $mapper->get_fields();
646 659 $file_type = $exporter_data->getFileType();
647 660 $file_settings = $exporter_data->getFileSettings();
661 + $exporter_unique_identifier = $exporter_data->getUniqueIdentifier();
648 662 }
649 663
650 - if (is_null($file_type) || !in_array($file_type, ['xml', 'csv'])) {
664 + if (is_null($file_type) || !in_array($file_type, ['xml', 'csv', 'json'])) {
651 665 return $this->http->end_rest_error('Invalid exporter file type');
652 666 }
653 667
654 668 $importer->setParser($file_type);
@@ -736,8 +750,71 @@
736 750 }
737 751
738 752 $headings = $tmp;
739 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;
740 817 }
741 818
742 819 if (!empty($headings)) {
743 820 $template = $this->importer_manager->get_importer_template($importer);
@@ -746,8 +823,24 @@
746 823
747 824 $post_data['map'] = $field_map['map'];
748 825 $post_data['enabled'] = $field_map['enabled'];
749 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 + }
750 843 }
751 844 }
752 845
753 846 if (isset($post_data['setting_start_row'])) {
@@ -812,9 +905,9 @@
812 905
813 906 if ($clear_config) {
814 907 $this->importer_manager->clear_config_files($importer->getId(), true);
815 908 }
816 - } elseif ($parser === 'xml') {
909 + } elseif ($parser === 'xml' || $parser === 'json') {
817 910 if (isset($post_data['file_settings_base_path'])) {
818 911 $importer->setFileSetting('base_path', $post_data['file_settings_base_path']);
819 912 }
820 913 }
@@ -840,8 +933,15 @@
840 933 }
841 934
842 935 if (isset($post_data['enabled']) && is_array($post_data['enabled'])) {
843 936 foreach ($post_data['enabled'] as $key => $value) {
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 +
844 944 if ($this->is_truthy($value)) {
845 945 $importer->setEnabled($key);
846 946 } else {
847 947 $importer->setEnabled($key, false);
@@ -1189,8 +1289,12 @@
1189 1289 if ('xml' === $parser) {
1190 1290
1191 1291 $nodes = $this->importer_manager->process_xml_file($id, true);
1192 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);
1193 1297 } elseif ('csv' === $parser) {
1194 1298
1195 1299 $delimiter = isset($post_data['delimiter']) ? $post_data['delimiter'] : null;
1196 1300 if (is_null($delimiter)) {
@@ -1247,11 +1351,21 @@
1247 1351 if (isset($post_data['file_encoding'])) {
1248 1352 $importer->setFileSetting('file_encoding', $post_data['file_encoding']);
1249 1353 }
1250 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 +
1251 1366 if ($importer->getParser() === 'xml') {
1252 1367
1253 - $config = $this->importer_manager->get_config($importer, true);
1254 1368 $file = $this->importer_manager->get_xml_file($importer, $config);
1255 1369
1256 1370 $base_path = $post_data['base_path'];
1257 1371 if (!is_null($base_path)) {
@@ -1257,18 +1371,55 @@
1257 1371 if (!is_null($base_path)) {
1258 1372 $file->setRecordPath($base_path);
1259 1373 }
1260 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 +
1261 1382 $preview = new XMLPreview($file, $base_path);
1262 - $result = $preview->data();
1383 + $result = $preview->data($record_index);
1263 1384 if (is_wp_error($result)) {
1264 1385 return $this->http->end_rest_error($result);
1265 1386 }
1266 1387
1267 - 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 + ]);
1268 1420 } elseif ($importer->getParser() === 'csv') {
1269 1421
1270 - $config = $this->importer_manager->get_config($importer, true);
1271 1422 $file = $this->importer_manager->get_csv_file($importer, $config);
1272 1423
1273 1424 $clear_config = false;
1274 1425 if (!is_null($post_data['delimiter']) && $post_data['delimiter'] !== $file->getDelimiter()) {
@@ -1281,8 +1432,9 @@
1281 1432
1282 1433 if ($clear_config) {
1283 1434 $this->importer_manager->clear_config_files($importer->getId(), true);
1284 1435 $config = $this->importer_manager->get_config($importer, true);
1436 + $config->set('preview_full_index', true);
1285 1437 $file = $this->importer_manager->get_csv_file($importer, $config);
1286 1438 }
1287 1439
1288 1440 $delimiter = $post_data['delimiter'];
@@ -1344,18 +1496,25 @@
1344 1496
1345 1497 $parser = $importer->getParser();
1346 1498
1347 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 + }
1348 1505 if (is_null($fields) || empty($fields)) {
1349 1506 $fields = $importer->getMap();
1350 1507 }
1351 1508
1352 1509 if ('xml' === $parser) {
1353 - $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);
1354 1513 } elseif ('csv' === $parser) {
1355 - $row = 0;
1514 + $row = $record_index;
1356 1515 if ($importer->getFileSetting('show_headings') === true) {
1357 - $row = 1;
1516 + $row = $record_index + 1;
1358 1517 }
1359 1518 $result = $this->importer_manager->preview_csv_file($importer, $fields, $row);
1360 1519 } else {
1361 1520
@@ -1502,9 +1661,19 @@
1502 1661 }
1503 1662 $importer_data = $this->importer_manager->get_importer($id);
1504 1663 $log = $this->importer_manager->get_importer_debug_log($importer_data, $page, 100);
1505 1664
1506 - $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 +
1507 1676 return $this->http->end_rest_success(['log' => $log, 'download' => $download]);
1508 1677 }
1509 1678
1510 1679 public function save_settings(\WP_REST_Request $request)
@@ -1686,9 +1855,9 @@
1686 1855 if (!isset($contents['name'], $contents['fields'], $contents['data'], $contents['data']['file_type'])) {
1687 1856 return $this->http->end_rest_error("Invalid exporter config file.");
1688 1857 }
1689 1858
1690 - if (!in_array($contents['data']['file_type'], ['xml', 'csv'])) {
1859 + if (!in_array($contents['data']['file_type'], ['xml', 'csv', 'json'])) {
1691 1860 return $this->http->end_rest_error("Exporter file type is not supported.");
1692 1861 }
1693 1862
1694 1863 return $this->http->end_rest_success(['exporter' => $contents]);