Completed
Push — master ( 22c6a7...d34fbb )
by Tim
03:20
created

AbstractSubject::registerObserver()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 12
Code Lines 4

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 5
CRAP Score 2

Importance

Changes 0
Metric Value
dl 0
loc 12
ccs 5
cts 5
cp 1
rs 9.4285
c 0
b 0
f 0
cc 2
eloc 4
nc 2
nop 2
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 TechDivision\Import\RowTrait;
24
use TechDivision\Import\HeaderTrait;
25
use TechDivision\Import\SystemLoggerTrait;
26
use TechDivision\Import\Utils\ScopeKeys;
27
use TechDivision\Import\Utils\ColumnKeys;
28
use TechDivision\Import\Utils\MemberNames;
29
use TechDivision\Import\Utils\RegistryKeys;
30
use TechDivision\Import\Utils\Generators\GeneratorInterface;
31
use TechDivision\Import\Callbacks\CallbackInterface;
32
use TechDivision\Import\Observers\ObserverInterface;
33
use TechDivision\Import\Adapter\ImportAdapterInterface;
34
use TechDivision\Import\Exceptions\WrappedColumnException;
35
use TechDivision\Import\Services\RegistryProcessorInterface;
36
use TechDivision\Import\Configuration\SubjectConfigurationInterface;
37
38
/**
39
 * An abstract subject implementation.
40
 *
41
 * @author    Tim Wagner <[email protected]>
42
 * @copyright 2016 TechDivision GmbH <[email protected]>
43
 * @license   http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0)
44
 * @link      https://github.com/techdivision/import
45
 * @link      http://www.techdivision.com
46
 */
