Completed
Push — 15.x ( 2aa3b6...167fc6 )
by Tim
02:35
created

AbstractSubject::getNewSourceDir()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 2

Importance

Changes 0
Metric Value
dl 0
loc 4
c 0
b 0
f 0
ccs 0
cts 4
cp 0
rs 10
cc 1
nc 1
nop 1
crap 2
1
<?php
2
3
/**
4
 * TechDivision\Import\Subjects\AbstractSubject
5
 *
6
 * NOTICE OF LICENSE
7
 *
8
 * This source file is subject to the Open Software License (OSL 3.0)
9
 * that is available through the world-wide-web at this URL:
10
 * http://opensource.org/licenses/osl-3.0.php
11
 *
12
 * PHP version 5
13
 *
14
 * @author    Tim Wagner <[email protected]>
15
 * @copyright 2016 TechDivision GmbH <[email protected]>
16
 * @license   http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0)
17
 * @link      https://github.com/techdivision/import
18
 * @link      http://www.techdivision.com
19
 */
20
21
namespace TechDivision\Import\Subjects;
22
23
use League\Event\EmitterInterface;
24
use Doctrine\Common\Collections\Collection;
25
use TechDivision\Import\RowTrait;
26
use TechDivision\Import\HeaderTrait;
27
use TechDivision\Import\SystemLoggerTrait;
28
use TechDivision\Import\Utils\ScopeKeys;
29
use TechDivision\Import\Utils\ColumnKeys;
30
use TechDivision\Import\Utils\EventNames;
31
use TechDivision\Import\Utils\MemberNames;
32
use TechDivision\Import\Utils\RegistryKeys;
33
use TechDivision\Import\Utils\EntityTypeCodes;
34
use TechDivision\Import\Utils\Generators\GeneratorInterface;
35
use TechDivision\Import\Callbacks\CallbackInterface;
36
use TechDivision\Import\Observers\ObserverInterface;
37
use TechDivision\Import\Adapter\ImportAdapterInterface;
38
use TechDivision\Import\Exceptions\WrappedColumnException;
39
use TechDivision\Import\Services\RegistryProcessorInterface;
40
use TechDivision\Import\Configuration\SubjectConfigurationInterface;
41
use TechDivision\Import\Services\RegistryProcessor;
42
43
/**
44
 * An abstract subject implementation.
45
 *
46
 * @author    Tim Wagner <[email protected]>
47
 * @copyright 2016 TechDivision GmbH <[email protected]>
48
 * @license   http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0)
49
 * @link      https://github.com/techdivision/import
50
 * @link      http://www.techdivision.com
51
 */
