Completed
Push — master ( 2b9e5a...666597 )
by Tim
17s queued 11s
created

AbstractSubject::getRootCategories()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 2

Importance

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

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...
1282 2
1283
        // iterate over the core config data and try to find the requested configuration value
1284 2
        if (isset($this->coreConfigData[$uniqueIdentifier])) {
1285 2
            return $this->coreConfigData[$uniqueIdentifier][MemberNames::VALUE];
1286
        }
1287 2
1288
        // query whether or not we've to query for the configuration value on fallback level 'websites' also
1289
        if ($scope === ScopeKeys::SCOPE_STORES) {
1290
            // query whether or not the website with the passed ID is available
1291
            foreach ($this->storeWebsites as $storeWebsite) {
1292
                if ($storeWebsite[MemberNames::WEBSITE_ID] === $scopeId) {
1293 2
                    // replace scope with 'websites' and website ID
1294
                    $coreConfigData = array_merge(
1295
                        $coreConfigData,
1296 2
                        array(
1297 2
                            MemberNames::SCOPE    => ScopeKeys::SCOPE_WEBSITES,
1298
                            MemberNames::SCOPE_ID => $storeWebsite[MemberNames::WEBSITE_ID]
1299
                        )
1300
                    );
1301
1302
                    // generate the UID from the passed data, merged with the 'websites' scope and ID
1303
                    $uniqueIdentifier = $this->coreConfigDataUidGenerator->generate($coreConfigData);
0 ignored issues
show
Unused Code introduced by
The call to GeneratorInterface::generate() has too many arguments starting with $coreConfigData.

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...
1304 3
1305 3
                    // query whether or not, the configuration value on 'websites' level
1306
                    if (isset($this->coreConfigData[$uniqueIdentifier][MemberNames::VALUE])) {
1307 3
                        return $this->coreConfigData[$uniqueIdentifier][MemberNames::VALUE];
1308
                    }
1309
                }
1310
            }
1311
        }
1312
1313 3
        // replace scope with 'default' and scope ID '0'
1314
        $coreConfigData = array_merge(
1315
            $coreConfigData,
1316 3
            array(
1317 1
                MemberNames::SCOPE    => ScopeKeys::SCOPE_DEFAULT,
1318
                MemberNames::SCOPE_ID => 0
1319
            )
1320
        );
1321 2
1322 1
        // generate the UID from the passed data, merged with the 'default' scope and ID 0
1323
        $uniqueIdentifier = $this->coreConfigDataUidGenerator->generate($coreConfigData);
0 ignored issues
show
Unused Code introduced by
The call to GeneratorInterface::generate() has too many arguments starting with $coreConfigData.

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...
1324
1325
        // query whether or not, the configuration value on 'default' level
1326
        if (isset($this->coreConfigData[$uniqueIdentifier][MemberNames::VALUE])) {
1327 1
            return $this->coreConfigData[$uniqueIdentifier][MemberNames::VALUE];
1328 1
        }
1329 1
1330 1
        // if not, return the passed default value
1331 1
        if ($default !== null) {
1332 1
            return $default;
1333
        }
1334
1335
        // throw an exception if no value can be found
1336
        // in the Magento configuration
1337
        throw new \Exception(
1338
            sprintf(
1339
                'Can\'t find a value for configuration "%s-%s-%d" in "core_config_data"',
1340
                $path,
1341
                $scope,
1342
                $scopeId
1343
            )
1344 2
        );
1345
    }
1346
1347
    /**
1348 2
     * Resolve the original column name for the passed one.
1349
     *
1350
     * @param string $columnName The column name that has to be resolved
1351 2
     *
1352
     * @return string|null The original column name
1353 1
     */
1354 1
    public function resolveOriginalColumnName($columnName)
