Passed
Push — master ( d0bb48...dda91a )
by Aimeos
01:52
created

Standard   A

Complexity

Total Complexity 33

Size/Duplication

Total Lines 608
Duplicated Lines 0 %

Importance

Changes 0
Metric Value
eloc 122
dl 0
loc 608
rs 9.76
c 0
b 0
f 0
wmc 33

8 Methods

Rating   Name   Duplication   Size   Complexity  
A getName() 0 3 1
A getDescription() 0 3 1
A getCatalogMap() 0 11 2
A getCodePosition() 0 10 3
B import() 0 63 8
C run() 0 326 11
A getParentId() 0 17 5
A getContainer() 0 97 2
1
<?php
2
3
/**
4
 * @license LGPLv3, http://opensource.org/licenses/LGPL-3.0
5
 * @copyright Aimeos (aimeos.org), 2018
6
 * @package Controller
7
 * @subpackage Jobs
8
 */
9
10
11
namespace Aimeos\Controller\Jobs\Catalog\Import\Csv;
12
13
14
/**
15
 * Job controller for CSV catalog imports.
16
 *
17
 * @package Controller
18
 * @subpackage Jobs
19
 */
20
class Standard
21
	extends \Aimeos\Controller\Common\Catalog\Import\Csv\Base
22
	implements \Aimeos\Controller\Jobs\Iface