52
abstract class AbstractSubject implements SubjectInterface, FilesystemSubjectInterface, DateConverterSubjectInterface
53
{
54
55
    /**
56
     * The trait that provides basic filesystem handling functionality.
57
     *
58
     * @var \TechDivision\Import\Subjects\FilesystemTrait
59
     */
60
    use FilesystemTrait;
61
62
    /**
63
     * The trait that provides basic filesystem handling functionality.
64
     *
65
     * @var \TechDivision\Import\SystemLoggerTrait
66
     */
67
    use SystemLoggerTrait;
68
69
    /**
70
     * The trait that provides date converting functionality.
71
     *
72
     * @var \TechDivision\Import\DateConverterTrait
73
     */
74
    use DateConverterTrait;
75
76
    /**
77
     * The trait that provides header handling functionality.
78
     *
79
     * @var \TechDivision\Import\HeaderTrait
80
     */
81
    use HeaderTrait;
82
83
    /**
84
     * The trait that provides row handling functionality.
85
     *
86
     * @var \TechDivision\Import\RowTrait
87
     */
88
    use RowTrait;
89
90
    /**
91
     * The name of the file to be imported.
92
     *
93
     * @var string
94
     */
95
    protected $filename;
96
97
    /**
98
     * The actual line number.
99
     *
100
     * @var integer
101
     */
102
    protected $lineNumber = 0;
103
104
    /**
105
     * The import adapter instance.
106
     *
107
     * @var \TechDivision\Import\Adapter\ImportAdapterInterface
108
     */
109
    protected $importAdapter;
110
111
    /**
112
     * The subject configuration.
113
     *
114
     * @var \TechDivision\Import\Configuration\SubjectConfigurationInterface
115
     */
116
    protected $configuration;
117
118
    /**
119
     * The plugin configuration.
120
     *
121
     * @var \TechDivision\Import\Configuration\PluginConfigurationInterface
122
     */
123
    protected $pluginConfiguration;
124
125
    /**
126
     * The RegistryProcessor instance to handle running threads.
127
     *
128
     * @var \TechDivision\Import\Services\RegistryProcessorInterface
129
     */
130
    protected $registryProcessor;
131
132
    /**
133
     * The actions unique serial.
134
     *
135
     * @var string
136
     */
137
    protected $serial;
138
139
    /**
140
     * Array with the subject's observers.
141
     *
142
     * @var array
143
     */
144
    protected $observers = array();
145
146
    /**
147
     * Array with the subject's callbacks.
148
     *
149
     * @var array
150
     */
151
    protected $callbacks = array();
152
153
    /**
154
     * The subject's callback mappings.
155
     *
156
     * @var array
157
     */
158
    protected $callbackMappings = array();
159
160
    /**
161
     * The available root categories.
162
     *
163
     * @var array
164
     */
165
    protected $rootCategories = array();
166
167
    /**
168
     * The Magento configuration.
169
     *
170
     * @var array
171
     */
172
    protected $coreConfigData = array();
173
174
    /**
175
     * The available stores.
176
     *
177
     * @var array
178
     */
179
    protected $stores = array();
180
181
    /**
182
     * The available websites.
183
     *
184
     * @var array
185
     */
186
    protected $storeWebsites = array();
187
188
    /**
189
     * The default store.
190
     *
191
     * @var array
192
     */
193
    protected $defaultStore;
194
195
    /**
196
     * The store view code the create the product/attributes for.
197
     *
198
     * @var string
199
     */
200
    protected $storeViewCode;
201
202
    /**
203
     * The UID generator for the core config data.
204
     *
205
     * @var \TechDivision\Import\Utils\Generators\GeneratorInterface
206
     */
207
    protected $coreConfigDataUidGenerator;
208
209
    /**
210
     * UNIX timestamp with the date the last row counter has been logged.
211
     *
212
     * @var integer
213
     */
214
    protected $lastLog = 0;
215
216
    /**
217
     * The number of the last line that has been logged with the row counter
218
     * @var integer
219
     */
220
    protected $lastLineNumber = 0;
221
222
    /**
223
     * The event emitter instance.
224
     *
225
     * @var \League\Event\EmitterInterface
226
     */
227
    protected $emitter;
228
229
    /**
230
     * Mapping for the virtual entity type code to the real Magento 2 EAV entity type code.
231
     *
232
     * @var array
233
     */
234
    protected $entityTypeCodeMappings = array(
235
        EntityTypeCodes::EAV_ATTRIBUTE                 => EntityTypeCodes::CATALOG_PRODUCT,
236
        EntityTypeCodes::EAV_ATTRIBUTE_SET             => EntityTypeCodes::CATALOG_PRODUCT,
237
        EntityTypeCodes::CATALOG_PRODUCT_PRICE         => EntityTypeCodes::CATALOG_PRODUCT,
238
        EntityTypeCodes::CATALOG_PRODUCT_INVENTORY     => EntityTypeCodes::CATALOG_PRODUCT,
239
        EntityTypeCodes::CATALOG_PRODUCT_INVENTORY_MSI => EntityTypeCodes::CATALOG_PRODUCT,
240
        EntityTypeCodes::CATALOG_PRODUCT_TIER_PRICE    => EntityTypeCodes::CATALOG_PRODUCT
241
    );
242
243
    /**
244
     * Initialize the subject instance.
245
     *
246
     * @param \TechDivision\Import\Services\RegistryProcessorInterface $registryProcessor          The registry processor instance
247
     * @param \TechDivision\Import\Utils\Generators\GeneratorInterface $coreConfigDataUidGenerator The UID generator for the core config data
248
     * @param \Doctrine\Common\Collections\Collection                  $systemLoggers              The array with the system loggers instances
249
     * @param \League\Event\EmitterInterface                           $emitter                    The event emitter instance
250
     */
251
    public function __construct(
252
        RegistryProcessorInterface $registryProcessor,
253
        GeneratorInterface $coreConfigDataUidGenerator,
254
        Collection $systemLoggers,
255
        EmitterInterface $emitter
256
    ) {
257
        $this->emitter = $emitter;
258
        $this->systemLoggers = $systemLoggers;
0 ignored issues
show
Documentation Bug introduced by
It seems like $systemLoggers of type object<Doctrine\Common\Collections\Collection> is incompatible with the declared type array of property $systemLoggers.

Our type inference engine has found an assignment to a property that is incompatible with the declared type of that property.

Either this assignment is in error or the assigned type should be added to the documentation/type hint for that property..

Loading history...
259
        $this->registryProcessor = $registryProcessor;
260
        $this->coreConfigDataUidGenerator = $coreConfigDataUidGenerator;
261
    }
262
263
    /**
264
     * Return's the event emitter instance.
265
     *
266
     * @return \League\Event\EmitterInterface The event emitter instance
267
     */
268
    public function getEmitter()
269
    {
270
        return $this->emitter;
271
    }
272
273
    /**
274
     * Set's the name of the file to import
275
     *
276
     * @param string $filename The filename
277
     *
278
     * @return void
279
     */
280
    public function setFilename($filename)
281
    {
282
        $this->filename = $filename;
283
    }
284
285
    /**
286
     * Return's the name of the file to import.
287
     *
288
     * @return string The filename
289
     */
290
    public function getFilename()
291
    {
292
        return $this->filename;
293
    }
294
295
    /**
296
     * Set's the actual line number.
297
     *
298
     * @param integer $lineNumber The line number
299
     *
300
     * @return void
301
     */
302
    public function setLineNumber($lineNumber)
303
    {
304
        $this->lineNumber = $lineNumber;
305
    }
306
307
    /**
308
     * Return's the actual line number.
309
     *
310
     * @return integer The line number
311
     */
312
    public function getLineNumber()
313
    {
314
        return $this->lineNumber;
315
    }
316
317
    /**
318
     * Return's the default callback mappings.
319
     *
320
     * @return array The default callback mappings
321
     */
322
    public function getDefaultCallbackMappings()
323
    {
324
        return array();
325
    }
326
327
    /**
328
     * Tries to format the passed value to a valid date with format 'Y-m-d H:i:s'.
329
     * If the passed value is NOT a valid date, NULL will be returned.
330
     *
331
     * @param string $value The value to format
332
     *
333
     * @return string|null The formatted date or NULL if the date is not valid
334
     */
335
    public function formatDate($value)
336
    {
337
        return $this->getDateConverter()->convert($value);
338
    }
339
340
    /**
341
     * Extracts the elements of the passed value by exploding them
342
     * with the also passed delimiter.
343
     *
344
     * @param string      $value     The value to extract
345
     * @param string|null $delimiter The delimiter used to extrace the elements
346
     *
347
     * @return array The exploded values
348
     */
349
    public function explode($value, $delimiter = null)
350
    {
351
        return $this->getImportAdapter()->explode($value, $delimiter);
352
    }
353
354
    /**
355
     * Queries whether or not debug mode is enabled or not, default is TRUE.
356
     *
357
     * @return boolean TRUE if debug mode is enabled, else FALSE
358
     */
359
    public function isDebugMode()
360
    {
361
        return $this->getConfiguration()->isDebugMode();
362
    }
363
364
    /**
365
     * Return's the subject's execution context configuration.
366
     *
367
     * @return \TechDivision\Import\ExecutionContextInterface The execution context configuration to use
368
     */
369
    public function getExecutionContext()
370
    {
371
        return $this->getConfiguration()->getPluginConfiguration()->getExecutionContext();
0 ignored issues
show
Bug introduced by
The method getExecutionContext() does not seem to exist on object<TechDivision\Impo...ConfigurationInterface>.

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
372
    }
373
374
    /**
375
     * Set's the subject configuration.
376
     *
377
     * @param \TechDivision\Import\Configuration\SubjectConfigurationInterface $configuration The subject configuration
378
     *
379
     * @return void
380
     */
381
    public function setConfiguration(SubjectConfigurationInterface $configuration)
382
    {
383
        $this->configuration = $configuration;
384
    }
385
386
    /**
387
     * Return's the subject configuration.
388
     *
389
     * @return \TechDivision\Import\Configuration\SubjectConfigurationInterface The subject configuration
390
     */
391
    public function getConfiguration()
392
    {
393
        return $this->configuration;
394
    }
395
396
    /**
397
     * Set's the import adapter instance.
398
     *
399
     * @param \TechDivision\Import\Adapter\ImportAdapterInterface $importAdapter The import adapter instance
400
     *
401
     * @return void
402
     */
403
    public function setImportAdapter(ImportAdapterInterface $importAdapter)
404
    {
405
        $this->importAdapter = $importAdapter;
406
    }
407
408
    /**
409
     * Return's the import adapter instance.
410
     *
411
     * @return \TechDivision\Import\Adapter\ImportAdapterInterface The import adapter instance
412
     */
413
    public function getImportAdapter()
414
    {
415
        return $this->importAdapter;
416
    }
417
418
    /**
419
     * Return's the RegistryProcessor instance to handle the running threads.
420
     *
421
     * @return \TechDivision\Import\Services\RegistryProcessorInterface The registry processor instance
422
     */
423
    public function getRegistryProcessor()
424
    {
425
        return $this->registryProcessor;
426
    }
427
428
    /**
429
     * Set's the unique serial for this import process.
430
     *
431
     * @param string $serial The unique serial
432
     *
433
     * @return void
434
     */
435
    public function setSerial($serial)
436
    {
437
        $this->serial = $serial;
438
    }
439
440
    /**
441
     * Return's the unique serial for this import process.
442
     *
443
     * @return string The unique serial
444
     */
445
    public function getSerial()
446
    {
447
        return $this->serial;
448
    }
449
450
    /**
451
     * Return's the source date format to use.
452
     *
453
     * @return string The source date format
454
     */
455
    public function getSourceDateFormat()
456
    {
457
        return $this->getConfiguration()->getSourceDateFormat();
0 ignored issues
show
Bug introduced by
The method getSourceDateFormat() does not seem to exist on object<TechDivision\Impo...ConfigurationInterface>.

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
458
    }
459
460
    /**
461
     * Return's the multiple field delimiter character to use, default value is comma (,).
462
     *
463
     * @return string The multiple field delimiter character
464
     */
465
    public function getMultipleFieldDelimiter()
466
    {
467
        return $this->getConfiguration()->getMultipleFieldDelimiter();
468
    }
469
470
    /**
471
     * Return's the multiple value delimiter character to use, default value is comma (|).
472
     *
473
     * @return string The multiple value delimiter character
474
     */
475
    public function getMultipleValueDelimiter()
476
    {
477
        return $this->getConfiguration()->getMultipleValueDelimiter();
478
    }
479
480
    /**
481
     * Intializes the previously loaded global data for exactly one bunch.
482
     *
483
     * @param string $serial The serial of the actual import
484
     *
485
     * @return void
486
     */
487
    public function setUp($serial)
488
    {
489
490
        // load the status of the actual import
491
        $status = $this->getRegistryProcessor()->getAttribute(RegistryKeys::STATUS);
492
493
        // load the global data, if prepared initially
494
        if (isset($status[RegistryKeys::GLOBAL_DATA])) {
495
            $this->stores = $status[RegistryKeys::GLOBAL_DATA][RegistryKeys::STORES];
496
            $this->defaultStore = $status[RegistryKeys::GLOBAL_DATA][RegistryKeys::DEFAULT_STORE];
497
            $this->storeWebsites  = $status[RegistryKeys::GLOBAL_DATA][RegistryKeys::STORE_WEBSITES];
498
            $this->rootCategories = $status[RegistryKeys::GLOBAL_DATA][RegistryKeys::ROOT_CATEGORIES];
499
            $this->coreConfigData = $status[RegistryKeys::GLOBAL_DATA][RegistryKeys::CORE_CONFIG_DATA];
500
        }
501
502
        // merge the callback mappings with the mappings from the child instance
503
        $this->callbackMappings = array_merge($this->callbackMappings, $this->getDefaultCallbackMappings());
504
505
        // merge the header mappings with the values found in the configuration
506
        $this->headerMappings = array_merge($this->headerMappings, $this->getConfiguration()->getHeaderMappings());
507
508
        // load the available callbacks from the configuration
509
        $availableCallbacks = $this->getConfiguration()->getCallbacks();
510
511
        // merge the callback mappings the the one from the configuration file
512
        foreach ($availableCallbacks as $callbackMappings) {
513
            foreach ($callbackMappings as $attributeCode => $mappings) {
514
                // write a log message, that default callback configuration will
515
                // be overwritten with the one from the configuration file
516
                if (isset($this->callbackMappings[$attributeCode])) {
517
                    $this->getSystemLogger()->notice(
518
                        sprintf('Now override callback mappings for attribute %s with values found in configuration file', $attributeCode)
519
                    );
520
                }
521
522
                // override the attributes callbacks
523
                $this->callbackMappings[$attributeCode] = $mappings;
524
            }
525
        }
526
    }
527
528
    /**
529
     * Clean up the global data after importing the variants.
530
     *
531
     * @param string $serial The serial of the actual import
532
     *
533
     * @return void
534
     */
535
    public function tearDown($serial)
536
    {
537
538
        // load the registry processor
539
        $registryProcessor = $this->getRegistryProcessor();
540
541
        // update the source directory for the next subject
542
        $registryProcessor->mergeAttributesRecursive(
543
            RegistryKeys::STATUS,
544
            array(
545
                RegistryKeys::FILES => array($this->getFilename() => array(RegistryKeys::STATUS => 1))
546
            )
547
        );
548
549
        // log a debug message with the new source directory
550
        $this->getSystemLogger()->debug(
551
            sprintf('Subject %s successfully updated status data for import %s', get_class($this), $serial)
552
        );
553
    }
554
555
    /**
556
     * Return's the target directory for the artefact export.
557
     *
558
     * @return string The target directory for the artefact export
559
     */
560 View Code Duplication
    public function getTargetDir()
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
561
    {
562
563
        // load the status from the registry processor
564
        $status = $this->getRegistryProcessor()->getAttribute(RegistryKeys::STATUS);
565
566
        // query whether or not a target directory (mandatory) has been configured
567
        if (isset($status[RegistryKeys::TARGET_DIRECTORY])) {
568
            return $status[RegistryKeys::TARGET_DIRECTORY];
569
        }
570
571
        // throw an exception if the root category is NOT available
572
        throw new \Exception(sprintf('Can\'t find a target directory in status data for import %s', $this->getSerial()));
573
    }
574
575
    /**
576
     * Register the passed observer with the specific type.
577
     *
578
     * @param \TechDivision\Import\Observers\ObserverInterface $observer The observer to register
579
     * @param string                                           $type     The type to register the observer with
580
     *
581
     * @return void
582
     */
583
    public function registerObserver(ObserverInterface $observer, $type)
584
    {
585
586
        // query whether or not the array with the callbacks for the
587
        // passed type has already been initialized, or not
588
        if (!isset($this->observers[$type])) {
589
            $this->observers[$type] = array();
590
        }
591
592
        // append the callback with the instance of the passed type
593
        $this->observers[$type][] = $observer;
594
    }
595
596
    /**
597
     * Register the passed callback with the specific type.
598
     *
599
     * @param \TechDivision\Import\Callbacks\CallbackInterface $callback The subject to register the callbacks for
600
     * @param string                                           $type     The type to register the callback with
601
     *
602
     * @return void
603
     */
604
    public function registerCallback(CallbackInterface $callback, $type)
605
    {
606
607
        // query whether or not the array with the callbacks for the
608
        // passed type has already been initialized, or not
609
        if (!isset($this->callbacks[$type])) {
610
            $this->callbacks[$type] = array();
611
        }
612
613
        // append the callback with the instance of the passed type
614
        $this->callbacks[$type][] = $callback;
615
    }
616
617
    /**
618
     * Return's the array with callbacks for the passed type.
619
     *
620
     * @param string $type The type of the callbacks to return
621
     *
622
     * @return array The callbacks
623
     */
624
    public function getCallbacksByType($type)
625
    {
626
627
        // initialize the array for the callbacks
628
        $callbacks = array();
629
630
        // query whether or not callbacks for the type are available
631
        if (isset($this->callbacks[$type])) {
632
            $callbacks = $this->callbacks[$type];
633
        }
634
635
        // return the array with the type's callbacks
636
        return $callbacks;
637
    }
638
639
    /**
640
     * Return's the array with the available observers.
641
     *
642
     * @return array The observers
643
     */
644
    public function getObservers()
645
    {
646
        return $this->observers;
647
    }
648
649
    /**
650
     * Return's the array with the available callbacks.
651
     *
652
     * @return array The callbacks
653
     */
654
    public function getCallbacks()
655
    {
656
        return $this->callbacks;
657
    }
658
659
    /**
660
     * Return's the callback mappings for this subject.
661
     *
662
     * @return array The array with the subject's callback mappings
663
     */
664
    public function getCallbackMappings()
665
    {
666
        return $this->callbackMappings;
667
    }
668
669
    /**
670
     * Imports the content of the file with the passed filename.
671
     *
672
     *
673
     * @param string $serial   The serial of the actual import
674
     * @param string $filename The filename to process
675
     *
676
     * @return void
677
     * @throws \Exception Is thrown, if the import can't be processed
678
     */
679
    public function import($serial, $filename)
680
    {
681
682
        try {
683
            // initialize the serial/filename
684
            $this->setSerial($serial);
685
            $this->setFilename($filename);
686
687
            // invoke the events that has to be fired before the artfact will be processed
688
            $this->getEmitter()->emit(EventNames::SUBJECT_ARTEFACT_PROCESS_START, $this);
0 ignored issues
show
Unused Code introduced by
The call to EmitterInterface::emit() has too many arguments starting with $this.

This check compares calls to functions or methods with their respective definitions. If the call has more arguments than are defined, it raises an issue.

If a function is defined several times with a different number of parameters, the check may pick up the wrong definition and report false positives. One codebase where this has been known to happen is Wordpress.

In this case you can add the @ignore PhpDoc annotation to the duplicate definition and it will be ignored.

Loading history...
689
690
            // load the system logger instance
691
            $systemLogger = $this->getSystemLogger();
692
693
            // prepare the flag filenames
694
            $inProgressFilename = sprintf('%s.inProgress', $filename);
695
            $importedFilename = sprintf('%s.imported', $filename);
696
            $failedFilename = sprintf('%s.failed', $filename);
697
698
            // query whether or not the file has already been imported
699
            if ($this->isFile($failedFilename) ||
700
                $this->isFile($importedFilename) ||
701
                $this->isFile($inProgressFilename)
702
            ) {
703
                // log a debug message and exit
704
                $systemLogger->debug(sprintf('Import running, found inProgress file %s', $inProgressFilename));
705
                return;
706
            }
707
708
            // flag file as in progress
709
            $this->touch($inProgressFilename);
710
711
            // track the start time
712
            $startTime = microtime(true);
713
714
            // initialize the last time we've logged the counter with the processed rows per minute
715
            $this->lastLog = time();
716
717
            // log a message that the file has to be imported
718
            $systemLogger->info(sprintf('Now start processing file %s', $filename));
719
720
            // let the adapter process the file
721
            $this->getImportAdapter()->import(array($this, 'importRow'), $filename);
722
723
            // track the time needed for the import in seconds
724
            $endTime = microtime(true) - $startTime;
725
726
            // log a message that the file has successfully been imported
727
            $systemLogger->info(sprintf('Successfully processed file %s with %d lines in %f s', $filename, $this->lineNumber, $endTime));
728
729
            // rename flag file, because import has been successfull
730
            if ($this->getConfiguration()->isCreatingImportedFile()) {
731
                $this->rename($inProgressFilename, $importedFilename);
732
            } else {
733
                $this->getFilesystemAdapter()->delete($inProgressFilename);
734
            }
735
736
            // invoke the events that has to be fired when the artfact has been successfully processed
737
            $this->getEmitter()->emit(EventNames::SUBJECT_ARTEFACT_PROCESS_SUCCESS, $this);
0 ignored issues
show
Unused Code introduced by
The call to EmitterInterface::emit() has too many arguments starting with $this.

This check compares calls to functions or methods with their respective definitions. If the call has more arguments than are defined, it raises an issue.

If a function is defined several times with a different number of parameters, the check may pick up the wrong definition and report false positives. One codebase where this has been known to happen is Wordpress.

In this case you can add the @ignore PhpDoc annotation to the duplicate definition and it will be ignored.

Loading history...
738
        } catch (\Exception $e) {
739
            // rename the flag file, because import failed and write the stack trace
740
            $this->rename($inProgressFilename, $failedFilename);
0 ignored issues
show
Bug introduced by
The variable $inProgressFilename does not seem to be defined for all execution paths leading up to this point.

If you define a variable conditionally, it can happen that it is not defined for all execution paths.

Let’s take a look at an example:

function myFunction($a) {
    switch ($a) {
        case 'foo':
            $x = 1;
            break;

        case 'bar':
            $x = 2;
            break;
    }

    // $x is potentially undefined here.
    echo $x;
}

In the above example, the variable $x is defined if you pass “foo” or “bar” as argument for $a. However, since the switch statement has no default case statement, if you pass any other value, the variable $x would be undefined.

Available Fixes

  1. Check for existence of the variable explicitly:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        if (isset($x)) { // Make sure it's always set.
            echo $x;
        }
    }
    
  2. Define a default value for the variable:

    function myFunction($a) {
        $x = ''; // Set a default which gets overridden for certain paths.
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        echo $x;
    }
    
  3. Add a value for the missing path:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
    
            // We add support for the missing case.
            default:
                $x = '';
                break;
        }
    
        echo $x;
    }
    
