Completed
Pull Request — 16.x (#167)
by Jitendra
05:11
created

AbstractSubject::__construct()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 11

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 6
CRAP Score 1

Importance

Changes 0
Metric Value
dl 0
loc 11
ccs 6
cts 6
cp 1
rs 9.9
c 0
b 0
f 0
cc 1
nc 1
nop 4
crap 1
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 Ramsey\Uuid\Uuid;
24
use League\Event\EmitterInterface;
25
use Doctrine\Common\Collections\Collection;
26
use TechDivision\Import\RowTrait;
27
use TechDivision\Import\HeaderTrait;
28
use TechDivision\Import\SystemLoggerTrait;
29
use TechDivision\Import\Utils\ScopeKeys;
30
use TechDivision\Import\Utils\ColumnKeys;
31
use TechDivision\Import\Utils\EventNames;
32
use TechDivision\Import\Utils\MemberNames;
33
use TechDivision\Import\Utils\RegistryKeys;
34
use TechDivision\Import\Utils\EntityTypeCodes;
35
use TechDivision\Import\Utils\Generators\GeneratorInterface;
36
use TechDivision\Import\Callbacks\CallbackInterface;
37
use TechDivision\Import\Observers\ObserverInterface;
38
use TechDivision\Import\Adapter\ImportAdapterInterface;
39
use TechDivision\Import\Exceptions\WrappedColumnException;
40
use TechDivision\Import\Services\RegistryProcessorInterface;
41
use TechDivision\Import\Configuration\SubjectConfigurationInterface;
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 unique identifier for the actual invocation.
92
     *
93
     * @var string
94
     */
95
    protected $uniqueId;
96
97
    /**
98
     * The name of the file to be imported.
99
     *
100
     * @var string
101
     */
102
    protected $filename;
103
104
    /**
105
     * The actual line number.
106
     *
107
     * @var integer
108
     */
109
    protected $lineNumber = 0;
110
111
    /**
112
     * The import adapter instance.
113
     *
114
     * @var \TechDivision\Import\Adapter\ImportAdapterInterface
115
     */
116
    protected $importAdapter;
117
118
    /**
119
     * The subject configuration.
120
     *
121
     * @var \TechDivision\Import\Configuration\SubjectConfigurationInterface
122
     */
123
    protected $configuration;
124
125
    /**
126
     * The plugin configuration.
127
     *
128
     * @var \TechDivision\Import\Configuration\PluginConfigurationInterface
129
     */
130
    protected $pluginConfiguration;
131
132
    /**
133
     * The RegistryProcessor instance to handle running threads.
134
     *
135
     * @var \TechDivision\Import\Services\RegistryProcessorInterface
136
     */
137
    protected $registryProcessor;
138
139
    /**
140
     * The actions unique serial.
141
     *
142
     * @var string
143
     */
144
    protected $serial;
145
146
    /**
147
     * Array with the subject's observers.
148
     *
149
     * @var array
150
     */
151
    protected $observers = array();
152
153
    /**
154
     * Array with the subject's callbacks.
155
     *
156
     * @var array
157
     */
158
    protected $callbacks = array();
159
160
    /**
161
     * The subject's callback mappings.
162
     *
163
     * @var array
164
     */
165
    protected $callbackMappings = array();
166
167
    /**
168
     * The available root categories.
169
     *
170
     * @var array
171
     */
172
    protected $rootCategories = array();
173
174
    /**
175
     * The Magento configuration.
176
     *
177
     * @var array
178
     */
179
    protected $coreConfigData = array();
180
181
    /**
182
     * The available stores.
183
     *
184
     * @var array
185
     */
186
    protected $stores = array();
187
188
    /**
189
     * The available websites.
190
     *
191
     * @var array
192
     */
193
    protected $storeWebsites = array();
194
195
    /**
196
     * The default store.
197
     *
198
     * @var array
199
     */
200
    protected $defaultStore;
201
202
    /**
203
     * The store view code the create the product/attributes for.
204
     *
205
     * @var string
206
     */
207
    protected $storeViewCode;
208
209
    /**
210
     * The UID generator for the core config data.
211
     *
212
     * @var \TechDivision\Import\Utils\Generators\GeneratorInterface
213
     */
214
    protected $coreConfigDataUidGenerator;
215
216
    /**
217
     * UNIX timestamp with the date the last row counter has been logged.
218
     *
219
     * @var integer
220
     */
221
    protected $lastLog = 0;
222
223
    /**
224
     * The number of the last line that has been logged with the row counter
225
     * @var integer
226
     */
227
    protected $lastLineNumber = 0;
228
229
    /**
230
     * The event emitter instance.
231
     *
232
     * @var \League\Event\EmitterInterface
233
     */
234
    protected $emitter;
235
236
    /**
237
     * The status of the file (0 = not processed, 1 = successfully processed, 2 = processed with failure)
238
     *
239
     * @var array
240
     */
241
    protected $status = array();
242
243
    /**
244
     * The default values for the columns from the configuration.
245
     *
246
     * @var array
247
     */
248
    protected $defaultColumnValues = array();
249
250
    protected $columnValues = array();
251
252
    /**
253
     * Mapping for the virtual entity type code to the real Magento 2 EAV entity type code.
254
     *
255
     * @var array
256
     */
257
    protected $entityTypeCodeMappings = array(
258
        EntityTypeCodes::EAV_ATTRIBUTE                 => EntityTypeCodes::CATALOG_PRODUCT,
259
        EntityTypeCodes::EAV_ATTRIBUTE_SET             => EntityTypeCodes::CATALOG_PRODUCT,
260
        EntityTypeCodes::CATALOG_PRODUCT_PRICE         => EntityTypeCodes::CATALOG_PRODUCT,
261
        EntityTypeCodes::CATALOG_PRODUCT_INVENTORY     => EntityTypeCodes::CATALOG_PRODUCT,
262
        EntityTypeCodes::CATALOG_PRODUCT_INVENTORY_MSI => EntityTypeCodes::CATALOG_PRODUCT,
263
        EntityTypeCodes::CATALOG_PRODUCT_TIER_PRICE    => EntityTypeCodes::CATALOG_PRODUCT
264
    );
265
266
    /**
267
     * Initialize the subject instance.
268
     *
269
     * @param \TechDivision\Import\Services\RegistryProcessorInterface $registryProcessor          The registry processor instance
270
     * @param \TechDivision\Import\Utils\Generators\GeneratorInterface $coreConfigDataUidGenerator The UID generator for the core config data
271
     * @param \Doctrine\Common\Collections\Collection                  $systemLoggers              The array with the system loggers instances
272
     * @param \League\Event\EmitterInterface                           $emitter                    The event emitter instance
273
     */
274 81
    public function __construct(
275
        RegistryProcessorInterface $registryProcessor,
276
        GeneratorInterface $coreConfigDataUidGenerator,
277
        Collection $systemLoggers,
278
        EmitterInterface $emitter
279
    ) {
280 81
        $this->emitter = $emitter;
281 81
        $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...
282 81
        $this->registryProcessor = $registryProcessor;
283 81
        $this->coreConfigDataUidGenerator = $coreConfigDataUidGenerator;
284 81
    }
285
286
    /**
287
     * Return's the event emitter instance.
288
     *
289
     * @return \League\Event\EmitterInterface The event emitter instance
290
     */
291 9
    public function getEmitter()
292
    {
293 9
        return $this->emitter;
294
    }
295
296
    /**
297
     * Set's the name of the file to import
298
     *
299
     * @param string $filename The filename
300
     *
301
     * @return void
302
     */
303 13
    public function setFilename($filename)
304
    {
305 13
        $this->filename = $filename;
306 13
    }
307
308
    /**
309
     * Return's the name of the file to import.
310
     *
311
     * @return string The filename
312
     */
313 12
    public function getFilename()
314
    {
315 12
        return $this->filename;
316
    }
317
318
    /**
319
     * Set's the actual line number.
320
     *
321
     * @param integer $lineNumber The line number
322
     *
323
     * @return void
324
     */
325 1
    public function setLineNumber($lineNumber)
326
    {
327 1
        $this->lineNumber = $lineNumber;
328 1
    }
329
330
    /**
331
     * Return's the actual line number.
332
     *
333
     * @return integer The line number
334
     */
335 17
    public function getLineNumber()
336
    {
337 17
        return $this->lineNumber;
338
    }
339
340
    /**
341
     * Return's the default callback mappings.
342
     *
343
     * @return array The default callback mappings
344
     */
345 1
    public function getDefaultCallbackMappings()
346
    {
347 1
        return array();
348
    }
349
350
    /**
351
     * Load the default column values from the configuration.
352
     *
353
     * @return array The array with the default column values
354
     */
355 81 View Code Duplication
    public function getDefaultColumnValues()
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...
356
    {
357
358
        // initialize the array for the default column values
359 81
        $defaultColumnValues = array();
360
361
        // load the entity type from the execution context
362 81
        $entityTypeCode = $this->getExecutionContext()->getEntityTypeCode();
363
364
        // load the column values from the configuration
365 81
        $columnValues = $this->getConfiguration()->getDefaultValues();
366
367
        // query whether or not default column values for the entity type are available
368 81
        if (isset($columnValues[$entityTypeCode])) {
369
            $defaultColumnValues = $columnValues[$entityTypeCode];
370
        }
371
372
        // return the default column values
373 81
        return $defaultColumnValues;
374
    }
375
376
    /**
377
     * Load the default header mappings from the configuration.
378
     *
379
     * @return array The array with the default header mappings
380
     */
381 81 View Code Duplication
    public function getDefaultHeaderMappings()
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...
382
    {
383
384
        // initialize the array for the default header mappings
385 81
        $defaultHeaderMappings = array();
386
387
        // load the entity type from the execution context
388 81
        $entityTypeCode = $this->getExecutionContext()->getEntityTypeCode();
389
390
        // load the header mappings from the configuration
391 81
        $headerMappings = $this->getConfiguration()->getHeaderMappings();
392
393
        // query whether or not header mappings for the entity type are available
394 81
        if (isset($headerMappings[$entityTypeCode])) {
395
            $defaultHeaderMappings = $headerMappings[$entityTypeCode];
396
        }
397
398
        // return the default header mappings
399 81
        return $defaultHeaderMappings;
400
    }
401
402
    /**
403
     * Tries to format the passed value to a valid date with format 'Y-m-d H:i:s'.
404
     * If the passed value is NOT a valid date, NULL will be returned.
405
     *
406
     * @param string $value The value to format
407
     *
408
     * @return string|null The formatted date or NULL if the date is not valid
409
     */
410
    public function formatDate($value)
411
    {
412
        return $this->getDateConverter()->convert($value);
413
    }
414
415
    /**
416
     * Extracts the elements of the passed value by exploding them
417
     * with the also passed delimiter.
418
     *
419
     * @param string      $value     The value to extract
420
     * @param string|null $delimiter The delimiter used to extrace the elements
421
     *
422
     * @return array The exploded values
423
     */
424
    public function explode($value, $delimiter = null)
425
    {
426
        return $this->getImportAdapter()->explode($value, $delimiter);
427
    }
428
429
    /**
430
     * Queries whether or not debug mode is enabled or not, default is TRUE.
431
     *
432
     * @return boolean TRUE if debug mode is enabled, else FALSE
433
     */
434 1
    public function isDebugMode()
435
    {
436 1
        return $this->getConfiguration()->isDebugMode();
437
    }
438
439
    /**
440
     * Return's the subject's execution context configuration.
441
     *
442
     * @return \TechDivision\Import\ExecutionContextInterface The execution context configuration to use
443
     */
444
    public function getExecutionContext()
445
    {
446
        return $this->getConfiguration()->getPluginConfiguration()->getExecutionContext();
447
    }
448
449
    /**
450
     * Set's the subject configuration.
451
     *
452
     * @param \TechDivision\Import\Configuration\SubjectConfigurationInterface $configuration The subject configuration
453
     *
454
     * @return void
455
     */
456 81
    public function setConfiguration(SubjectConfigurationInterface $configuration)
457
    {
458 81
        $this->configuration = $configuration;
459 81
    }
460
461
    /**
462
     * Return's the subject configuration.
463
     *
464
     * @return \TechDivision\Import\Configuration\SubjectConfigurationInterface The subject configuration
465
     */
466 81
    public function getConfiguration()
467
    {
468 81
        return $this->configuration;
469
    }
470
471
    /**
472
     * Set's the import adapter instance.
473
     *
474
     * @param \TechDivision\Import\Adapter\ImportAdapterInterface $importAdapter The import adapter instance
475
     *
476
     * @return void
477
     */
478 1
    public function setImportAdapter(ImportAdapterInterface $importAdapter)
479
    {
480 1
        $this->importAdapter = $importAdapter;
481 1
    }
482
483
    /**
484
     * Return's the import adapter instance.
485
     *
486
     * @return \TechDivision\Import\Adapter\ImportAdapterInterface The import adapter instance
487
     */
488 1
    public function getImportAdapter()
489
    {
490 1
        return $this->importAdapter;
491
    }
492
493
    /**
494
     * Return's the RegistryProcessor instance to handle the running threads.
495
     *
496
     * @return \TechDivision\Import\Services\RegistryProcessorInterface The registry processor instance
497
     */
498 81
    public function getRegistryProcessor()
499
    {
500 81
        return $this->registryProcessor;
501
    }
502
503
    /**
504
     * Set's the unique serial for this import process.
505
     *
506
     * @param string $serial The unique serial
507
     *
508
     * @return void
509
     */
510 8
    public function setSerial($serial)
511
    {
512 8
        $this->serial = $serial;
513 8
    }
514
515
    /**
516
     * Return's the unique serial for this import process.
517
     *
518
     * @return string The unique serial
519
     */
520 1
    public function getSerial()
521
    {
522 1
        return $this->serial;
523
    }
524
525
    /**
526
     * Merge's the passed status into the actual one.
527
     *
528
     * @param array $status The status to MergeBuilder
529
     *
530
     * @return void
531
     */
532 4
    public function mergeStatus(array $status)
533
    {
534 4
        $this->status = array_merge_recursive($this->status, $status);
535 4
    }
536
537
    /**
538
     * Retur's the actual status.
539
     *
540
     * @return array The actual status
541
     */
542
    public function getStatus()
543
    {
544
        return $this->status;
545
    }
546
547
    /**
548
     * Return's the unique identifier for the actual invocation.
549
     *
550
     * @return string The unique identifier
551
     */
552 4
    public function getUniqueId()
553
    {
554 4
        return $this->uniqueId;
555
    }
556
557
    /**
558
     * Return's the source date format to use.
559
     *
560
     * @return string The source date format
561
     */
562
    public function getSourceDateFormat()
563
    {
564
        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...
565
    }
566
567
    /**
568
     * Return's the multiple field delimiter character to use, default value is comma (,).
569
     *
570
     * @return string The multiple field delimiter character
571
     */
572 1
    public function getMultipleFieldDelimiter()
573
    {
574 1
        return $this->getConfiguration()->getMultipleFieldDelimiter();
575
    }
576
577
    /**
578
     * Return's the multiple value delimiter character to use, default value is comma (|).
579
     *
580
     * @return string The multiple value delimiter character
581
     */
582 1
    public function getMultipleValueDelimiter()
583
    {
584 1
        return $this->getConfiguration()->getMultipleValueDelimiter();
585
    }
586
587
    /**
588
     * Intializes the previously loaded global data for exactly one bunch.
589
     *
590
     * @param string $serial The serial of the actual import
591
     *
592
     * @return void
593
     */
594 81
    public function setUp($serial)
595
    {
596
597
        // initialize the unique ID for the actual invocation
598 81
        $this->uniqueId = Uuid::uuid4()->toString();
599
600
        // load the status of the actual import
601 81
        $status = $this->getRegistryProcessor()->getAttribute(RegistryKeys::STATUS);
602
603
        // load the global data, if prepared initially
604 81
        if (isset($status[RegistryKeys::GLOBAL_DATA])) {
605 81
            $this->stores = $status[RegistryKeys::GLOBAL_DATA][RegistryKeys::STORES];
606 81
            $this->defaultStore = $status[RegistryKeys::GLOBAL_DATA][RegistryKeys::DEFAULT_STORE];
607 81
            $this->storeWebsites  = $status[RegistryKeys::GLOBAL_DATA][RegistryKeys::STORE_WEBSITES];
608 81
            $this->rootCategories = $status[RegistryKeys::GLOBAL_DATA][RegistryKeys::ROOT_CATEGORIES];
609 81
            $this->coreConfigData = $status[RegistryKeys::GLOBAL_DATA][RegistryKeys::CORE_CONFIG_DATA];
610
        }
611
612
        // merge the header mappings with the values found in the configuration
613 81
        $this->headerMappings = array_merge($this->headerMappings, $this->getDefaultHeaderMappings());
614
615
        // merge the callback mappings with the mappings from the child instance
616 81
        $this->callbackMappings = array_merge($this->callbackMappings, $this->getDefaultCallbackMappings());
617
618
        // merge the default column values with the values found in the configuration
619 81
        $this->defaultColumnValues = array_merge($this->defaultColumnValues, $this->getDefaultColumnValues());
620
621
        // load the available callbacks from the configuration
622 81
        $availableCallbacks = $this->getConfiguration()->getCallbacks();
623
624
        // merge the callback mappings the the one from the configuration file
625 81
        foreach ($availableCallbacks as $callbackMappings) {
626 81
            foreach ($callbackMappings as $attributeCode => $mappings) {
627
                // write a log message, that default callback configuration will
628
                // be overwritten with the one from the configuration file
629 81
                if (isset($this->callbackMappings[$attributeCode])) {
630 81
                    $this->getSystemLogger()->notice(
631 81
                        sprintf('Now override callback mappings for attribute %s with values found in configuration file', $attributeCode)
632
                    );
633
                }
634
635
                // override the attributes callbacks
636 81
                $this->callbackMappings[$attributeCode] = $mappings;
637
            }
638
        }
639 81
    }
640
641
    /**
642
     * Clean up the global data after importing the variants.
643
     *
644
     * @param string $serial The serial of the actual import
645
     *
646
     * @return void
647
     */
648 1
    public function tearDown($serial)
649
    {
650
651
        // load the registry processor
652 1
        $registryProcessor = $this->getRegistryProcessor();
653
654
        // update the source directory for the next subject
655 1
        foreach ($this->getStatus() as $key => $status) {
656 1
            $registryProcessor->mergeAttributesRecursive($key, $status);
657
        }
658
659
        // log a debug message with the new source directory
660 1
        $this->getSystemLogger()->debug(
661 1
            sprintf('Subject %s successfully updated status data for import %s', get_class($this), $serial)
662
        );
663 1
    }
664
665
    /**
666
     * Return's the target directory for the artefact export.
667
     *
668
     * @return string The target directory for the artefact export
669
     */
670 1 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...
671
    {
672
673
        // load the status from the registry processor
674 1
        $status = $this->getRegistryProcessor()->getAttribute(RegistryKeys::STATUS);
675
676
        // query whether or not a target directory (mandatory) has been configured
677 1
        if (isset($status[RegistryKeys::TARGET_DIRECTORY])) {
678 1
            return $status[RegistryKeys::TARGET_DIRECTORY];
679
        }
680
681
        // throw an exception if the root category is NOT available
682
        throw new \Exception(sprintf('Can\'t find a target directory in status data for import %s', $this->getSerial()));
683
    }
684
685
    /**
686
     * Register the passed observer with the specific type.
687
     *
688
     * @param \TechDivision\Import\Observers\ObserverInterface $observer The observer to register
689
     * @param string                                           $type     The type to register the observer with
690
     *
691
     * @return void
692
     */
693 6
    public function registerObserver(ObserverInterface $observer, $type)
694
    {
695
696
        // query whether or not the array with the callbacks for the
697
        // passed type has already been initialized, or not
698 6
        if (!isset($this->observers[$type])) {
699 6
            $this->observers[$type] = array();
700
        }
701
702
        // append the callback with the instance of the passed type
703 6
        $this->observers[$type][] = $observer;
704 6
    }
705
706
    /**
707
     * Register the passed callback with the specific type.
708
     *
709
     * @param \TechDivision\Import\Callbacks\CallbackInterface $callback The subject to register the callbacks for
710
     * @param string                                           $type     The type to register the callback with
711
     *
712
     * @return void
713
     */
714 2
    public function registerCallback(CallbackInterface $callback, $type)
715
    {
716
717
        // query whether or not the array with the callbacks for the
718
        // passed type has already been initialized, or not
719 2
        if (!isset($this->callbacks[$type])) {
720 2
            $this->callbacks[$type] = array();
721
        }
722
723
        // append the callback with the instance of the passed type
724 2
        $this->callbacks[$type][] = $callback;
725 2
    }
726
727
    /**
728
     * Return's the array with callbacks for the passed type.
729
     *
730
     * @param string $type The type of the callbacks to return
731
     *
732
     * @return array The callbacks
733
     */
734 1
    public function getCallbacksByType($type)
735
    {
736
737
        // initialize the array for the callbacks
738 1
        $callbacks = array();
739
740
        // query whether or not callbacks for the type are available
741 1
        if (isset($this->callbacks[$type])) {
742 1
            $callbacks = $this->callbacks[$type];
743
        }
744
745
        // return the array with the type's callbacks
746 1
        return $callbacks;
747
    }
748
749
    /**
750
     * Return's the array with the available observers.
751
     *
752
     * @return array The observers
753
     */
754 6
    public function getObservers()
755
    {
756 6
        return $this->observers;
757
    }
758
759
    /**
760
     * Return's the array with the available callbacks.
761
     *
762
     * @return array The callbacks
763
     */
764 1
    public function getCallbacks()
765
    {
766 1
        return $this->callbacks;
767
    }
768
769
    /**
770
     * Return's the callback mappings for this subject.
771
     *
772
     * @return array The array with the subject's callback mappings
773
     */
774 2
    public function getCallbackMappings()
775
    {
776 2
        return $this->callbackMappings;
777
    }
778
779
    /**
780
     * Imports the content of the file with the passed filename.
781
     *
782
     *
783
     * @param string $serial   The serial of the actual import
784
     * @param string $filename The filename to process
785
     *
786
     * @return void
787
     * @throws \Exception Is thrown, if the import can't be processed
788
     */
789 2
    public function import($serial, $filename)
790
    {
791
792
        try {
793
            // initialize the serial/filename
794 2
            $this->setSerial($serial);
795 2
            $this->setFilename($filename);
796
797
            // invoke the events that has to be fired before the artfact will be processed
798 2
            $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...
799 2
            $this->getEmitter()->emit($this->getEventName(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...
800
801
            // load the system logger instance
802 2
            $systemLogger = $this->getSystemLogger();
803
804
            // prepare the flag filenames
805 2
            $inProgressFilename = sprintf('%s.inProgress', $filename);
806 2
            $importedFilename = sprintf('%s.imported', $filename);
807 2
            $failedFilename = sprintf('%s.failed', $filename);
808
809
            // query whether or not the file has already been imported
810 2
            if ($this->isFile($failedFilename) ||
811 1
                $this->isFile($importedFilename) ||
812 2
                $this->isFile($inProgressFilename)
813
            ) {
814
                // log a debug message and exit
815 1
                $systemLogger->debug(sprintf('Import running, found inProgress file "%s"', $inProgressFilename));
816 1
                return;
817
            }
818
819
            // flag file as in progress
820 1
            $this->touch($inProgressFilename);
821
822
            // track the start time
823 1
            $startTime = microtime(true);
824
825
            // initialize the last time we've logged the counter with the processed rows per minute
826 1
            $this->lastLog = time();
827
828
            // log a message that the file has to be imported
829 1
            $systemLogger->info(
830 1
                sprintf('Now start processing file "%s"', basename($filename)),
831 1
                array('operation-name' => $operationName = $this->getFullOperationName())
832
            );
833
834
            // let the adapter process the file
835 1
            $this->getImportAdapter()->import(array($this, 'importRow'), $filename);
836
837
            // track the time needed for the import in seconds
838 1
            $endTime = microtime(true) - $startTime;
839
840
            // log a message that the file has successfully been imported
841 1
            $systemLogger->info(
842 1
                sprintf('Successfully processed file "%s" with "%d" lines in "%f" s', basename($filename), $this->lineNumber, $endTime),
843 1
                array('operation-name' => $operationName)
844
            );
845
846
            // rename flag file, because import has been successfull
847 1
            if ($this->getConfiguration()->isCreatingImportedFile()) {
848 1
                $this->rename($inProgressFilename, $importedFilename);
849
            } else {
850
                $this->getFilesystemAdapter()->delete($inProgressFilename);
851
            }
852
853
            // update the status
854 1
            $this->mergeStatus(
855
                array(
856
                    RegistryKeys::STATUS => array(
857
                        RegistryKeys::FILES => array(
858
                            $filename => array(
859 1
                                $this->getUniqueId() => array(
860 1
                                    RegistryKeys::STATUS => 1,
861 1
                                    RegistryKeys::PROCESSED_ROWS => $this->getLineNumber()
862
                                )
863
                            )
864
                        )
865
                    )
866
                )
867
            );
868
869
            // invoke the events that has to be fired when the artfact has been successfully processed
870 1
            $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...
871 1
            $this->getEmitter()->emit($this->getEventName(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...
872
        } catch (\Exception $e) {
873
            // rename the flag file, because import failed and write the stack trace
874
            $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...
875
            $this->write($failedFilename, $e->__toString());
876
877
            // update the status with the error message
878
            $this->mergeStatus(
879
                array(
880
                    RegistryKeys::STATUS => array(
881
                        RegistryKeys::FILES => array(
882
                            $filename => array(
883
                                $this->getUniqueId() => array(
884
                                    RegistryKeys::STATUS         => 2,
885
                                    RegistryKeys::ERROR_MESSAGE  => $e->getMessage(),
886
                                    RegistryKeys::PROCESSED_ROWS => $this->getLineNumber()
887
                                )
888
                            )
889
                        )
890
                    )
891
                )
892
            );
893
894
            // invoke the events that has to be fired when the artfact can't be processed
895
            $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...
896
            $this->getEmitter()->emit($this->getEventName(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...
897
898
            // do not wrap the exception if not already done
899
            if ($e instanceof WrappedColumnException) {
900
                throw $e;
901
            }
902
903
            // else wrap and throw the exception
904
            throw $this->wrapException(array(), $e);
905
        }
906 1
    }
907
908
    /**
909
     * Imports the passed row into the database. If the import failed, the exception
910
     * will be catched and logged, but the import process will be continued.
911
     *
912
     * @param array $row The row with the data to be imported
913
     *
914
     * @return void
915
     */
916 7
    public function importRow(array $row)
917
    {
918
919
        // initialize the row
920 7
        $this->row = $row;
921
922
        // raise the line number and reset the skip row flag
923 7
        $this->lineNumber++;
924 7
        $this->skipRow = false;
925
926
        // invoke the events that has to be fired before the artfact's row will be processed
927 7
        $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...
928 7
        $this->getEmitter()->emit($this->getEventName(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...
929
930
        // initialize the headers with the columns from the first line
931 7
        if (sizeof($this->headers) === 0) {
932
            // invoke the events that has to be fired before the artfact's header row will be processed
933 1
            $this->getEmitter()->emit(EventNames::SUBJECT_ARTEFACT_HEADER_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...
934 1
            $this->getEmitter()->emit($this->getEventName(EventNames::SUBJECT_ARTEFACT_HEADER_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...
935
936
            // iterate over the column name => key an map the header names, if necessary
937 1
            foreach ($this->row as $value => $key) {
938 1
                $this->headers[$this->mapAttributeCodeByHeaderMapping($key)] = $value;
939
            }
940
941
            // iterate over the default column values to figure out whether or not the column exists
942 1
            foreach ($this->defaultColumnValues as $name => $value) {
943
                // do nothing, if the column already exists
944
                if (array_key_exists($key = $this->mapAttributeCodeByHeaderMapping($name), $this->headers)) {
945
                    continue;
946
                }
947
                // add the header and the default value for the column
948
                $this->headers[$key] = $columnKey = sizeof($this->headers);
949
                $this->columnValues[$columnKey] = $value;
950
            }
951
952
            // invoke the events that has to be fired when the artfact's header row has been successfully processed
953 1
            $this->getEmitter()->emit(EventNames::SUBJECT_ARTEFACT_HEADER_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...
954 1
            $this->getEmitter()->emit($this->getEventName(EventNames::SUBJECT_ARTEFACT_HEADER_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...
955
        } else {
956
            // merge the default column value into the actual row
957 6
            $this->row = array_replace($this->row, $this->columnValues);
958
959
            // load the available observers
960 6
            $availableObservers = $this->getObservers();
961
962
            // process the observers
963 6
            foreach ($availableObservers as $observers) {
964
                // invoke the pre-import/import and post-import observers
965
                /** @var \TechDivision\Import\Observers\ObserverInterface $observer */
966 6
                foreach ($observers as $observer) {
967
                    // query whether or not we have to skip the row
968 6
                    if ($this->skipRow) {
969
                        // log a debug message with the actual line nr/file information
970 1
                        $this->getSystemLogger()->debug(
971 1
                            $this->appendExceptionSuffix(
972 1
                                sprintf(
973 1
                                    'Skip processing operation "%s" after observer "%s"',
974 1
                                    $this->getFullOperationName(),
975 1
                                    get_class($observer)
976
                                )
977
                            )
978
                        );
979
980
                        // skip the row
981 1
                        break 2;
982
                    }
983
984
                    // if not, set the subject and process the observer
985 6
                    if ($observer instanceof ObserverInterface) {
986 6
                        $this->row = $observer->handle($this);
987
                    }
988
                }
989
            }
990
        }
991
992
        // query whether or not a minute has been passed
993 7
        if ($this->lastLog < time() - 59) {
994
            // log the number processed rows per minute
995 7
            $this->getSystemLogger()->info(
996 7
                sprintf(
997 7
                    'Operation "%s" successfully processed "%d (%d)" rows per minute of file "%s"',
998 7
                    $this->getFullOperationName(),
999 7
                    $this->lineNumber - $this->lastLineNumber,
1000 7
                    $this->lineNumber,
1001 7
                    basename($this->getFilename())
1002
                )
1003
            );
1004
1005
            // reset the last log time and the line number
1006 7
            $this->lastLog = time();
1007 7
            $this->lastLineNumber = $this->lineNumber;
1008
        }
1009
1010
        // log a debug message with the actual line nr/file information
1011 7
        $this->getSystemLogger()->debug(
1012 7
            $this->appendExceptionSuffix(
1013 7
                sprintf(
1014 7
                    'Successfully processed operation "%s"',
1015 7
                    implode(' > ', $this->getConfiguration()->getConfiguration()->getOperationNames())
1016
                )
1017
            )
1018
        );
1019
1020
        // invoke the events that has to be fired when the artfact's row has been successfully processed
1021 7
        $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...
1022 7
        $this->getEmitter()->emit($this->getEventName(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...
1023 7
    }
1024
1025
    /**
1026
     * Queries whether or not that the subject needs an OK file to be processed.
1027
     *
1028
     * @return boolean TRUE if the subject needs an OK file, else FALSE
1029
     */
1030 1
    public function isOkFileNeeded()
1031
    {
1032 1
        return $this->getConfiguration()->isOkFileNeeded();
1033
    }
1034
1035
    /**
1036
     * Return's the default store.
1037
     *
1038
     * @return array The default store
1039
     */
1040
    public function getDefaultStore()
1041
    {
1042
        return $this->defaultStore;
1043
    }
1044
1045
    /**
1046
     * Return's the default store view code.
1047
     *
1048
     * @return array The default store view code
1049
     */
1050 5
    public function getDefaultStoreViewCode()
1051
    {
1052 5
        return $this->defaultStore[MemberNames::CODE];
1053
    }
1054
1055
    /**
1056
     * Set's the store view code the create the product/attributes for.
1057
     *
1058
     * @param string $storeViewCode The store view code
1059
     *
1060
     * @return void
1061
     */
1062 4
    public function setStoreViewCode($storeViewCode)
1063
    {
1064 4
        $this->storeViewCode = $storeViewCode;
1065 4
    }
1066
1067
    /**
1068
     * Return's the store view code the create the product/attributes for.
1069
     *
1070
     * @param string|null $default The default value to return, if the store view code has not been set
1071
     *
1072
     * @return string The store view code
1073
     */
1074 8
    public function getStoreViewCode($default = null)
1075
    {
1076
1077
        // return the store view code, if available
1078 8
        if ($this->storeViewCode !== null) {
1079 4
            return $this->storeViewCode;
1080
        }
1081
1082
        // if NOT and a default code is available
1083 4
        if ($default !== null) {
1084
            // return the default value
1085 3
            return $default;
1086
        }
1087
1088
        // return the default store view code
1089 1
        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...
1090
    }
1091
1092
    /**
1093
     * Prepare's the store view code in the subject. If the store_view_code row doesn't contain
1094
     * any value, the default code of the default store view will be set.
1095
     *
1096
     * @return void
1097
     */
1098 2
    public function prepareStoreViewCode()
1099
    {
1100
1101
        // re-set the store view code
1102 2
        $this->setStoreViewCode(null);
1103
1104
        // initialize the store view code
1105 2
        if ($storeViewCode = $this->getValue(ColumnKeys::STORE_VIEW_CODE)) {
1106 2
            $this->setStoreViewCode($storeViewCode);
1107
        }
1108 2
    }
1109
1110
    /**
1111
     * Return's the store ID of the store with the passed store view code
1112
     *
1113
     * @param string $storeViewCode The store view code to return the store ID for
1114
     *
1115
     * @return integer The ID of the store with the passed ID
1116
     * @throws \Exception Is thrown, if the store with the actual code is not available
1117
     */
1118 4
    public function getStoreId($storeViewCode)
1119
    {
1120
1121
        // query whether or not, the requested store is available
1122 4
        if (isset($this->stores[$storeViewCode])) {
1123 3
            return (integer) $this->stores[$storeViewCode][MemberNames::STORE_ID];
1124
        }
1125
1126
        // throw an exception, if not
1127 1
        throw new \Exception(
1128 1
            sprintf(
1129 1
                'Found invalid store view code %s in file %s on line %d',
1130
                $storeViewCode,
1131 1
                $this->getFilename(),
1132 1
                $this->getLineNumber()
1133
            )
1134
        );
1135
    }
1136
1137
    /**
1138
     * Return's the store ID of the actual row, or of the default store
1139
     * if no store view code is set in the CSV file.
1140
     *
1141
     * @param string|null $default The default store view code to use, if no store view code is set in the CSV file
1142
     *
1143
     * @return integer The ID of the actual store
1144
     * @throws \Exception Is thrown, if the store with the actual code is not available
1145
     */
1146 2
    public function getRowStoreId($default = null)
1147
    {
1148
1149
        // initialize the default store view code, if not passed
1150 2
        if ($default === null) {
1151 2
            $default = $this->getDefaultStoreViewCode();
1152
        }
1153
1154
        // load the store view code the create the product/attributes for
1155 2
        return $this->getStoreId($this->getStoreViewCode($default));
0 ignored issues
show
Bug introduced by
It seems like $default defined by $this->getDefaultStoreViewCode() on line 1151 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...
1156
    }
1157
1158
    /**
1159
     * Return's the root category for the actual view store.
1160
     *
1161
     * @return array The store's root category
1162
     * @throws \Exception Is thrown if the root category for the passed store code is NOT available
1163
     */
1164 2
    public function getRootCategory()
1165
    {
1166
1167
        // load the actual store view code
1168 2
        $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...
1169
1170
        // query weather or not we've a root category or not
1171 2
        if (isset($this->rootCategories[$storeViewCode])) {
1172 1
            return $this->rootCategories[$storeViewCode];
1173
        }
1174
1175
        // throw an exception if the root category is NOT available
1176 1
        throw new \Exception(sprintf('Root category for %s is not available', $storeViewCode));
1177
    }
1178
1179
    /**
1180
     * Return's the Magento configuration value.
1181
     *
1182
     * @param string  $path    The Magento path of the requested configuration value
1183
     * @param mixed   $default The default value that has to be returned, if the requested configuration value is not set
1184
     * @param string  $scope   The scope the configuration value has been set
1185
     * @param integer $scopeId The scope ID the configuration value has been set
1186
     *
1187
     * @return mixed The configuration value
1188
     * @throws \Exception Is thrown, if nor a value can be found or a default value has been passed
1189
     */
1190 5
    public function getCoreConfigData($path, $default = null, $scope = ScopeKeys::SCOPE_DEFAULT, $scopeId = 0)
1191
    {
1192
1193
        // initialize the core config data
1194
        $coreConfigData = array(
1195 5
            MemberNames::PATH => $path,
1196 5
            MemberNames::SCOPE => $scope,
1197 5
            MemberNames::SCOPE_ID => $scopeId
1198
        );
1199
1200
        // generate the UID from the passed data
1201 5
        $uniqueIdentifier = $this->coreConfigDataUidGenerator->generate($coreConfigData);
1202
1203
        // iterate over the core config data and try to find the requested configuration value
1204 5
        if (isset($this->coreConfigData[$uniqueIdentifier])) {
1205 1
            return $this->coreConfigData[$uniqueIdentifier][MemberNames::VALUE];
1206
        }
1207
1208
        // query whether or not we've to query for the configuration value on fallback level 'websites' also
1209 4
        if ($scope === ScopeKeys::SCOPE_STORES) {
1210
            // query whether or not the website with the passed ID is available
1211 2
            foreach ($this->storeWebsites as $storeWebsite) {
1212 2
                if ($storeWebsite[MemberNames::WEBSITE_ID] === $scopeId) {
1213
                    // replace scope with 'websites' and website ID
1214 2
                    $coreConfigData = array_merge(
1215 2
                        $coreConfigData,
1216
                        array(
1217 2
                            MemberNames::SCOPE    => ScopeKeys::SCOPE_WEBSITES,
1218
                            MemberNames::SCOPE_ID => $storeWebsite[MemberNames::WEBSITE_ID]
1219
                        )
1220
                    );
1221
1222
                    // generate the UID from the passed data, merged with the 'websites' scope and ID
1223 2
                    $uniqueIdentifier = $this->coreConfigDataUidGenerator->generate($coreConfigData);
1224
1225
                    // query whether or not, the configuration value on 'websites' level
1226 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...
1227 1
                        return $this->coreConfigData[$uniqueIdentifier][MemberNames::VALUE];
1228
                    }
1229
                }
1230
            }
1231
        }
1232
1233
        // replace scope with 'default' and scope ID '0'
1234 3
        $coreConfigData = array_merge(
1235 3
            $coreConfigData,
1236
            array(
1237 3
                MemberNames::SCOPE    => ScopeKeys::SCOPE_DEFAULT,
1238
                MemberNames::SCOPE_ID => 0
1239
            )
1240
        );
1241
1242
        // generate the UID from the passed data, merged with the 'default' scope and ID 0
1243 3
        $uniqueIdentifier = $this->coreConfigDataUidGenerator->generate($coreConfigData);
1244
1245
        // query whether or not, the configuration value on 'default' level
1246 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...
1247 1
            return $this->coreConfigData[$uniqueIdentifier][MemberNames::VALUE];
1248
        }
1249
1250
        // if not, return the passed default value
1251 2
        if ($default !== null) {
1252 1
            return $default;
1253
        }
1254
1255
        // throw an exception if no value can be found
1256
        // in the Magento configuration
1257 1
        throw new \Exception(
1258 1
            sprintf(
1259 1
                'Can\'t find a value for configuration "%s-%s-%d" in "core_config_data"',
1260
                $path,
1261
                $scope,
1262
                $scopeId
1263
            )
1264
        );
1265
    }
1266
1267
    /**
1268
     * Resolve the original column name for the passed one.
1269
     *
1270
     * @param string $columnName The column name that has to be resolved
1271
     *
1272
     * @return string|null The original column name
1273
     */
1274 2
    public function resolveOriginalColumnName($columnName)
1275
    {
1276
1277
        // try to load the original data
1278 2
        $originalData = $this->getOriginalData();
1279
1280
        // query whether or not original data is available
1281 2
        if (isset($originalData[ColumnKeys::ORIGINAL_COLUMN_NAMES])) {
1282
            // query whether or not the original column name is available
1283 1
            if (isset($originalData[ColumnKeys::ORIGINAL_COLUMN_NAMES][$columnName])) {
1284 1
                return $originalData[ColumnKeys::ORIGINAL_COLUMN_NAMES][$columnName];
1285
            }
1286
1287
            // query whether or a wildcard column name is available
1288 1
            if (isset($originalData[ColumnKeys::ORIGINAL_COLUMN_NAMES]['*'])) {
1289 1
                return $originalData[ColumnKeys::ORIGINAL_COLUMN_NAMES]['*'];
1290
            }
1291
        }
1292
1293
        // return the original column name
1294 1
        return $columnName;
1295
    }
1296
1297
    /**
1298
     * Return's the original data if available, or an empty array.
1299
     *
1300
     * @return array The original data
1301
     */
1302 2
    public function getOriginalData()
1303
    {
1304
1305
        // initialize the array for the original data
1306 2
        $originalData = array();
1307
1308
        // query whether or not the column contains original data
1309 2
        if ($this->hasOriginalData()) {
1310
            // unerialize the original data from the column
1311 1
            $originalData = unserialize($this->row[$this->headers[ColumnKeys::ORIGINAL_DATA]]);
1312
        }
1313
1314
        // return an empty array, if not
1315 2
        return $originalData;
1316
    }
1317
1318
    /**
1319
     * Query's whether or not the actual column contains original data like
1320
     * filename, line number and column names.
1321
     *
1322
     * @return boolean TRUE if the actual column contains origin data, else FALSE
1323
     */
1324 2
    public function hasOriginalData()
1325
    {
1326 2
        return isset($this->headers[ColumnKeys::ORIGINAL_DATA]) && isset($this->row[$this->headers[ColumnKeys::ORIGINAL_DATA]]);
1327
    }
1328
1329
    /**
1330
     * Wraps the passed exeception into a new one by trying to resolve the original filname,
1331
     * line number and column names and use it for a detailed exception message.
1332
     *
1333
     * @param array      $columnNames The column names that should be resolved and wrapped
1334
     * @param \Exception $parent      The exception we want to wrap
1335
     * @param string     $className   The class name of the exception type we want to wrap the parent one
1336
     *
1337
     * @return \Exception the wrapped exception
1338
     */
1339 1
    public function wrapException(
1340
        array $columnNames = array(),
1341
        \Exception $parent = null,
1342
        $className = '\TechDivision\Import\Exceptions\WrappedColumnException'
1343
    ) {
1344
1345
        // initialize the message
1346 1
        $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...
1347
1348
        // query whether or not has been a result of invalid data of a previous column of a CSV file
1349 1
        if ($this->hasOriginalData()) {
1350
            // load the original data
1351 1
            $originalData = $this->getOriginalData();
1352
1353
            // replace old filename and line number of the original message
1354 1
            $message = $this->appendExceptionSuffix(
1355 1
                $this->stripExceptionSuffix($message),
1356 1
                $originalData[ColumnKeys::ORIGINAL_FILENAME],
1357 1
                $originalData[ColumnKeys::ORIGINAL_LINE_NUMBER]
1358
            );
1359
        } else {
1360
            // append filename and line number to the original message
1361
            $message = $this->appendExceptionSuffix(
1362
                $this->stripExceptionSuffix($message),
1363
                $this->filename,
1364
                $this->lineNumber
1365
            );
1366
        }
1367
1368
        // query whether or not, column names has been passed
1369 1
        if (sizeof($columnNames) > 0) {
1370
            // prepare the original column names
1371 1
            $originalColumnNames = array();
1372 1
            foreach ($columnNames as $columnName) {
1373 1
                $originalColumnNames[] = $this->resolveOriginalColumnName($columnName);
1374
            }
1375
1376
            // append the column information
1377 1
            $message = sprintf('%s in column(s) %s', $message, implode(', ', $originalColumnNames));
1378
        }
1379
1380
        // create a new exception and wrap the parent one
1381 1
        return new $className($message, null, $parent);
1382
    }
1383
1384
    /**
1385
     * Strip's the exception suffix containing filename and line number from the
1386
     * passed message.
1387
     *
1388
     * @param string $message The message to strip the exception suffix from
1389
     *
1390
     * @return mixed The message without the exception suffix
1391
     */
1392 1
    public function stripExceptionSuffix($message)
1393
    {
1394 1
        return str_replace($this->appendExceptionSuffix(), '', $message);
1395
    }
1396
1397
    /**
1398
     * Append's the exception suffix containing filename and line number to the
1399
     * passed message. If no message has been passed, only the suffix will be
1400
     * returned
1401
     *
1402
     * @param string|null $message    The message to append the exception suffix to
1403
     * @param string|null $filename   The filename used to create the suffix
1404
     * @param string|null $lineNumber The line number used to create the suffx
1405
     *
1406
     * @return string The message with the appended exception suffix
1407
     */
1408 12
    public function appendExceptionSuffix($message = null, $filename = null, $lineNumber = null)
1409
    {
1410
1411
        // query whether or not a filename has been passed
1412 12
        if ($filename === null) {
1413 12
            $filename = $this->getFilename();
1414
        }
1415
1416
        // query whether or not a line number has been passed
1417 12
        if ($lineNumber === null) {
1418 12
            $lineNumber = $this->getLineNumber();
1419
        }
1420
1421
        // if no message has been passed, only return the suffix
1422 12
        if ($message === null) {
1423 1
            return sprintf(' in file %s on line %d', basename($filename), $lineNumber);
1424
        }
1425
1426
        // concatenate the message with the suffix and return it
1427 12
        return sprintf('%s in file %s on line %d', $message, basename($filename), $lineNumber);
1428
    }
1429
1430
    /**
1431
     * Raises the value for the counter with the passed key by one.
1432
     *
1433
     * @param mixed $counterName The name of the counter to raise
1434
     *
1435
     * @return integer The counter's new value
1436
     */
1437 1
    public function raiseCounter($counterName)
1438
    {
1439
1440
        // raise the counter with the passed name
1441 1
        return $this->getRegistryProcessor()->raiseCounter(
1442 1
            RegistryKeys::COUNTERS,
1443
            $counterName
1444
        );
1445
    }
1446
1447
    /**
1448
     * Merge the passed array into the status of the actual import.
1449
     *
1450
     * @param array $status The status information to be merged
1451
     *
1452
     * @return void
1453
     */
1454 1
    public function mergeAttributesRecursive(array $status)
1455
    {
1456
1457
        // merge the passed status
1458 1
        return $this->getRegistryProcessor()->mergeAttributesRecursive(
1459 1
            RegistryKeys::STATUS,
1460
            $status
1461
        );
1462
    }
1463
1464
    /**
1465
     * Return's the entity type code to be used.
1466
     *
1467
     * @return string The entity type code to be used
1468
     */
1469
    public function getEntityTypeCode()
1470
    {
1471
1472
        // load the configuration specific entity type code from the plugin configuration
1473
        $entityTypeCode = $this->getExecutionContext()->getEntityTypeCode();
1474
1475
        // try to map the entity type code
1476
        if (isset($this->entityTypeCodeMappings[$entityTypeCode])) {
1477
            $entityTypeCode = $this->entityTypeCodeMappings[$entityTypeCode];
1478
        }
1479
1480
        // return the (mapped) entity type code
1481
        return $entityTypeCode;
1482
    }
1483
1484
    /**
1485
     * Concatenates and returns the event name for the actual plugin and subject context.
1486
     *
1487
     * @param string $eventName The event name to concatenate
1488
     *
1489
     * @return string The concatenated event name
1490
     */
1491 9
    protected function getEventName($eventName)
1492
    {
1493 9
        return  sprintf(
1494 9
            '%s.%s.%s',
1495 9
            $this->getConfiguration()->getPluginConfiguration()->getId(),
1496 9
            $this->getConfiguration()->getId(),
1497
            $eventName
1498
        );
1499
    }
1500
1501
    /**
1502
     * Return's the full opration name, which consists of the Magento edition, the entity type code and the operation name.
1503
     *
1504
     * @param string $separator The separator used to seperate the elements
1505
     *
1506
     * @return string The full operation name
1507
     */
1508
    public function getFullOperationName($separator = '/')
1509
    {
1510
        return $this->getConfiguration()->getFullOperationName($separator);
1511
    }
1512
}
1513