PluginProbe
Import WP – CSV & XML Import Export for WordPress / 2.15.0
Import WP – CSV & XML Import Export for WordPress v2.15.0
2.15.1 2.15.0 2.14.24 2.14.23 2.7.0 2.7.1 2.7.10 2.7.11 2.7.12 2.7.13 2.7.14 2.7.2 2.7.3 2.7.4 2.7.5 2.7.6 2.7.7 2.7.8 2.7.9 2.8.0 2.8.1 2.8.2 2.8.3 2.9.0 2.9.1 All 144 releases
← All changes | class/Common/Importer/Importer.php +278 -283 2.7.5 → 2.15.0 View file →
@@ -2,20 +2,23 @@
2 2
3 3 namespace ImportWP\Common\Importer;
4 4
5 5 use ImportWP\Common\Importer\ConfigInterface;
6 -use ImportWP\Common\Importer\DataParser;
7 6 use ImportWP\Common\Importer\Exception\FileException;
8 7 use ImportWP\Common\Importer\Exception\MapperException;
9 8 use ImportWP\Common\Importer\Exception\ParserException;
9 +use ImportWP\Common\Importer\Exception\RecordUpdatedSkippedException;
10 10 use ImportWP\Common\Importer\File\CSVFile;
11 +use ImportWP\Common\Importer\File\JSONFile;
11 12 use ImportWP\Common\Importer\File\XMLFile;
12 13 use ImportWP\Common\Importer\MapperInterface;
13 14 use ImportWP\Common\Importer\Parser\CSVParser;
15 +use ImportWP\Common\Importer\Parser\JSONParser;
14 16 use ImportWP\Common\Importer\Parser\XMLParser;
15 17 use ImportWP\Common\Importer\ParserInterface;
16 18 use ImportWP\Common\Importer\State\ImporterState;
17 19 use ImportWP\Common\Properties\Properties;
20 +use ImportWP\Common\Runner\ImporterRunnerState;
18 21 use ImportWP\Common\Util\Logger;
19 22 use ImportWP\Common\Util\Util;
20 23 use ImportWP\Container;
21 24
@@ -21,11 +24,15 @@
21 24
22 25 class Importer
23 26 {
24 27 /**
28 + * @var int
29 + */
30 + protected $memory_limit;
31 + /**
25 32 * @var ConfigInterface $config
26 33 */
27 - private $config;
34 + public $config;
28 35
29 36 /**
30 37 * @var MapperInterface $mapper
31 38 */
@@ -60,20 +67,8 @@
60 67 */
61 68 private $filter_data = [];
62 69
63 70 /**
64 - * Are there any dangling records that need fixed.
65 - *
66 - * @var boolean
67 - */
68 - private $is_timeout = false;
69 -
70 - /**
71 - * @var int
72 - */
73 - private $memory_limit;
74 -
75 - /**
76 71 * @param ConfigInterface $config
77 72 */
78 73 public function __construct($config)
79 74 {
@@ -138,8 +133,23 @@
138 133 return $this;
139 134 }
140 135
141 136 /**
137 + * Load JSON File
138 + *
139 + * @param string $file_path
140 + *
141 + * @return $this
142 + */
143 + public function jsonFile($file_path)
144 + {
145 + $file = new JSONFile($file_path, $this->config);
146 + $this->parser = new JSONParser($file);
147 +
148 + return $this;
149 + }
150 +
151 + /**
142 152 * Set record to start importing from
143 153 *
144 154 * @param int $start
145 155 */
@@ -222,9 +232,9 @@
222 232 * Run Import
223 233 *
224 234 * @param int $id Importer Id
225 235 * @param string $user Unique user id
226 - * @param ImporterState $importer_state
236 + * @param ImporterRunnerState $importer_state
227 237 *
228 238 * @throws \Exception
229 239 */
230 240 public function import($id, $user, $importer_state)
@@ -229,13 +239,13 @@
229 239 */
230 240 public function import($id, $user, $importer_state)
231 241 {
232 242 if ($this->parser == null) {
233 - throw new \Exception("Parser Not Loaded.");
243 + throw new \Exception(__("Parser Not Loaded.", 'jc-importer'));
234 244 }
235 245
236 246 if ($this->mapper == null) {
237 - throw new \Exception("Mapper Not Loaded.");
247 + throw new \Exception(__("Mapper Not Loaded.", 'jc-importer'));
238 248 }
239 249
240 250 $this->mapper->setup();
241 251
@@ -240,8 +250,10 @@
240 250 $this->mapper->setup();
241 251
242 252 $this->register_shutdown($importer_state);
243 253
254 + $this->disable_caching();
255 +
244 256 /**
245 257 * @var Util $util
246 258 */
247 259 $util = Container::getInstance()->get('util');
@@ -246,377 +258,360 @@
246 258 */
247 259 $util = Container::getInstance()->get('util');
248 260 $util->set_time_limit();
249 261
262 + // TODO:
263 + // $runner = new ImporterRunner($properties, $this);
264 + // $runner->process($id, $user, $importer_state);
265 + $this->process_chunk($id, $user, $importer_state);
266 +
267 + $this->mapper->teardown();
268 + $this->unregister_shutdown();
269 + }
270 +
271 + protected function disable_caching()
272 + {
273 + if (!defined('WP_IMPORTING')) {
274 + define('WP_IMPORTING', true);
275 + }
276 +
277 + // WP Rocket Integration
278 + add_filter('rocket_is_importing', '__return_true');
279 + }
280 +
281 + protected function process_chunk($id, $user, $importer_state)
282 + {
283 + // Introduce new running state, to stop cron running duplicates
284 + $importer_state->populate([
285 + 'status' => 'processing'
286 + ]);
287 + ImporterState::set_state($id, $importer_state->get_raw());
288 +
250 289 /**
251 290 * @var Properties $properties
252 291 */
253 292 $properties = Container::getInstance()->get('properties');
293 + $time_limit = $properties->get_setting('timeout');
294 + Logger::info('time_limit ' . $time_limit . 's');
254 295
255 - $time_limit = $properties->get_setting('timeout');
256 296 $start = microtime(true);
257 297 $max_record_time = 0;
258 298 $memory_max_usage = 0;
259 - $i = 0;
260 299
261 - // Does this current user have any dangling jobs?
262 - $this->try_import_dangling_rows($id, $user, $importer_state, $user);
300 + $progress = $importer_state->get_progress();
301 + $session = $importer_state->get_session();
302 + $max_total = $progress['end'] - 1;
303 + $i = $progress['start'] + $progress['current_row'] - 1;
263 304
264 - $config = get_site_option('iwp_importer_config_' . $id, []);
305 + // limit to max 20 rows per chunk
306 + $i_max = $i + apply_filters('iwp/chunk_max_records', 20);
265 307
266 308 while (
267 - ($i = 0 || (
268 - ($time_limit === 0 || $this->has_enough_time($start, $time_limit, $max_record_time))
269 - && $this->has_enough_memory($memory_max_usage))
309 + $i < $max_total
310 + && (!defined('REST_REQUEST') || !REST_REQUEST || $i < $i_max)
311 + && (
312 + $time_limit === 0 || $this->has_enough_time($start, $time_limit, $max_record_time)
270 313 )
271 - && $importer_state
272 - && $importer_state->has_section(['import', 'delete', 'timeout'])
314 + && $this->has_enough_memory($memory_max_usage)
273 315 ) {
316 + $i++;
274 317
275 - $memory_usage = $this->get_memory_usage();
318 + $flag = ImporterState::get_flag($id);
276 319
277 - $this->is_timeout = false;
320 + if (ImporterState::is_paused($flag)) {
278 321
279 - $importer_state = $importer_state->update(function ($state) use ($importer_state, $config, $user, $id) {
280 - return $this->setup_importer_state($importer_state, $state, $config, $user, $id);
281 - });
322 + $importer_state->populate([
323 + 'status' => 'paused'
324 + ]);
282 325
283 - if (!$importer_state || $this->is_timeout || !$importer_state->has_status('running')) {
284 - break;
326 + ImporterState::set_state($id, $importer_state->get_raw());
327 + Util::write_status_session_to_file($id, $importer_state);
328 + return;
285 329 }
286 330
287 - $record_time = microtime(true);
288 - $this->import_row($id, $user, $importer_state, $importer_state->get_session(), $importer_state->get_section(), $importer_state->get_progress());
289 - $max_record_time = max($max_record_time, microtime(true) - $record_time);
331 + if (ImporterState::is_cancelled($flag)) {
332 + $importer_state->populate([
333 + 'status' => 'cancelled'
334 + ]);
290 335
291 - if (!wp_using_ext_object_cache()) {
292 - wp_cache_flush();
336 + ImporterState::set_state($id, $importer_state->get_raw());
337 + Util::write_status_session_to_file($id, $importer_state);
338 + return;
293 339 }
294 340
295 - do_action('iwp/importer/shutdown');
341 + $stats = [
342 + 'inserts' => 0,
343 + 'updates' => 0,
344 + 'deletes' => 0,
345 + 'skips' => 0,
346 + 'errors' => 0,
347 + ];
296 348
297 - // keep track of largest memory change
298 - $memory_delta = $this->get_memory_usage() - $memory_usage;
299 - if ($memory_delta > $memory_max_usage) {
300 - $memory_max_usage = $memory_delta;
301 - }
349 + $record_time = microtime(true);
302 350
303 - $i++;
304 - }
351 + if ($importer_state->get_section() === 'import') {
305 352
306 - Util::write_status_session_to_file($id, $importer_state);
353 + /**
354 + * @var ParsedData $data
355 + */
356 + $data = null;
307 357
308 - $this->mapper->teardown();
309 - $this->unregister_shutdown();
310 - }
358 + $data_parser = new DataParser($this->getParser(), $this->getMapper(), $this->config->getData());
311 359
312 - function has_enough_time($start, $time_limit, $max_record_time)
313 - {
314 - return (microtime(true) - $start) < $time_limit - $max_record_time;
315 - }
360 + try {
316 361
317 - function get_memory_usage()
318 - {
319 - return memory_get_usage(true);
320 - }
362 + $data = $data_parser->get($i);
363 + do_action('iwp/importer/before_row', $data);
321 364
322 - function has_enough_memory($memory_max_usage)
323 - {
324 - $limit = $this->get_memory_limit() * 0.9;
325 - $current_usage = $this->get_memory_usage();
365 + $skip_record = $this->filterRecords();
366 + $skip_record = apply_filters('iwp/importer/skip_record', $skip_record, $data, $this);
326 367
327 - if ($current_usage + $memory_max_usage < $limit) {
328 - return true;
329 - }
368 + if ($skip_record) {
330 369
331 - Logger::error(sprintf("Not Enough Memory left to use %s, %s/%s", Logger::formatBytes($memory_max_usage, 2), Logger::formatBytes($current_usage, 2), Logger::formatBytes($limit, 2)));
370 + Logger::write('import -skip-record=' . $i);
332 371
333 - return false;
334 - }
372 + $stats['skips']++;
335 373
336 - function get_memory_limit($force = false)
337 - {
338 - if ($force || is_null($this->memory_limit)) {
374 + // set data to null, to flag chunk as skipped
375 + $message = apply_filters('iwp/status/record_skipped', "Skipped Record");
376 + Util::write_status_log_file_message($id, $session, $message, 'S', $progress['current_row']);
339 377
340 - $memory_limit = ini_get('memory_limit');
341 - if (preg_match('/^(\d+)(.)$/', $memory_limit, $matches)) {
342 - if ($matches[2] == 'G') {
343 - $memory_limit = $matches[1] * 1024 * 1024 * 1024; // nnnM -> nnn MB
344 - } elseif ($matches[2] == 'M') {
345 - $memory_limit = $matches[1] * 1024 * 1024; // nnnM -> nnn MB
346 - } else if ($matches[2] == 'K') {
347 - $memory_limit = $matches[1] * 1024; // nnnK -> nnn KB
348 - }
349 - }
378 + $data = null;
379 + } else {
350 380
351 - $this->memory_limit = $memory_limit;
352 - }
381 + // import
382 + $data = apply_filters('iwp/importer/before_mapper', $data, $this);
383 + $data->map();
353 384
354 - return $this->memory_limit;
355 - }
385 + $unique_identifier_str = $this->get_unique_identifier_log_text();
356 386
357 - function setup_importer_state($importer_state, $state, $config, $user, $id)
358 - {
359 - $importer_state->populate($state);
387 + if ($data->isInsert()) {
360 388
361 - if (!$importer_state->validate($config['id'])) {
362 - throw new \Exception("Importer session has changed");
363 - }
389 + Logger::write('import:' . $i . ' -success -insert');
364 390
365 - if ($importer_state->has_status('running')) {
391 + $stats['inserts']++;
366 392
367 - $section = $importer_state->get_section();
368 - if (isset($state['progress'][$section]) && $state['progress'][$section]['end'] - $state['progress'][$section]['start'] <= $state['progress'][$section]['current_row']) {
393 + $message = apply_filters('iwp/status/record_inserted', 'Record Inserted: #' . $data->getId(), $data->getId(), $data);
394 + Util::write_status_log_file_message($id, $session, $message . $unique_identifier_str, 'S', $progress['current_row']);
395 + }
369 396
370 - // Does this user or any user have any dangling jobs?
371 - $dangling = $this->try_import_dangling_rows($id, $user, $importer_state);
372 - if (!$dangling) {
373 - $this->is_timeout = true;
374 - $state['duration'] = floatval($state['duration']) + Logger::timer();
375 - return $state;
376 - }
397 + if ($data->isUpdate()) {
377 398
378 - switch ($importer_state->get_section()) {
379 - case 'import':
399 + Logger::write('import:' . $i . ' -success -update');
380 400
381 - if ($this->mapper->permission() && $this->mapper->permission()->allowed_method('remove')) {
401 + $stats['updates']++;
382 402
383 - // importer delete
384 - $state['section'] = 'delete';
403 + $message = apply_filters('iwp/status/record_updated', 'Record Updated: #' . $data->getId(), $data->getId(), $data);
404 + Util::write_status_log_file_message($id, $session, $message . $unique_identifier_str, 'S', $progress['current_row']);
405 + }
406 + }
407 + } catch (RecordUpdatedSkippedException $e) {
385 408
386 - // generate list of items to be deleted
387 - $object_ids = $this->mapper->get_objects_for_removal();
409 + Logger::write('import:' . $i . ' -success -update -skipped="hash"');
410 + $stats['updates']++;
411 + $message = 'Record Update Skipped: #' . $data->getId() . ' ' . $e->getMessage();
412 + $unique_identifier_str = $this->get_unique_identifier_log_text();
388 413
389 - $config = get_site_option('iwp_importer_config_' . $id);
390 - $config['delete_ids'] = $object_ids;
391 - update_site_option('iwp_importer_config_' . $id, $config);
414 + Util::write_status_log_file_message($id, $session, $message . $unique_identifier_str, 'S', $progress['current_row']);
415 + } catch (ParserException $e) {
392 416
393 - $state['progress']['delete']['start'] = 0;
394 - $state['progress']['delete']['end'] = $object_ids ? count($object_ids) : 0;
395 - } else {
396 - $state['section'] = '';
397 - $state['status'] = 'complete';
398 - }
417 + $stats['errors']++;
418 + Logger::error('import:' . $i . ' -parser-error=' . $e->getMessage());
419 + Util::write_status_log_file_message($id, $session, $e->getMessage(), 'E', $progress['current_row']);
420 + } catch (MapperException $e) {
399 421
400 - break;
401 - case 'delete':
422 + $stats['errors']++;
423 + Logger::error('import:' . $i . ' -mapper-error=' . $e->getMessage());
424 + Util::write_status_log_file_message($id, $session, $e->getMessage(), 'E', $progress['current_row']);
425 + } catch (FileException $e) {
402 426
403 - // importer complete
404 - $state['section'] = '';
405 - $state['status'] = 'complete';
427 + $stats['errors']++;
428 + Logger::error('import:' . $i . ' -file-error=' . $e->getMessage());
429 + Util::write_status_log_file_message($id, $session, $e->getMessage(), 'E', $progress['current_row']);
430 + }
406 431
407 - break;
408 - }
409 - }
432 + do_action('iwp/importer/after_row');
433 + } elseif ($importer_state->get_section() === 'delete') {
410 434
411 - // Get increase index, locking record, and saving to user importer state
412 - if (!empty($state['section'])) {
413 - $state['progress'][$state['section']]['current_row']++;
414 - update_site_option('iwp_importer_state_' . $id . '_' . $user, array_merge($state, ['last_modified' => current_time('timestamp')]));
415 - }
416 - }
435 + if ($this->getMapper()->permission() && $this->getMapper()->permission()->allowed_method('remove')) {
417 436
418 - $state['duration'] = floatval($state['duration']) + Logger::timer();
437 + try {
438 + $GLOBALS['wp_object_cache']->delete('iwp_importer_config_' . $id, 'options');
439 + $config = get_option('iwp_importer_config_' . $id);
419 440
420 - return $state;
421 - }
441 + $object_ids = $config['delete_ids'];
442 + if ($object_ids && count($object_ids) > $i) {
422 443
423 - function try_import_dangling_rows($id, $user, $importer_state, $user_to_check = null)
424 - {
425 - $dangling = $this->has_dangling_state($id, $user_to_check);
426 - if (!empty($dangling)) {
427 - $fixed = 0;
428 - foreach ($dangling as $dangling_id) {
444 + $object_id = $object_ids[$i];
429 445
430 - // TODO: Should the option be renamed to the current user first? to make sure its not ran multiple times.
431 - // TODO: status should not be overwritten like this.
432 - $GLOBALS['wp_object_cache']->delete($dangling_id, 'options');
433 - $status = get_site_option($dangling_id);
434 - if ($status && $status['last_modified'] < current_time('timestamp') - 30) {
446 + if (apply_filters('iwp/importer/enable_custom_delete_action', false, $id)) {
435 447
436 - Logger::write('try_import_dangling_rows -id=' . $id . ' -user=' . $user . ' -dangling=' . $dangling_id);
448 + Logger::write('custom_delete_action:' . $i . ' -object=' . $object_id);
449 + do_action('iwp/importer/custom_delete_action', $id, $object_id);
450 + } else {
437 451
438 - $GLOBALS['wp_object_cache']->delete($dangling_id, 'options');
439 - $status['last_modified'] = current_time('timestamp');
440 - update_site_option($dangling_id, $status);
452 + Logger::write('delete:' . $i . ' -object=' . $object_id);
453 + $this->getMapper()->delete($object_id);
454 + }
441 455
456 + $message = apply_filters('iwp/status/record_deleted', 'Record Deleted: #' . $object_id, $object_id);
457 + $stats['deletes']++;
442 458
443 - $this->import_row($id, $user, $importer_state, $importer_state->get_session(), $status['section'], $status['progress'][$status['section']]);
444 - delete_site_option($dangling_id);
445 - $fixed++;
459 + Util::write_status_log_file_message($id, $session, $message, 'D', $progress['current_row']);
460 + }
461 + } catch (MapperException $e) {
462 +
463 + $stats['errors']++;
464 + Logger::error('delete:' . $i . ' -mapper-error=' . $e->getMessage());
465 + Util::write_status_log_file_message($id, $session, $e->getMessage(), 'E', $progress['current_row']);
466 + }
446 467 }
447 468 }
448 469
449 - if ($fixed !== count($dangling)) {
450 - // escape due to dangling records that have not timed out
451 - return false;
452 - }
453 - }
470 + $importer_state->update_importer_stats($stats);
471 + Util::write_status_session_to_file($id, $importer_state);
454 472
455 - return true;
456 - }
473 + $importer_state->increment_current_row();
474 + $progress = $importer_state->get_progress();
457 475
458 - function import_row($id, $user, $importer_state, $session, $section, $progress)
459 - {
460 - $stats = [
461 - 'inserts' => 0,
462 - 'updates' => 0,
463 - 'deletes' => 0,
464 - 'skips' => 0,
465 - 'errors' => 0,
466 - ];
476 + ImporterState::set_state($id, $importer_state->get_raw());
467 477
468 - if ($section === 'import') {
478 + $max_record_time = max($max_record_time, microtime(true) - $record_time);
479 + }
469 480
481 + // TODO: need a new state that will stop the running from happening more than once.
482 + // if returning timeout then the cron will stop on older versions
483 + if (defined('IWP_PRO_VERSION') && version_compare(IWP_PRO_VERSION, '2.8.0', '>')) {
484 + // default status to idle after run
485 + $importer_state->populate([
486 + 'status' => 'timeout'
487 + ]);
488 + } else {
489 + $importer_state->populate([
490 + 'status' => 'running'
491 + ]);
492 + }
470 493
471 - // TODO: Run through field map from config (xml or csv)
472 - $data_parser = new DataParser($this->parser, $this->mapper, $this->config->getData());
494 + $state_data = $importer_state->get_raw();
473 495
474 - $i = $progress['start'] + $progress['current_row'] - 1;
496 + $progress = $importer_state->get_progress();
497 + if ($progress['end'] - $progress['start'] <= $progress['current_row']) {
475 498
476 - /**
477 - * @var ParsedData $data
478 - */
479 - $data = null;
499 + switch ($importer_state->get_section()) {
500 + case 'import':
480 501
481 - try {
482 502
483 - $data = $data_parser->get($i);
503 + if ($this->getMapper()->permission() && $this->getMapper()->permission()->allowed_method('remove')) {
484 504
485 - $skip_record = $this->filterRecords();
486 - $skip_record = apply_filters('iwp/importer/skip_record', $skip_record, $data, $this);
505 + // importer delete
506 + $state_data['section'] = 'delete';
487 507
488 - if ($skip_record) {
508 + // generate list of items to be deleted
509 + $object_ids = $this->getMapper()->get_objects_for_removal();
510 + if (!empty($object_ids)) {
489 511
490 - Logger::write('import -skip-record=' . $i);
512 + $config = get_option('iwp_importer_config_' . $id);
513 + $config['delete_ids'] = $object_ids;
514 + update_option('iwp_importer_config_' . $id, $config);
491 515
492 - $stats['skips']++;
516 + $state_data['progress']['delete']['start'] = 0;
517 + $state_data['progress']['delete']['end'] = $object_ids ? count($object_ids) : 0;
518 + } else {
519 + $state_data['section'] = '';
520 + $state_data['status'] = 'complete';
521 + }
522 + } else {
523 + $state_data['section'] = '';
524 + $state_data['status'] = 'complete';
525 + }
493 526
494 - // set data to null, to flag chunk as skipped
495 - Util::write_status_log_file_message($id, $session, "Skipped Record", 'S', $progress['current_row']);
527 + break;
528 + case 'delete':
496 529
497 - $data = null;
498 - } else {
530 + // importer complete
531 + $state_data['section'] = '';
532 + $state_data['status'] = 'complete';
499 533
500 - // import
501 - $data = apply_filters('iwp/importer/before_mapper', $data, $this);
502 - $data->map();
534 + break;
535 + }
536 + }
503 537
504 - if ($data->isInsert()) {
538 + ImporterState::set_state($id, $state_data);
539 + $importer_state->populate($state_data);
505 540
506 - Logger::write('import:' . $i . ' -success -insert');
541 + Util::write_status_session_to_file($id, $importer_state);
542 + }
507 543
508 - $stats['inserts']++;
544 + function get_unique_identifier_log_text()
545 + {
546 + $unique_identifier_str = '';
509 547
510 - $message = apply_filters('iwp/status/record_inserted', 'Record Inserted: #' . $data->getId(), $data->getId(), $data);
511 - Util::write_status_log_file_message($id, $session, $message, 'S', $progress['current_row']);
512 - }
548 + $unqiue_identifier_settings = $this->getMapper()->get_unqiue_identifier_settings();
549 + if (!empty($unqiue_identifier_settings) && isset($unqiue_identifier_settings['field'], $unqiue_identifier_settings['value'])) {
513 550
514 - if ($data->isUpdate()) {
515 -
516 - Logger::write('import:' . $i . ' -success -update');
517 -
518 - $stats['updates']++;
519 -
520 - $message = apply_filters('iwp/status/record_updated', 'Record Updated: #' . $data->getId(), $data->getId(), $data);
521 - Util::write_status_log_file_message($id, $session, $message, 'S', $progress['current_row']);
522 - }
523 - }
524 - } catch (ParserException $e) {
525 -
526 - $stats['errors']++;
527 - Logger::error('import:' . $i . ' -parser-error=' . $e->getMessage());
528 - Util::write_status_log_file_message($id, $session, $e->getMessage(), 'E', $progress['current_row']);
529 - } catch (MapperException $e) {
530 -
531 - $stats['errors']++;
532 - Logger::error('import:' . $i . ' -mapper-error=' . $e->getMessage());
533 - Util::write_status_log_file_message($id, $session, $e->getMessage(), 'E', $progress['current_row']);
534 - } catch (FileException $e) {
535 -
536 - $stats['errors']++;
537 - Logger::error('import:' . $i . ' -file-error=' . $e->getMessage());
538 - Util::write_status_log_file_message($id, $session, $e->getMessage(), 'E', $progress['current_row']);
551 + $unique_identifier_str = ' using unique identifier ';
552 + if ($unqiue_identifier_settings['field'] === '_iwp_ref_uid') {
553 + $unique_identifier_str .= sprintf('("_iwp_ref_uid" = "%s")', $unqiue_identifier_settings['value']);
554 + } else {
555 + $unique_identifier_str .= sprintf('("%s" = "%s")', $unqiue_identifier_settings['field'], $unqiue_identifier_settings['value']);
539 556 }
540 -
541 - $this->update_importer_stats($importer_state, $stats);
542 - Util::write_status_session_to_file($id, $importer_state);
543 -
544 - delete_site_option('iwp_importer_state_' . $id . '_' . $user);
545 - return;
546 557 }
547 558
548 - if ($section === 'delete') {
549 - if ($this->mapper->permission() && $this->mapper->permission()->allowed_method('remove')) {
559 + return $unique_identifier_str;
560 + }
550 561
551 - $GLOBALS['wp_object_cache']->delete('iwp_importer_config_' . $id, 'options');
552 - $config = get_site_option('iwp_importer_config_' . $id);
553 - $i = $progress['current_row'] - 1;
562 + function has_enough_time($start, $time_limit, $max_record_time)
563 + {
564 + return (microtime(true) - $start) < $time_limit - $max_record_time;
565 + }
554 566
555 - $object_ids = $config['delete_ids'];
556 - if ($object_ids && count($object_ids) > $i) {
557 - $object_id = $object_ids[$i];
558 - $this->mapper->delete($object_id);
559 - $stats['deletes']++;
567 + function get_memory_usage()
568 + {
569 + return memory_get_usage(true);
570 + }
560 571
561 - Logger::write('delete:' . $i . ' -object=' . $object_id);
572 + function has_enough_memory($memory_max_usage)
573 + {
574 + $limit = $this->get_memory_limit();
562 575
563 - $message = apply_filters('iwp/status/record_deleted', 'Record Deleted: #' . $object_id, $object_id);
564 - Util::write_status_log_file_message($id, $session, $message, 'D', $progress['current_row']);
565 - }
566 - }
576 + // Has unlimited memory
577 + if ($limit == '-1') {
578 + return true;
579 + }
567 580
568 - $this->update_importer_stats($importer_state, $stats);
569 - Util::write_status_session_to_file($id, $importer_state);
581 + $limit *= 0.9;
582 + $current_usage = $this->get_memory_usage();
570 583
571 - delete_site_option('iwp_importer_state_' . $id . '_' . $user);
572 - return;
584 + if ($current_usage + $memory_max_usage < $limit) {
585 + return true;
573 586 }
574 - }
575 587
576 - function update_importer_stats($importer_state, $stats)
577 - {
578 - $importer_state->update(function ($state) use ($stats) {
579 - if (!isset($state['stats'])) {
580 - $state['stats'] = [
581 - 'inserts' => 0,
582 - 'updates' => 0,
583 - 'deletes' => 0,
584 - 'skips' => 0,
585 - 'errors' => 0,
586 - ];
587 - }
588 + Logger::error(sprintf("Not Enough Memory left to use %s, %s/%s", Logger::formatBytes($memory_max_usage, 2), Logger::formatBytes($current_usage, 2), Logger::formatBytes($limit, 2)));
588 589
589 - $state['stats']['inserts'] += $stats['inserts'];
590 - $state['stats']['updates'] += $stats['updates'];
591 - $state['stats']['deletes'] += $stats['deletes'];
592 - $state['stats']['skips'] += $stats['skips'];
593 - $state['stats']['errors'] += $stats['errors'];
594 -
595 - return $state;
596 - });
590 + return false;
597 591 }
598 592
599 - function has_dangling_state($id, $user = null, $key_prefix = 'iwp_importer_state')
593 + function get_memory_limit($force = false)
600 594 {
601 - /**
602 - * @var \WPDB $wpdb
603 - */
604 - global $wpdb;
595 + if ($force || is_null($this->memory_limit)) {
605 596
606 - $key_prefix = str_replace('_', '\_', $key_prefix);
607 - $query = "SELECT option_name FROM {$wpdb->options} WHERE option_name LIKE '{$key_prefix}\_{$id}\_";
597 + $memory_limit = ini_get('memory_limit');
598 + if (preg_match('/^(\d+)(.)$/', $memory_limit, $matches)) {
599 + if ($matches[2] == 'G') {
600 + $memory_limit = $matches[1] * 1024 * 1024 * 1024; // nnnM -> nnn MB
601 + } elseif ($matches[2] == 'M') {
602 + $memory_limit = $matches[1] * 1024 * 1024; // nnnM -> nnn MB
603 + } else if ($matches[2] == 'K') {
604 + $memory_limit = $matches[1] * 1024; // nnnK -> nnn KB
605 + }
606 + }
608 607
609 - if (!empty($user)) {
610 - $query .= $user;
611 - } else {
612 - $query .= '%';
608 + $this->memory_limit = $memory_limit;
609 +
610 + Logger::info('memory_limit ' . $this->memory_limit . ' bytes');
613 611 }
614 612
615 - $query .= "'";
616 -
617 - $option_names = $wpdb->get_col($query);
618 - return $option_names;
613 + return $this->memory_limit;
619 614 }
620 615
621 616 /**
622 617 * Apply any importer filters to skip records