Loading history...
Bug introduced by
The variable $failedFilename does not seem to be defined for all execution paths leading up to this point.

If you define a variable conditionally, it can happen that it is not defined for all execution paths.

Let’s take a look at an example:

function myFunction($a) {
    switch ($a) {
        case 'foo':
            $x = 1;
            break;

        case 'bar':
            $x = 2;
            break;
    }

    // $x is potentially undefined here.
    echo $x;
}

In the above example, the variable $x is defined if you pass “foo” or “bar” as argument for $a. However, since the switch statement has no default case statement, if you pass any other value, the variable $x would be undefined.

Available Fixes

  1. Check for existence of the variable explicitly:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        if (isset($x)) { // Make sure it's always set.
            echo $x;
        }
    }
    
  2. Define a default value for the variable:

    function myFunction($a) {
        $x = ''; // Set a default which gets overridden for certain paths.
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        echo $x;
    }
    
  3. Add a value for the missing path:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
    
            // We add support for the missing case.
            default:
                $x = '';
                break;
        }
    
        echo $x;
    }
    
Loading history...
741
            $this->write($failedFilename, $e->__toString());
742
743
            // invoke the events that has to be fired when the artfact can't be processed
744
            $this->getEmitter()->emit(EventNames::SUBJECT_ARTEFACT_PROCESS_FAILURE, $this, $e);