47
abstract class AbstractSubject implements SubjectInterface
48
{
49
50
    /**
51
     * The trait that provides basic filesystem handling functionality.
52
     *
53
     * @var TechDivision\Import\Subjects\FilesystemTrait
54
     */
55
    use FilesystemTrait;
56
57
    /**
58
     * The trait that provides basic filesystem handling functionality.
59
     *
60
     * @var TechDivision\Import\SystemLoggerTrait
61
     */
62
    use SystemLoggerTrait;
63
64
    /**
65
     * The trait that provides header handling functionality.
66
     *
67
     * @var TechDivision\Import\HeaderTrait
68
     */
69
    use HeaderTrait;
70
71
    /**
72
     * The trait that provides row handling functionality.
73
     *
74
     * @var TechDivision\Import\RowTrait
75
     */
76
    use RowTrait;
77
78
    /**
79
     * The name of the file to be imported.
80
     *
81
     * @var string
82
     */
83
    protected $filename;
84
85
    /**
86
     * The actual line number.
87
     *
88
     * @var integer
89
     */
90
    protected $lineNumber = 0;
91
92
    /**
93
     * The actual operation name.
94
     *
95
     * @var string
96
     */
97
    protected $operationName ;
98
99
    /**
100
     * The flag that stop's overserver execution on the actual row.
101
     *
102
     * @var boolean
103
     */
104
    protected $skipRow = false;
105
106
    /**
107
     * The import adapter instance.
108
     *
109
     * @var \TechDivision\Import\Adapter\ImportAdapterInterface
110
     */
111
    protected $importAdapter;
112
113
    /**
114
     * The system configuration.
115
     *
116
     * @var \TechDivision\Import\Configuration\SubjectConfigurationInterface
117
     */
118
    protected $configuration;
119
120
    /**
121
     * The RegistryProcessor instance to handle running threads.
122
     *
123
     * @var \TechDivision\Import\Services\RegistryProcessorInterface
124
     */
125
    protected $registryProcessor;
126
127
    /**
128
     * The actions unique serial.
129
     *
130
     * @var string
131
     */
132
    protected $serial;
133
134
    /**
135
     * Array with the subject's observers.
136
     *
137
     * @var array
138
     */
139
    protected $observers = array();
140
141
    /**
142
     * Array with the subject's callbacks.
143
     *
144
     * @var array
145
     */
146
    protected $callbacks = array();
147
148
    /**
149
     * The subject's callback mappings.
150
     *
151
     * @var array
152
     */
153
    protected $callbackMappings = array();
154
155
    /**
156
     * The available root categories.
157
     *
158
     * @var array
159
     */
160
    protected $rootCategories = array();
161
162
    /**
163
     * The Magento configuration.
164
     *
165
     * @var array
166
     */
167
    protected $coreConfigData = array();
168
169
    /**
170
     * The available stores.
171
     *
172
     * @var array
173
     */
174
    protected $stores = array();
175
176
    /**
177
     * The available websites.
178
     *
179
     * @var array
180
     */
181
    protected $storeWebsites = array();
182
183
    /**
184
     * The default store.
185
     *
186
     * @var array
187
     */
188
    protected $defaultStore;
189
190
    /**
191
     * The store view code the create the product/attributes for.
192
     *
193
     * @var string
194
     */
195
    protected $storeViewCode;
196
197
    /**
198
     * The UID generator for the core config data.
199
     *
200
     * @var \TechDivision\Import\Utils\Generators\GeneratorInterface
201
     */
202
    protected $coreConfigDataUidGenerator;
203
204
    /**
205
     * Initialize the subject instance.
206
     *
207
     * @param \TechDivision\Import\Services\RegistryProcessorInterface $registryProcessor          The registry processor instance
208
     * @param \TechDivision\Import\Utils\Generators\GeneratorInterface $coreConfigDataUidGenerator The UID generator for the core config data
209
     * @param array                                                    $systemLoggers              The array with the system loggers instances
210
     */
211 77
    public function __construct(
212
        RegistryProcessorInterface $registryProcessor,
213
        GeneratorInterface $coreConfigDataUidGenerator,
214
        array $systemLoggers
215
    ) {
216 77
        $this->systemLoggers = $systemLoggers;
217 77
        $this->registryProcessor = $registryProcessor;
218 77
        $this->coreConfigDataUidGenerator = $coreConfigDataUidGenerator;
219 77
    }
220
221
    /**
222
     * Set's the name of the file to import
223
     *
224
     * @param string $filename The filename
225
     *
226
     * @return void
227
     */
228 10
    public function setFilename($filename)
229
    {
230 10
        $this->filename = $filename;
231 10
    }
232
233
    /**
234
     * Return's the name of the file to import.
235
     *
236
     * @return string The filename
237
     */
238 8
    public function getFilename()
239
    {
240 8
        return $this->filename;
241
    }
242
243
    /**
244
     * Set's the actual operation name.
245
     *
246
     * @param string $operationName The actual operation name
247
     *
248
     * @return void
249
     */
250 1
    public function setOperationName($operationName)
251
    {
252 1
        $this->operationName = $operationName;
253 1
    }
254
255
    /**
256
     * Return's the actual operation name.
257
     *
258
     * @return string
259
     */
260 1
    public function getOperationName()
261
    {
262 1
        return $this->operationName;
263
    }
264
265
    /**
266
     * Set's the actual line number.
267
     *
268
     * @param integer $lineNumber The line number
269
     *
270
     * @return void
271
     */
272 1
    public function setLineNumber($lineNumber)
273
    {
274 1
        $this->lineNumber = $lineNumber;
275 1
    }
276
277
    /**
278
     * Return's the actual line number.
279
     *
280
     * @return integer The line number
281
     */
282 9
    public function getLineNumber()
283
    {
284 9
        return $this->lineNumber;
285
    }
286
287
    /**
288
     * Stop's observer execution on the actual row.
289
     *
290
     * @return void
291
     */
292 1
    public function skipRow()
293
    {
294 1
        $this->skipRow = true;
295 1
    }
296
297
    /**
298
     * Return's the default callback mappings.
299
     *
300
     * @return array The default callback mappings
301
     */
302 1
    public function getDefaultCallbackMappings()
303
    {
304 1
        return array();
305
    }
306
307
    /**
308
     * Tries to format the passed value to a valid date with format 'Y-m-d H:i:s'.
309
     * If the passed value is NOT a valid date, NULL will be returned.
310
     *
311
     * @param string $value The value to format
312
     *
313
     * @return string|null The formatted date or NULL if the date is not valid
314
     */
315 2
    public function formatDate($value)
316
    {
317
318
        // create a DateTime instance from the passed value
319 2
        if ($dateTime = \DateTime::createFromFormat($this->getSourceDateFormat(), $value)) {
320 1
            return $dateTime->format('Y-m-d H:i:s');
321
        }
322
323
        // return NULL, if the passed value is NOT a valid date
324 1
        return null;
325
    }
326
327
    /**
328
     * Extracts the elements of the passed value by exploding them
329
     * with the also passed delimiter.
330
     *
331
     * @param string      $value     The value to extract
332
     * @param string|null $delimiter The delimiter used to extrace the elements
333
     *
334
     * @return array The exploded values
335
     */
336 2
    public function explode($value, $delimiter = null)
337
    {
338
        // load the global configuration
339 2
        $configuration = $this->getConfiguration();
340
341
        // initializet delimiter, enclosure and escape char
342 2
        $delimiter = $delimiter ? $delimiter : $configuration->getDelimiter();
343 2
        $enclosure = $configuration->getEnclosure();
344 2
        $escape = $configuration->getEscape();
345
346
        // parse and return the found data as array
347 2
        return str_getcsv($value, $delimiter, $enclosure, $escape);
348
    }
349
350
    /**
351
     * Queries whether or not debug mode is enabled or not, default is TRUE.
352
     *
353
     * @return boolean TRUE if debug mode is enabled, else FALSE
354
     */
355 1
    public function isDebugMode()
356
    {
357 1
        return $this->getConfiguration()->isDebugMode();
358
    }
359
360
    /**
361
     * Set's the subject configuration.
362
     *
363
     * @param \TechDivision\Import\Configuration\SubjectConfigurationInterface $configuration The subject configuration
364
     *
365
     * @return void
366
     */
367 77
    public function setConfiguration(SubjectConfigurationInterface $configuration)
368
    {
369 77
        $this->configuration = $configuration;
370 77
    }
371
372
    /**
373
     * Return's the subject configuration.
374
     *
375
     * @return \TechDivision\Import\Configuration\SubjectConfigurationInterface The subject configuration
376
     */
377 77
    public function getConfiguration()
378
    {
379 77
        return $this->configuration;
380
    }
381
382
    /**
383
     * Set's the import adapter instance.
384
     *
385
     * @param \TechDivision\Import\Adapter\ImportAdapterInterface $importAdapter The import adapter instance
386
     *
387
     * @return void
388
     */
389 3
    public function setImportAdapter(ImportAdapterInterface $importAdapter)
390
    {
391 3
        $this->importAdapter = $importAdapter;
392 3
    }
393
394
    /**
395
     * Return's the import adapter instance.
396
     *
397
     * @return \TechDivision\Import\Adapter\ImportAdapterInterface The import adapter instance
398
     */
399 3
    public function getImportAdapter()
400
    {
401 3
        return $this->importAdapter;
402
    }
403
404
    /**
405
     * Return's the RegistryProcessor instance to handle the running threads.
406
     *
407
     * @return \TechDivision\Import\Services\RegistryProcessorInterface The registry processor instance
408
     */
409 77
    public function getRegistryProcessor()
410
    {
411 77
        return $this->registryProcessor;
412
    }
413
414
    /**
415
     * Set's the unique serial for this import process.
416
     *
417
     * @param string $serial The unique serial
418
     *
419
     * @return void
420
     */
421 9
    public function setSerial($serial)
422
    {
423 9
        $this->serial = $serial;
424 9
    }
425
426
    /**
427
     * Return's the unique serial for this import process.
428
     *
429
     * @return string The unique serial
430
     */
431 4
    public function getSerial()
432
    {
433 4
        return $this->serial;
434
    }
435
436
    /**
437
     * Return's the source date format to use.
438
     *
439
     * @return string The source date format
440
     */
441 4
    public function getSourceDateFormat()
442
    {
443 4
        return $this->getConfiguration()->getSourceDateFormat();
444
    }
445
446
    /**
447
     * Return's the multiple field delimiter character to use, default value is comma (,).
448
     *
449
     * @return string The multiple field delimiter character
450
     */
451 1
    public function getMultipleFieldDelimiter()
452
    {
453 1
        return $this->getConfiguration()->getMultipleFieldDelimiter();
454
    }
455
456
    /**
457
     * Return's the multiple value delimiter character to use, default value is comma (|).
458
     *
459
     * @return string The multiple value delimiter character
460
     */
461 1
    public function getMultipleValueDelimiter()
462
    {
463 1
        return $this->getConfiguration()->getMultipleValueDelimiter();
464
    }
465
466
    /**
467
     * Intializes the previously loaded global data for exactly one bunch.
468
     *
469
     * @param string $serial The serial of the actual import
470
     *
471
     * @return void
472
     */
473 77
    public function setUp($serial)
474
    {
475
476
        // load the status of the actual import
477 77
        $status = $this->getRegistryProcessor()->getAttribute($serial);
478
479
        // load the global data we've prepared initially
480 77
        $this->stores = $status[RegistryKeys::GLOBAL_DATA][RegistryKeys::STORES];
481 77
        $this->defaultStore = $status[RegistryKeys::GLOBAL_DATA][RegistryKeys::DEFAULT_STORE];
482 77
        $this->storeWebsites  = $status[RegistryKeys::GLOBAL_DATA][RegistryKeys::STORE_WEBSITES];
483 77
        $this->rootCategories = $status[RegistryKeys::GLOBAL_DATA][RegistryKeys::ROOT_CATEGORIES];
484 77
        $this->coreConfigData = $status[RegistryKeys::GLOBAL_DATA][RegistryKeys::CORE_CONFIG_DATA];
485
486
        // initialize the operation name
487 77
        $this->operationName = $this->getConfiguration()->getConfiguration()->getOperationName();
488
489
        // merge the callback mappings with the mappings from the child instance
490 77
        $this->callbackMappings = array_merge($this->callbackMappings, $this->getDefaultCallbackMappings());
491
492
        // merge the callback mappings the the one from the configuration file
493 77
        foreach ($this->getConfiguration()->getCallbacks() as $callbackMappings) {
494 77
            foreach ($callbackMappings as $attributeCode => $mappings) {
495
                // write a log message, that default callback configuration will
496
                // be overwritten with the one from the configuration file
497 77
                if (isset($this->callbackMappings[$attributeCode])) {
498 72
                    $this->getSystemLogger()->notice(
499 72
                        sprintf('Now override callback mappings for attribute %s with values found in configuration file', $attributeCode)
500
                    );
501
                }
502
503
                // override the attributes callbacks
504 77
                $this->callbackMappings[$attributeCode] = $mappings;
505
            }
506
        }
507 77
    }
508
509
    /**
510
     * Clean up the global data after importing the variants.
511
     *
512
     * @param string $serial The serial of the actual import
513
     *
514
     * @return void
515
     */
516 1
    public function tearDown($serial)
517
    {
518
519
        // load the registry processor
520 1
        $registryProcessor = $this->getRegistryProcessor();
521
522
        // update the source directory for the next subject
523 1
        $registryProcessor->mergeAttributesRecursive(
524 1
            $serial,
525 1
            array(RegistryKeys::SOURCE_DIRECTORY => $newSourceDir = $this->getNewSourceDir($serial))
526
        );
527
528
        // log a debug message with the new source directory
529 1
        $this->getSystemLogger()->info(
530 1
            sprintf('Subject %s successfully updated source directory to %s', get_class($this), $newSourceDir)
531
        );
532 1
    }
533
534
    /**
535
     * Return's the target directory for the artefact export.
536
     *
537
     * @return string The target directory for the artefact export
538
     */
539 1
    public function getTargetDir()
540
    {
541 1
        return $this->getNewSourceDir($this->getSerial());
542
    }
543
544
    /**
545
     * Return's the next source directory, which will be the target directory
546
     * of this subject, in most cases.
547
     *
548
     * @param string $serial The serial of the actual import
549
     *
550
     * @return string The new source directory
551
     */
552 4
    public function getNewSourceDir($serial)
553
    {
554 4
        return sprintf('%s/%s', $this->getConfiguration()->getTargetDir(), $serial);
555
    }
556
557
    /**
558
     * Register the passed observer with the specific type.
559
     *
560
     * @param \TechDivision\Import\Observers\ObserverInterface $observer The observer to register
561
     * @param string                                           $type     The type to register the observer with
562
     *
563
     * @return void
564
     */
565 6
    public function registerObserver(ObserverInterface $observer, $type)
566
    {
567
568
        // query whether or not the array with the callbacks for the
569
        // passed type has already been initialized, or not
570 6
        if (!isset($this->observers[$type])) {
571 6
            $this->observers[$type] = array();
572
        }
573
574
        // append the callback with the instance of the passed type
575 6
        $this->observers[$type][] = $observer;
576 6
    }
577
578
    /**
579
     * Register the passed callback with the specific type.
580
     *
581
     * @param \TechDivision\Import\Callbacks\CallbackInterface $callback The subject to register the callbacks for
582
     * @param string                                           $type     The type to register the callback with
583
     *
584
     * @return void
585
     */
586 2
    public function registerCallback(CallbackInterface $callback, $type)
587
    {
588
589
        // query whether or not the array with the callbacks for the
590
        // passed type has already been initialized, or not
591 2
        if (!isset($this->callbacks[$type])) {
592 2
            $this->callbacks[$type] = array();
593
        }
594
595
        // append the callback with the instance of the passed type
596 2
        $this->callbacks[$type][] = $callback;
597 2
    }
598
599
    /**
600
     * Return's the array with callbacks for the passed type.
601
     *
602
     * @param string $type The type of the callbacks to return
603
     *
604
     * @return array The callbacks
605
     */
606 1
    public function getCallbacksByType($type)
607
    {
608
609
        // initialize the array for the callbacks
610 1
        $callbacks = array();
611
612
        // query whether or not callbacks for the type are available
613 1
        if (isset($this->callbacks[$type])) {
614 1
            $callbacks = $this->callbacks[$type];
615
        }
616
617
        // return the array with the type's callbacks
618 1
        return $callbacks;
619
    }
620
621
    /**
622
     * Return's the array with the available observers.
623
     *
624
     * @return array The observers
625
     */
626 6
    public function getObservers()
627
    {
628 6
        return $this->observers;
629
    }
630
631
    /**
632
     * Return's the array with the available callbacks.
633
     *
634
     * @return array The callbacks
635
     */
636 1
    public function getCallbacks()
637
    {
638 1
        return $this->callbacks;
639
    }
640
641
    /**
642
     * Return's the callback mappings for this subject.
643
     *
644
     * @return array The array with the subject's callback mappings
645
     */
646 2
    public function getCallbackMappings()
647
    {
648 2
        return $this->callbackMappings;
649
    }
650
651
    /**
652
     * Imports the content of the file with the passed filename.
653
     *
654
     *
655
     * @param string $serial   The serial of the actual import
656
     * @param string $filename The filename to process
657
     *
658
     * @return void
659
     * @throws \Exception Is thrown, if the import can't be processed
660
     */
661 5
    public function import($serial, $filename)
662
    {
663
664
        try {
665
            // stop processing, if the filename doesn't match
666 5
            if (!$this->match($filename)) {
667 1
                return;
668
            }
669
670
            // load the system logger instance
671 4
            $systemLogger = $this->getSystemLogger();
672
673
            // prepare the flag filenames
674 4
            $inProgressFilename = sprintf('%s.inProgress', $filename);
675 4
            $importedFilename = sprintf('%s.imported', $filename);
676 4
            $failedFilename = sprintf('%s.failed', $filename);
677
678
            // query whether or not the file has already been imported
679 4
            if ($this->isFile($failedFilename) ||
680 3
                $this->isFile($importedFilename) ||
681 4
                $this->isFile($inProgressFilename)
682
            ) {
683
                // log a debug message and exit
684 1
                $systemLogger->debug(sprintf('Import running, found inProgress file %s', $inProgressFilename));
685 1
                return;
686
            }
687
688
            // flag file as in progress
689 3
            $this->touch($inProgressFilename);
690
691
            // track the start time
692 3
            $startTime = microtime(true);
693
694
            // initialize the serial/filename
695 3
            $this->setSerial($serial);
696 3
            $this->setFilename($filename);
697
698
            // log a message that the file has to be imported
699 3
            $systemLogger->debug(sprintf('Now start importing file %s', $filename));
700
701
            // let the adapter process the file
702 3
            $this->getImportAdapter()->import(array($this, 'importRow'), $filename);
703
704
            // track the time needed for the import in seconds
705 1
            $endTime = microtime(true) - $startTime;
706
707
            // log a message that the file has successfully been imported
708 1
            $systemLogger->debug(sprintf('Successfully imported file %s in %f s', $filename, $endTime));
709
710
            // rename flag file, because import has been successfull
711 1
            $this->rename($inProgressFilename, $importedFilename);
712
0 ignored issues
show
Coding Style introduced by
Blank line found at end of control structure
Loading history...
713 2
        } catch (\Exception $e) {
714
            // rename the flag file, because import failed and write the stack trace
715 2
            $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...
716 2
            $this->write($failedFilename, $e->__toString());
717
718
            // do not wrap the exception if not already done
719 2
            if ($e instanceof WrappedColumnException) {
720 1
                throw $e;
721
            }
722
723
            // else wrap and throw the exception
724 1
            throw $this->wrapException(array(), $e);
725
        }
726 1
    }
727
728
    /**
729
     * This method queries whether or not the passed filename matches
730
     * the pattern, based on the subjects configured prefix.
731
     *
732
     * @param string $filename The filename to match
733
     *
734
     * @return boolean TRUE if the filename matches, else FALSE
735
     */
736 5
    protected function match($filename)
737
    {
738
739
        // prepare the pattern to query whether the file has to be processed or not
740 5
        $pattern = sprintf(
741 5
            '/^.*\/%s.*\\.%s$/',
742 5
            $this->getConfiguration()->getPrefix(),
743 5
            $this->getConfiguration()->getSuffix()
744
        );
745
746
        // stop processing, if the filename doesn't match
747 5
        return (boolean) preg_match($pattern, $filename);
748
    }
749
750
    /**
751
     * Imports the passed row into the database. If the import failed, the exception
752
     * will be catched and logged, but the import process will be continued.
753
     *
754
     * @param array $row The row with the data to be imported
755
     *
756
     * @return void
757
     */
758 7
    public function importRow(array $row)
759
    {
760
761
        // initialize the row
762 7
        $this->row = $row;
763
764
        // raise the line number and reset the skip row flag
765 7
        $this->lineNumber++;
766 7
        $this->skipRow = false;
767
768
        // initialize the headers with the columns from the first line
769 7
        if (sizeof($this->headers) === 0) {
770 1
            foreach ($this->row as $value => $key) {
771 1
                $this->headers[$this->mapAttributeCodeByHeaderMapping($key)] = $value;
772
            }
773 1
            return;
774
        }
775
776
        // process the observers
777 6
        foreach ($this->getObservers() as $observers) {
778
            // invoke the pre-import/import and post-import observers
779 6
            foreach ($observers as $observer) {
780
                // query whether or not we have to skip the row
781 6
                if ($this->skipRow) {
782 1
                    break;
783
                }
784
785
                // if not, set the subject and process the observer
786 6
                if ($observer instanceof ObserverInterface) {
787 6
                    $this->row = $observer->handle($this);
788
                }
789
            }
790
        }
791
792
        // log a debug message with the actual line nr/file information
793 6
        $this->getSystemLogger()->debug(
794 6
            sprintf(
795 6
                'Successfully processed row (operation: %s) in file %s on line %d',
796 6
                $this->operationName,
797 6
                $this->filename,
798 6
                $this->lineNumber
799
            )
800
        );
801 6
    }
802
803
    /**
804
     * Map the passed attribute code, if a header mapping exists and return the
805
     * mapped mapping.
806
     *
807
     * @param string $attributeCode The attribute code to map
808
     *
809
     * @return string The mapped attribute code, or the original one
810
     */
811 2
    public function mapAttributeCodeByHeaderMapping($attributeCode)
812
    {
813
814
        // load the header mappings
815 2
        $headerMappings = $this->getHeaderMappings();
816
817
        // query weather or not we've a mapping, if yes, map the attribute code
818 2
        if (isset($headerMappings[$attributeCode])) {
819 1
            $attributeCode = $headerMappings[$attributeCode];
820
        }
821
822
        // return the (mapped) attribute code
823 2
        return $attributeCode;
824
    }
825
826
    /**
827
     * Queries whether or not that the subject needs an OK file to be processed.
828
     *
829
     * @return boolean TRUE if the subject needs an OK file, else FALSE
830
     */
831 1
    public function isOkFileNeeded()
832
    {
833 1
        return $this->getConfiguration()->isOkFileNeeded();
834
    }
835
836
    /**
837
     * Return's the default store.
838
     *
839
     * @return array The default store
840
     */
841 4
    public function getDefaultStore()
842
    {
843 4
        return $this->defaultStore;
844
    }
845
846
    /**
847
     * Set's the store view code the create the product/attributes for.
848
     *
849
     * @param string $storeViewCode The store view code
850
     *
851
     * @return void
852
     */
853 4
    public function setStoreViewCode($storeViewCode)
854
    {
855 4
        $this->storeViewCode = $storeViewCode;
856 4
    }
857
858
    /**
859
     * Return's the store view code the create the product/attributes for.
860
     *
861
     * @param string|null $default The default value to return, if the store view code has not been set
862
     *
863
     * @return string The store view code
864
     */
865 8
    public function getStoreViewCode($default = null)
866
    {
867
868
        // return the store view code, if available
869 8
        if ($this->storeViewCode != null) {
870 4
            return $this->storeViewCode;
871
        }
872
873
        // if NOT and a default code is available
874 4
        if ($default != null) {
0 ignored issues
show
Bug introduced by
It seems like you are loosely comparing $default of type string|null against null; this is ambiguous if the string can be empty. Consider using a strict comparison !== instead.
Loading history...
875
            // return the default value
876 3
            return $default;
877
        }
878 1
    }
879
880
    /**
881
     * Return's the store ID of the store with the passed store view code
882
     *
883
     * @param string $storeViewCode The store view code to return the store ID for
884
     *
885
     * @return integer The ID of the store with the passed ID
886
     * @throws \Exception Is thrown, if the store with the actual code is not available
887
     */
888 4
    public function getStoreId($storeViewCode)
889
    {
890
891
        // query whether or not, the requested store is available
892 4
        if (isset($this->stores[$storeViewCode])) {
893 3
            return (integer) $this->stores[$storeViewCode][MemberNames::STORE_ID];
894
        }
895
896
        // throw an exception, if not
897 1
        throw new \Exception(
898 1
            sprintf(
899 1
                'Found invalid store view code %s in file %s on line %d',
900 1
                $storeViewCode,
901 1
                $this->getFilename(),
902 1
                $this->getLineNumber()
903
            )
904
        );
905
    }
906
907
    /**
908
     * Return's the store ID of the actual row, or of the default store
909
     * if no store view code is set in the CSV file.
910
     *
911
     * @param string|null $default The default store view code to use, if no store view code is set in the CSV file
912
     *
913
     * @return integer The ID of the actual store
914
     * @throws \Exception Is thrown, if the store with the actual code is not available
915
     */
916 2
    public function getRowStoreId($default = null)
917
    {
918
919
        // initialize the default store view code, if not passed
920 2
        if ($default == null) {
0 ignored issues
show
Bug introduced by
It seems like you are loosely comparing $default of type string|null against null; this is ambiguous if the string can be empty. Consider using a strict comparison === instead.
Loading history...
921 2
            $defaultStore = $this->getDefaultStore();
922 2
            $default = $defaultStore[MemberNames::CODE];
923
        }
924
925
        // load the store view code the create the product/attributes for
926 2
        return $this->getStoreId($this->getStoreViewCode($default));
927
    }
928
929
    /**
930
     * Prepare's the store view code in the subject.
931
     *
932
     * @return void
933
     */
934 2
    public function prepareStoreViewCode()
935
    {
936
937
        // re-set the store view code
938 2
        $this->setStoreViewCode(null);
939
940
        // initialize the store view code
941 2
        if ($storeViewCode = $this->getValue(ColumnKeys::STORE_VIEW_CODE)) {
942 2
            $this->setStoreViewCode($storeViewCode);
943
        }
944 2
    }
945
946
    /**
947
     * Return's the root category for the actual view store.
948
     *
949
     * @return array The store's root category
950
     * @throws \Exception Is thrown if the root category for the passed store code is NOT available
951
     */
952 2
    public function getRootCategory()
953
    {
954
955
        // load the default store
956 2
        $defaultStore = $this->getDefaultStore();
957
958
        // load the actual store view code
959 2
        $storeViewCode = $this->getStoreViewCode($defaultStore[MemberNames::CODE]);
960
961
        // query weather or not we've a root category or not
962 2
        if (isset($this->rootCategories[$storeViewCode])) {
963 1
            return $this->rootCategories[$storeViewCode];
964
        }
965
966
        // throw an exception if the root category is NOT available
967 1
        throw new \Exception(sprintf('Root category for %s is not available', $storeViewCode));
968
    }
969
970
    /**
971
     * Return's the Magento configuration value.
972
     *
973
     * @param string  $path    The Magento path of the requested configuration value
974
     * @param mixed   $default The default value that has to be returned, if the requested configuration value is not set
975
     * @param string  $scope   The scope the configuration value has been set
976
     * @param integer $scopeId The scope ID the configuration value has been set
977
     *
978
     * @return mixed The configuration value
979
     * @throws \Exception Is thrown, if nor a value can be found or a default value has been passed
980
     */
981 5
    public function getCoreConfigData($path, $default = null, $scope = ScopeKeys::SCOPE_DEFAULT, $scopeId = 0)
982
    {
983
984
        // initialize the core config data
985
        $coreConfigData = array(
986 5
            MemberNames::PATH => $path,
987 5
            MemberNames::SCOPE => $scope,
988 5
            MemberNames::SCOPE_ID => $scopeId
989
        );
990
991
        // generate the UID from the passed data
992 5
        $uniqueIdentifier = $this->coreConfigDataUidGenerator->generate($coreConfigData);
993
994
        // iterate over the core config data and try to find the requested configuration value
995 5
        if (isset($this->coreConfigData[$uniqueIdentifier])) {
996 1
            return $this->coreConfigData[$uniqueIdentifier][MemberNames::VALUE];
997
        }
998
999
        // query whether or not we've to query for the configuration value on fallback level 'websites' also
1000 4
        if ($scope === ScopeKeys::SCOPE_STORES) {
1001
            // query whether or not the website with the passed ID is available
1002 2
            foreach ($this->storeWebsites as $storeWebsite) {
1003 2
                if ($storeWebsite[MemberNames::WEBSITE_ID] === $scopeId) {
1004
                    // replace scope with 'websites' and website ID
1005 2
                    $coreConfigData = array_merge(
1006 2
                        $coreConfigData,
1007
                        array(
1008 2
                            MemberNames::SCOPE    => ScopeKeys::SCOPE_WEBSITES,
1009
                            MemberNames::SCOPE_ID => $storeWebsite[MemberNames::WEBSITE_ID]
1010
                        )
1011
                    );
1012
1013
                    // generate the UID from the passed data, merged with the 'websites' scope and ID
1014 2
                    $uniqueIdentifier = $this->coreConfigDataUidGenerator->generate($coreConfigData);
1015
1016
                    // query whether or not, the configuration value on 'websites' level
1017 2 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...
1018 2
                        return $this->coreConfigData[$uniqueIdentifier][MemberNames::VALUE];
1019
                    }
1020
                }
1021
            }
1022
        }
1023
1024
        // replace scope with 'default' and scope ID '0'
1025 3
        $coreConfigData = array_merge(
1026 3
            $coreConfigData,
1027
            array(
1028 3
                MemberNames::SCOPE    => ScopeKeys::SCOPE_DEFAULT,
1029
                MemberNames::SCOPE_ID => 0
1030
            )
1031
        );
1032
1033
        // generate the UID from the passed data, merged with the 'default' scope and ID 0
1034 3
        $uniqueIdentifier = $this->coreConfigDataUidGenerator->generate($coreConfigData);
1035
1036
        // query whether or not, the configuration value on 'default' level
1037 3 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...
1038 1
            return $this->coreConfigData[$uniqueIdentifier][MemberNames::VALUE];
1039
        }
