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