0 ignored issues
show
Unused Code introduced by
The call to EmitterInterface::emit() has too many arguments starting with $this.

This check compares calls to functions or methods with their respective definitions. If the call has more arguments than are defined, it raises an issue.

If a function is defined several times with a different number of parameters, the check may pick up the wrong definition and report false positives. One codebase where this has been known to happen is Wordpress.

In this case you can add the @ignore PhpDoc annotation to the duplicate definition and it will be ignored.

Loading history...
745
746
            // do not wrap the exception if not already done
747
            if ($e instanceof WrappedColumnException) {
748
                throw $e;
749
            }
750
751
            // else wrap and throw the exception
752
            throw $this->wrapException(array(), $e);
753
        }
754
    }
755
756
    /**
757
     * Imports the passed row into the database. If the import failed, the exception
758
     * will be catched and logged, but the import process will be continued.
759
     *
760
     * @param array $row The row with the data to be imported
761
     *
762
     * @return void
763
     */
764
    public function importRow(array $row)
765
    {
766
767
        // initialize the row
768
        $this->row = $row;
769
770
        // raise the line number and reset the skip row flag
771
        $this->lineNumber++;
772
        $this->skipRow = false;
773
774
        // invoke the events that has to be fired before the artfact's row will be processed
775
        $this->getEmitter()->emit(EventNames::SUBJECT_ARTEFACT_ROW_PROCESS_START, $this);
0 ignored issues
show
Unused Code introduced by
The call to EmitterInterface::emit() has too many arguments starting with $this.

This check compares calls to functions or methods with their respective definitions. If the call has more arguments than are defined, it raises an issue.

If a function is defined several times with a different number of parameters, the check may pick up the wrong definition and report false positives. One codebase where this has been known to happen is Wordpress.

In this case you can add the @ignore PhpDoc annotation to the duplicate definition and it will be ignored.

Loading history...
776
777
        // initialize the headers with the columns from the first line
778
        if (sizeof($this->headers) === 0) {
779
            foreach ($this->row as $value => $key) {
780
                $this->headers[$this->mapAttributeCodeByHeaderMapping($key)] = $value;
781
            }
782
            return;
783
        }
784
785
        // load the available observers
786
        $availableObservers = $this->getObservers();
787
788
        // process the observers
789
        foreach ($availableObservers as $observers) {
790
            // invoke the pre-import/import and post-import observers
791
            /** @var \TechDivision\Import\Observers\ObserverInterface $observer */
792
            foreach ($observers as $observer) {
793
                // query whether or not we have to skip the row
794
                if ($this->skipRow) {
795
                    // log a debug message with the actual line nr/file information
796
                    $this->getSystemLogger()->warning(
797
                        $this->appendExceptionSuffix(
798
                            sprintf(
799
                                'Skip processing operation "%s" after observer "%s"',
800
                                implode(' > ', $this->getConfiguration()->getConfiguration()->getOperationNames()),
801
                                get_class($observer)
802
                            )
803
                        )
804
                    );
805
806
                    // skip the row
807
                    break 2;
808
                }
809
810
                // if not, set the subject and process the observer
811
                if ($observer instanceof ObserverInterface) {
812
                    $this->row = $observer->handle($this);
813
                }
814
            }
815
        }
816
817
        // query whether or not a minute has been passed
818
        if ($this->lastLog < time() - 60) {
819
            // log the number processed rows per minute
820
            $this->getSystemLogger()->info(
821
                sprintf(
822
                    'Successfully processed "%d (%d)" rows per minute of file "%s"',
823
                    $this->lineNumber - $this->lastLineNumber,
824
                    $this->lineNumber,
825
                    $this->getFilename()
826
                )
827
            );
828
829
            // reset the last log time and the line number
830
            $this->lastLog = time();
831
            $this->lastLineNumber = $this->lineNumber;
832
        }
833
834
        // log a debug message with the actual line nr/file information
835
        $this->getSystemLogger()->debug(
836
            $this->appendExceptionSuffix(
837
                sprintf(
838
                    'Successfully processed operation "%s"',
839
                    implode(' > ', $this->getConfiguration()->getConfiguration()->getOperationNames())
840
                )
841
            )
842
        );
843
844
        // invoke the events that has to be fired when the artfact's row has been successfully processed
845
        $this->getEmitter()->emit(EventNames::SUBJECT_ARTEFACT_ROW_PROCESS_SUCCESS, $this);
0 ignored issues
show
Unused Code introduced by
The call to EmitterInterface::emit() has too many arguments starting with $this.

This check compares calls to functions or methods with their respective definitions. If the call has more arguments than are defined, it raises an issue.

If a function is defined several times with a different number of parameters, the check may pick up the wrong definition and report false positives. One codebase where this has been known to happen is Wordpress.

In this case you can add the @ignore PhpDoc annotation to the duplicate definition and it will be ignored.

Loading history...
846
    }