1040
1041
        // if not, return the passed default value
1042 2
        if ($default !== null) {
1043 1
            return $default;
1044
        }
1045
1046
        // throw an exception if no value can be found
1047
        // in the Magento configuration
1048 1
        throw new \Exception(
1049 1
            sprintf(
1050 1
                'Can\'t find a value for configuration "%s-%s-%d" in "core_config_data"',
1051 1
                $path,
1052 1
                $scope,
1053 1
                $scopeId
1054
            )
1055
        );
1056
    }
1057
1058
    /**
1059
     * Resolve the original column name for the passed one.
1060
     *
1061
     * @param string $columnName The column name that has to be resolved
1062
     *
1063
     * @return string|null The original column name
1064
     */
1065 2
    public function resolveOriginalColumnName($columnName)
1066
    {
1067
1068
        // try to load the original data
1069 2
        $originalData = $this->getOriginalData();
1070
1071
        // query whether or not original data is available
1072 2
        if (isset($originalData[ColumnKeys::ORIGINAL_COLUMN_NAMES])) {
1073
            // query whether or not the original column name is available
1074 1
            if (isset($originalData[ColumnKeys::ORIGINAL_COLUMN_NAMES][$columnName])) {
1075 1
                return $originalData[ColumnKeys::ORIGINAL_COLUMN_NAMES][$columnName];
1076
            }
1077
1078
            // query whether or a wildcard column name is available
1079 1
            if (isset($originalData[ColumnKeys::ORIGINAL_COLUMN_NAMES]['*'])) {
1080 1
                return $originalData[ColumnKeys::ORIGINAL_COLUMN_NAMES]['*'];
1081
            }
1082
        }
1083
1084
        // return the original column name
1085 1
        return $columnName;
1086
    }
