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

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

1,212 lines 41.1 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2
3 namespace ImportWP\Common\Importer;
4
5 use ImportWP\Common\Filesystem\Filesystem;
6 use ImportWP\Common\Importer\Config\Config;
7 use ImportWP\Common\Importer\File\CSVFile;
8 use ImportWP\Common\Importer\File\JSONFile;
9 use ImportWP\Common\Importer\File\XMLFile;
10 use ImportWP\Common\Importer\Mapper\AttachmentMapper;
11 use ImportWP\Common\Importer\Mapper\CommentMapper;
12 use ImportWP\Common\Importer\Mapper\PostMapper;
13 use ImportWP\Common\Importer\Mapper\TermMapper;
14 use ImportWP\Common\Importer\Mapper\UserMapper;
15 use ImportWP\Common\Importer\Parser\CSVParser;
16 use ImportWP\Common\Importer\Parser\JSONParser;
17 use ImportWP\Common\Importer\Parser\XMLParser;
18 use ImportWP\Common\Importer\Permission\Permission;
19 use ImportWP\Common\Importer\State\ImporterState;
20 use ImportWP\Common\Importer\Template\AttachmentTemplate;
21 use ImportWP\Common\Importer\Template\CommentTemplate;
22 use ImportWP\Common\Importer\Template\CustomPostTypeTemplate;
23 use ImportWP\Common\Importer\Template\PageTemplate;
24 use ImportWP\Common\Importer\Template\PostTemplate;
25 use ImportWP\Common\Importer\Template\Template;
26 use ImportWP\Common\Importer\Template\TemplateManager;
27 use ImportWP\Common\Importer\Template\TermTemplate;
28 use ImportWP\Common\Importer\Template\UserTemplate;
29 use ImportWP\Common\Migration\Migrations;
30 use ImportWP\Common\Model\ImporterModel;
31 use ImportWP\Common\Properties\Properties;
32 use ImportWP\Common\Runner\ImporterRunnerState;
33 use ImportWP\Common\Util\Logger;
34 use ImportWP\Common\Util\Util;
35 use ImportWP\Container;
36 use ImportWP\EventHandler;
37
38 class ImporterManager
39 {
40
41 /**
42 * @var Filesystem
43 */
44 private $filesystem;
45
46 /**
47 * @var TemplateManager $template_manager
48 */
49 private $template_manager;
50
51 /**
52 * @var EventHandler $event_handler
53 */
54 protected $event_handler;
55
56 public function __construct(Filesystem $filesystem, TemplateManager $template_manager, EventHandler $event_handler)
57 {
58 $this->filesystem = $filesystem;
59 $this->template_manager = $template_manager;
60 $this->event_handler = $event_handler;
61
62 add_action('admin_init', [$this, 'download_debug_log']);
63 }
64
65 /**
66 * Stream an importer debug log through an authenticated admin request.
67 *
68 * Direct public URLs under uploads/importwp are blocked by .htaccess (CVE-2025-12894).
69 *
70 * @return void
71 */
72 public function download_debug_log()
73 {
74 if (!isset($_GET['page'], $_GET['import'], $_GET['download_debug']) || $_GET['page'] !== 'importwp') {
75 return;
76 }
77
78 if (!is_user_logged_in() || !current_user_can('manage_options')) {
79 wp_die(esc_html__('You do not have permission to download this file.', 'jc-importer'), '', array('response' => 403));
80 }
81
82 if (!isset($_GET['_wpnonce']) || !wp_verify_nonce(sanitize_key(wp_unslash($_GET['_wpnonce'])), 'iwp_debug_log_download')) {
83 wp_die(esc_html__('Invalid download request.', 'jc-importer'), '', array('response' => 403));
84 }
85
86 $importer_id = intval($_GET['import']);
87 $importer_data = $this->get_importer($importer_id);
88 if (!$importer_data) {
89 wp_die(esc_html__('Invalid download request.', 'jc-importer'), '', array('response' => 403));
90 }
91
92 if (!$this->is_debug()) {
93 wp_die(esc_html__('Debug mode is not enabled.', 'jc-importer'), '', array('response' => 403));
94 }
95
96 $file_path = Logger::getLogFile($importer_id);
97 if (!is_string($file_path) || !file_exists($file_path)) {
98 wp_die(esc_html__('Debug log file not found.', 'jc-importer'), '', array('response' => 404));
99 }
100
101 nocache_headers();
102 header('Content-Type: text/plain; charset=utf-8');
103 header('Content-Disposition: attachment; filename="' . basename($file_path) . '"');
104 header('Content-Length: ' . (string) filesize($file_path));
105 readfile($file_path);
106 exit;
107 }
108
109 /**
110 * Get Importers
111 *
112 * @return ImporterModel[]
113 */
114 public function get_importers()
115 {
116
117 $result = array();
118 $query = new \WP_Query(array(
119 'post_type' => IWP_POST_TYPE,
120 'posts_per_page' => -1,
121 ));
122
123 foreach ($query->posts as $post) {
124 $result[] = $this->get_importer($post);
125 }
126 return $result;
127 }
128
129 /**
130 * Get Importer
131 *
132 * @param int $id
133 * @return ImporterModel
134 */
135 public function get_importer($id)
136 {
137 if ($id instanceof ImporterModel) {
138 return $id;
139 }
140
141 if (IWP_POST_TYPE !== get_post_type($id)) {
142 return false;
143 }
144
145 return new ImporterModel($id, $this->is_debug());
146 }
147
148 public function is_debug()
149 {
150
151 if (defined('IWP_DEBUG') && true === IWP_DEBUG) {
152 return true;
153 }
154
155 $uninstall_enabled = get_option('iwp_settings');
156 if (isset($uninstall_enabled['debug']) && true === $uninstall_enabled['debug']) {
157 return true;
158 }
159
160 return false;
161 }
162
163 public function set_current_user($id)
164 {
165 $importer_model = $this->get_importer($id);
166 $user_id = $importer_model->getUserId();
167
168 Logger::write('set_current_user -user=' . $user_id, $importer_model->getId());
169
170 if ($user_id) {
171 return wp_set_current_user($user_id);
172 }
173
174 return false;
175 }
176
177 /**
178 * Delete Importer
179 *
180 * @param int $id
181 * @return void
182 */
183 public function delete_importer($id)
184 {
185 $importer = $this->get_importer($id);
186 $importer->delete();
187 }
188
189 public function get_file($id)
190 {
191 $importer = $this->get_importer($id);
192 $config = $this->get_config($importer);
193 $parser = $importer->getParser();
194
195 if ('xml' === $parser) {
196 return $this->get_xml_file($importer, $config);
197 } elseif ('csv' === $parser) {
198 return $this->get_csv_file($importer, $config);
199 } elseif ('json' === $parser) {
200 return $this->get_json_file($importer, $config);
201 }
202
203 return false;
204 }
205
206 public function get_csv_file($id, $config)
207 {
208 $importer = $this->get_importer($id);
209 $file = new CSVFile($importer->getFile(), $config);
210 $file->setDelimiter($importer->getFileSetting('delimiter'));
211 $file->setEnclosure($importer->getFileSetting('enclosure'));
212 $file->setEscape($importer->getFileSetting('escape', "\\"));
213 return $file;
214 }
215
216 public function get_xml_file($id, $config)
217 {
218 $importer = $this->get_importer($id);
219 $file = new XMLFile($importer->getFile(), $config);
220 $file->setRecordPath($importer->getFileSetting('base_path'));
221 return $file;
222 }
223
224 public function get_json_file($id, $config)
225 {
226 $importer = $this->get_importer($id);
227 $file = new JSONFile($importer->getFile(), $config);
228 $file->setRecordPath($importer->getFileSetting('base_path'));
229 return $file;
230 }
231
232 public function preview_csv_file($id, $fields = [], $row = 0)
233 {
234 $importer = $this->get_importer($id);
235 $config = $this->get_config($importer, true);
236
237 $file = $this->get_csv_file($importer, $config);
238 $parser = new CSVParser($file);
239
240 $record = $parser->getRecord($row);
241
242 return $record->queryGroup(['fields' => $fields]);
243 }
244
245 public function preview_xml_file($id, $fields = [], $row = 0)
246 {
247 $importer = $this->get_importer($id);
248 $config = $this->get_config($importer, true);
249
250 $file = $this->get_xml_file($importer, $config);
251 $parser = new XMLParser($file);
252
253 $record = $parser->getRecord($row);
254 return $record->queryGroup(['fields' => $fields]);
255 }
256
257 public function preview_json_file($id, $fields = [], $row = 0)
258 {
259 $importer = $this->get_importer($id);
260 $config = $this->get_config($importer, true);
261
262 $file = $this->get_json_file($importer, $config);
263 $parser = new JSONParser($file);
264
265 $record = $parser->getRecord($row);
266 return $record->queryGroup(['fields' => $fields]);
267 }
268
269 public function process_csv_file($id, $delimiter, $enclosure, $tmp = false)
270 {
271 $importer = $this->get_importer($id);
272 $config = $this->get_config($importer->getId(), $tmp);
273
274 $file = $this->get_csv_file($importer, $config);
275 $file->setDelimiter($delimiter);
276 $file->setEnclosure($enclosure);
277 $file->processing(true);
278
279 return $file->getRecordCount();
280 }
281
282 public function process_xml_file($id, $tmp = false)
283 {
284
285 $importer = $this->get_importer($id);
286 $config = $this->get_config($importer->getId(), $tmp);
287
288 $filePath = $importer->getFile();
289 $file = new XMLFile($filePath, $config);
290 $file->processing(true);
291 $nodes = $file->get_node_list();
292 $results = [];
293
294 foreach ($nodes as $node) {
295 $config = $this->get_config($importer->getId(), $tmp);
296 $file = new XMLFile($filePath, $config);
297 $file->setRecordPath($node);
298 // TODO: Seperate record count, to when a node has been selected.
299 $results[$node] = 0; //$file->getRecordCount();
300 }
301
302 return $results;
303 }
304
305 public function process_json_file($id, $tmp = false)
306 {
307 $importer = $this->get_importer($id);
308 $config = $this->get_config($importer->getId(), $tmp);
309
310 $file = new JSONFile($importer->getFile(), $config);
311 $file->processing(true);
312
313 return $file->get_path_list();
314 }
315
316 /**
317 * Link import file to importer via post meta
318 *
319 * @param ImporterModel $id
320 * @param string $file_path
321 * @return integer Id of inserted file
322 */
323 public function link_importer_file($id, $file_path)
324 {
325 $importer = $this->get_importer($id);
326 $index = get_post_meta($importer->getId(), '_importer_files', true);
327 if (!$index) {
328 $index = 1;
329 }
330
331 $index++;
332
333 // Store uploads-relative paths so site moves / open_basedir changes do not break lookups.
334 $file_path = wp_normalize_path($file_path);
335 $relative = Filesystem::to_uploads_relative_path($file_path);
336 if ($relative !== '' && $relative !== $file_path) {
337 $file_path = $relative;
338 }
339
340 update_post_meta($importer->getId(), '_importer_files', $index);
341 update_post_meta($importer->getId(), '_importer_file_' . $index, $file_path);
342 return $index;
343 }
344
345 public function get_importer_file_prefix($importer)
346 {
347 $importer = $this->get_importer($importer);
348
349 $file_index = get_post_meta($importer->getId(), '_importer_files', true);
350 if (!$file_index) {
351 $file_index = 1;
352 }
353 $file_index++;
354
355 return $importer->getId() . '-' . intval($file_index) . '-' . wp_generate_password(12, false, false) . '-';
356 }
357
358 public function set_custom_upload_path($dir)
359 {
360 remove_filter('upload_dir', [$this, 'set_custom_upload_path']);
361
362 $path = $this->filesystem->get_temp_directory();
363 $url = $this->filesystem->get_temp_directory(true);
364
365 add_filter('upload_dir', [$this, 'set_custom_upload_path']);
366
367 $path .= '/uploads';
368 $url .= '/uploads';
369
370 if (!is_dir($path)) {
371 wp_mkdir_p($path);
372 }
373
374 if (!file_exists($path . '/.htaccess')) {
375 file_put_contents($path . '/.htaccess', "# Apache 2.4+
376 <IfModule mod_authz_core.c>
377 Require all denied
378 </IfModule>
379
380 # Apache 2.2 and older (or when mod_authz_core isn't available)
381 <IfModule !mod_authz_core.c>
382 Deny from all
383 </IfModule>");
384 }
385
386 if (!file_exists($path . '/index.html')) {
387 touch($path . '/index.html');
388 }
389
390 return array(
391 'path' => $path,
392 'url' => $url,
393 'subdir' => '/importwp/uploads',
394 ) + $dir;
395 }
396
397 public function upload_file($id, $file)
398 {
399 $importer = $this->get_importer($id);
400 Logger::setId($importer->getId());
401
402 $allowed_file_types = $this->event_handler->run('importer.allowed_file_types', [$importer->getAllowedFileTypes()]);
403
404 $prefix = $this->get_importer_file_prefix($importer);
405
406 $prefix_upload = function ($file) use ($prefix) {
407 $file['name'] = $prefix . $file['name'];
408 return $file;
409 };
410
411 add_filter('wp_handle_upload_prefilter', $prefix_upload);
412
413 $result = $this->filesystem->upload_file($file, $allowed_file_types);
414
415 remove_filter('wp_handle_upload_prefilter', $prefix_upload);
416
417 if (is_wp_error($result)) {
418 return $result;
419 }
420
421 $attachment_id = $this->insert_file_attachment($importer, $result['dest'], $result['type']);
422 if (is_wp_error($attachment_id)) {
423 return $attachment_id;
424 }
425
426 return $attachment_id;
427 }
428
429 public function remote_file($id, $source, $filetype = null)
430 {
431 $importer = $this->get_importer($id);
432 Logger::setId($importer->getId());
433
434 $allowed_file_types = $this->event_handler->run('importer.allowed_file_types', [$importer->getAllowedFileTypes()]);
435 $prefix = $this->get_importer_file_prefix($importer);
436
437 try {
438
439 if (preg_match('/^s?ftp?:\/\//', $source) === 1) {
440
441 if (preg_match('/^(?<protocol>s?ftp):\/\/(?:(?<user>[^\:@]+)(?:\:(?<pass>[^@]+))?@)?(?<host>[^\:\/]+)(?:\:(?<port>[0-9]+))?(?:\/(?<path>.*))$/', $source, $matches) !== 1) {
442
443 return new \WP_Error("IM_RM_FTP_PARSE", __("Unable to parse FTP connection string", 'jc-importer'));
444 }
445
446 $protocol = isset($matches['protocol']) ? $matches['protocol'] : 'ftp';
447 $user = isset($matches['user']) ? urldecode($matches['user']) : '';
448 $pass = isset($matches['pass']) ? urldecode($matches['pass']) : '';
449 $host = isset($matches['host']) ? $matches['host'] : false;
450 $port = isset($matches['port']) && !empty($matches['port']) ? $matches['port'] : intval(21);
451 $path = isset($matches['path']) ? $matches['path'] : false;
452
453 if (!$host) {
454 return new \WP_Error("IM_RM_FTP_HOST", __("Unable to parse ftp host from connection string", 'jc-importer'));
455 }
456
457 if (!$path) {
458 return new \WP_Error("IM_RM_FTP_HOST", __("Unable to parse ftp host from connection string", 'jc-importer'));
459 }
460
461 /**
462 * @var \ImportWP\Common\Ftp\Ftp $ftp
463 */
464 $ftp = Container::getInstance()->get('ftp');
465
466 $path = apply_filters('iwp/importer/remote_file', $path, $importer);
467 $path = apply_filters(sprintf('iwp/importer=%d/remote_file', $importer->getId()), $path, $importer);
468
469 $filter_connection_args = [
470 'user' => $user,
471 'pass' => $pass,
472 'host' => $host,
473 'port' => $port,
474 'path' => $path,
475 ];
476 $path = apply_filters(sprintf('iwp/importer/remote_file/source=%s', 'ftp'), $path, $importer, $filter_connection_args);
477 $path = apply_filters(sprintf('iwp/importer=%d/remote_file/source=ftp', $importer->getId(), 'ftp'), $path, $importer, $filter_connection_args);
478
479 if ($protocol == 'sftp') {
480
481 // require sftp package
482 require_once __DIR__ . '/../../../libs/autoload.php';
483
484 $sftp = new \phpseclib3\Net\SFTP($host, $port);
485 if (!$sftp->login($user, $pass)) {
486 return new \WP_Error('IWP_FTP_0', __("Unable to login to ftp server", 'jc-importer'));
487 }
488
489 $wp_upload_dir = wp_upload_dir();
490
491 $dest = wp_unique_filename($wp_upload_dir['path'], basename($path));
492 $wp_dest = $wp_upload_dir['path'] . '/' . $dest;
493
494 if (!$sftp->get($path, $wp_dest)) {
495 return new \WP_Error('IWP_FTP_2', sprintf(__('Unable to download: %s file via sftp.', 'jc-importer'), $path));
496 }
497
498 $result = array(
499 'dest' => $wp_dest,
500 'type' => $this->filesystem->get_filetype($wp_dest),
501 'mime' => $this->filesystem->get_file_mime($wp_dest)
502 );
503 } else {
504 $result = $ftp->download_file($path, $host, $user, $pass, false, $port);
505 }
506 } else {
507
508 $result = $this->filesystem->download_file($source, $filetype, $allowed_file_types, null, $prefix);
509 }
510 } catch (\Exception $e) {
511 return new \WP_Error($e->getCode(), $e->getMessage());
512 }
513
514 if (is_wp_error($result)) {
515 return $result;
516 }
517
518 $attachment_id = $this->insert_file_attachment($importer, $result['dest'], $result['type']);
519 if (is_wp_error($attachment_id)) {
520 return $attachment_id;
521 }
522
523 return $attachment_id;
524 }
525
526 public function local_file($id, $source, $filetype = null)
527 {
528 $importer = $this->get_importer($id);
529 Logger::setId($importer->getId());
530
531 $allowed_bases = apply_filters('iwp/importer/local_file/allowed_directories', [
532 realpath(WP_CONTENT_DIR) . DIRECTORY_SEPARATOR
533 ]);
534
535 $source = realpath($source);
536
537 $is_allowed = false;
538 foreach ($allowed_bases as $allowed_base) {
539 if ($allowed_base && strpos($source, $allowed_base) === 0) {
540 $is_allowed = true;
541 break;
542 }
543 }
544
545 if (!$source || !$is_allowed) {
546 return new \WP_Error('IWP_LOCAL_FILE_1', __('Access to this file path is not allowed.', 'jc-importer'));
547 }
548
549 $allowed_file_types = $this->event_handler->run('importer.allowed_file_types', [$importer->getAllowedFileTypes()]);
550 $prefix = $this->get_importer_file_prefix($importer);
551 $result = $this->filesystem->copy_file($source, $allowed_file_types, null, $prefix, $filetype);
552
553 if (is_wp_error($result)) {
554 return $result;
555 }
556
557 $attachment_id = $this->insert_file_attachment($importer, $result['dest'], $result['type']);
558 if (is_wp_error($attachment_id)) {
559 return $attachment_id;
560 }
561
562 return $attachment_id;
563 }
564
565 /**
566 * Add uploaded file to importer
567 *
568 * Insert file record in database, set as current import file.
569 *
570 * @param integer|ImporterModel $id Importer to attach file
571 * @param string $file_path File location on server
572 * @param string $file_type Type of file
573 *
574 * @return \WP_Error|int Id of file
575 */
576 private function insert_file_attachment($id, $file_path, $file_type)
577 {
578 $importer_model = $this->get_importer($id);
579 Logger::setId($importer_model->getId());
580
581 // Allow the modification of file path
582 $file_path = wp_normalize_path($file_path);
583 $file_path = apply_filters('iwp/importer/file_uploaded/file_path', $file_path, $importer_model);
584
585 $file_id = $this->link_importer_file($id, $file_path);
586 if (!$file_id) {
587 return new \WP_Error('IWP_IM_01', __('Unable to link importer file', 'jc-importer'));
588 }
589
590 if (is_null($importer_model->getParser())) {
591 $importer_model->setParser($file_type);
592 }
593
594 $importer_model->setFileId($file_id);
595 $importer_model->save();
596
597 do_action('iwp/importer/file_uploaded', $file_path, $importer_model);
598
599 return $file_id;
600 }
601
602 /**
603 * Clear config files
604 *
605 * @param int $id
606 * @return void
607 */
608 public function clear_config_files($id, $tmp = false, $all = true)
609 {
610 $config_path = $this->get_config_path($id, $tmp);
611 if (file_exists($config_path)) {
612
613 unlink($config_path);
614 if (true === $all) {
615 foreach (glob($config_path . '*') as $file) {
616
617 // don't remove status file
618 if (basename($file) === basename($config_path) . '.status') {
619 continue;
620 }
621
622 unlink($file);
623 }
624 }
625 }
626 }
627
628 public function get_config($importer, $tmp = false)
629 {
630 $config_path = $this->get_config_path($importer, $tmp);
631 $config = new Config($config_path);
632
633
634 $importer = $this->get_importer($importer);
635 $file_encoding = $importer->getFileSetting('file_encoding');
636
637 // file encoding
638 $config->set('file_encoding', apply_filters('iwp/importer/file_encoding', $file_encoding, $importer));
639
640 return $config;
641 }
642
643 public function get_session_path($id, $session, $max_depth = 4)
644 {
645 $base = $this->filesystem->get_temp_directory() . DIRECTORY_SEPARATOR;
646
647 $base .= str_pad($id, 2, STR_PAD_LEFT) . DIRECTORY_SEPARATOR;
648 if (!file_exists($base)) {
649 if (!is_writable(dirname($base)) || !mkdir($base)) {
650 throw new \Exception(sprintf(__("Unable to create directory: %s", 'jc-importer'), $base));
651 }
652 }
653
654 for ($i = 0; $i < ceil(strlen($session) / 2); $i++) {
655 $base .= substr($session, $i * 2, 2) . DIRECTORY_SEPARATOR;
656 if (!file_exists($base)) {
657
658 if (!is_writable(dirname($base)) || !mkdir($base)) {
659 throw new \Exception(sprintf(__("Unable to create directory: %s", 'jc-importer'), $base));
660 }
661 }
662
663 if ($i >= $max_depth - 2) {
664 break;
665 }
666 }
667 return $base;
668 }
669
670 public function get_config_path($id, $tmp = false, $session_id = null)
671 {
672 $importer = $this->get_importer($id);
673
674 $key = 'config-%d.json';
675 if ($tmp) {
676 $key = 'temp-config-%d.json';
677 }
678
679 $base = !is_null($session_id) ? $this->get_session_path($id, $session_id) : $this->filesystem->get_temp_directory() . DIRECTORY_SEPARATOR;
680
681 return $base . sprintf($key, $importer->getId());
682 }
683
684 public function import($id, $user, $session = null)
685 {
686 Logger::timer();
687
688 // Run pending DB/data migrations before loading importer config so cron,
689 // CLI, and REST imports do not depend on the admin setup wizard.
690 $migrations = new Migrations();
691 if (!$migrations->isSetup()) {
692 $migrations->migrate();
693 if ($id instanceof ImporterModel) {
694 $id = $id->getId();
695 }
696 }
697
698 $importer_data = $this->get_importer($id);
699 $importer_id = $importer_data->getId();
700
701 // store current importer
702 iwp()->importer = $importer_data;
703
704 $config_data = get_option('iwp_importer_config_' . $importer_id, []);
705
706 $this->event_handler->run('importer_manager.import', [$importer_data]);
707
708 $state = new ImporterState($importer_id, $user);
709
710 try {
711
712 Logger::debug('IM -init_state');
713
714 // 1. Set State Session, and load its state
715 $state->init($session);
716
717 // if this is a new session, clear config files
718 if ($state->has_status('init')) {
719 // rest importer log.
720 Logger::clear($importer_id);
721 Logger::debug('IM -clear_config_files');
722 $this->clear_config_files($importer_id, false, true);
723 $config_data['features'] = [
724 'session_table' => true
725 ];
726 }
727
728 Logger::debug('IM -get_config');
729 $config = $this->get_config($importer_data);
730
731 // template
732 Logger::debug('IM -get_importer_template');
733 $template = $this->get_importer_template($importer_data);
734
735 Logger::debug('IM -register_hooks');
736 $template->register_hooks($importer_data);
737
738 // permission
739 Logger::debug('IM -permissions');
740 $permission = new Permission($importer_data);
741
742 // mapper
743 Logger::debug('IM -get_importer_mapper');
744 $mapper = $this->get_importer_mapper($importer_data, $template, $permission);
745
746 // if this is a new session, build config
747 if ($state->has_status('init')) {
748
749 Logger::debug('IM -generate_config');
750
751 $config_data['data'] = $template->config_field_map($importer_data->getMap());
752 $config->set('data', $config_data['data']);
753
754 $config_data['id'] = $state->get_session();
755
756 // This is used for storing version on imported records
757 update_post_meta($importer_id, '_iwp_session', $config_data['id']);
758
759 // Increase Version
760 $version = get_post_meta($importer_id, '_iwp_version', true);
761 if ($version !== false) {
762 $version++;
763 } else {
764 $version = 0;
765 }
766 update_post_meta($importer_id, '_iwp_version', $version);
767 $config_data['version'] = $version;
768
769 /**
770 * Fetch new file if setting is checked
771 * @since 2.7.15
772 */
773 $run_fetch_file = $importer_data->getSetting('run_fetch') || false;
774 $run_fetch_file = apply_filters('iwp/importer/run_fetch_file', $run_fetch_file);
775 if ($run_fetch_file) {
776
777 add_filter('upload_dir', [$this, 'set_custom_upload_path']);
778
779 $datasource = $importer_data->getDatasource();
780 switch ($datasource) {
781 case 'remote':
782 $raw_source = $importer_data->getDatasourceSetting('remote_url');
783 $source = apply_filters('iwp/importer/datasource', $raw_source, $raw_source, $importer_data);
784 $source = apply_filters('iwp/importer/datasource/remote', $source, $raw_source, $importer_data);
785 $attachment_id = $this->remote_file($importer_data, $source, $importer_data->getParser());
786 break;
787 case 'local':
788 $raw_source = $importer_data->getDatasourceSetting('local_url');
789 $source = apply_filters('iwp/importer/datasource', $raw_source, $raw_source, $importer_data);
790 $source = apply_filters('iwp/importer/datasource/local', $source, $raw_source, $importer_data);
791 $attachment_id = $this->local_file($importer_data, $source, $importer_data->getParser());
792 break;
793 default:
794 // TODO: record error
795 $attachment_id = new \WP_Error('IWP_CRON_1', sprintf(__('Unable to get new file using datasource: %s', 'jc-importer'), $datasource));
796 break;
797 }
798
799 remove_filter('upload_dir', [$this, 'set_custom_upload_path']);
800
801 if (is_wp_error($attachment_id)) {
802 throw new \Exception(sprintf(__('Importer Datasource: %s', 'jc-importer'), $attachment_id->get_error_message()));
803 }
804 }
805 }
806
807 $start = 0;
808
809 // get parser
810 if ($importer_data->getParser() === 'csv') {
811 Logger::debug('IM -get_csv_file');
812 $file = $this->get_csv_file($importer_data, $config);
813 Logger::debug('IM -load_parser');
814 $parser = new CSVParser($file);
815 if (true === $importer_data->getFileSetting('show_headings')) {
816 $start = 1;
817 }
818 } elseif ($importer_data->getParser() === 'xml') {
819 Logger::debug('IM -get_xml_file');
820 $file = $this->get_xml_file($importer_data, $config);
821 Logger::debug('IM -load_parser');
822 $parser = new XMLParser($file);
823 } elseif ($importer_data->getParser() === 'json') {
824 Logger::debug('IM -get_json_file');
825 $file = $this->get_json_file($importer_data, $config);
826 Logger::debug('IM -load_parser');
827 $parser = new JSONParser($file);
828 } else {
829 $parser = apply_filters('iwp/importer/init_parser', false, $importer_data, $config);
830 }
831
832 if (!$parser || !is_object($parser) || !method_exists($parser, 'file')) {
833 $parser_type = $importer_data->getParser();
834 throw new \Exception(
835 sprintf(
836 __('Unable to load importer parser for type: %s', 'jc-importer'),
837 $parser_type ? $parser_type : __('unknown', 'jc-importer')
838 )
839 );
840 }
841
842 // if this is a new session, set start / end rows to state
843 if ($state->has_status('init')) {
844
845 Logger::debug('IM -get_record_count');
846 $end = $parser->file()->getRecordCount();
847
848 // Capture cancelled status from file processor
849 $raw_state = ImporterState::get_state($importer_data->getId());
850 if ($raw_state['status'] === 'cancelled') {
851 return $raw_state;
852 }
853
854 $config_data['start'] = $this->get_start($importer_data, $start);
855 $config_data['end'] = $this->get_end($importer_data, $config_data['start'], $end);
856
857 update_option('iwp_importer_config_' . $importer_id, $config_data);
858
859 Logger::debug('IM -update_state');
860 $state->update(function ($state) use ($config_data) {
861
862 Logger::debug('IM -update_state=running');
863
864 $state['id'] = $config_data['id'];
865 $state['status'] = 'running';
866 $state['progress']['import']['start'] = $config_data['start'];
867 $state['progress']['import']['end'] = $config_data['end'];
868 return $state;
869 });
870
871 Logger::debug('IM -write_status_session_to_file');
872 Util::write_status_session_to_file($id, $state);
873
874 do_action('iwp/importer/init', $importer_data);
875 }
876
877
878 add_filter('iwp/importer/mapper/hash_check_enabled', function ($enabled) use ($importer_data) {
879 return $importer_data->getSetting('hash_check');
880 });
881
882 Logger::debug('IM -import');
883 $importer = new \ImportWP\Common\Importer\Importer($config);
884 $importer->parser($parser);
885 $importer->mapper($mapper);
886 $importer->from($config_data['start']);
887 $importer->to($config_data['end']);
888 $importer->filter($importer_data->getFilters());
889 $importer->import($importer_id, $user, $state);
890 Logger::debug('IM -import_complete');
891 } catch (\Exception $e) {
892
893 // TODO: Missing template errors are currently not being logged to history, possibly others?
894 Logger::error('import -error=' . $e->getMessage(), $importer_id);
895 $state->error($e);
896 Util::write_status_session_to_file($id, $state);
897 return $state->get_raw();
898 }
899
900 /**
901 * @var Properties $properties
902 */
903 $properties = Container::getInstance()->get('properties');
904
905 // rotate files to not fill up server
906 $importer_data->limit_importer_files($properties->file_rotation);
907 $this->prune_importer_logs($importer_data, $properties->log_rotation);
908
909 $template->unregister_hooks();
910
911 $this->event_handler->run('importer_manager.import_shutdown', [$importer_data]);
912
913 return $state->update(function ($data) {
914 $data['duration'] = floatval($data['duration']) + Logger::timer();
915 return $data;
916 })->get_raw();
917 }
918
919 public function pause_import($importer_id, $paused)
920 {
921 // TODO: set flag for paused.
922 $state = ImporterState::get_state($importer_id);
923 if ($paused === 'no') {
924 ImporterState::clear_flag($importer_id);
925 $state['status'] = 'running';
926 } else {
927 ImporterState::set_paused($importer_id);
928 $state['status'] = 'paused';
929 }
930
931 // good chance this will be overwritten
932 ImporterState::set_state($importer_id, $state);
933
934 return $state;
935 }
936
937 public function stop_import($importer_id)
938 {
939 ImporterState::set_cancelled($importer_id);
940
941 // good chance this will be overwritten
942 $state = ImporterState::get_state($importer_id);
943 $state['status'] = 'cancelled';
944 ImporterState::set_state($importer_id, $state);
945
946 return $state;
947 }
948
949 public function get_start($importer_data, $start)
950 {
951 $tmp_start = $importer_data->getStartRow();
952 if (!is_null($tmp_start) && "" !== $tmp_start) {
953 $tmp_start = intval($tmp_start);
954
955 if ($tmp_start > $start) {
956 $start = $tmp_start;
957 }
958 }
959
960 return $start;
961 }
962
963 public function get_end($importer_data, $start, $end)
964 {
965 $tmp_max_row = $importer_data->getMaxRow();
966 if (!is_null($tmp_max_row) && $tmp_max_row !== '') {
967 $tmp_end = $start + intval($tmp_max_row);
968 if ($tmp_end < $end) {
969 $end = $tmp_end;
970 }
971 }
972
973 return $end;
974 }
975
976 public function get_importer_template($id)
977 {
978 $importer_model = $this->get_importer($id);
979 $templates = $this->get_templates();
980 $template_name = $importer_model->getTemplate();
981
982 if (!isset($templates[$template_name])) {
983 $exception_msg = sprintf(__("Unable to locate importer template: %s", 'jc-importer'), $template_name);
984 Logger::error('import -get_importer_template=' . $exception_msg, $importer_model->getId());
985 throw new \Exception($exception_msg);
986 }
987
988 return $this->template_manager->load_template($templates[$template_name]);
989 }
990
991 /**
992 * Get importer mapper
993 *
994 * @param int $id
995 * @param Template $template
996 * @param Permission $permission
997 * @return MapperInterface
998 */
999 public function get_importer_mapper($id, $template, $permission = null)
1000 {
1001 $importer = $this->get_importer($id);
1002 $mapper_name = $template->get_mapper();
1003
1004 $mappers = $this->get_mappers();
1005 return isset($mappers[$mapper_name]) ? new $mappers[$mapper_name]($importer, $template, $permission) : false;
1006 }
1007
1008 public function get_mappers()
1009 {
1010 $mappers = $this->event_handler->run('mappers.register', [[]]); // apply_filters('iwp/mappers/register', []);
1011 $mappers = array_merge($mappers, [
1012 'post' => PostMapper::class,
1013 'user' => UserMapper::class,
1014 'term' => TermMapper::class,
1015 'attachment' => AttachmentMapper::class,
1016 'comment' => CommentMapper::class,
1017 ]);
1018 return $mappers;
1019 }
1020
1021 public function get_mapper($key)
1022 {
1023 $mappers = $this->get_mappers();
1024 if (!isset($mappers[$key])) {
1025 return new \WP_Error('IWP_IM_1', sprintf(__('Unable to locate mapper: %s', 'jc-importer'), $key));
1026 }
1027
1028 return $mappers[$key];
1029 }
1030
1031 public function get_templates()
1032 {
1033 $templates = $this->event_handler->run('templates.register', [[]]);
1034 $templates = array_merge($templates, [
1035 'post' => PostTemplate::class,
1036 'page' => PageTemplate::class,
1037 'user' => UserTemplate::class,
1038 'term' => TermTemplate::class,
1039 'attachment' => AttachmentTemplate::class,
1040 'comment' => CommentTemplate::class,
1041 'custom-post-type' => CustomPostTypeTemplate::class,
1042 ]);
1043 return $templates;
1044 }
1045
1046 public function get_template($key)
1047 {
1048 $templates = $this->get_templates();
1049 if (!isset($templates[$key])) {
1050 return new \WP_Error('IWP_IM_1', sprintf(__('Unable to locate template: %s', 'jc-importer'), $key));
1051 }
1052
1053 return $templates[$key];
1054 }
1055
1056 public function get_importer_debug_log(ImporterModel $importer_data, $page = 0, $per_page = -1)
1057 {
1058 $file_path = Logger::getLogFile($importer_data->getId());
1059
1060 $line_counter = 0;
1061 $lines = [];
1062 $start = $end = -1;
1063
1064 if ($per_page > 0) {
1065 $start = ($page - 1) * $per_page;
1066 $end = $start + $per_page;
1067 }
1068
1069 if (file_exists($file_path)) {
1070 $fh = fopen($file_path, 'r');
1071 if ($fh !== false) {
1072 while (($data = fgetcsv($fh)) !== false) {
1073 if ($line_counter === $end) {
1074 return $lines;
1075 } elseif ($per_page === -1 || $line_counter >= $start) {
1076 $lines[] = $data;
1077 }
1078
1079 $line_counter++;
1080 }
1081 fclose($fh);
1082 }
1083 }
1084 return $lines;
1085 }
1086
1087 public function prune_importer_logs($importer_model, $limit)
1088 {
1089 $limit = intval($limit);
1090 if ($limit <= -1) {
1091 return;
1092 }
1093
1094 $file_path = Util::get_importer_status_file_path($importer_model->getId());
1095 $tmp_file_path = Util::get_importer_status_file_path($importer_model->getId()) . '.tmp';
1096 $lines = $this->get_importer_logs($importer_model);
1097
1098 if (count($lines) > $limit) {
1099
1100 require_once(ABSPATH . 'wp-admin/includes/class-wp-filesystem-base.php');
1101 require_once(ABSPATH . 'wp-admin/includes/class-wp-filesystem-direct.php');
1102 $fileSystemDirect = new \WP_Filesystem_Direct(false);
1103
1104 $fh = fopen($tmp_file_path, 'w');
1105 for ($i = 0; $i < count($lines); $i++) {
1106
1107 if ($i < count($lines) - $limit) {
1108 $log_file_path = Util::get_importer_log_file_path($importer_model->getId(), $lines[$i]['id']);
1109 if ($fileSystemDirect->exists($log_file_path)) {
1110
1111 $fileSystemDirect->delete($log_file_path);
1112
1113 $tmp = $log_file_path;
1114 for ($j = 0; $j < 3; $j++) {
1115 $tmp = dirname($tmp);
1116 $sub_files = $fileSystemDirect->dirlist($log_file_path);
1117 if (empty($sub_files)) {
1118 $fileSystemDirect->rmdir($tmp);
1119 }
1120 }
1121 }
1122 } else {
1123 fputs($fh, json_encode($lines[$i]) . "\n");
1124 }
1125 }
1126 fclose($fh);
1127
1128 if ($fileSystemDirect->move($tmp_file_path, $file_path, true)) {
1129 $fileSystemDirect->delete($tmp_file_path);
1130 }
1131 }
1132 }
1133
1134 public function get_importer_logs(ImporterModel $importer_data, $page = 0, $per_page = -1)
1135 {
1136 $file_path = Util::get_importer_status_file_path($importer_data->getId());
1137
1138 $line_counter = 0;
1139 $lines = [];
1140
1141 $start = $end = -1;
1142
1143 if ($per_page > 0) {
1144 $start = ($page - 1) * $per_page;
1145 $end = $start + $per_page;
1146 }
1147
1148 if (file_exists($file_path)) {
1149 $fh = fopen($file_path, 'r');
1150 if ($fh !== false) {
1151 while (($data = fgets($fh)) !== false) {
1152 if ($data[strlen($data) - 1] === "\n") {
1153 $data = substr($data, 0, strlen($data) - 1);
1154 }
1155
1156 if ($line_counter === $end) {
1157 return $lines;
1158 } elseif ($per_page === -1 || $line_counter >= $start) {
1159 $lines[] = json_decode($data, true);
1160 }
1161
1162 $line_counter++;
1163 }
1164 fclose($fh);
1165 }
1166 }
1167 return $lines;
1168 }
1169
1170 public function get_importer_log(ImporterModel $importer_data, $session_id, $page = 0, $per_page = -1)
1171 {
1172 $file_path = Util::get_importer_log_file_path($importer_data->getId(), $session_id);
1173
1174 $line_counter = 0;
1175 $lines = [];
1176 $start = $end = -1;
1177
1178 if ($per_page > 0) {
1179 $start = ($page - 1) * $per_page;
1180 $end = $start + $per_page;
1181 }
1182
1183 if (file_exists($file_path)) {
1184 $fh = fopen($file_path, 'r');
1185 if ($fh !== false) {
1186 while (($data = fgetcsv($fh)) !== false) {
1187 if ($line_counter === $end) {
1188 return $lines;
1189 } elseif ($per_page === -1 || $line_counter >= $start) {
1190 $lines[] = $data;
1191 }
1192
1193 $line_counter++;
1194 }
1195 fclose($fh);
1196 }
1197 }
1198 return $lines;
1199 }
1200
1201 public function get_importer_status_report(ImporterModel $immpoter_data, $session)
1202 {
1203 $logs = $this->get_importer_logs($immpoter_data);
1204 foreach ($logs as $log) {
1205 if ($log && isset($log['id']) && $log['id'] === $session) {
1206 return $log;
1207 }
1208 }
1209 return false;
1210 }
1211 }
1212