847
848
    /**
849
     * Queries whether or not that the subject needs an OK file to be processed.
850
     *
851
     * @return boolean TRUE if the subject needs an OK file, else FALSE
852
     */
853
    public function isOkFileNeeded()
854
    {
855
        return $this->getConfiguration()->isOkFileNeeded();
856
    }
857
858
    /**
859
     * Return's the default store.
860
     *
861
     * @return array The default store
862
     */
863
    public function getDefaultStore()
864
    {
865
        return $this->defaultStore;
866
    }
867
868
    /**
869
     * Return's the default store view code.
870
     *
871
     * @return array The default store view code
872
     */
873
    public function getDefaultStoreViewCode()
874
    {
875
        return $this->defaultStore[MemberNames::CODE];
876
    }
877
878
    /**
879
     * Set's the store view code the create the product/attributes for.
880
     *
881
     * @param string $storeViewCode The store view code
882
     *
883
     * @return void
884
     */
885
    public function setStoreViewCode($storeViewCode)
886
    {
887
        $this->storeViewCode = $storeViewCode;
888
    }
889
890
    /**
891
     * Return's the store view code the create the product/attributes for.
892
     *
893
     * @param string|null $default The default value to return, if the store view code has not been set
894
     *
895
     * @return string The store view code
896
     */
