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 +275 -292 2.7.7 → 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,389 +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');
254 -
255 293 $time_limit = $properties->get_setting('timeout');
256 -
257 294 Logger::info('time_limit ' . $time_limit . 's');
258 295
259 296 $start = microtime(true);
260 297 $max_record_time = 0;
261 298 $memory_max_usage = 0;
262 - $i = 0;
263 299
264 - // Does this current user have any dangling jobs?
265 - $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;
266 304
267 - $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);
268 307
269 308 while (
270 - ($i == 0 || (
271 - ($time_limit === 0 || $this->has_enough_time($start, $time_limit, $max_record_time))
272 - && $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)
273 313 )
274 - && $importer_state
275 - && $importer_state->has_section(['import', 'delete', 'timeout'])
314 + && $this->has_enough_memory($memory_max_usage)
276 315 ) {
316 + $i++;
277 317
278 - $memory_usage = $this->get_memory_usage();
318 + $flag = ImporterState::get_flag($id);
279 319
280 - $this->is_timeout = false;
320 + if (ImporterState::is_paused($flag)) {
281 321
282 - $importer_state = $importer_state->update(function ($state) use ($importer_state, $config, $user, $id) {
283 - return $this->setup_importer_state($importer_state, $state, $config, $user, $id);
284 - });
322 + $importer_state->populate([
323 + 'status' => 'paused'
324 + ]);
285 325
286 - if (!$importer_state || $this->is_timeout || !$importer_state->has_status('running')) {
287 - break;
326 + ImporterState::set_state($id, $importer_state->get_raw());
327 + Util::write_status_session_to_file($id, $importer_state);
328 + return;
288 329 }
289 330
290 - $record_time = microtime(true);
291 - $this->import_row($id, $user, $importer_state, $importer_state->get_session(), $importer_state->get_section(), $importer_state->get_progress());
292 - $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 + ]);
293 335
294 - if (!wp_using_ext_object_cache()) {
295 - wp_cache_flush();
336 + ImporterState::set_state($id, $importer_state->get_raw());
337 + Util::write_status_session_to_file($id, $importer_state);
338 + return;
296 339 }
297 340
298 - do_action('iwp/importer/shutdown');
341 + $stats = [
342 + 'inserts' => 0,
343 + 'updates' => 0,
344 + 'deletes' => 0,
345 + 'skips' => 0,
346 + 'errors' => 0,
347 + ];
299 348
300 - // keep track of largest memory change
301 - $memory_delta = $this->get_memory_usage() - $memory_usage;
302 - if ($memory_delta > $memory_max_usage) {
303 - $memory_max_usage = $memory_delta;
304 - }
349 + $record_time = microtime(true);
305 350
306 - $i++;
307 - }
351 + if ($importer_state->get_section() === 'import') {
308 352
309 - Util::write_status_session_to_file($id, $importer_state);
353 + /**
354 + * @var ParsedData $data
355 + */
356 + $data = null;
310 357
311 - $this->mapper->teardown();
312 - $this->unregister_shutdown();
313 - }
358 + $data_parser = new DataParser($this->getParser(), $this->getMapper(), $this->config->getData());
314 359
315 - function has_enough_time($start, $time_limit, $max_record_time)
316 - {
317 - return (microtime(true) - $start) < $time_limit - $max_record_time;
318 - }
360 + try {
319 361
320 - function get_memory_usage()
321 - {
322 - return memory_get_usage(true);
323 - }
362 + $data = $data_parser->get($i);
363 + do_action('iwp/importer/before_row', $data);
324 364
325 - function has_enough_memory($memory_max_usage)
326 - {
327 - $limit = $this->get_memory_limit();
365 + $skip_record = $this->filterRecords();
366 + $skip_record = apply_filters('iwp/importer/skip_record', $skip_record, $data, $this);
328 367
329 - // Has unlimited memory
330 - if ($limit == '-1') {
331 - return true;
332 - }
368 + if ($skip_record) {
333 369
334 - $limit *= 0.9;
335 - $current_usage = $this->get_memory_usage();
370 + Logger::write('import -skip-record=' . $i);
336 371
337 - if ($current_usage + $memory_max_usage < $limit) {
338 - return true;
339 - }
372 + $stats['skips']++;
340 373
341 - 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)));
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']);
342 377
343 - return false;
344 - }
378 + $data = null;
379 + } else {
345 380
346 - function get_memory_limit($force = false)
347 - {
348 - if ($force || is_null($this->memory_limit)) {
381 + // import
382 + $data = apply_filters('iwp/importer/before_mapper', $data, $this);
383 + $data->map();
349 384
350 - $memory_limit = ini_get('memory_limit');
351 - if (preg_match('/^(\d+)(.)$/', $memory_limit, $matches)) {
352 - if ($matches[2] == 'G') {
353 - $memory_limit = $matches[1] * 1024 * 1024 * 1024; // nnnM -> nnn MB
354 - } elseif ($matches[2] == 'M') {
355 - $memory_limit = $matches[1] * 1024 * 1024; // nnnM -> nnn MB
356 - } else if ($matches[2] == 'K') {
357 - $memory_limit = $matches[1] * 1024; // nnnK -> nnn KB
358 - }
359 - }
385 + $unique_identifier_str = $this->get_unique_identifier_log_text();
360 386
361 - $this->memory_limit = $memory_limit;
387 + if ($data->isInsert()) {
362 388
363 - Logger::info('memory_limit ' . $this->memory_limit . ' bytes');
364 - }
389 + Logger::write('import:' . $i . ' -success -insert');
365 390
366 - return $this->memory_limit;
367 - }
391 + $stats['inserts']++;
368 392
369 - function setup_importer_state($importer_state, $state, $config, $user, $id)
370 - {
371 - $importer_state->populate($state);
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 + }
372 396
373 - if (!$importer_state->validate($config['id'])) {
374 - throw new \Exception("Importer session has changed");
375 - }
397 + if ($data->isUpdate()) {
376 398
377 - if ($importer_state->has_status('running')) {
399 + Logger::write('import:' . $i . ' -success -update');
378 400
379 - $section = $importer_state->get_section();
380 - if (isset($state['progress'][$section]) && $state['progress'][$section]['end'] - $state['progress'][$section]['start'] <= $state['progress'][$section]['current_row']) {
401 + $stats['updates']++;
381 402
382 - // Does this user or any user have any dangling jobs?
383 - $dangling = $this->try_import_dangling_rows($id, $user, $importer_state);
384 - if (!$dangling) {
385 - $this->is_timeout = true;
386 - $state['duration'] = floatval($state['duration']) + Logger::timer();
387 - return $state;
388 - }
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) {
389 408
390 - switch ($importer_state->get_section()) {
391 - case 'import':
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();
392 413
393 - if ($this->mapper->permission() && $this->mapper->permission()->allowed_method('remove')) {
414 + Util::write_status_log_file_message($id, $session, $message . $unique_identifier_str, 'S', $progress['current_row']);
415 + } catch (ParserException $e) {
394 416
395 - // importer delete
396 - $state['section'] = 'delete';
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) {
397 421
398 - // generate list of items to be deleted
399 - $object_ids = $this->mapper->get_objects_for_removal();
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) {
400 426
401 - $config = get_site_option('iwp_importer_config_' . $id);
402 - $config['delete_ids'] = $object_ids;
403 - update_site_option('iwp_importer_config_' . $id, $config);
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 + }
404 431
405 - $state['progress']['delete']['start'] = 0;
406 - $state['progress']['delete']['end'] = $object_ids ? count($object_ids) : 0;
407 - } else {
408 - $state['section'] = '';
409 - $state['status'] = 'complete';
410 - }
432 + do_action('iwp/importer/after_row');
433 + } elseif ($importer_state->get_section() === 'delete') {
411 434
412 - break;
413 - case 'delete':
435 + if ($this->getMapper()->permission() && $this->getMapper()->permission()->allowed_method('remove')) {
414 436
415 - // importer complete
416 - $state['section'] = '';
417 - $state['status'] = 'complete';
437 + try {
438 + $GLOBALS['wp_object_cache']->delete('iwp_importer_config_' . $id, 'options');
439 + $config = get_option('iwp_importer_config_' . $id);
418 440
419 - break;
420 - }
421 - }
441 + $object_ids = $config['delete_ids'];
442 + if ($object_ids && count($object_ids) > $i) {
422 443
423 - // Get increase index, locking record, and saving to user importer state
424 - if (!empty($state['section'])) {
425 - $state['progress'][$state['section']]['current_row']++;
426 - update_site_option('iwp_importer_state_' . $id . '_' . $user, array_merge($state, ['last_modified' => current_time('timestamp')]));
427 - }
428 - }
444 + $object_id = $object_ids[$i];
429 445
430 - $state['duration'] = floatval($state['duration']) + Logger::timer();
446 + if (apply_filters('iwp/importer/enable_custom_delete_action', false, $id)) {
431 447
432 - return $state;
433 - }
448 + Logger::write('custom_delete_action:' . $i . ' -object=' . $object_id);
449 + do_action('iwp/importer/custom_delete_action', $id, $object_id);
450 + } else {
434 451
435 - function try_import_dangling_rows($id, $user, $importer_state, $user_to_check = null)
436 - {
437 - $dangling = $this->has_dangling_state($id, $user_to_check);
438 - if (!empty($dangling)) {
439 - $fixed = 0;
440 - foreach ($dangling as $dangling_id) {
452 + Logger::write('delete:' . $i . ' -object=' . $object_id);
453 + $this->getMapper()->delete($object_id);
454 + }
441 455
442 - // TODO: Should the option be renamed to the current user first? to make sure its not ran multiple times.
443 - // TODO: status should not be overwritten like this.
444 - $GLOBALS['wp_object_cache']->delete($dangling_id, 'options');
445 - $status = get_site_option($dangling_id);
446 - if ($status && $status['last_modified'] < current_time('timestamp') - 30) {
456 + $message = apply_filters('iwp/status/record_deleted', 'Record Deleted: #' . $object_id, $object_id);
457 + $stats['deletes']++;
447 458
448 - Logger::write('try_import_dangling_rows -id=' . $id . ' -user=' . $user . ' -dangling=' . $dangling_id);
459 + Util::write_status_log_file_message($id, $session, $message, 'D', $progress['current_row']);
460 + }
461 + } catch (MapperException $e) {
449 462
450 - $GLOBALS['wp_object_cache']->delete($dangling_id, 'options');
451 - $status['last_modified'] = current_time('timestamp');
452 - update_site_option($dangling_id, $status);
453 -
454 -
455 - $this->import_row($id, $user, $importer_state, $importer_state->get_session(), $status['section'], $status['progress'][$status['section']]);
456 - delete_site_option($dangling_id);
457 - $fixed++;
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 + }
458 467 }
459 468 }
460 469
461 - if ($fixed !== count($dangling)) {
462 - // escape due to dangling records that have not timed out
463 - return false;
464 - }
465 - }
470 + $importer_state->update_importer_stats($stats);
471 + Util::write_status_session_to_file($id, $importer_state);
466 472
467 - return true;
468 - }
473 + $importer_state->increment_current_row();
474 + $progress = $importer_state->get_progress();
469 475
470 - function import_row($id, $user, $importer_state, $session, $section, $progress)
471 - {
472 - $stats = [
473 - 'inserts' => 0,
474 - 'updates' => 0,
475 - 'deletes' => 0,
476 - 'skips' => 0,
477 - 'errors' => 0,
478 - ];
476 + ImporterState::set_state($id, $importer_state->get_raw());
479 477
480 - if ($section === 'import') {
478 + $max_record_time = max($max_record_time, microtime(true) - $record_time);
479 + }
481 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 + }
482 493
483 - // TODO: Run through field map from config (xml or csv)
484 - $data_parser = new DataParser($this->parser, $this->mapper, $this->config->getData());
494 + $state_data = $importer_state->get_raw();
485 495
486 - $i = $progress['start'] + $progress['current_row'] - 1;
496 + $progress = $importer_state->get_progress();
497 + if ($progress['end'] - $progress['start'] <= $progress['current_row']) {
487 498
488 - /**
489 - * @var ParsedData $data
490 - */
491 - $data = null;
499 + switch ($importer_state->get_section()) {
500 + case 'import':
492 501
493 - try {
494 502
495 - $data = $data_parser->get($i);
503 + if ($this->getMapper()->permission() && $this->getMapper()->permission()->allowed_method('remove')) {
496 504
497 - $skip_record = $this->filterRecords();
498 - $skip_record = apply_filters('iwp/importer/skip_record', $skip_record, $data, $this);
505 + // importer delete
506 + $state_data['section'] = 'delete';
499 507
500 - 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)) {
501 511
502 - 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);
503 515
504 - $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 + }
505 526
506 - // set data to null, to flag chunk as skipped
507 - Util::write_status_log_file_message($id, $session, "Skipped Record", 'S', $progress['current_row']);
527 + break;
528 + case 'delete':
508 529
509 - $data = null;
510 - } else {
530 + // importer complete
531 + $state_data['section'] = '';
532 + $state_data['status'] = 'complete';
511 533
512 - // import
513 - $data = apply_filters('iwp/importer/before_mapper', $data, $this);
514 - $data->map();
534 + break;
535 + }
536 + }
515 537
516 - if ($data->isInsert()) {
538 + ImporterState::set_state($id, $state_data);
539 + $importer_state->populate($state_data);
517 540
518 - Logger::write('import:' . $i . ' -success -insert');
541 + Util::write_status_session_to_file($id, $importer_state);
542 + }
519 543
520 - $stats['inserts']++;
544 + function get_unique_identifier_log_text()
545 + {
546 + $unique_identifier_str = '';
521 547
522 - $message = apply_filters('iwp/status/record_inserted', 'Record Inserted: #' . $data->getId(), $data->getId(), $data);
523 - Util::write_status_log_file_message($id, $session, $message, 'S', $progress['current_row']);
524 - }
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'])) {
525 550
526 - if ($data->isUpdate()) {
527 -
528 - Logger::write('import:' . $i . ' -success -update');
529 -
530 - $stats['updates']++;
531 -
532 - $message = apply_filters('iwp/status/record_updated', 'Record Updated: #' . $data->getId(), $data->getId(), $data);
533 - Util::write_status_log_file_message($id, $session, $message, 'S', $progress['current_row']);
534 - }
535 - }
536 - } catch (ParserException $e) {
537 -
538 - $stats['errors']++;
539 - Logger::error('import:' . $i . ' -parser-error=' . $e->getMessage());
540 - Util::write_status_log_file_message($id, $session, $e->getMessage(), 'E', $progress['current_row']);
541 - } catch (MapperException $e) {
542 -
543 - $stats['errors']++;
544 - Logger::error('import:' . $i . ' -mapper-error=' . $e->getMessage());
545 - Util::write_status_log_file_message($id, $session, $e->getMessage(), 'E', $progress['current_row']);
546 - } catch (FileException $e) {
547 -
548 - $stats['errors']++;
549 - Logger::error('import:' . $i . ' -file-error=' . $e->getMessage());
550 - 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']);
551 556 }
552 -
553 - $this->update_importer_stats($importer_state, $stats);
554 - Util::write_status_session_to_file($id, $importer_state);
555 -
556 - delete_site_option('iwp_importer_state_' . $id . '_' . $user);
557 - return;
558 557 }
559 558
560 - if ($section === 'delete') {
561 - if ($this->mapper->permission() && $this->mapper->permission()->allowed_method('remove')) {
559 + return $unique_identifier_str;
560 + }
562 561
563 - $GLOBALS['wp_object_cache']->delete('iwp_importer_config_' . $id, 'options');
564 - $config = get_site_option('iwp_importer_config_' . $id);
565 - $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 + }
566 566
567 - $object_ids = $config['delete_ids'];
568 - if ($object_ids && count($object_ids) > $i) {
569 - $object_id = $object_ids[$i];
570 - $this->mapper->delete($object_id);
571 - $stats['deletes']++;
567 + function get_memory_usage()
568 + {
569 + return memory_get_usage(true);
570 + }
572 571
573 - Logger::write('delete:' . $i . ' -object=' . $object_id);
572 + function has_enough_memory($memory_max_usage)
573 + {
574 + $limit = $this->get_memory_limit();
574 575
575 - $message = apply_filters('iwp/status/record_deleted', 'Record Deleted: #' . $object_id, $object_id);
576 - Util::write_status_log_file_message($id, $session, $message, 'D', $progress['current_row']);
577 - }
578 - }
576 + // Has unlimited memory
577 + if ($limit == '-1') {
578 + return true;
579 + }
579 580
580 - $this->update_importer_stats($importer_state, $stats);
581 - Util::write_status_session_to_file($id, $importer_state);
581 + $limit *= 0.9;
582 + $current_usage = $this->get_memory_usage();
582 583
583 - delete_site_option('iwp_importer_state_' . $id . '_' . $user);
584 - return;
584 + if ($current_usage + $memory_max_usage < $limit) {
585 + return true;
585 586 }
586 - }
587 587
588 - function update_importer_stats($importer_state, $stats)
589 - {
590 - $importer_state->update(function ($state) use ($stats) {
591 - if (!isset($state['stats'])) {
592 - $state['stats'] = [
593 - 'inserts' => 0,
594 - 'updates' => 0,
595 - 'deletes' => 0,
596 - 'skips' => 0,
597 - 'errors' => 0,
598 - ];
599 - }
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)));
600 589
601 - $state['stats']['inserts'] += $stats['inserts'];
602 - $state['stats']['updates'] += $stats['updates'];
603 - $state['stats']['deletes'] += $stats['deletes'];
604 - $state['stats']['skips'] += $stats['skips'];
605 - $state['stats']['errors'] += $stats['errors'];
606 -
607 - return $state;
608 - });
590 + return false;
609 591 }
610 592
611 - function has_dangling_state($id, $user = null, $key_prefix = 'iwp_importer_state')
593 + function get_memory_limit($force = false)
612 594 {
613 - /**
614 - * @var \WPDB $wpdb
615 - */
616 - global $wpdb;
595 + if ($force || is_null($this->memory_limit)) {
617 596
618 - $key_prefix = str_replace('_', '\_', $key_prefix);
619 - $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 + }
620 607
621 - if (!empty($user)) {
622 - $query .= $user;
623 - } else {
624 - $query .= '%';
608 + $this->memory_limit = $memory_limit;
609 +
610 + Logger::info('memory_limit ' . $this->memory_limit . ' bytes');
625 611 }
626 612
627 - $query .= "'";
628 -
629 - $option_names = $wpdb->get_col($query);
630 - return $option_names;
613 + return $this->memory_limit;
631 614 }
632 615
633 616 /**
634 617 * Apply any importer filters to skip records