1355
    {
1356
1357
        // try to load the original data
1358 1
        $originalData = $this->getOriginalData();
1359 1
1360
        // query whether or not original data is available
1361
        if (isset($originalData[ColumnKeys::ORIGINAL_COLUMN_NAMES])) {
1362
            // query whether or not the original column name is available
1363
            if (isset($originalData[ColumnKeys::ORIGINAL_COLUMN_NAMES][$columnName])) {
1364 1
                return $originalData[ColumnKeys::ORIGINAL_COLUMN_NAMES][$columnName];
1365
            }
1366
1367
            // query whether or a wildcard column name is available
1368
            if (isset($originalData[ColumnKeys::ORIGINAL_COLUMN_NAMES]['*'])) {
1369
                return $originalData[ColumnKeys::ORIGINAL_COLUMN_NAMES]['*'];
1370
            }
1371
        }
1372 2
1373
        // return the original column name
1374
        return $columnName;
1375
    }
1376 2
1377
    /**
1378
     * Return's the original data if available, or an empty array.
1379 2
     *
1380
     * @return array The original data
1381 1
     */
1382
    public function getOriginalData()
1383
    {
1384
1385 2
        // initialize the array for the original data
1386
        $originalData = array();
1387
1388
        // query whether or not the column contains original data
1389
        if ($this->hasOriginalData()) {
1390
            // unerialize the original data from the column
1391
            $originalData = unserialize($this->row[$this->headers[ColumnKeys::ORIGINAL_DATA]]);
1392
        }
1393
1394 2
        // return an empty array, if not
1395
        return $originalData;
1396 2
    }
1397
1398
    /**
1399
     * Query's whether or not the actual column contains original data like
1400
     * filename, line number and column names.
1401
     *
1402
     * @return boolean TRUE if the actual column contains origin data, else FALSE
1403
     */
1404
    public function hasOriginalData()
1405
    {
1406
        return isset($this->headers[ColumnKeys::ORIGINAL_DATA]) && isset($this->row[$this->headers[ColumnKeys::ORIGINAL_DATA]]);
1407
    }
1408
1409 1
    /**
1410
     * Wraps the passed exeception into a new one by trying to resolve the original filname,
1411
     * line number and column names and use it for a detailed exception message.
1412
     *
1413
     * @param array      $columnNames The column names that should be resolved and wrapped
1414
     * @param \Exception $parent      The exception we want to wrap
1415
     * @param string     $className   The class name of the exception type we want to wrap the parent one
1416 1
     *
1417
     * @return \Exception the wrapped exception
1418
     */
1419 1
    public function wrapException(
1420
        array $columnNames = array(),
1421 1
        \Exception $parent = null,
1422
        $className = '\TechDivision\Import\Exceptions\WrappedColumnException'
1423
    ) {
1424 1
1425 1
        // initialize the message
1426 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...
1427 1
1428
        // query whether or not has been a result of invalid data of a previous column of a CSV file
1429
        if ($this->hasOriginalData()) {
1430
            // load the original data
1431
            $originalData = $this->getOriginalData();
1432
1433
            // replace old filename and line number of the original message
1434
            $message = $this->appendExceptionSuffix(
1435
                $this->stripExceptionSuffix($message),
1436
                $originalData[ColumnKeys::ORIGINAL_FILENAME],
1437
                $originalData[ColumnKeys::ORIGINAL_LINE_NUMBER]
1438
            );
1439 1
        } else {
1440
            // append filename and line number to the original message
1441 1
            $message = $this->appendExceptionSuffix(
1442 1
                $this->stripExceptionSuffix($message),
1443 1
                $this->filename,
1444
                $this->lineNumber
1445
            );
1446
        }
1447 1
1448
        // query whether or not, column names has been passed
1449
        if (sizeof($columnNames) > 0) {
1450
            // prepare the original column names
1451 1
            $originalColumnNames = array();
1452
            foreach ($columnNames as $columnName) {
1453
                $originalColumnNames[] = $this->resolveOriginalColumnName($columnName);
1454
            }
1455
1456
            // append the column information
1457
            $message = sprintf('%s in column(s) %s', $message, implode(', ', $originalColumnNames));
1458
        }
1459
1460
        // create a new exception and wrap the parent one
1461
        return new $className($message, null, $parent);
1462 1
    }
1463
1464 1
    /**
1465
     * Strip's the exception suffix containing filename and line number from the
1466
     * passed message.
1467
     *
1468
     * @param string $message The message to strip the exception suffix from
1469
     *
1470
     * @return mixed The message without the exception suffix
1471
     */