897
    public function getStoreViewCode($default = null)
898
    {
899
900
        // return the store view code, if available
901
        if ($this->storeViewCode !== null) {
902
            return $this->storeViewCode;
903
        }
904
905
        // if NOT and a default code is available
906
        if ($default !== null) {
907
            // return the default value
908
            return $default;
909
        }
910
911
        // return the default store view code
912
        return $this->getDefaultStoreViewCode();
0 ignored issues
show
Bug Best Practice introduced by
The return type of return $this->getDefaultStoreViewCode(); (array) is incompatible with the return type declared by the interface TechDivision\Import\Subj...rface::getStoreViewCode of type string.

If you return a value from a function or method, it should be a sub-type of the type that is given by the parent type f.e. an interface, or abstract method. This is more formally defined by the Lizkov substitution principle, and guarantees that classes that depend on the parent type can use any instance of a child type interchangably. This principle also belongs to the SOLID principles for object oriented design.

Let’s take a look at an example:

class Author {
    private $name;

    public function __construct($name) {
        $this->name = $name;
    }

    public function getName() {
        return $this->name;
    }
}

abstract class Post {
    public function getAuthor() {
        return 'Johannes';
    }
}

class BlogPost extends Post {
    public function getAuthor() {
        return new Author('Johannes');
    }
}

class ForumPost extends Post { /* ... */ }

function my_function(Post $post) {
    echo strtoupper($post->getAuthor());
}

Our function my_function expects a Post object, and outputs the author of the post. The base class Post returns a simple string and outputting a simple string will work just fine. However, the child class BlogPost which is a sub-type of Post instead decided to return an object, and is therefore violating the SOLID principles. If a BlogPost were passed to my_function, PHP would not complain, but ultimately fail when executing the strtoupper call in its body.

Loading history...
913
    }
914
915
    /**
916
     * Prepare's the store view code in the subject. If the store_view_code row doesn't contain
917
     * any value, the default code of the default store view will be set.
918
     *
919
     * @return void
920
     */
921
    public function prepareStoreViewCode()
922
    {
923
924
        // re-set the store view code
925
        $this->setStoreViewCode(null);
926
927
        // initialize the store view code
928
        if ($storeViewCode = $this->getValue(ColumnKeys::STORE_VIEW_CODE)) {
929
            $this->setStoreViewCode($storeViewCode);
930
        }
931
    }
932
933
    /**
934
     * Return's the store ID of the store with the passed store view code
935
     *
936
     * @param string $storeViewCode The store view code to return the store ID for
937
     *
938
     * @return integer The ID of the store with the passed ID
939
     * @throws \Exception Is thrown, if the store with the actual code is not available
940
     */
941
    public function getStoreId($storeViewCode)
942
    {
943
944
        // query whether or not, the requested store is available
945
        if (isset($this->stores[$storeViewCode])) {
946
            return (integer) $this->stores[$storeViewCode][MemberNames::STORE_ID];
947
        }
948
949
        // throw an exception, if not
950
        throw new \Exception(
951
            sprintf(
952
                'Found invalid store view code %s in file %s on line %d',
953
                $storeViewCode,
954
                $this->getFilename(),
955
                $this->getLineNumber()
956
            )
957
        );
958
    }
959
960
    /**
961
     * Return's the store ID of the actual row, or of the default store
962
     * if no store view code is set in the CSV file.
963
     *
964
     * @param string|null $default The default store view code to use, if no store view code is set in the CSV file
965
     *
966
     * @return integer The ID of the actual store
967
     * @throws \Exception Is thrown, if the store with the actual code is not available
968
     */
969
    public function getRowStoreId($default = null)
970
    {
971
972
        // initialize the default store view code, if not passed
973
        if ($default === null) {
974
            $default = $this->getDefaultStoreViewCode();
975
        }
976
977
        // load the store view code the create the product/attributes for
978
        return $this->getStoreId($this->getStoreViewCode($default));
0 ignored issues
show
Bug introduced by
It seems like $default defined by $this->getDefaultStoreViewCode() on line 974 can also be of type array; however, TechDivision\Import\Subj...ect::getStoreViewCode() does only seem to accept string|null, maybe add an additional type check?

If a method or function can return multiple different values and unless you are sure that you only can receive a single value in this context, we recommend to add an additional type check:

/**
 * @return array|string
 */
function returnsDifferentValues($x) {
    if ($x) {
        return 'foo';
    }

    return array();
}

$x = returnsDifferentValues($y);
if (is_array($x)) {
    // $x is an array.
}

If this a common case that PHP Analyzer should handle natively, please let us know by opening an issue.

Loading history...
Bug introduced by
It seems like $this->getStoreViewCode($default) targeting TechDivision\Import\Subj...ect::getStoreViewCode() can also be of type array; however, TechDivision\Import\Subj...ctSubject::getStoreId() does only seem to accept string, maybe add an additional type check?

This check looks at variables that are passed out again to other methods.

If the outgoing method call has stricter type requirements than the method itself, an issue is raised.

An additional type check may prevent trouble.

Loading history...
979
    }