1087
1088
    /**
1089
     * Return's the original data if available, or an empty array.
1090
     *
1091
     * @return array The original data
1092
     */
1093 2
    public function getOriginalData()
1094
    {
1095
1096
        // initialize the array for the original data
1097 2
        $originalData = array();
1098
1099
        // query whether or not the column contains original data
1100 2
        if ($this->hasOriginalData()) {
1101
            // unerialize the original data from the column
1102 1
            $originalData = unserialize($this->row[$this->headers[ColumnKeys::ORIGINAL_DATA]]);
1103
        }
1104
1105
        // return an empty array, if not
1106 2
        return $originalData;
1107
    }
1108
1109
    /**
1110
     * Query's whether or not the actual column contains original data like
1111
     * filename, line number and column names.
1112
     *
1113
     * @return boolean TRUE if the actual column contains origin data, else FALSE
1114
     */
1115 3
    public function hasOriginalData()
1116
    {
1117 3
        return isset($this->headers[ColumnKeys::ORIGINAL_DATA]) && isset($this->row[$this->headers[ColumnKeys::ORIGINAL_DATA]]);
1118
    }
1119
1120
    /**
1121
     * Wraps the passed exeception into a new one by trying to resolve the original filname,
1122
     * line number and column names and use it for a detailed exception message.
1123
     *
1124
     * @param array      $columnNames The column names that should be resolved and wrapped
1125
     * @param \Exception $parent      The exception we want to wrap
1126
     * @param string     $className   The class name of the exception type we want to wrap the parent one
1127
     *
1128
     * @return \Exception the wrapped exception
1129
     */