1472
    public function stripExceptionSuffix($message)
1473
    {
1474
        return str_replace($this->appendExceptionSuffix(), '', $message);
1475
    }
1476
1477
    /**
1478 12
     * Append's the exception suffix containing filename and line number to the
1479
     * passed message. If no message has been passed, only the suffix will be
1480
     * returned
1481
     *
1482 12
     * @param string|null $message    The message to append the exception suffix to
1483 12
     * @param string|null $filename   The filename used to create the suffix
1484
     * @param string|null $lineNumber The line number used to create the suffx
1485
     *
1486
     * @return string The message with the appended exception suffix
1487 12
     */
1488 12
    public function appendExceptionSuffix($message = null, $filename = null, $lineNumber = null)
1489
    {
1490
1491
        // query whether or not a filename has been passed
1492 12
        if ($filename === null) {
1493 1
            $filename = $this->getFilename();
1494
        }
1495
1496
        // query whether or not a line number has been passed
1497 12
        if ($lineNumber === null) {
1498
            $lineNumber = $this->getLineNumber();
1499
        }
1500
1501
        // if no message has been passed, only return the suffix
1502
        if ($message === null) {
1503
            return sprintf(' in file %s on line %d', basename($filename), $lineNumber);
1504
        }
1505
1506
        // concatenate the message with the suffix and return it
1507 1
        return sprintf('%s in file %s on line %d', $message, basename($filename), $lineNumber);
1508
    }
1509
1510
    /**
1511 1
     * Raises the value for the counter with the passed key by one.
1512 1
     *
1513 1
     * @param mixed $counterName The name of the counter to raise
1514
     *
1515
     * @return integer The counter's new value
1516
     */
1517
    public function raiseCounter($counterName)
1518
    {
1519
1520
        // raise the counter with the passed name
1521
        return $this->getRegistryProcessor()->raiseCounter(
1522
            RegistryKeys::COUNTERS,
1523
            $counterName
1524 1
        );
1525
    }
1526
1527
    /**
1528 1
     * Merge the passed array into the status of the actual import.
1529 1
     *
1530 1
     * @param array $status The status information to be merged
1531
     *
1532
     * @return void
1533
     */
1534
    public function mergeAttributesRecursive(array $status)
1535
    {
1536
1537
        // merge the passed status
1538
        return $this->getRegistryProcessor()->mergeAttributesRecursive(
1539
            RegistryKeys::STATUS,
1540
            $status
1541
        );
1542
    }
1543
1544
    /**
1545
     * Return's the entity type code to be used.
1546
     *
1547
     * @return string The entity type code to be used
1548
     */
1549
    public function getEntityTypeCode()
1550
    {
1551
1552
        // load the configuration specific entity type code from the plugin configuration
1553
        $entityTypeCode = $this->getExecutionContext()->getEntityTypeCode();
1554
1555
        // try to map the entity type code
1556
        if (isset($this->entityTypeCodeMappings[$entityTypeCode])) {
1557
            $entityTypeCode = $this->entityTypeCodeMappings[$entityTypeCode];
1558
        }
1559
1560
        // return the (mapped) entity type code
1561 9
        return $entityTypeCode;
1562
    }
1563 9
1564 9
    /**
1565 9
     * Concatenates and returns the event name for the actual plugin and subject context.
1566 9
     *
1567 9
     * @param string $eventName The event name to concatenate
1568
     *
1569
     * @return string The concatenated event name
1570
     */
1571
    protected function getEventName($eventName)
1572
    {
1573
        return  sprintf(
1574
            '%s.%s.%s',
1575
            $this->getConfiguration()->getPluginConfiguration()->getId(),
1576
            $this->getConfiguration()->getId(),
1577
            $eventName
1578
        );
1579
    }
1580
1581
    /**
1582
     * Return's the full opration name, which consists of the Magento edition, the entity type code and the operation name.
1583
     *
1584
     * @param string $separator The separator used to seperate the elements
1585
     *
1586
     * @return string The full operation name
1587
     */
1588
    public function getFullOperationName($separator = '/')
1589
    {
1590
        return $this->getConfiguration()->getFullOperationName($separator);
1591
    }
1592
}
1593