980
981
    /**
982
     * Return's the root category for the actual view store.
983
     *
984
     * @return array The store's root category
985
     * @throws \Exception Is thrown if the root category for the passed store code is NOT available
986
     */
987
    public function getRootCategory()
988
    {
989
990
        // load the actual store view code
991
        $storeViewCode = $this->getStoreViewCode($this->getDefaultStoreViewCode());
0 ignored issues
show
Documentation introduced by
$this->getDefaultStoreViewCode() is of type array, but the function expects a string|null.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
992
993
        // query weather or not we've a root category or not
994
        if (isset($this->rootCategories[$storeViewCode])) {
995
            return $this->rootCategories[$storeViewCode];
996
        }
997
998
        // throw an exception if the root category is NOT available
999
        throw new \Exception(sprintf('Root category for %s is not available', $storeViewCode));
1000
    }
1001
1002
    /**
1003
     * Return's the Magento configuration value.
1004
     *
1005
     * @param string  $path    The Magento path of the requested configuration value
1006
     * @param mixed   $default The default value that has to be returned, if the requested configuration value is not set
1007
     * @param string  $scope   The scope the configuration value has been set
1008
     * @param integer $scopeId The scope ID the configuration value has been set
1009
     *
1010
     * @return mixed The configuration value
1011
     * @throws \Exception Is thrown, if nor a value can be found or a default value has been passed
1012
     */
1013
    public function getCoreConfigData($path, $default = null, $scope = ScopeKeys::SCOPE_DEFAULT, $scopeId = 0)
1014
    {
1015
1016
        // initialize the core config data
1017
        $coreConfigData = array(
1018
            MemberNames::PATH => $path,
1019
            MemberNames::SCOPE => $scope,
1020
            MemberNames::SCOPE_ID => $scopeId
1021
        );
1022
1023
        // generate the UID from the passed data
1024
        $uniqueIdentifier = $this->coreConfigDataUidGenerator->generate($coreConfigData);
1025
1026
        // iterate over the core config data and try to find the requested configuration value
1027
        if (isset($this->coreConfigData[$uniqueIdentifier])) {
1028
            return $this->coreConfigData[$uniqueIdentifier][MemberNames::VALUE];
1029
        }
1030
1031
        // query whether or not we've to query for the configuration value on fallback level 'websites' also
1032
        if ($scope === ScopeKeys::SCOPE_STORES) {
1033
            // query whether or not the website with the passed ID is available
1034
            foreach ($this->storeWebsites as $storeWebsite) {
1035
                if ($storeWebsite[MemberNames::WEBSITE_ID] === $scopeId) {
1036
                    // replace scope with 'websites' and website ID
1037
                    $coreConfigData = array_merge(
1038
                        $coreConfigData,
1039
                        array(
1040
                            MemberNames::SCOPE    => ScopeKeys::SCOPE_WEBSITES,
1041
                            MemberNames::SCOPE_ID => $storeWebsite[MemberNames::WEBSITE_ID]
1042
                        )
1043
                    );
1044
1045
                    // generate the UID from the passed data, merged with the 'websites' scope and ID
1046
                    $uniqueIdentifier = $this->coreConfigDataUidGenerator->generate($coreConfigData);
1047
1048
                    // query whether or not, the configuration value on 'websites' level
1049 View Code Duplication
                    if (isset($this->coreConfigData[$uniqueIdentifier][MemberNames::VALUE])) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
1050
                        return $this->coreConfigData[$uniqueIdentifier][MemberNames::VALUE];
1051
                    }
1052
                }
1053
            }
1054
        }
1055
1056
        // replace scope with 'default' and scope ID '0'
1057
        $coreConfigData = array_merge(
1058
            $coreConfigData,
1059
            array(
1060
                MemberNames::SCOPE    => ScopeKeys::SCOPE_DEFAULT,
1061
                MemberNames::SCOPE_ID => 0
1062
            )
1063
        );
1064
1065
        // generate the UID from the passed data, merged with the 'default' scope and ID 0
1066
        $uniqueIdentifier = $this->coreConfigDataUidGenerator->generate($coreConfigData);
1067
1068
        // query whether or not, the configuration value on 'default' level
1069 View Code Duplication
        if (isset($this->coreConfigData[$uniqueIdentifier][MemberNames::VALUE])) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
1070
            return $this->coreConfigData[$uniqueIdentifier][MemberNames::VALUE];
1071
        }
1072
1073
        // if not, return the passed default value
1074
        if ($default !== null) {
1075
            return $default;
1076
        }
1077
1078
        // throw an exception if no value can be found
1079
        // in the Magento configuration
1080
        throw new \Exception(
1081
            sprintf(
1082
                'Can\'t find a value for configuration "%s-%s-%d" in "core_config_data"',
1083
                $path,
1084
                $scope,
1085
                $scopeId
1086
            )
1087
        );
1088
    }
1089
1090
    /**
1091
     * Resolve the original column name for the passed one.
1092
     *
1093
     * @param string $columnName The column name that has to be resolved
1094
     *
1095
     * @return string|null The original column name
1096
     */
1097
    public function resolveOriginalColumnName($columnName)
1098
    {
1099
1100
        // try to load the original data
1101
        $originalData = $this->getOriginalData();
1102
1103
        // query whether or not original data is available
1104
        if (isset($originalData[ColumnKeys::ORIGINAL_COLUMN_NAMES])) {
1105
            // query whether or not the original column name is available
1106
            if (isset($originalData[ColumnKeys::ORIGINAL_COLUMN_NAMES][$columnName])) {
1107
                return $originalData[ColumnKeys::ORIGINAL_COLUMN_NAMES][$columnName];
1108
            }
1109
1110
            // query whether or a wildcard column name is available
1111
            if (isset($originalData[ColumnKeys::ORIGINAL_COLUMN_NAMES]['*'])) {
1112
                return $originalData[ColumnKeys::ORIGINAL_COLUMN_NAMES]['*'];
1113
            }
1114
        }
1115
1116
        // return the original column name
1117
        return $columnName;
1118
    }
1119
1120
    /**
1121
     * Return's the original data if available, or an empty array.
1122
     *
1123
     * @return array The original data
1124
     */
1125
    public function getOriginalData()
1126
    {
1127
1128
        // initialize the array for the original data
1129
        $originalData = array();
1130
1131
        // query whether or not the column contains original data
1132
        if ($this->hasOriginalData()) {
1133
            // unerialize the original data from the column
1134
            $originalData = unserialize($this->row[$this->headers[ColumnKeys::ORIGINAL_DATA]]);
1135
        }
1136
1137
        // return an empty array, if not
1138
        return $originalData;
1139
    }