1130 2
    public function wrapException(
1131
        array $columnNames = array(),
1132
        \Exception $parent = null,
1133
        $className = '\TechDivision\Import\Exceptions\WrappedColumnException'
1134
    ) {
1135
1136
        // initialize the message
1137 2
        $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...
1138
1139
        // query whether or not has been a result of invalid data of a previous column of a CSV file
1140 2
        if ($this->hasOriginalData()) {
1141
            // load the original data
1142 1
            $originalData = $this->getOriginalData();
1143
1144
            // replace old filename and line number of the original message
1145 1
            $message = $this->appendExceptionSuffix(
1146 1
                $this->stripExceptionSuffix($message),
1147 1
                $originalData[ColumnKeys::ORIGINAL_FILENAME],
1148 1
                $originalData[ColumnKeys::ORIGINAL_LINE_NUMBER]
1149
            );
1150
0 ignored issues
show
Coding Style introduced by
Blank line found at end of control structure
Loading history...
1151
        } else {
1152
            // append filename and line number to the original message
1153 1
            $message = $this->appendExceptionSuffix(
1154 1
                $this->stripExceptionSuffix($message),
1155 1
                $this->filename,
1156 1
                $this->lineNumber
1157
            );
1158
        }
1159
1160
        // query whether or not, column names has been passed
1161 2
        if (sizeof($columnNames) > 0) {
1162
            // prepare the original column names
1163 1
            $originalColumnNames = array();
1164 1
            foreach ($columnNames as $columnName) {
1165 1
                $originalColumnNames[] = $this->resolveOriginalColumnName($columnName);
1166
            }
1167
1168
            // append the column information
1169 1
            $message = sprintf('%s in column(s) %s', $message, implode(', ', $originalColumnNames));
1170
        }
1171
1172
        // create a new exception and wrap the parent one
1173 2
        return new $className($message, null, $parent);
1174
    }
