Completed
Push — 16.x ( bd3602...588b8a )
by Tim
02:11
created

AbstractSubject::getTargetDir()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 14

Duplication

Lines 14
Ratio 100 %

Code Coverage

Tests 4
CRAP Score 2.032

Importance

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