1140
1141
    /**
1142
     * Query's whether or not the actual column contains original data like
1143
     * filename, line number and column names.
1144
     *
1145
     * @return boolean TRUE if the actual column contains origin data, else FALSE
1146
     */
1147
    public function hasOriginalData()
1148
    {
1149
        return isset($this->headers[ColumnKeys::ORIGINAL_DATA]) && isset($this->row[$this->headers[ColumnKeys::ORIGINAL_DATA]]);
1150
    }
1151
1152
    /**
1153
     * Wraps the passed exeception into a new one by trying to resolve the original filname,
1154
     * line number and column names and use it for a detailed exception message.
1155
     *
1156
     * @param array      $columnNames The column names that should be resolved and wrapped
1157
     * @param \Exception $parent      The exception we want to wrap
1158
     * @param string     $className   The class name of the exception type we want to wrap the parent one
1159
     *
1160
     * @return \Exception the wrapped exception
1161
     */
1162
    public function wrapException(
1163
        array $columnNames = array(),
1164
        \Exception $parent = null,
1165
        $className = '\TechDivision\Import\Exceptions\WrappedColumnException'
1166
    ) {
1167
1168
        // initialize the message
1169
        $message = $parent->getMessage();
0 ignored issues
show
Bug introduced by
It seems like $parent is not always an object, but can also be of type null. Maybe add an additional type check?

If a variable is not always an object, we recommend to add an additional type check to ensure your method call is safe:

function someFunction(A $objectMaybe = null)
{
    if ($objectMaybe instanceof A) {
        $objectMaybe->doSomething();
    }
}
Loading history...
1170
1171
        // query whether or not has been a result of invalid data of a previous column of a CSV file
1172
        if ($this->hasOriginalData()) {
1173
            // load the original data
1174
            $originalData = $this->getOriginalData();
1175
1176
            // replace old filename and line number of the original message
1177
            $message = $this->appendExceptionSuffix(
1178
                $this->stripExceptionSuffix($message),
1179
                $originalData[ColumnKeys::ORIGINAL_FILENAME],
1180
                $originalData[ColumnKeys::ORIGINAL_LINE_NUMBER]
1181
            );
1182
        } else {
1183
            // append filename and line number to the original message
1184
            $message = $this->appendExceptionSuffix(
1185
                $this->stripExceptionSuffix($message),
1186
                $this->filename,
1187
                $this->lineNumber
1188
            );
1189
        }
1190
1191
        // query whether or not, column names has been passed
1192
        if (sizeof($columnNames) > 0) {
1193
            // prepare the original column names
1194
            $originalColumnNames = array();
1195
            foreach ($columnNames as $columnName) {
1196
                $originalColumnNames[] = $this->resolveOriginalColumnName($columnName);
1197
            }
1198
1199
            // append the column information
1200
            $message = sprintf('%s in column(s) %s', $message, implode(', ', $originalColumnNames));
1201
        }
1202
1203
        // create a new exception and wrap the parent one
1204
        return new $className($message, null, $parent);
1205
    }
1206
1207
    /**
1208
     * Strip's the exception suffix containing filename and line number from the
1209
     * passed message.
1210
     *
1211
     * @param string $message The message to strip the exception suffix from
1212
     *
1213
     * @return mixed The message without the exception suffix
1214
     */
1215
    public function stripExceptionSuffix($message)
1216
    {
1217
        return str_replace($this->appendExceptionSuffix(), '', $message);
1218
    }
1219
1220
    /**
1221
     * Append's the exception suffix containing filename and line number to the
1222
     * passed message. If no message has been passed, only the suffix will be
1223
     * returned
1224
     *
1225
     * @param string|null $message    The message to append the exception suffix to
1226
     * @param string|null $filename   The filename used to create the suffix
1227
     * @param string|null $lineNumber The line number used to create the suffx
1228
     *
1229
     * @return string The message with the appended exception suffix
1230
     */
1231
    public function appendExceptionSuffix($message = null, $filename = null, $lineNumber = null)
1232
    {
1233
1234
        // query whether or not a filename has been passed
1235
        if ($filename === null) {
1236
            $filename = $this->getFilename();
1237
        }
1238
1239
        // query whether or not a line number has been passed
1240
        if ($lineNumber === null) {
1241
            $lineNumber = $this->getLineNumber();
1242
        }
1243
1244
        // if no message has been passed, only return the suffix
1245
        if ($message === null) {
1246
            return sprintf(' in file %s on line %d', $filename, $lineNumber);
1247
        }
1248
1249
        // concatenate the message with the suffix and return it
1250
        return sprintf('%s in file %s on line %d', $message, $filename, $lineNumber);
1251
    }
1252
1253
    /**
1254
     * Raises the value for the counter with the passed key by one.
1255
     *
1256
     * @param mixed $counterName The name of the counter to raise
1257
     *
1258
     * @return integer The counter's new value
1259
     */
1260
    public function raiseCounter($counterName)
1261
    {
1262
1263
        // raise the counter with the passed name
1264
        return $this->getRegistryProcessor()->raiseCounter(
1265
            RegistryKeys::COUNTERS,
1266
            $counterName
1267
        );
1268
    }
1269
1270
    /**
1271
     * Merge the passed array into the status of the actual import.
1272
     *
1273
     * @param array $status The status information to be merged
1274
     *
1275
     * @return void
1276
     */
1277
    public function mergeAttributesRecursive(array $status)
1278
    {
1279
1280
        // merge the passed status
1281
        return $this->getRegistryProcessor()->mergeAttributesRecursive(
1282
            RegistryKeys::STATUS,
1283
            $status
1284
        );
1285
    }
1286
1287
    /**
1288
     * Return's the entity type code to be used.
1289
     *
1290
     * @return string The entity type code to be used
1291
     */
1292
    public function getEntityTypeCode()
1293
    {
1294
1295
        // load the configuration specific entity type code from the plugin configuration
1296
        $entityTypeCode = $this->getExecutionContext()->getEntityTypeCode();
1297
1298
        // try to map the entity type code
1299
        if (isset($this->entityTypeCodeMappings[$entityTypeCode])) {
1300
            $entityTypeCode = $this->entityTypeCodeMappings[$entityTypeCode];
1301
        }
1302
1303
        // return the (mapped) entity type code
1304
        return $entityTypeCode;
1305
    }
1306
}
1307