23
{
24
	/**
25
	 * Returns the localized name of the job.
26
	 *
27
	 * @return string Name of the job
28
	 */
29
	public function getName()
30
	{
31
		return $this->getContext()->getI18n()->dt( 'controller/jobs', 'Catalog import CSV' );
32
	}
33
34
35
	/**
36
	 * Returns the localized description of the job.
37
	 *
38
	 * @return string Description of the job
39
	 */
40
	public function getDescription()
41
	{
42
		return $this->getContext()->getI18n()->dt( 'controller/jobs', 'Imports new and updates existing categories from CSV files' );
43
	}
44
45
46
	/**
47
	 * Executes the job.
48
	 *
49
	 * @throws \Aimeos\Controller\Jobs\Exception If an error occurs
50
	 */
51
	public function run()
52
	{
53
		$total = $errors = 0;
54
		$context = $this->getContext();
55
		$config = $context->getConfig();
56
		$logger = $context->getLogger();
57
		$domains = array( 'media', 'text' );
58
		$mappings = $this->getDefaultMapping();
59
60
61
		if( file_exists( $config->get( 'controller/jobs/catalog/import/csv/location' ) ) === false ) {
62
			return;
63
		}
64
65
66
		/** controller/common/catalog/import/csv/domains
67
		 * List of item domain names that should be retrieved along with the catalog items
68
		 *
69
		 * For efficient processing, the items associated to the catalogs can be
70
		 * fetched to, minimizing the number of database queries required. To be
71
		 * most effective, the list of item domain names should be used in the
72
		 * mapping configuration too, so the retrieved items will be used during
73
		 * the import.
74
		 *
75
		 * @param array Associative list of MShop item domain names
76
		 * @since 2018.04
77
		 * @category Developer
78
		 * @see controller/common/catalog/import/csv/mapping
79
		 * @see controller/common/catalog/import/csv/converter
80
		 * @see controller/common/catalog/import/csv/max-size
81
		 */
82
		$domains = $config->get( 'controller/common/catalog/import/csv/domains', $domains );
83
84
		/** controller/jobs/catalog/import/csv/domains
85
		 * List of item domain names that should be retrieved along with the catalog items
86
		 *
87
		 * This configuration setting overwrites the shared option
88
		 * "controller/common/catalog/import/csv/domains" if you need a
89
		 * specific setting for the job controller. Otherwise, you should
90
		 * use the shared option for consistency.
91
		 *
92
		 * @param array Associative list of MShop item domain names
93
		 * @since 2018.04
94
		 * @category Developer
95
		 * @see controller/jobs/catalog/import/csv/mapping
96
		 * @see controller/jobs/catalog/import/csv/skip-lines
97
		 * @see controller/jobs/catalog/import/csv/converter
98
		 * @see controller/jobs/catalog/import/csv/strict
99
		 * @see controller/jobs/catalog/import/csv/backup
100
		 * @see controller/common/catalog/import/csv/max-size
101
		 */
102
		$domains = $config->get( 'controller/jobs/catalog/import/csv/domains', $domains );
103
104
105
		/** controller/common/catalog/import/csv/mapping
106
		 * List of mappings between the position in the CSV file and item keys
107
		 *
108
		 * The importer have to know which data is at which position in the CSV
109
		 * file. Therefore, you need to specify a mapping between each position
110
		 * and the MShop domain item key (e.g. "catalog.code") it represents.
111
		 *
112
		 * You can use all domain item keys which are used in the fromArray()
113
		 * methods of the item classes.
114
		 *
115
		 * These mappings are grouped together by their processor names, which
116
		 * are responsible for importing the data, e.g. all mappings in "item"
117
		 * will be processed by the base catalog importer while the mappings in
118
		 * "text" will be imported by the text processor.
119
		 *
120
		 * @param array Associative list of processor names and lists of key/position pairs
121
		 * @since 2018.04
122
		 * @category Developer
123
		 * @see controller/common/catalog/import/csv/domains
124
		 * @see controller/common/catalog/import/csv/converter
125
		 * @see controller/common/catalog/import/csv/max-size
126
		 */
127
		$mappings = $config->get( 'controller/common/catalog/import/csv/mapping', $mappings );
128
129
		/** controller/jobs/catalog/import/csv/mapping
130
		 * List of mappings between the position in the CSV file and item keys
131
		 *
132
		 * This configuration setting overwrites the shared option
133
		 * "controller/common/catalog/import/csv/mapping" if you need a
134
		 * specific setting for the job controller. Otherwise, you should
135
		 * use the shared option for consistency.
136
		 *
137
		 * @param array Associative list of processor names and lists of key/position pairs
138
		 * @since 2018.04
139
		 * @category Developer
140
		 * @see controller/jobs/catalog/import/csv/domains
141
		 * @see controller/jobs/catalog/import/csv/skip-lines
142
		 * @see controller/jobs/catalog/import/csv/converter
143
		 * @see controller/jobs/catalog/import/csv/strict
144
		 * @see controller/jobs/catalog/import/csv/backup
145
		 * @see controller/common/catalog/import/csv/max-size
146
		 */
147
		$mappings = $config->get( 'controller/jobs/catalog/import/csv/mapping', $mappings );
148
149
150
		/** controller/common/catalog/import/csv/converter
151
		 * List of converter names for the values at the position in the CSV file
152
		 *
153
		 * Not all data in the CSV file is already in the required format. Maybe
154
		 * the text encoding isn't UTF-8, the date is not in ISO format or something
155
		 * similar. In order to convert the data before it's imported, you can
156
		 * specify a list of converter objects that should be applied to the data
157
		 * from the CSV file.
158
		 *
159
		 * To each field in the CSV file, you can apply one or more converters,
160
		 * e.g. to encode a Latin text to UTF8 for the second CSV field:
161
		 *
162
		 *  array( 1 => 'Text/LatinUTF8' )
163
		 *
164
		 * Similarly, you can also apply several converters at once to the same
165
		 * field:
166
		 *
167
		 *  array( 1 => array( 'Text/LatinUTF8', 'DateTime/EnglishISO' ) )
168
		 *
169
		 * It would convert the data of the second CSV field first to UTF-8 and
170
		 * afterwards try to translate it to an ISO date format.
171
		 *
172
		 * The available converter objects are named "\Aimeos\MW\Convert\<type>_<conversion>"
173
		 * where <type> is the data type and <conversion> the way of the conversion.
174
		 * In the configuration, the type and conversion must be separated by a
175
		 * slash (<type>/<conversion>).
176
		 *
177
		 * '''Note:''' Keep in mind that the position of the CSV fields start at
178
		 * zero (0). If you only need to convert a few fields, you don't have to
179
		 * configure all fields. Only specify the positions in the array you
180
		 * really need!
181
		 *
182
		 * @param array Associative list of position/converter name (or list of names) pairs
183
		 * @since 2018.04
184
		 * @category Developer
185
		 * @see controller/common/catalog/import/csv/domains
186
		 * @see controller/common/catalog/import/csv/mapping
187
		 * @see controller/common/catalog/import/csv/max-size
188
		 */
189
		$converters = $config->get( 'controller/common/catalog/import/csv/converter', [] );
190
191
		/** controller/jobs/catalog/import/csv/converter
192
		 * List of converter names for the values at the position in the CSV file
193
		 *
194
		 * This configuration setting overwrites the shared option
195
		 * "controller/common/catalog/import/csv/converter" if you need a
196
		 * specific setting for the job controller. Otherwise, you should
197
		 * use the shared option for consistency.
198
		 *
199
		 * @param array Associative list of position/converter name (or list of names) pairs
200
		 * @since 2018.04
201
		 * @category Developer
202
		 * @see controller/jobs/catalog/import/csv/domains
203
		 * @see controller/jobs/catalog/import/csv/mapping
204
		 * @see controller/jobs/catalog/import/csv/skip-lines
205
		 * @see controller/jobs/catalog/import/csv/strict
206
		 * @see controller/jobs/catalog/import/csv/backup
207
		 * @see controller/common/catalog/import/csv/max-size
208
		 */
209
		$converters = $config->get( 'controller/jobs/catalog/import/csv/converter', $converters );
210
211
212
		/** controller/common/catalog/import/csv/max-size
213
		 * Maximum number of CSV rows to import at once
214
		 *
215
		 * It's more efficient to read and import more than one row at a time
216
		 * to speed up the import. Usually, the bigger the chunk that is imported
217
		 * at once, the less time the importer will need. The downside is that
218
		 * the amount of memory required by the import process will increase as
219
		 * well. Therefore, it's a trade-off between memory consumption and
220
		 * import speed.
221
		 *
222
		 * @param integer Number of rows
223
		 * @since 2018.04
224
		 * @category Developer
225
		 * @see controller/common/catalog/import/csv/domains
226
		 * @see controller/common/catalog/import/csv/mapping
227
		 * @see controller/common/catalog/import/csv/converter
228
		 */
229
		$maxcnt = (int) $config->get( 'controller/common/catalog/import/csv/max-size', 1000 );
230
231
232
		/** controller/jobs/catalog/import/csv/skip-lines
233
		 * Number of rows skipped in front of each CSV files
234
		 *
235
		 * Some CSV files contain header information describing the content of
236
		 * the column values. These data is for informational purpose only and
237
		 * can't be imported into the database. Using this option, you can
238
		 * define the number of lines that should be left out before the import
239
		 * begins.
240
		 *
241
		 * @param integer Number of rows
242
		 * @since 2015.08
243
		 * @category Developer
244
		 * @see controller/jobs/catalog/import/csv/domains
245
		 * @see controller/jobs/catalog/import/csv/mapping
246
		 * @see controller/jobs/catalog/import/csv/converter
247
		 * @see controller/jobs/catalog/import/csv/strict
248
		 * @see controller/jobs/catalog/import/csv/backup
249
		 * @see controller/common/catalog/import/csv/max-size
250
		 */
251
		$skiplines = (int) $config->get( 'controller/jobs/catalog/import/csv/skip-lines', 0 );
252
253
254
		/** controller/jobs/catalog/import/csv/strict
255
		 * Log all columns from the file that are not mapped and therefore not imported
256
		 *
257
		 * Depending on the mapping, there can be more columns in the CSV file
258
		 * than those which will be imported. This can be by purpose if you want
259
		 * to import only selected columns or if you've missed to configure one
260
		 * or more columns. This configuration option will log all columns that
261
		 * have not been imported if set to true. Otherwise, the left over fields
262
		 * in the imported line will be silently ignored.
263
		 *
264
		 * @param boolen True if not imported columns should be logged, false if not
265
		 * @since 2015.08
266
		 * @category User
267
		 * @category Developer
268
		 * @see controller/jobs/catalog/import/csv/domains
269
		 * @see controller/jobs/catalog/import/csv/mapping
270
		 * @see controller/jobs/catalog/import/csv/skip-lines
271
		 * @see controller/jobs/catalog/import/csv/converter
272
		 * @see controller/jobs/catalog/import/csv/backup
273
		 * @see controller/common/catalog/import/csv/max-size
274
		 */
275
		$strict = (bool) $config->get( 'controller/jobs/catalog/import/csv/strict', true );
276
277
278
		/** controller/jobs/catalog/import/csv/backup
279
		 * Name of the backup for sucessfully imported files
280
		 *
281
		 * After a CSV file was imported successfully, you can move it to another
282
		 * location, so it won't be imported again and isn't overwritten by the
283
		 * next file that is stored at the same location in the file system.
284
		 *
285
		 * You should use an absolute path to be sure but can be relative path
286
		 * if you absolutely know from where the job will be executed from. The
287
		 * name of the new backup location can contain placeholders understood
288
		 * by the PHP strftime() function to create dynamic paths, e.g. "backup/%Y-%m-%d"
289
		 * which would create "backup/2000-01-01". For more information about the
290
		 * strftime() placeholders, please have a look into the PHP documentation of
291
		 * the {@link http://php.net/manual/en/function.strftime.php strftime() function}.
292
		 *
293
		 * '''Note:''' If no backup name is configured, the file or directory
294
		 * won't be moved away. Please make also sure that the parent directory
295
		 * and the new directory are writable so the file or directory could be
296
		 * moved.
297
		 *
298
		 * @param integer Name of the backup file, optionally with date/time placeholders
299
		 * @since 2018.04
300
		 * @category Developer
301
		 * @see controller/jobs/catalog/import/csv/domains
302
		 * @see controller/jobs/catalog/import/csv/mapping
303
		 * @see controller/jobs/catalog/import/csv/skip-lines
304
		 * @see controller/jobs/catalog/import/csv/converter
305
		 * @see controller/jobs/catalog/import/csv/strict
306
		 * @see controller/common/catalog/import/csv/max-size
307
		 */
308
		$backup = $config->get( 'controller/jobs/catalog/import/csv/backup' );
309
310
311
		if( !isset( $mappings['item'] ) || !is_array( $mappings['item'] ) )
312
		{
313
			$msg = sprintf( 'Required mapping key "%1$s" is missing or contains no array', 'item' );
314
			throw new \Aimeos\Controller\Jobs\Exception( $msg );
315
		}
316
317
		try
318
		{
319
			$procMappings = $mappings;
320
			unset( $procMappings['item'] );
321
322
			$codePos = $this->getCodePosition( $mappings['item'] );
323
			$convlist = $this->getConverterList( $converters );
324
			$processor = $this->getProcessors( $procMappings );
325
			$catalogMap = $this->getCatalogMap( $domains );
326
			$container = $this->getContainer();
327
			$path = $container->getName();
328
329
330
			$msg = sprintf( 'Started catalog import from "%1$s" (%2$s)', $path, __CLASS__ );
331
			$logger->log( $msg, \Aimeos\MW\Logger\Base::NOTICE );
332
333
			foreach( $container as $content )
334
			{
335
				$name = $content->getName();
336
337
				for( $i = 0; $i < $skiplines; $i++ ) {
338
					$content->next();
339
				}
340
341
				while( ( $data = $this->getData( $content, $maxcnt, $codePos ) ) !== [] )
342
				{
343
					$data = $this->convertData( $convlist, $data );
344
					$errcnt = $this->import( $catalogMap, $data, $mappings['item'], $processor, $strict );
345
					$chunkcnt = count( $data );
346
347
					$msg = 'Imported catalog lines from "%1$s": %2$d/%3$d (%4$s)';
348
					$logger->log( sprintf( $msg, $name, $chunkcnt - $errcnt, $chunkcnt, __CLASS__ ), \Aimeos\MW\Logger\Base::NOTICE );
349
350
					$errors += $errcnt;
351
					$total += $chunkcnt;
352
					unset( $data );
353
				}
354
			}
355
356
			$container->close();
357
		}
358
		catch( \Exception $e )
359
		{
360
			$logger->log( 'Catalog import error: ' . $e->getMessage() );
361
			$logger->log( $e->getTraceAsString() );
362
363
			throw new \Aimeos\Controller\Jobs\Exception( $e->getMessage() );
364
		}
365
366
		$msg = 'Finished catalog import from "%1$s": %2$d successful, %3$s errors, %4$s total (%5$s)';
367
		$logger->log( sprintf( $msg, $path, $total - $errors, $errors, $total, __CLASS__ ), \Aimeos\MW\Logger\Base::NOTICE );
368
369
		if( $errors > 0 )
370
		{
371
			$msg = sprintf( 'Invalid catalog lines in "%1$s": %2$d/%3$d', $path, $errors, $total );
372
			throw new \Aimeos\Controller\Jobs\Exception( $msg );
373
		}
374
375
		if( !empty( $backup ) && @rename( $path, strftime( $backup ) ) === false ) {
376
			throw new \Aimeos\Controller\Jobs\Exception( sprintf( 'Unable to move imported file' ) );
377
		}
378
	}
379
380
381
	/**
382
	 * Returns the position of the "catalog.code" column from the catalog item mapping
383
	 *
384
	 * @param array $mapping Mapping of the "item" columns with position as key and code as value
385
	 * @return integer Position of the "catalog.code" column
386
	 * @throws \Aimeos\Controller\Jobs\Exception If no mapping for "catalog.code" is found
387
	 */
388
	protected function getCodePosition( array $mapping )
389
	{
390
		foreach( $mapping as $pos => $key )
391
		{
392
			if( $key === 'catalog.code' ) {
393
				return $pos;
394
			}
395
		}
396
397
		throw new \Aimeos\Controller\Jobs\Exception( sprintf( 'No "catalog.code" column in CSV mapping found' ) );
398
	}
399
400
401
	/**
402
	 * Opens and returns the container which includes the catalog data
403
	 *
404
	 * @return \Aimeos\MW\Container\Iface Container object
405
	 */
406
	protected function getContainer()
407
	{
408
		$config = $this->getContext()->getConfig();
409
410
		/** controller/jobs/catalog/import/csv/location
411
		 * File or directory where the content is stored which should be imported
412
		 *
413
		 * You need to configure the file or directory that acts as container
414
		 * for the CSV files that should be imported. It should be an absolute
415
		 * path to be sure but can be relative path if you absolutely know from
416
		 * where the job will be executed from.
417
		 *
418
		 * The path can point to any supported container format as long as the
419
		 * content is in CSV format, e.g.
420
		 * * Directory container / CSV file
421
		 * * Zip container / compressed CSV file
422
		 * * PHPExcel container / PHPExcel sheet
423
		 *
424
		 * @param string Absolute file or directory path
425
		 * @since 2018.04
426
		 * @category Developer
427
		 * @category User
428
		 * @see controller/jobs/catalog/import/csv/container/type
429
		 * @see controller/jobs/catalog/import/csv/container/content
430
		 * @see controller/jobs/catalog/import/csv/container/options
431
		 */
432
		$location = $config->get( 'controller/jobs/catalog/import/csv/location' );
433
434
		/** controller/jobs/catalog/import/csv/container/type
435
		 * Nave of the container type to read the data from
436
		 *
437
		 * The container type tells the importer how it should retrieve the data.
438
		 * There are currently three container types that support the necessary
439
		 * CSV content:
440
		 * * Directory
441
		 * * Zip
442
		 * * PHPExcel
443
		 *
444
		 * '''Note:''' For the PHPExcel container, you need to install the
445
		 * "ai-container" extension.
446
		 *
447
		 * @param string Container type name
448
		 * @since 2018.04
449
		 * @category Developer
450
		 * @category User
451
		 * @see controller/jobs/catalog/import/csv/location
452
		 * @see controller/jobs/catalog/import/csv/container/content
453
		 * @see controller/jobs/catalog/import/csv/container/options
454
		 */
455
		$container = $config->get( 'controller/jobs/catalog/import/csv/container/type', 'Directory' );
456
457
		/** controller/jobs/catalog/import/csv/container/content
458
		 * Name of the content type inside the container to read the data from
459
		 *
460
		 * The content type must always be a CSV-like format and there are
461
		 * currently two format types that are supported:
462
		 * * CSV
463
		 * * PHPExcel
464
		 *
465
		 * '''Note:''' for the PHPExcel content type, you need to install the
466
		 * "ai-container" extension.
467
		 *
468
		 * @param array Content type name
469
		 * @since 2018.04
470
		 * @category Developer
471
		 * @category User
472
		 * @see controller/jobs/catalog/import/csv/location
473
		 * @see controller/jobs/catalog/import/csv/container/type
474
		 * @see controller/jobs/catalog/import/csv/container/options
475
		 */
476
		$content = $config->get( 'controller/jobs/catalog/import/csv/container/content', 'CSV' );
477
478
		/** controller/jobs/catalog/import/csv/container/options
479
		 * List of file container options for the catalog import files
480
		 *
481
		 * Some container/content type allow you to hand over additional settings
482
		 * for configuration. Please have a look at the article about
483
		 * {@link http://aimeos.org/docs/Developers/Utility/Create_and_read_files container/content files}
484
		 * for more information.
485
		 *
486
		 * @param array Associative list of option name/value pairs
487
		 * @since 2018.04
488
		 * @category Developer
489
		 * @category User
490
		 * @see controller/jobs/catalog/import/csv/location
491
		 * @see controller/jobs/catalog/import/csv/container/content
492
		 * @see controller/jobs/catalog/import/csv/container/type
493
		 */
494
		$options = $config->get( 'controller/jobs/catalog/import/csv/container/options', [] );
495
496
		if( $location === null )
497
		{
498
			$msg = sprintf( 'Required configuration for "%1$s" is missing', 'controller/jobs/catalog/import/csv/location' );
499
			throw new \Aimeos\Controller\Jobs\Exception( $msg );
500
		}
501
502
		return \Aimeos\MW\Container\Factory::getContainer( $location, $container, $content, $options );
503
	}
504
505
506
	/**
507
	 * Returns the catalog items building the tree as list
508
	 *
509
	 * @param array $domains List of domain names whose items should be fetched too
510
	 * @return array Associative list of catalog codes as keys and items implementing \Aimeos\MShop\Catalog\Item\Iface as values
511
	 */
512
	protected function getCatalogMap( array $domains )
513
	{
514
		$map = [];
515
		$manager = \Aimeos\MShop::create( $this->getContext(), 'catalog' );
516
		$search = $manager->createSearch()->setSlice( 0, 0x7fffffff );
517
518
		foreach( $manager->searchItems( $search, $domains ) as $item ) {
519
			$map[$item->getCode()] = $item;
520
		}
521
522
		return $map;
523
	}
524
525
526
	/**
527
	 * Returns the parent ID of the catalog node for the given code
528
	 *
529
	 * @param array $catalogMap Associative list of catalog items with codes as keys and items implementing \Aimeos\MShop\Catalog\Item\Iface as values
530
	 * @param array $map Associative list of catalog item key/value pairs
531
	 * @param string $code Catalog item code of the parent category
532
	 * @return string|null ID of the parent category or null for top level nodes
533
	 */
534
	protected function getParentId( array $catalogMap, array $map, $code )
535
	{
536
		if( !isset( $map['catalog.parent'] ) )
537
		{
538
			$msg = sprintf( 'Required column "%1$s" not found for code "%2$s"', 'catalog.parent', $code );
539
			throw new \Aimeos\Controller\Jobs\Exception( $msg );
540
		}
541
542
		$parent = trim( $map['catalog.parent'] );
543
544
		if( $parent != '' && !isset( $catalogMap[$parent] ) )
545
		{
546
			$msg = sprintf( 'Parent node for code "%1$s" not found', $parent );
547
			throw new \Aimeos\Controller\Jobs\Exception( $msg );
548
		}
549
550
		return ( $parent != '' ? $catalogMap[$parent]->getId() : null );
551
	}
552
553
554
	/**
555
	 * Imports the CSV data and creates new categories or updates existing ones
556
	 *
557
	 * @param array &$catalogMap Associative list of catalog items with codes as keys and items implementing \Aimeos\MShop\Catalog\Item\Iface as values
558
	 * @param array $data Associative list of import data as index/value pairs
559
	 * @param array $mapping Associative list of positions and domain item keys
560
	 * @param \Aimeos\Controller\Common\Catalog\Import\Csv\Processor\Iface $processor Processor object
561
	 * @param boolean $strict Log columns not mapped or silently ignore them
562
	 * @return integer Number of catalogs that couldn't be imported
563
	 * @throws \Aimeos\Controller\Jobs\Exception
564
	 */
565
	protected function import( array &$catalogMap, array $data, array $mapping,
566
		\Aimeos\Controller\Common\Catalog\Import\Csv\Processor\Iface $processor, $strict )
567
	{
568
		$errors = 0;
569
		$context = $this->getContext();
570
		$manager = \Aimeos\MShop::create( $context, 'catalog' );
571
572
		foreach( $data as $code => $list )
573
		{
574
			$manager->begin();
575
576
			try
577
			{
578
				$code = trim( $code );
579
580
				if( isset( $catalogMap[$code] ) ) {
581
					$item = $catalogMap[$code];
582
				} else {
583
					$item = $manager->createItem();
584
				}
585
586
				$map = $this->getMappedChunk( $list, $mapping );
587
588
				if( isset( $map[0] ) )
589
				{
590
					$map = $map[0]; // there can only be one chunk for the base catalog data
591
					$parentid = $this->getParentId( $catalogMap, $map, $code );
592
					$item->fromArray( $map, true );
593
594
					if( isset( $catalogMap[$code] ) )
595
					{
596
						$manager->moveItem( $item->getId(), $item->getParentId(), $parentid );
597
						$item = $manager->saveItem( $item );
598
					}
599
					else
600
					{
601
						$item = $manager->insertItem( $item, $parentid );
602
					}
603
604
					$list = $processor->process( $item, $list );
605
					$catalogMap[$code] = $item;
606
607
					$manager->saveItem( $item );
608
				}
609
610
				$manager->commit();
611
			}
612
			catch( \Exception $e )
613
			{
614
				$manager->rollback();
615
616
				$msg = sprintf( 'Unable to import catalog with code "%1$s": %2$s', $code, $e->getMessage() );
617
				$context->getLogger()->log( $msg );
618
619
				$errors++;
620
			}
621
622
			if( $strict && !empty( $list ) ) {
623
				$context->getLogger()->log( 'Not imported: ' . print_r( $list, true ) );
624
			}
625
		}
626
627
		return $errors;
628
	}
629
}
630