1175
1176
    /**
1177
     * Strip's the exception suffix containing filename and line number from the
1178
     * passed message.
1179
     *
1180
     * @param string $message The message to strip the exception suffix from
1181
     *
1182
     * @return mixed The message without the exception suffix
1183
     */
1184 2
    public function stripExceptionSuffix($message)
1185
    {
1186 2
        return str_replace($this->appendExceptionSuffix(), '', $message);
1187
    }
1188
1189
    /**
1190
     * Append's the exception suffix containing filename and line number to the
1191
     * passed message. If no message has been passed, only the suffix will be
1192
     * returned
1193
     *
1194
     * @param string|null $message    The message to append the exception suffix to
1195
     * @param string|null $filename   The filename used to create the suffix
1196
     * @param string|null $lineNumber The line number used to create the suffx
1197
     *
1198
     * @return string The message with the appended exception suffix
1199
     */
1200 7
    public function appendExceptionSuffix($message = null, $filename = null, $lineNumber = null)
1201
    {
1202
1203
        // query whether or not a filename has been passed
1204 7
        if ($filename === null) {
1205 7
            $filename = $this->getFilename();
1206
        }
1207
1208
        // query whether or not a line number has been passed
1209 7
        if ($lineNumber === null) {
1210 7
            $lineNumber = $this->getLineNumber();
1211
        }
1212
1213
        // if no message has been passed, only return the suffix
1214 7
        if ($message === null) {
1215 2
            return sprintf(' in file %s on line %d', $filename, $lineNumber);
1216
        }
1217
1218
        // concatenate the message with the suffix and return it
1219 7
        return sprintf('%s in file %s on line %d', $message, $filename, $lineNumber);
1220
    }
1221
1222
    /**
1223
     * Raises the value for the counter with the passed key by one.
1224
     *
1225
     * @param mixed $counterName The name of the counter to raise
1226
     *
1227
     * @return integer The counter's new value
1228
     */
1229 1
    public function raiseCounter($counterName)
1230
    {
1231
1232
        // raise the counter with the passed name
1233 1
        return $this->getRegistryProcessor()->raiseCounter(
1234 1
            $this->getSerial(),
1235 1
            $counterName
1236
        );
1237
    }
1238
1239
    /**
1240
     * Merge the passed array into the status of the actual import.
1241
     *
1242
     * @param array $status The status information to be merged
1243
     *
1244
     * @return void
1245
     */
1246 1
    public function mergeAttributesRecursive(array $status)
1247
    {
1248
1249
        // merge the passed status
1250 1
        $this->getRegistryProcessor()->mergeAttributesRecursive(
1251 1
            $this->getSerial(),
1252 1
            $status
1253
        );
1254 1
    }
1255
}
1256