PluginProbe
Import WP – CSV & XML Import Export for WordPress / trunk
Import WP – CSV & XML Import Export for WordPress vtrunk
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 trunk 0.1.6 All 142 releases
jc-importer / class / Common / Rest / RestManager.php

RestManager.php in Import WP – CSV & XML Import Export for WordPress trunk, at class/Common/Rest/RestManager.php

2,056 lines 75.5 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2
3 namespace ImportWP\Common\Rest;
4
5 use ImportWP\Common\Exporter\ExporterManager;
6 use ImportWP\Common\Exporter\State\ExporterState;
7 use ImportWP\Common\Filesystem\Filesystem;
8 use ImportWP\Common\Filesystem\ZipArchive;
9 use ImportWP\Common\Http\Http;
10 use ImportWP\Common\Importer\ImporterManager;
11 use ImportWP\Common\Importer\Preview\CSVPreview;
12 use ImportWP\Common\Importer\Preview\XMLPreview;
13 use ImportWP\Common\Importer\State\ImporterState;
14 use ImportWP\Common\Importer\Template\Template;
15 use ImportWP\Common\Importer\Template\TemplateManager;
16 use ImportWP\Common\Migration\Migrations;
17 use ImportWP\Common\Model\ExporterModel;
18 use ImportWP\Common\Model\ImporterModel;
19 use ImportWP\Common\Properties\Properties;
20 use ImportWP\Common\Util\Logger;
21 use ImportWP\Common\Util\Util;
22 use ImportWP\Container;
23 use ImportWP\EventHandler;
24
25 class RestManager extends \WP_REST_Controller
26 {
27 /**
28 * @var Properties
29 */
30 protected $properties;
31
32 /**
33 * @var Http
34 */
35 protected $http;
36
37 /**
38 * @var Filesystem
39 */
40 protected $filesystem;
41
42 /**
43 * @var ImporterManager
44 */
45 protected $importer_manager;
46
47 /**
48 * @var ExporterManager
49 */
50 protected $exporter_manager;
51
52 /**
53 * @var TemplateManager $template_manager
54 */
55 protected $template_manager;
56
57 /**
58 * @var EventHandler
59 */
60 protected $event_handler;
61
62 public function __construct(ImporterManager $importer_manager, ExporterManager $exporter_manager, Properties $properties, Http $http, Filesystem $filesystem, TemplateManager $template_manager, $event_handler)
63 {
64 $this->importer_manager = $importer_manager;
65 $this->exporter_manager = $exporter_manager;
66 $this->properties = $properties;
67 $this->http = $http;
68 $this->filesystem = $filesystem;
69 $this->template_manager = $template_manager;
70 $this->event_handler = $event_handler;
71 }
72
73 public function register()
74 {
75 add_action('rest_api_init', array($this, 'register_routes'));
76 }
77
78 public function register_routes()
79 {
80 $namespace = $this->properties->rest_namespace . '/' . $this->properties->rest_version;
81
82 register_rest_route($namespace, '/system/check', array(
83 array(
84 'methods' => \WP_REST_Server::READABLE,
85 'callback' => array($this, 'check_rest_status'),
86 'permission_callback' => array($this, 'get_permission')
87 )
88 ));
89 register_rest_route($namespace, '/system/migrate', array(
90 array(
91 'methods' => \WP_REST_Server::READABLE,
92 'callback' => array($this, 'migrate'),
93 'permission_callback' => array($this, 'get_permission')
94 )
95 ));
96
97 register_rest_route($namespace, '/importer', array(
98 array(
99 'methods' => \WP_REST_Server::CREATABLE,
100 'callback' => array($this, 'save_importer'),
101 'permission_callback' => array($this, 'get_permission')
102 )
103 ));
104
105 register_rest_route($namespace, '/importers', array(
106 array(
107 'methods' => \WP_REST_Server::READABLE,
108 'callback' => array($this, 'get_importers'),
109 'permission_callback' => array($this, 'get_permission')
110 ),
111 ));
112
113 register_rest_route($namespace, '/status', array(
114 array(
115 'methods' => \WP_REST_Server::READABLE,
116 'callback' => array($this, 'get_status'),
117 'permission_callback' => array($this, 'get_permission')
118 ),
119 ));
120
121 register_rest_route($namespace, '/importer/(?P<id>\d+)', array(
122 array(
123 'methods' => \WP_REST_Server::READABLE,
124 'callback' => array($this, 'get_importer'),
125 'permission_callback' => array($this, 'get_permission')
126 ),
127 array(
128 'methods' => \WP_REST_Server::EDITABLE,
129 'callback' => array($this, 'save_importer'),
130 'permission_callback' => array($this, 'get_permission')
131 ),
132 array(
133 'methods' => \WP_REST_Server::DELETABLE,
134 'callback' => array($this, 'delete_importer'),
135 'permission_callback' => array($this, 'get_permission')
136 ),
137 ));
138
139 register_rest_route($namespace, '/importer/(?P<id>\d+)/upload', array(
140 array(
141 'methods' => \WP_REST_Server::EDITABLE,
142 'callback' => array($this, 'attach_file'),
143 'permission_callback' => array($this, 'get_permission')
144 )
145 ));
146
147 register_rest_route($namespace, '/importer/(?P<id>\d+)/file-process', array(
148 array(
149 'methods' => \WP_REST_Server::CREATABLE,
150 'callback' => array($this, 'get_file_process'),
151 'permission_callback' => array($this, 'get_permission')
152 )
153 ));
154
155 register_rest_route($namespace, '/importer/(?P<id>\d+)/file-preview', array(
156 array(
157 'methods' => \WP_REST_Server::CREATABLE,
158 'callback' => array($this, 'get_file_preview'),
159 'permission_callback' => array($this, 'get_permission')
160 )
161 ));
162
163 register_rest_route($namespace, '/importer/(?P<id>\d+)/preview', array(
164 array(
165 'methods' => \WP_REST_Server::CREATABLE,
166 'callback' => array($this, 'get_preview'),
167 'permission_callback' => array($this, 'get_permission')
168 )
169 ));
170
171 register_rest_route($namespace, '/importer/(?P<id>\d+)/init', array(
172 array(
173 'methods' => \WP_REST_Server::READABLE,
174 'callback' => array($this, 'init_import'),
175 'permission_callback' => array($this, 'get_permission')
176 )
177 ));
178
179 register_rest_route($namespace, '/importer/(?P<id>\d+)/run', array(
180 array(
181 'methods' => \WP_REST_Server::CREATABLE,
182 'callback' => array($this, 'run_import'),
183 'permission_callback' => array($this, 'get_permission')
184 )
185 ));
186
187 register_rest_route($namespace, '/importer/(?P<id>\d+)/pause', array(
188 array(
189 'methods' => \WP_REST_Server::CREATABLE,
190 'callback' => array($this, 'pause_import'),
191 'permission_callback' => array($this, 'get_permission')
192 )
193 ));
194
195 register_rest_route($namespace, '/importer/(?P<id>\d+)/stop', array(
196 array(
197 'methods' => \WP_REST_Server::CREATABLE,
198 'callback' => array($this, 'stop_import'),
199 'permission_callback' => array($this, 'get_permission')
200 )
201 ));
202
203 register_rest_route($namespace, '/importer/(?P<id>\d+)/field', array(
204 array(
205 'methods' => \WP_REST_Server::CREATABLE,
206 'callback' => array($this, 'get_template_field_options'),
207 'permission_callback' => array($this, 'get_permission')
208 )
209 ));
210
211 register_rest_route($namespace, '/importer/(?P<id>\d+)/logs/(?P<session>\S+)', array(
212 array(
213 'methods' => \WP_REST_Server::READABLE,
214 'callback' => array($this, 'get_log'),
215 'permission_callback' => array($this, 'get_permission')
216 )
217 ));
218
219 register_rest_route($namespace, '/importer/(?P<id>\d+)/logs', array(
220 array(
221 'methods' => \WP_REST_Server::READABLE,
222 'callback' => array($this, 'get_logs'),
223 'permission_callback' => array($this, 'get_permission')
224 )
225 ));
226
227 register_rest_route($namespace, '/settings', array(
228 array(
229 'methods' => \WP_REST_Server::CREATABLE,
230 'callback' => array($this, 'save_settings'),
231 'permission_callback' => array($this, 'get_permission')
232 ),
233 array(
234 'methods' => \WP_REST_Server::READABLE,
235 'callback' => array($this, 'get_settings'),
236 'permission_callback' => array($this, 'get_permission')
237 ),
238 ));
239
240 register_rest_route($namespace, '/compatibility', array(
241 array(
242 'methods' => \WP_REST_Server::CREATABLE,
243 'callback' => array($this, 'save_compatibility'),
244 'permission_callback' => array($this, 'get_permission')
245 ),
246 array(
247 'methods' => \WP_REST_Server::READABLE,
248 'callback' => array($this, 'get_compatibility'),
249 'permission_callback' => array($this, 'get_permission')
250 ),
251 ));
252
253 register_rest_route($namespace, '/import-export', array(
254 array(
255 'methods' => \WP_REST_Server::CREATABLE,
256 'callback' => array($this, 'import_exporters'),
257 'permission_callback' => array($this, 'get_permission')
258 ),
259 array(
260 'methods' => \WP_REST_Server::READABLE,
261 'callback' => array($this, 'export_importers'),
262 'permission_callback' => array($this, 'get_permission')
263 ),
264 ));
265
266 register_rest_route($namespace, '/importer/(?P<id>\d+)/debug_log', array(
267 array(
268 'methods' => \WP_REST_Server::READABLE,
269 'callback' => array($this, 'get_debug_log'),
270 'permission_callback' => array($this, 'get_permission')
271 )
272 ));
273
274 register_rest_route($namespace, '/importer/(?P<id>\d+)/template', array(
275 array(
276 'methods' => \WP_REST_Server::READABLE,
277 'callback' => array($this, 'get_template'),
278 'permission_callback' => array($this, 'get_permission')
279 )
280 ));
281
282 register_rest_route($namespace, '/importer/(?P<id>\d+)/template_unique_identifiers', array(
283 array(
284 'methods' => \WP_REST_Server::READABLE,
285 'callback' => array($this, 'get_importer_unique_identifier_options'),
286 'permission_callback' => array($this, 'get_permission')
287 )
288 ));
289
290 // Exporter
291
292 register_rest_route($namespace, '/exporter', array(
293 array(
294 'methods' => \WP_REST_Server::CREATABLE,
295 'callback' => array($this, 'save_exporter'),
296 'permission_callback' => array($this, 'get_permission')
297 )
298 ));
299
300 register_rest_route($namespace, '/exporters', array(
301 array(
302 'methods' => \WP_REST_Server::READABLE,
303 'callback' => array($this, 'get_exporters'),
304 'permission_callback' => array($this, 'get_permission')
305 ),
306 ));
307
308 register_rest_route($namespace, '/exporter/(?P<id>\d+)', array(
309 array(
310 'methods' => \WP_REST_Server::READABLE,
311 'callback' => array($this, 'get_exporter'),
312 'permission_callback' => array($this, 'get_permission')
313 ),
314 array(
315 'methods' => \WP_REST_Server::EDITABLE,
316 'callback' => array($this, 'save_exporter'),
317 'permission_callback' => array($this, 'get_permission')
318 ),
319 array(
320 'methods' => \WP_REST_Server::DELETABLE,
321 'callback' => array($this, 'delete_exporter'),
322 'permission_callback' => array($this, 'get_permission')
323 ),
324 ));
325
326 register_rest_route($namespace, '/exporter/(?P<id>\d+)/init', array(
327 array(
328 'methods' => \WP_REST_Server::READABLE,
329 'callback' => array($this, 'init_export'),
330 'permission_callback' => array($this, 'get_permission')
331 )
332 ));
333
334 register_rest_route($namespace, '/exporter/(?P<id>\d+)/download-config', array(
335 array(
336 'methods' => \WP_REST_Server::READABLE,
337 'callback' => array($this, 'download_exporter_config'),
338 'permission_callback' => array($this, 'get_permission')
339 )
340 ));
341
342 register_rest_route($namespace, '/importer/read-config', array(
343 array(
344 'methods' => \WP_REST_Server::CREATABLE,
345 'callback' => array($this, 'read_exporter_config'),
346 'permission_callback' => array($this, 'get_permission')
347 )
348 ));
349
350 register_rest_route($namespace, '/exporter/(?P<id>\d+)/run', array(
351 array(
352 'methods' => \WP_REST_Server::CREATABLE,
353 'callback' => array($this, 'run_exporter'),
354 'permission_callback' => array($this, 'get_permission')
355 ),
356 ));
357
358 register_rest_route($namespace, '/exporter/status', array(
359 array(
360 'methods' => \WP_REST_Server::READABLE,
361 'callback' => array($this, 'get_exporter_status'),
362 'permission_callback' => array($this, 'get_permission')
363 ),
364 ));
365 }
366
367 public function get_permission()
368 {
369
370 if (!current_user_can('manage_options')) {
371 return new \WP_Error('rest_forbidden', __('You do not have permissions.', 'jc-importer'), array('status' => 401));
372 }
373
374 return true;
375 }
376
377 public function sanitize($data, $name = null, $path = [])
378 {
379 $path[] = $name;
380
381 if (is_array($data)) {
382 foreach ($data as $k => $v) {
383 $data[$k] = $this->sanitize($v, $k, $path);
384 }
385
386 return $data;
387 }
388
389 // Preserve booleans/numbers from JSON request bodies
390 if (is_bool($data) || is_int($data) || is_float($data) || $data === null) {
391 return $data;
392 }
393
394 $full_path = implode('.', array_filter($path));
395 if (!is_null($full_path)) {
396
397 if (preg_match('/^map\.\S+/', $full_path) !== false) {
398 return $data;
399 }
400 }
401
402 if (!is_null($name)) {
403 switch ($name) {
404 case 'id':
405 return intval($data);
406 }
407 }
408
409 return sanitize_text_field($data);
410 }
411
412 /**
413 * Read request body from form-urlencoded or JSON.
414 *
415 * @param \WP_REST_Request $request
416 * @return array
417 */
418 protected function get_request_data(\WP_REST_Request $request)
419 {
420 $post_data = $request->get_body_params();
421 if (!is_array($post_data)) {
422 $post_data = [];
423 }
424
425 $json_params = $request->get_json_params();
426 if (is_array($json_params) && !empty($json_params)) {
427 $post_data = array_replace_recursive($post_data, $json_params);
428 }
429
430 return $this->decode_packed_fields($post_data);
431 }
432
433 /**
434 * Decode map/enabled when sent as a single JSON string (avoids max_input_vars).
435 *
436 * @param array $post_data
437 * @return array
438 */
439 protected function decode_packed_fields($post_data)
440 {
441 foreach (['map', 'enabled'] as $field) {
442 if (!isset($post_data[$field]) || !is_string($post_data[$field])) {
443 continue;
444 }
445
446 $decoded = json_decode(wp_unslash($post_data[$field]), true);
447 if (json_last_error() === JSON_ERROR_NONE && is_array($decoded)) {
448 $post_data[$field] = $decoded;
449 }
450 }
451
452 return $post_data;
453 }
454
455 /**
456 * @param mixed $value
457 * @return bool
458 */
459 protected function is_truthy($value)
460 {
461 return $value === true || $value === 1 || $value === '1' || $value === 'true';
462 }
463
464 public function check_rest_status(\WP_REST_Request $request)
465 {
466 $result = [];
467 // find required php ext: https://github.com/llaville/php-compat-info
468
469 $tmp_writable = wp_is_writable($this->filesystem->get_temp_directory());
470
471
472 $result = [
473 'tmp_writable' => [
474 'status' => $tmp_writable ? 'yes' : 'no',
475 '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()),
476 ],
477 'rest_enabled' => [
478 'status' => 'yes',
479 'message' => '',
480 ],
481 'ext_simplexml' => [
482 'status' => extension_loaded('SimpleXML') ? 'yes' : 'no',
483 'message' => '',
484 ],
485 'ext_mbstring' => [
486 'status' => extension_loaded('mbstring') ? 'yes' : 'no',
487 'message' => '',
488 ],
489 'ext_xmlreader' => [
490 'status' => extension_loaded('xmlreader') ? 'yes' : 'no',
491 'message' => '',
492 ],
493 'php_version' => [
494 'status' => version_compare(phpversion(), '5.5.0') ? 'yes' : 'no',
495 'message' => '',
496 ],
497 'zip_archive' => [
498 'status' => ZipArchive::has_requirements_met() ? 'yes' : 'no'
499 ]
500 ];
501 return $this->http->end_rest_success($result);
502 }
503
504 public function migrate(\WP_REST_Request $request)
505 {
506 $migration = new Migrations();
507 $result = $migration->migrate();
508
509 return $this->http->end_rest_success(__("Migration Complete.", 'jc-importer'));
510 }
511
512 public function get_importers(\WP_REST_Request $request)
513 {
514 $importers = $this->importer_manager->get_importers();
515 $result = [];
516
517 foreach ($importers as $importer) {
518 $result[] = $importer->data();
519 }
520
521 return $this->http->end_rest_success($result);
522 }
523
524 public function get_importer(\WP_REST_Request $request)
525 {
526 Logger::setRequestType('get_importer');
527
528 $id = intval($request->get_param('id'));
529 $importer_data = $this->importer_manager->get_importer($id);
530 $response = $importer_data->data('public');
531 return $this->http->end_rest_success($response);
532 }
533
534 public function save_importer(\WP_REST_Request $request)
535 {
536 Logger::setRequestType('save_importer');
537
538 $post_data = $this->get_request_data($request);
539 $id = isset($post_data['id']) ? intval($post_data['id']) : null;
540
541 $temp_post_data = [
542 'create' => [],
543 'update' => []
544 ];
545
546 if (isset($post_data['permissions'])) {
547 // TODO: update sanitize method to keep track of hierarchy
548 foreach (['create', 'update'] as $permission_method) {
549 if (!isset($post_data['permissions'][$permission_method]['fields'])) {
550 continue;
551 }
552 $permission_fields = $post_data['permissions'][$permission_method]['fields'];
553 if (is_array($permission_fields)) {
554 $temp_post_data[$permission_method] = $permission_fields;
555 } else {
556 $temp_post_data[$permission_method] = explode("\n", (string) $permission_fields);
557 }
558 }
559 }
560
561 $post_data = $this->sanitize($post_data);
562
563 if (isset($post_data['permissions'])) {
564 // TODO: Method to bypass sanitization not needed once sanitize method has been upgraded
565 foreach (['create', 'update'] as $permission_method) {
566 $permission_fields = $temp_post_data[$permission_method];
567 if (!empty($permission_fields)) {
568 $post_data['permissions'][$permission_method]['fields'] = array_values(array_filter(array_map([$this, 'sanitize'], $permission_fields, array_fill(0, count($permission_fields), 'permission_' . $permission_method . '_fields'))));
569 } else {
570 $post_data['permissions'][$permission_method]['fields'] = [];
571 }
572 }
573 }
574
575 $importer = new ImporterModel($id, $this->importer_manager->is_debug());
576 if (isset($post_data['datasource'])) {
577 $importer->setDatasource($post_data['datasource']);
578 }
579
580 if (isset($post_data['remote_url'])) {
581 $importer->setDatasourceSetting('remote_url', $post_data['remote_url']);
582 }
583
584 if (isset($post_data['local_url'])) {
585 $importer->setDatasourceSetting('local_url', wp_normalize_path($post_data['local_url']));
586 }
587
588 if (isset($post_data['existing_id'])) {
589 $importer->setFileId(intval($post_data['existing_id']));
590 }
591
592 if (isset($post_data['template'], $post_data['template_type'])) {
593 $importer->setTemplate($post_data['template'], $post_data['template_type']);
594
595 // Get default template options
596 $template_class = $this->importer_manager->get_template($post_data['template']);
597
598 /**
599 * @var Template $template
600 */
601 $template = $this->template_manager->load_template($template_class);
602 $template_options = $template->get_default_template_options();
603
604 if (isset($post_data['template_options'])) {
605 $template_options = array_merge($template_options, $post_data['template_options']);
606 }
607
608 // setup default enabled fields
609 $default_enabled_fields = $template->get_default_enabled_fields();
610 foreach ($default_enabled_fields as $default_enabled_field) {
611 $importer->setEnabled($default_enabled_field);
612 }
613
614 foreach ($template_options as $key => $val) {
615 $importer->setSetting($key, $val);
616 }
617 }
618
619 if (isset($post_data['name'])) {
620 $importer->setName($post_data['name']);
621 }
622
623 if (is_null($id)) {
624
625 // NOTE: New importer, should we generate field map from exporter.
626 if (isset($post_data['setup_type']) && in_array($post_data['setup_type'], ['upload', 'generate'])) {
627
628 $setup_type = $post_data['setup_type'] === 'upload' ? 'upload' : 'generate';
629 $file_type = null;
630
631 if ($setup_type === 'upload') {
632 $config = json_decode($post_data['exporter_config_file'], true);
633 $file_type = $config['data']['file_type'];
634 $fields = $config['fields'];
635 $formatted_fields = $config['formatted_fields'];
636 $file_settings = $config['data']['file_settings'];
637 } else {
638
639 /**
640 * @var \ImportWP\Common\Model\ExporterModel $exporter_data
641 */
642 $exporter_data = $this->exporter_manager->get_exporter($post_data['exporter']);
643 $fields = $this->exporter_manager->get_importer_map_fields($exporter_data);
644 $mapper = $this->exporter_manager->get_exporter_mapper($exporter_data);
645 $formatted_fields = $mapper->get_fields();
646 $file_type = $exporter_data->getFileType();
647 $file_settings = $exporter_data->getFileSettings();
648 }
649
650 if (is_null($file_type) || !in_array($file_type, ['xml', 'csv'])) {
651 return $this->http->end_rest_error('Invalid exporter file type');
652 }
653
654 $importer->setParser($file_type);
655
656 $headings = [];
657 switch ($file_type) {
658 case 'csv':
659 $headings = array_reduce($fields, function ($carry, $item) {
660 $carry[] = $item['selection'];
661 return $carry;
662 }, []);
663 $headings = array_map('trim', $headings);
664
665 if (isset($file_settings['delimiter']) && !empty($file_settings['delimiter']) && strlen($file_settings['delimiter']) === 1) {
666 $importer->setFileSetting('delimiter', $file_settings['delimiter']);
667 }
668 if (isset($file_settings['enclosure']) && !empty($file_settings['enclosure']) && strlen($file_settings['enclosure']) === 1) {
669 $importer->setFileSetting('enclosure', $file_settings['enclosure']);
670 }
671 if (isset($file_settings['escape']) && !empty($file_settings['escape']) && strlen($file_settings['escape']) === 1) {
672 $importer->setFileSetting('escape', $file_settings['escape']);
673 }
674
675 break;
676 case 'xml':
677
678 $main = false;
679 foreach ($fields as $field) {
680 if ($field['selection'] === 'main' && ($field['loop'] === true || $field['loop'] === "true")) {
681 $main = $field;
682 break;
683 }
684 }
685
686 if (!$main) {
687 return $this->http->end_rest_error("XML exporter is missing main loop.");
688 }
689
690 // generate base_path
691 $post_data['file_settings_base_path'] = '/' . implode('/', array_reverse(array_filter($this->generate_base_path($main, $fields))));
692
693 $allowed = [$main['id']];
694
695 $current_section = null;
696 $current_section_ancestors = [];
697 $current_section_map = [];
698
699 foreach ($fields as $field) {
700
701 if (in_array($field['parent'], $allowed)) {
702
703 $field_map = '/' . implode('/', array_reverse(array_filter($this->generate_base_path($field, $fields, [], $main['id']))));
704 $headings[$field_map] = $field['selection'];
705
706 if ($field['loop'] === true || $field['loop'] === 'true') {
707
708 // we need to convert all sub fields to tax_category.id ....
709 $current_section = $field['selection'];
710 $current_section_ancestors = [$field['id']];
711 } else {
712
713 if (!is_null($current_section) && in_array($field['parent'], $current_section_ancestors)) {
714
715 $current_section_ancestors[] = $field['id'];
716 $current_section_map[$field_map] = $current_section . '.' . $field['selection'];
717 } else {
718 $headings = $this->complete_section_map($headings, $current_section_map, $current_section, $formatted_fields);
719 $current_section = null;
720 $current_section_ancestors = [];
721 $current_section_map = [];
722 }
723 }
724
725 if (!in_array($field['id'], $allowed)) {
726 $allowed[] = $field['id'];
727 }
728 }
729 }
730
731 $headings = $this->complete_section_map($headings, $current_section_map, $current_section, $formatted_fields);
732
733 $tmp = [];
734 foreach ($headings as $map => $heading) {
735 $tmp[$map] = $heading;
736 }
737
738 $headings = $tmp;
739 break;
740 }
741
742 if (!empty($headings)) {
743 $template = $this->importer_manager->get_importer_template($importer);
744 $field_map = $template->generate_field_map($headings, $importer);
745 $field_map = apply_filters('iwp/importer/generate_field_map', $field_map, $headings, $importer);
746
747 $post_data['map'] = $field_map['map'];
748 $post_data['enabled'] = $field_map['enabled'];
749 }
750 }
751 }
752
753 if (isset($post_data['setting_start_row'])) {
754 $importer->setStartRow($post_data['setting_start_row']);
755 }
756
757 if (isset($post_data['setting_max_row'])) {
758 $importer->setMaxRow($post_data['setting_max_row']);
759 }
760
761 // save settings
762 foreach ($post_data as $key => $value) {
763 if (1 === preg_match('/^setting_(?<key>.*)/', $key, $matches)) {
764
765 $value = $this->sanitize_setting($value);
766 $importer->setSetting($matches['key'], $value);
767 }
768 }
769
770 if (isset($post_data['setting_import_method']) && $post_data['setting_import_method'] === 'background') {
771 delete_post_meta($importer->getId(), '_iwp_cron_scheduled');
772 delete_post_meta($importer->getId(), '_iwp_cron_last_ran');
773 }
774
775 $parser = $importer->getParser();
776
777 if (isset($post_data['file_settings_encoding'])) {
778 $importer->setFileSetting('file_encoding', $post_data['file_settings_encoding']);
779 }
780
781 if ($parser === 'csv') {
782
783 $clear_config = false;
784 if (isset($post_data['file_settings_delimiter'])) {
785
786 $old_delimiter = $importer->getFileSetting('delimiter');
787 if ($old_delimiter !== $post_data['file_settings_delimiter']) {
788 $clear_config = true;
789 }
790 $importer->setFileSetting('delimiter', $post_data['file_settings_delimiter']);
791 }
792 if (isset($post_data['file_settings_enclosure'])) {
793 $old_enclosure = $importer->getFileSetting('enclosure');
794 if ($old_enclosure !== $post_data['file_settings_enclosure']) {
795 $clear_config = true;
796 }
797 $importer->setFileSetting('enclosure', $post_data['file_settings_enclosure']);
798 }
799
800 if (isset($post_data['file_settings_escape'])) {
801 $old_escape = $importer->getFileSetting('escape');
802 if ($old_escape !== $post_data['file_settings_escape']) {
803 $clear_config = true;
804 }
805 $importer->setFileSetting('escape', $post_data['file_settings_escape']);
806 }
807
808
809 if (isset($post_data['file_settings_show_headings'])) {
810 $importer->setFileSetting('show_headings', $this->is_truthy($post_data['file_settings_show_headings']));
811 }
812
813 if ($clear_config) {
814 $this->importer_manager->clear_config_files($importer->getId(), true);
815 }
816 } elseif ($parser === 'xml') {
817 if (isset($post_data['file_settings_base_path'])) {
818 $importer->setFileSetting('base_path', $post_data['file_settings_base_path']);
819 }
820 }
821
822 if (isset($post_data['file_settings_setup'])) {
823 $importer->setFileSetting('setup', $this->is_truthy($post_data['file_settings_setup']));
824 }
825
826 if (isset($post_data['map']) && is_array($post_data['map'])) {
827 $map = [];
828 foreach ($post_data['map'] as $key => $value) {
829 if ($value === null) {
830 continue;
831 }
832
833 if (1 === preg_match('/\._index$/', $key)) {
834 $value = intval($value);
835 }
836
837 $map[$key] = $value;
838 }
839 $importer->replaceMap($map);
840 }
841
842 if (isset($post_data['enabled']) && is_array($post_data['enabled'])) {
843 foreach ($post_data['enabled'] as $key => $value) {
844 if ($this->is_truthy($value)) {
845 $importer->setEnabled($key);
846 } else {
847 $importer->setEnabled($key, false);
848 }
849 }
850 }
851
852 if (isset($post_data['permissions']) && is_array($post_data['permissions'])) {
853
854 // create
855 if (isset($post_data['permissions']['create'])) {
856 $create = $post_data['permissions']['create'];
857 $importer->setPermission('create', [
858 'enabled' => isset($create['enabled']) && $this->is_truthy($create['enabled']) ? true : false,
859 'type' => isset($create['type']) && in_array($create['type'], ['include', 'exclude'], true) ? $create['type'] : null,
860 'fields' => isset($create['fields']) ? $create['fields'] : [],
861 ]);
862 }
863
864 // update
865 if (isset($post_data['permissions']['update'])) {
866 $update = $post_data['permissions']['update'];
867 $importer->setPermission('update', [
868 'enabled' => isset($update['enabled']) && $this->is_truthy($update['enabled']) ? true : false,
869 'type' => isset($update['type']) && in_array($update['type'], ['include', 'exclude'], true) ? $update['type'] : null,
870 'fields' => isset($update['fields']) ? $update['fields'] : [],
871 ]);
872 }
873
874 // remove
875 if (isset($post_data['permissions']['remove'])) {
876 $importer->setPermission('remove', [
877 'enabled' => $this->is_truthy($post_data['permissions']['remove']['enabled']),
878 'trash' => $this->is_truthy($post_data['permissions']['remove']['trash']),
879 'media' => $this->is_truthy($post_data['permissions']['remove']['media']),
880 ]);
881 }
882 }
883
884 $result = $importer->save();
885 if (is_wp_error($result)) {
886 return $this->http->end_rest_error($result->get_error_message());
887 }
888 return $this->http->end_rest_success($importer->data());
889 }
890
891 public function generate_base_path($field, $fields, $output = [], $stop = 0)
892 {
893 $output[] = isset($field['label']) && !empty($field['label']) ? $field['label'] : $field['selection'];
894
895 if ($field['parent'] !== $stop) {
896
897 foreach ($fields as $sub_field) {
898 if ($sub_field['id'] == $field['parent']) {
899
900 return $this->generate_base_path($sub_field, $fields, $output, $stop);
901 }
902 }
903 }
904 return $output;
905 }
906
907 public function complete_section_map($headings, $current_section_map, $current_section, $fields)
908 {
909 if (is_null($current_section)) {
910 return $headings;
911 }
912
913 if ($current_section === 'custom_fields') {
914 // $headings['/test/@value'] = 'custom_fields.{/test/@key}';
915
916 $meta_key = false;
917 $meta_val = false;
918
919 if (count($current_section_map) == 2) {
920 foreach ($current_section_map as $row_map => $row_value) {
921 if ($row_value == 'custom_fields.meta_key') {
922 $meta_key = $row_map;
923 } elseif ($row_value == 'custom_fields.meta_value') {
924 $meta_val = $row_map;
925 }
926 }
927
928 if ($meta_key && $meta_val) {
929
930 // Get full list of custom fields, that can be then be used to generate a full list
931 // /custom_fields_wrapper/custom_fields[meta_key="_edit_lock"]/meta_key
932 // /custom_fields_wrapper/custom_fields[meta_key="_edit_lock"]/meta_value
933
934 // Compare the two strings and get the matching parts
935 $meta_key_parts = array_values(array_filter(explode('/', $meta_key)));
936 $meta_val_parts = array_values(array_filter(explode('/', $meta_val)));
937
938 for ($i = 0; $i < count($meta_key_parts); $i++) {
939 if ($meta_key_parts[$i] !== $meta_val_parts[$i]) {
940 break;
941 }
942 }
943
944 $start = array_slice($meta_key_parts, 0, $i);
945 $end_key = array_slice($meta_key_parts, $i);
946 $end_val = array_slice($meta_val_parts, $i);
947
948 $start_path = implode('/', $start);
949 $end_key_path = implode('/', $end_key);
950 $end_val_path = implode('/', $end_val);
951
952 $custom_field_key_list = $fields['children']['custom_fields']['fields'];
953 foreach ($custom_field_key_list as $custom_field_key) {
954
955 // $tmp_field_key = '{/' . $start_path . sprintf('[%s="%s"]', $end_key_path, $custom_field_key) . '/' . $end_key_path . '}';
956 $tmp_field_val = '/' . $start_path . sprintf('[%s="%s"]', $end_key_path, $custom_field_key) . '/' . $end_val_path;
957
958 $headings[$tmp_field_val] = sprintf('custom_fields.%s', $custom_field_key);
959 }
960
961 return $headings;
962 }
963 }
964
965 return array_merge($headings, $current_section_map);
966 }
967
968 return array_merge($headings, $current_section_map);
969 }
970
971 private function sanitize_setting($value)
972 {
973 if (is_array($value)) {
974 foreach ($value as &$tmp) {
975 $tmp = $this->sanitize_setting($tmp);
976 }
977 return $value;
978 } else {
979 if (in_array($value, ['true', 'false'], true)) {
980 if ($value === 'true') {
981 $value = true;
982 } else {
983 $value = false;
984 }
985 }
986 }
987
988 return $value;
989 }
990
991 public function get_status(\WP_REST_Request $request)
992 {
993 $this->http->set_stream_headers();
994
995 Logger::disable();
996
997 $importer_ids = $request->get_param('ids');
998
999 $result = [];
1000
1001 $query_data = array(
1002 'post_type' => IWP_POST_TYPE,
1003 'posts_per_page' => -1,
1004 'fields' => 'ids'
1005 );
1006 if (!empty($importer_ids)) {
1007 $query_data['post__in'] = $importer_ids;
1008 }
1009
1010 $query = new \WP_Query($query_data);
1011
1012 foreach ($query->posts as $importer_id) {
1013
1014 $importer_model = $this->importer_manager->get_importer($importer_id);
1015 $config = $this->importer_manager->get_config($importer_model);
1016
1017 $output = ImporterState::get_state($importer_id);
1018 if (!$output) {
1019 $output = [
1020 'version' => 2,
1021 'message' => 'Not setup',
1022 'importer' => $importer_id
1023 ];
1024 } else {
1025
1026 $output['version'] = 2;
1027 $output['message'] = $this->generate_status_message($output);
1028 $output['importer'] = $importer_id;
1029 $output['process'] = intval($config->get('process'));
1030 }
1031
1032 $result[] = $this->event_handler->run('iwp/importer/status/output', [$output, $importer_model]);
1033 }
1034
1035 echo json_encode($result) . "\n";
1036 die();
1037 }
1038
1039 /**
1040 * @param array $data
1041 * @return string
1042 */
1043 protected function generate_status_message($data)
1044 {
1045 $output = '';
1046
1047 if (!$data || !isset($data['status']) || is_null($data['status']) || empty($data['status'])) {
1048 return '';
1049 }
1050
1051 if (isset($data['status'])) {
1052 switch ($data['status']) {
1053 case 'cancelled':
1054 $output .= __('Import cancelled', 'jc-importer');
1055 break;
1056 case 'complete':
1057 $output .= __('Import complete', 'jc-importer');
1058 break;
1059 case 'error':
1060 $output = $data['message'];
1061 return $output;
1062 break;
1063 }
1064 }
1065
1066 if (isset($data['status']) && ($data['status'] === 'running' || $data['status'] === 'processing' || $data['status'] === 'timeout') && isset($data['section'])) {
1067 switch ($data['section']) {
1068 case 'import':
1069 case 'delete':
1070 $progress = $data['progress'][$data['section']]['current_row'] . ' / ' . ($data['progress'][$data['section']]['end'] - $data['progress'][$data['section']]['start']);
1071 $output = sprintf($data['section'] === 'import' ? __('Importing: %s', 'jc-importer') : __('Deleting: %s', 'jc-importer'), $progress);
1072 break;
1073 }
1074 }
1075
1076 if (isset($data['stats'])) {
1077 if ($data['stats']['inserts'] > 0) {
1078 $output .= sprintf(__(', Inserts: %d', 'jc-importer'), $data['stats']['inserts']);
1079 }
1080 if ($data['stats']['updates'] > 0) {
1081 $output .= sprintf(__(', Updates: %d', 'jc-importer'), $data['stats']['updates']);
1082 }
1083 if ($data['stats']['deletes'] > 0) {
1084 $output .= sprintf(__(', Deletes: %d', 'jc-importer'), $data['stats']['deletes']);
1085 }
1086 if ($data['stats']['skips'] > 0) {
1087 $output .= sprintf(__(', Skips: %d', 'jc-importer'), $data['stats']['skips']);
1088 }
1089 if ($data['stats']['errors'] > 0) {
1090 $output .= sprintf(__(', Errors: %d', 'jc-importer'), $data['stats']['errors']);
1091 }
1092 }
1093
1094 if (isset($data['timestamp'], $data['updated'])) {
1095 /**
1096 * @var Util $util
1097 */
1098 $util = Container::getInstance()->get('util');
1099 $output .= sprintf(__(', Run Time: %s', 'jc-importer'), $util->format_time($data['updated'] - $data['timestamp']));
1100 } elseif (isset($data['duration'])) {
1101 /**
1102 * @var Util $util
1103 */
1104 $util = Container::getInstance()->get('util');
1105 $output .= sprintf(__(', Run Time: %s', 'jc-importer'), $util->format_time($data['duration']));
1106 }
1107
1108 return $output;
1109 }
1110
1111 public function delete_importer(\WP_REST_Request $request)
1112 {
1113 Logger::setRequestType('delete_importer');
1114
1115 $id = intval($request->get_param('id'));
1116 $this->importer_manager->delete_importer($id);
1117 return $this->http->end_rest_success(true);
1118 }
1119
1120 public function attach_file(\WP_REST_Request $request)
1121 {
1122 $id = intval($request->get_param('id'));
1123 $post_data = $this->sanitize($request->get_body_params());
1124
1125 $action = $post_data['action'];
1126
1127 $importer = $this->importer_manager->get_importer($id);
1128
1129 // TODO: Remove duplicate code, found in cron manager
1130
1131 add_filter('upload_dir', [$this->importer_manager, 'set_custom_upload_path']);
1132
1133 switch ($action) {
1134 case 'file_upload':
1135 $attachment_id = $this->importer_manager->upload_file($importer, $_FILES['file']);
1136 break;
1137 case 'file_remote':
1138 $raw_source = $post_data['remote_url'];
1139 $source = apply_filters('iwp/importer/datasource', $raw_source, $raw_source, $importer);
1140 $source = apply_filters('iwp/importer/datasource/remote', $source, $raw_source, $importer);
1141
1142 $filetype = $importer->getParser();
1143 if (is_null($filetype)) {
1144 $filetype = $post_data['filetype'];
1145 }
1146
1147 $attachment_id = $this->importer_manager->remote_file($importer, $source, $filetype, null, $importer->getId() . '-');
1148 break;
1149 case 'file_local':
1150 $raw_source = $post_data['local_url'];
1151 $source = apply_filters('iwp/importer/datasource', $raw_source, $raw_source, $importer);
1152 $source = apply_filters('iwp/importer/datasource/local', $source, $raw_source, $importer);
1153
1154 $filetype = $importer->getParser();
1155 if (is_null($filetype)) {
1156 $filetype = $post_data['filetype'];
1157 }
1158
1159 $attachment_id = $this->importer_manager->local_file($importer, $source, $filetype);
1160 break;
1161 default:
1162 $attachment_id = new \WP_Error('IWP_RM_1', __('Invalid form action', 'jc-importer'));
1163 break;
1164 }
1165
1166 remove_filter('upload_dir', [$this->importer_manager, 'set_custom_upload_path']);
1167
1168 if (is_wp_error($attachment_id)) {
1169 return $this->http->end_rest_error($attachment_id->get_error_message());
1170 }
1171
1172 return $this->http->end_rest_success($importer->data());
1173 }
1174
1175 public function get_file_process(\WP_REST_Request $request)
1176 {
1177 try {
1178 $id = intval($request->get_param('id'));
1179 $post_data = $this->sanitize($request->get_body_params());
1180
1181 $importer = $this->importer_manager->get_importer($id);
1182 if (!$importer) {
1183 return $this->http->end_rest_error(__('Invalid importer', 'jc-importer'));
1184 }
1185
1186 $this->importer_manager->clear_config_files($id, true);
1187 $parser = $importer->getParser();
1188
1189 if ('xml' === $parser) {
1190
1191 $nodes = $this->importer_manager->process_xml_file($id, true);
1192 $importer->setFileSetting('nodes', $nodes);
1193 } elseif ('csv' === $parser) {
1194
1195 $delimiter = isset($post_data['delimiter']) ? $post_data['delimiter'] : null;
1196 if (is_null($delimiter)) {
1197 $delimiter = $importer->getFileSetting('delimiter', ',');
1198 }
1199
1200 $enclosure = isset($post_data['enclosure']) ? $post_data['enclosure'] : null;
1201 if (is_null($enclosure)) {
1202 $enclosure = $importer->getFileSetting('enclosure', '"');
1203 }
1204
1205 $escape = isset($post_data['escape']) ? $post_data['escape'] : null;
1206 if (is_null($escape)) {
1207 $escape = $importer->getFileSetting('escape', '\\');
1208 }
1209
1210 $count = $this->importer_manager->process_csv_file($id, $delimiter, $enclosure, true);
1211
1212 // Need fresh importer, due to process_csv_file importer modifications might get overwritten.
1213 $importer = $this->importer_manager->get_importer($id);
1214 $importer->setFileSetting('count', $count);
1215 } else {
1216
1217 do_action('iwp/file-process/' . $parser, $importer, $post_data);
1218 }
1219
1220 $importer->setFileSetting('processed', true);
1221 $importer->save();
1222
1223 return $this->http->end_rest_success($importer->data());
1224 } catch (\Exception $e) {
1225 return $this->http->end_rest_error($e->getMessage());
1226 }
1227 }
1228
1229 public function get_file_preview(\WP_REST_Request $request)
1230 {
1231 try {
1232 $id = intval($request->get_param('id'));
1233 $post_data = $this->sanitize($request->get_body_params());
1234 $importer = $this->importer_manager->get_importer($id);
1235
1236 $import_file_exists = $this->filesystem->file_exists($importer->getFile());
1237 if (is_wp_error($import_file_exists)) {
1238 throw new \Exception(sprintf(__("Unable to load preview, %s", 'jc-importer'), $import_file_exists->get_error_message()));
1239 }
1240
1241 $record_index = isset($post_data['record']) ? $post_data['record'] : null;
1242 if (is_null($record_index)) {
1243 $record_index = 0;
1244 }
1245
1246 // Temp set file_encoding
1247 if (isset($post_data['file_encoding'])) {
1248 $importer->setFileSetting('file_encoding', $post_data['file_encoding']);
1249 }
1250
1251 if ($importer->getParser() === 'xml') {
1252
1253 $config = $this->importer_manager->get_config($importer, true);
1254 $file = $this->importer_manager->get_xml_file($importer, $config);
1255
1256 $base_path = $post_data['base_path'];
1257 if (!is_null($base_path)) {
1258 $file->setRecordPath($base_path);
1259 }
1260
1261 $preview = new XMLPreview($file, $base_path);
1262 $result = $preview->data();
1263 if (is_wp_error($result)) {
1264 return $this->http->end_rest_error($result);
1265 }
1266
1267 return $this->http->end_rest_success($result[0]);
1268 } elseif ($importer->getParser() === 'csv') {
1269
1270 $config = $this->importer_manager->get_config($importer, true);
1271 $file = $this->importer_manager->get_csv_file($importer, $config);
1272
1273 $clear_config = false;
1274 if (!is_null($post_data['delimiter']) && $post_data['delimiter'] !== $file->getDelimiter()) {
1275 $clear_config = true;
1276 } elseif (!is_null($post_data['enclosure']) && $post_data['enclosure'] !== $file->getEnclosure()) {
1277 $clear_config = true;
1278 } elseif (!is_null($post_data['escape']) && $post_data['escape'] !== $file->getEscape()) {
1279 $clear_config = true;
1280 }
1281
1282 if ($clear_config) {
1283 $this->importer_manager->clear_config_files($importer->getId(), true);
1284 $config = $this->importer_manager->get_config($importer, true);
1285 $file = $this->importer_manager->get_csv_file($importer, $config);
1286 }
1287
1288 $delimiter = $post_data['delimiter'];
1289 if (!is_null($delimiter)) {
1290 $file->setDelimiter($delimiter);
1291 }
1292
1293 $enclosure = $post_data['enclosure'];
1294 if (!is_null($enclosure)) {
1295 $file->setEnclosure($enclosure);
1296 }
1297
1298 $escape = $post_data['escape'];
1299 if (!is_null($escape)) {
1300 $file->setEscape($escape);
1301 }
1302
1303 // parse bool param from string
1304 $show_headings = $post_data['show_headings'];
1305 if (in_array($show_headings, ['true', 'false'])) {
1306 $show_headings = $show_headings === 'true' ? true : false;
1307 }
1308
1309 if (is_null($show_headings)) {
1310 $show_headings = $importer->getFileSetting('show_headings');
1311 }
1312
1313 $preview = new CSVPreview($file);
1314 $result = $preview->data($record_index, $show_headings);
1315 if (is_wp_error($result)) {
1316 return $this->http->end_rest_error($result);
1317 }
1318
1319 return $this->http->end_rest_success($result);
1320 } else {
1321
1322 $result = apply_filters('iwp/file-preview/' . $importer->getParser(), null, $importer);
1323 if (is_wp_error($result)) {
1324 return $this->http->end_rest_error($result);
1325 }
1326
1327 return $this->http->end_rest_success($result);
1328 }
1329 } catch (\Exception $error) {
1330 return $this->http->end_rest_error($error->getMessage());
1331 }
1332 }
1333
1334 public function get_preview(\WP_REST_Request $request)
1335 {
1336 // if no fields have been posted send all previews
1337 try {
1338 $id = intval($request->get_param('id'));
1339 $importer = $this->importer_manager->get_importer($id);
1340 $import_file_exists = $this->filesystem->file_exists($importer->getFile());
1341 if (is_wp_error($import_file_exists)) {
1342 throw new \Exception(sprintf(__("Unable to load preview, %s", 'jc-importer'), $import_file_exists->get_error_message()));
1343 }
1344
1345 $parser = $importer->getParser();
1346
1347 $fields = $this->sanitize($request->get_body_params());
1348 if (is_null($fields) || empty($fields)) {
1349 $fields = $importer->getMap();
1350 }
1351
1352 if ('xml' === $parser) {
1353 $result = $this->importer_manager->preview_xml_file($importer, $fields);
1354 } elseif ('csv' === $parser) {
1355 $row = 0;
1356 if ($importer->getFileSetting('show_headings') === true) {
1357 $row = 1;
1358 }
1359 $result = $this->importer_manager->preview_csv_file($importer, $fields, $row);
1360 } else {
1361
1362 $result = apply_filters('iwp/record-preview/' . $parser, [], $importer, $fields);
1363 if (is_wp_error($result)) {
1364 return $this->http->end_rest_error($result);
1365 }
1366
1367 return $this->http->end_rest_success($result);
1368 }
1369
1370 return $this->http->end_rest_success($result);
1371 } catch (\Exception $error) {
1372 return $this->http->end_rest_error($error->getMessage());
1373 }
1374 }
1375
1376 /**
1377 * Trigger a new import.
1378 *
1379 * @param \WP_REST_Request $request
1380 * @return array
1381 */
1382 public function init_import(\WP_REST_Request $request)
1383 {
1384 $id = intval($request->get_param('id'));
1385
1386 Logger::setRequestType('rest::init_import');
1387
1388 Logger::write('init_import -start', $id);
1389
1390 $importer_data = $this->importer_manager->get_importer($id);
1391
1392 // clear existing
1393 ImporterState::clear_options($importer_data->getId());
1394
1395 // This is used for storing version on imported records
1396 $session_id = md5($importer_data->getId() . time());
1397 update_post_meta($importer_data->getId(), '_iwp_session', $session_id);
1398
1399 if ('background' === $importer_data->getSetting('import_method')) {
1400 update_post_meta($importer_data->getId(), '_iwp_cron_scheduled', current_time('timestamp'));
1401 delete_post_meta($importer_data->getId(), '_iwp_cron_last_ran');
1402 }
1403
1404 Logger::clearRequestType();
1405
1406 return $this->http->end_rest_success([
1407 'session' => $session_id
1408 ]);
1409 }
1410
1411 public function run_import(\WP_REST_Request $request)
1412 {
1413 $this->http->set_stream_headers();
1414
1415 Logger::setRequestType('rest::run_import');
1416
1417 $session = $request->get_param('session');
1418 $user = uniqid('iwp', true);
1419 $id = intval($request->get_param('id'));
1420 Logger::write('run_import -session=' . $session, $id);
1421
1422 $importer_data = $this->importer_manager->get_importer($id);
1423 Logger::write('run_import -import=start', $id);
1424
1425 $state = $this->importer_manager->import($importer_data->getId(), $user, $session);
1426 $state['message'] = $this->generate_status_message($state);
1427
1428 Logger::clearRequestType();
1429
1430 return $this->http->end_rest_success($state);
1431 }
1432
1433 public function pause_import(\WP_REST_Request $request)
1434 {
1435 $id = intval($request->get_param('id'));
1436 $paused = $this->sanitize($request->get_param('paused'), 'paused');
1437
1438 $state = $this->importer_manager->pause_import($id, $paused);
1439
1440 return $this->http->end_rest_success($state);
1441 }
1442
1443 public function stop_import(\WP_REST_Request $request)
1444 {
1445 $id = intval($request->get_param('id'));
1446
1447 $state = $this->importer_manager->stop_import($id);
1448
1449 return $this->http->end_rest_success($state);
1450 }
1451
1452 /**
1453 * Get field options for current template
1454 *
1455 * @param \WP_REST_Request $request
1456 * @return void
1457 */
1458 public function get_template_field_options(\WP_REST_Request $request)
1459 {
1460 try {
1461
1462 $id = intval($request->get_param('id'));
1463 $field = $this->sanitize($request->get_param('field'), 'field');
1464
1465 $importer_data = $this->importer_manager->get_importer($id);
1466 $template = $this->importer_manager->get_importer_template($id);
1467
1468 return $this->http->end_rest_success($template->get_field_options($field, $importer_data));
1469 } catch (\Exception $e) {
1470 return $this->http->end_rest_error($e->getMessage());
1471 }
1472 }
1473
1474 public function get_logs(\WP_REST_Request $request)
1475 {
1476 $id = intval($request->get_param('id'));
1477 $importer_data = $this->importer_manager->get_importer($id);
1478 $logs = $this->importer_manager->get_importer_logs($importer_data);
1479 return $this->http->end_rest_success(array_reverse($logs));
1480 }
1481
1482 public function get_log(\WP_REST_Request $request)
1483 {
1484 $id = intval($request->get_param('id'));
1485 $session = $this->sanitize($request->get_param('session'), 'session');
1486 $page = intval($request->get_param('page'));
1487 if ($page < 1) {
1488 $page = 1;
1489 }
1490 $importer_data = $this->importer_manager->get_importer($id);
1491 $log = $this->importer_manager->get_importer_log($importer_data, $session, $page, 500);
1492 $status = $this->importer_manager->get_importer_status_report($importer_data, $session);
1493 return $this->http->end_rest_success(['logs' => $log, 'status' => $status]);
1494 }
1495
1496 public function get_debug_log(\WP_REST_Request $request)
1497 {
1498 $id = intval($request->get_param('id'));
1499 $page = intval($request->get_param('page'));
1500 if ($page < 1) {
1501 $page = 1;
1502 }
1503 $importer_data = $this->importer_manager->get_importer($id);
1504 $log = $this->importer_manager->get_importer_debug_log($importer_data, $page, 100);
1505
1506 $download = Logger::getLogFile($importer_data->getId(), true);
1507 return $this->http->end_rest_success(['log' => $log, 'download' => $download]);
1508 }
1509
1510 public function save_settings(\WP_REST_Request $request)
1511 {
1512 $post_data = $request->get_body_params();
1513 $post_data = $this->sanitize($post_data);
1514
1515 $defaults = $this->properties->_default_settings();
1516
1517 $output = [];
1518
1519 foreach ($defaults as $setting => $default) {
1520 if (isset($post_data[$setting])) {
1521 if ('bool' === $default['type']) {
1522 // Handle Boolean
1523 if (is_bool($post_data[$setting])) {
1524 $output[$setting] = $post_data[$setting];
1525 } elseif (in_array($post_data[$setting], ['true', 'false'])) {
1526 $output[$setting] = $post_data[$setting] === 'true' ? true : false;
1527 } else {
1528 $output[$setting] = boolval($post_data[$setting]);
1529 }
1530 } elseif ('number' === $default['type']) {
1531 $output[$setting] = intval($post_data[$setting]);
1532 } else {
1533 $output[$setting] = $post_data[$setting];
1534 }
1535 } else {
1536 $output[$setting] = $default['value'];
1537 }
1538 }
1539
1540 update_option('iwp_settings', $output);
1541
1542 return $this->http->end_rest_success($this->properties->get_settings());
1543 }
1544
1545 public function get_settings(\WP_REST_Request $request = null)
1546 {
1547 $output = $this->properties->get_settings();
1548 return $this->http->end_rest_success($output);
1549 }
1550
1551 public function get_template(\WP_REST_Request $request = null)
1552 {
1553 Logger::setRequestType('get_template');
1554
1555 $importer_id = $request->get_param('id');
1556 $importer_model = $this->importer_manager->get_importer($importer_id);
1557 $importer_template = $importer_model->getTemplate();
1558
1559 $templates = $this->importer_manager->get_templates();
1560
1561 if (!isset($templates[$importer_template])) {
1562 return $this->http->end_rest_error(sprintf(__("Invalid template \"%s\"", 'jc-importer'), $importer_template));
1563 }
1564
1565 $template_id = $templates[$importer_template];
1566 $template_class = $this->template_manager->load_template($template_id);
1567
1568 $output = [
1569 'id' => $template_id,
1570 'label' => $template_class->get_name(),
1571 'map' => $template_class->get_fields($importer_model),
1572 'settings' => $template_class->register_settings(),
1573 'options' => $template_class->register_options(),
1574 'permission_fields' => apply_filters('iwp/template/permission_fields', $template_class->get_permission_fields($importer_model), $template_class)
1575 ];
1576 return $this->http->end_rest_success($output);
1577 }
1578
1579 public function export_importers(\WP_REST_Request $request = null)
1580 {
1581 Logger::setRequestType('export_importers');
1582
1583 $importer_ids = $request->get_param('ids');
1584
1585 $sql_ids = array_map(function ($v) {
1586 return "'" . esc_sql($v) . "'";
1587 }, $importer_ids);
1588 $sql_ids = implode(',', $sql_ids);
1589
1590 $output = [];
1591
1592 /**
1593 * @var \WPDB $wpdb
1594 */
1595 global $wpdb;
1596 $results = $wpdb->get_results("SELECT post_title, post_content from {$wpdb->posts} WHERE post_type='" . IWP_POST_TYPE . "' AND ID IN (" . $sql_ids . ")", ARRAY_A);
1597 foreach ($results as $result) {
1598
1599 $data = unserialize($result['post_content']);
1600
1601 unset($data['file']['id']);
1602 unset($data['file']['settings']['count']);
1603 unset($data['file']['settings']['processed']);
1604
1605
1606 $row = [
1607 'name' => $result['post_title'],
1608 'data' => $data
1609 ];
1610
1611 if ($this->importer_manager->is_debug()) {
1612 $row['raw'] = base64_encode($result['post_content']);
1613 }
1614
1615 $output[] = $row;
1616 }
1617
1618 $json = json_encode($output);
1619
1620 header('Content-disposition: attachment; filename="ImportWP-export-' . date('Y-m-d') . '.json"');
1621 header('Content-type: "application/json"; charset="utf8"');
1622 header('Expires: 0');
1623 header('Cache-Control: must-revalidate');
1624 header('Pragma: public');
1625 header('Content-Length: ' . strlen($json));
1626
1627 echo $json;
1628 die();
1629 }
1630
1631 /**
1632 * Download exporter config json file.
1633 *
1634 * File is used when creating an importer to auto generate field mapping.
1635 *
1636 * @param WP_REST_Request|null $request
1637 * @return never
1638 */
1639 public function download_exporter_config(\WP_REST_Request $request = null)
1640 {
1641 $exporter_id = $request->get_param('id');
1642 $exporter_data = $this->exporter_manager->get_exporter($exporter_id);
1643 $mapper = $this->exporter_manager->get_exporter_mapper($exporter_data);
1644 $fields = $mapper->get_fields();
1645
1646
1647
1648 /**
1649 * @var \WPDB $wpdb
1650 */
1651 global $wpdb;
1652 $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);
1653
1654 $output = [
1655 'name' => $result['post_title'],
1656 'data' => unserialize($result['post_content']),
1657 'fields' => $this->exporter_manager->get_importer_map_fields($exporter_data),
1658 'formatted_fields' => $fields
1659 ];
1660
1661 $json = json_encode($output);
1662
1663 header('Content-disposition: attachment; filename="ImportWP-exporter-' . $exporter_id . '-' . date('Y-m-d') . '.json"');
1664 header('Content-type: "application/json"; charset="utf8"');
1665 header('Expires: 0');
1666 header('Cache-Control: must-revalidate');
1667 header('Pragma: public');
1668 header('Content-Length: ' . strlen($json));
1669
1670 echo $json;
1671 die();
1672 }
1673
1674 public function read_exporter_config(\WP_REST_Request $request = null)
1675 {
1676 $upload = $this->filesystem->upload_file($_FILES['file']);
1677 if (is_wp_error($upload)) {
1678 return $this->http->end_rest_error($upload->get_error_message());
1679 }
1680
1681 $json_contents = file_get_contents($upload['dest']);
1682 @unlink($upload['dest']);
1683
1684 $contents = json_decode($json_contents, true);
1685
1686 if (!isset($contents['name'], $contents['fields'], $contents['data'], $contents['data']['file_type'])) {
1687 return $this->http->end_rest_error("Invalid exporter config file.");
1688 }
1689
1690 if (!in_array($contents['data']['file_type'], ['xml', 'csv'])) {
1691 return $this->http->end_rest_error("Exporter file type is not supported.");
1692 }
1693
1694 return $this->http->end_rest_success(['exporter' => $contents]);
1695 }
1696
1697 public function get_compatibility_settings()
1698 {
1699 $output = [];
1700
1701 if (!function_exists('get_plugins')) {
1702 require_once ABSPATH . 'wp-admin/includes/plugin.php';
1703 }
1704
1705 $blacklisted = (array)get_option('iwp_compat_blacklist', []);
1706 $whitelisted = apply_filters('iwp/compat/whitelist', [
1707 'jc-importer/jc-importer.php'
1708 ]);
1709
1710 $all_plugins = get_plugins();
1711 foreach ($all_plugins as $plugin_id => $plugin_data) {
1712
1713
1714
1715 if (preg_match('/^importwp-/', $plugin_id, $matches) === 1 || in_array($plugin_id, $whitelisted)) {
1716 continue;
1717 }
1718
1719 $output[$plugin_id] = [
1720 'id' => $plugin_id,
1721 'name' => $plugin_data['Name'],
1722 'enabled' => in_array($plugin_id, $blacklisted) ? 'yes' : 'no'
1723 ];
1724 }
1725
1726 return $output;
1727 }
1728
1729 public function get_compatibility(\WP_REST_Request $request = null)
1730 {
1731 $output = $this->get_compatibility_settings();
1732
1733 return $this->http->end_rest_success($output);
1734 }
1735
1736 public function save_compatibility(\WP_REST_Request $request)
1737 {
1738 $post_data = $request->get_body_params();
1739 $post_data = $this->sanitize($post_data);
1740
1741 if (!function_exists('get_plugins')) {
1742 require_once ABSPATH . 'wp-admin/includes/plugin.php';
1743 }
1744 $plugin_keys = array_keys(get_plugins());
1745
1746
1747 $tmp = [];
1748
1749 if (isset($post_data['plugins'])) {
1750
1751 foreach ($post_data['plugins'] as $plugin_id) {
1752 if (!in_array($plugin_id, $plugin_keys)) {
1753 continue;
1754 }
1755
1756 $tmp[] = "" . $plugin_id;
1757 }
1758 }
1759
1760 update_option('iwp_compat_blacklist', $tmp);
1761
1762 $output = $this->get_compatibility_settings();
1763 return $this->http->end_rest_success($output);
1764 }
1765
1766 public function import_exporters(\WP_REST_Request $request = null)
1767 {
1768 Logger::setRequestType('import_exporters');
1769
1770 $upload = $this->filesystem->upload_file($_FILES['file']);
1771 if (is_wp_error($upload)) {
1772 return $this->http->end_rest_error($upload->get_error_message());
1773 }
1774
1775 $json_contents = file_get_contents($upload['dest']);
1776 @unlink($upload['dest']);
1777
1778 $contents = json_decode($json_contents, true);
1779 $counter = 0;
1780 $errors = [];
1781 if (!empty($contents)) {
1782 foreach ($contents as $importer) {
1783
1784 $result = wp_insert_post([
1785 'post_type' => IWP_POST_TYPE,
1786 'post_title' => $importer['name'],
1787 'post_status' => 'publish',
1788 'post_author' => get_current_user_id(),
1789 'post_content' => wp_slash(serialize($importer['data']))
1790 ], true);
1791
1792 if (is_wp_error($result)) {
1793 $errors[] = $result->get_error_message();
1794 continue;
1795 }
1796
1797 $counter++;
1798 }
1799 }
1800
1801 if (empty($counter)) {
1802 return $this->http->end_rest_error(__('No Importers found.', 'jc-importer'));
1803 }
1804
1805 return $this->http->end_rest_success(sprintf(_n("Import Complete, %d Importer", "Import Complete, %d Importers", $counter, 'jc-importer'), $counter));
1806 }
1807
1808 public function save_exporter(\WP_REST_Request $request = null)
1809 {
1810 Logger::setRequestType('save_exporter');
1811
1812 $post_data = $request->get_body_params();
1813 $id = isset($post_data['id']) ? intval($post_data['id']) : null;
1814
1815
1816 $exporter = new ExporterModel($id, $this->importer_manager->is_debug());
1817
1818 if (isset($post_data['name'])) {
1819 $exporter->setName($post_data['name']);
1820 }
1821
1822 if (isset($post_data['type'])) {
1823 $exporter->setType($post_data['type']);
1824 }
1825
1826 if (isset($post_data['file_type'])) {
1827 $exporter->setFileType($post_data['file_type']);
1828 }
1829
1830 if (isset($post_data['file_settings'])) {
1831 $exporter->setFileSettings($post_data['file_settings']);
1832 }
1833
1834 if (isset($post_data['fields']) || $id > 0) {
1835 $exporter->setFields(isset($post_data['fields']) ? $post_data['fields'] : []);
1836 }
1837
1838 if (isset($post_data['filters']) || $id > 0) {
1839 $exporter->setFilters(isset($post_data['filters']) ? $post_data['filters'] : []);
1840 }
1841
1842 if (isset($post_data['unique_identifier'])) {
1843 $exporter->setUniqueIdentifier($post_data['unique_identifier']);
1844 }
1845
1846 if (isset($post_data['export_method'])) {
1847 $exporter->setExportMethod($post_data['export_method']);
1848 }
1849
1850 if (isset($post_data['cron'])) {
1851
1852 // TODO: Save cron settings
1853
1854 $cron = $this->sanitize_setting($post_data['cron']);
1855 $exporter->setCron($cron);
1856 }
1857
1858 $result = $exporter->save();
1859 if (is_wp_error($result)) {
1860 return $this->http->end_rest_error($result->get_error_message());
1861 }
1862 return $this->http->end_rest_success($exporter->data());
1863 }
1864
1865 public function get_exporters(\WP_REST_Request $request = null)
1866 {
1867 Logger::setRequestType('get_exporters');
1868
1869 $exporters = $this->exporter_manager->get_exporters();
1870 $result = [];
1871
1872 foreach ($exporters as $exporter) {
1873 $result[] = $exporter->data();
1874 }
1875
1876 return $this->http->end_rest_success($result);
1877 }
1878
1879 public function get_exporter(\WP_REST_Request $request = null)
1880 {
1881 Logger::setRequestType('get_exporter');
1882
1883 $id = intval($request->get_param('id'));
1884 $exporter_data = $this->exporter_manager->get_exporter($id);
1885 $response = $exporter_data->data('public');
1886 return $this->http->end_rest_success($response);
1887 }
1888
1889 public function delete_exporter(\WP_REST_Request $request = null)
1890 {
1891 Logger::setRequestType('delete_exporter');
1892
1893 $id = intval($request->get_param('id'));
1894 $this->exporter_manager->delete_exporter($id);
1895 return $this->http->end_rest_success(true);
1896 }
1897
1898 /**
1899 * Trigger a new export.
1900 *
1901 * @param \WP_REST_Request $request
1902 * @return array
1903 */
1904 public function init_export(\WP_REST_Request $request)
1905 {
1906 $id = intval($request->get_param('id'));
1907
1908 Logger::setRequestType('rest::init_export');
1909
1910 Logger::write('init_import -start', $id);
1911
1912 $exporter_data = $this->exporter_manager->get_exporter($id);
1913
1914 // clear existing
1915 ExporterState::clear_options($exporter_data->getId());
1916
1917 // This is used for storing version on imported records
1918 $session_id = md5($exporter_data->getId() . time());
1919 update_post_meta($exporter_data->getId(), '_iwp_session', $session_id);
1920
1921 Logger::clearRequestType();
1922
1923 return $this->http->end_rest_success([
1924 'session' => $session_id
1925 ]);
1926 }
1927
1928 public function run_exporter(\WP_REST_Request $request)
1929 {
1930 Logger::setRequestType('run_exporter');
1931
1932 $this->http->set_stream_headers();
1933
1934 $id = intval($request->get_param('id'));
1935 $session = $request->get_param('session');
1936 $user = uniqid('iwp', true);
1937
1938 $exporter_data = $this->exporter_manager->get_exporter($id);
1939
1940 $status = $this->exporter_manager->export($exporter_data, $user, $session);
1941 echo json_encode($status);
1942 // Logger::write(__CLASS__ . '::run_import -import=end -status=' . $status->get_status(), $id);
1943
1944 // if (!empty($this->output_cache)) {
1945 // echo $this->output_cache;
1946 // $this->output_cache = '';
1947 // }
1948
1949 // $output = $status->output();
1950 // $output['message'] = $this->generate_exporter_status_message($status);
1951 // echo json_encode($output) . "\n";
1952 die();
1953 }
1954
1955 public function get_exporter_status(\WP_REST_Request $request)
1956 {
1957 Logger::setRequestType('get_exporter_status');
1958
1959 $exporter_ids = $request->get_param('ids');
1960 $result = array();
1961 $query_data = array(
1962 'post_type' => EWP_POST_TYPE,
1963 'posts_per_page' => -1,
1964 'fields' => 'ids'
1965 );
1966
1967 if (!empty($exporter_ids)) {
1968 $query_data['post__in'] = $exporter_ids;
1969 }
1970
1971 $query = new \WP_Query($query_data);
1972
1973 if (!empty($query->posts)) {
1974 foreach ($query->posts as $post_id) {
1975
1976 $exporter = $this->exporter_manager->get_exporter($post_id);
1977 $status = ExporterState::get_state($post_id);
1978 $status['exporter'] = $post_id;
1979 $status['version'] = 2;
1980
1981 if (isset($status['progress'])) {
1982 $current = $status['progress']['export']['current_row'];
1983 $end = $status['progress']['export']['end'] - $status['progress']['export']['start'];
1984 $status['progress'] = $end > 0 ? ($current / $end) * 100 : 0;
1985 } else {
1986 $status['progress'] = 0;
1987 }
1988
1989
1990 $output = '';
1991
1992 if (isset($status['status'])) {
1993 switch ($status['status']) {
1994 case 'cancelled':
1995 $output .= 'Export cancelled';
1996 break;
1997 case 'complete':
1998 $output .= 'Export complete';
1999 break;
2000 case 'processing':
2001 case 'timeout':
2002 case 'running':
2003 $output .= 'Exporting: ' . $current . '/' . $end;
2004 break;
2005 }
2006 }
2007
2008 $status['message'] = $output;
2009
2010 $result[] = $this->event_handler->run('iwp/exporter/status/output', [$status, $exporter]);
2011 }
2012 }
2013
2014 echo json_encode($result) . "\n";
2015 die();
2016 }
2017
2018 public function get_importer_unique_identifier_options(\WP_REST_Request $request)
2019 {
2020 $id = intval($request->get_param('id'));
2021 $importer_model = $this->importer_manager->get_importer($id);
2022 if (!$importer_model) {
2023 return $this->http->end_rest_error(['msg' => 'Invalid importer']);
2024 }
2025
2026 // Get default template options
2027 $template = $this->importer_manager->get_template($importer_model->getTemplate());
2028 if (is_wp_error($template)) {
2029 return $this->http->end_rest_error($template->get_error_message());
2030 }
2031
2032 $template_class = $this->template_manager->load_template($template);
2033 $unique_fields = $this->template_manager->get_template_unique_fields($template_class);
2034 $options = $template_class->get_unique_identifier_options($importer_model, $unique_fields);
2035
2036 // Only add in unqiue fields if they have not been found.
2037 // Allowing for old templates to continue to list unique identifiers.
2038 foreach ($unique_fields as $field_id) {
2039 if (!isset($options[$field_id])) {
2040 $options[$field_id] = [
2041 'value' => $field_id,
2042 'label' => $field_id,
2043 'uid' => true,
2044 'active' => true,
2045 ];
2046 }
2047 }
2048
2049 $options = array_filter($options, function ($item) {
2050 return $item['active'];
2051 });
2052
2053 return $this->http->end_rest_success(['options' => array_values($options), 'unique_fields' => array_values($unique_fields)]);
2054 }
2055 }
2056