Completed
Push — 15.x ( 0b1d63...4cb802 )
by Tim
02:51
created

AbstractSubject::formatDate()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 2

Importance

Changes 0
Metric Value
dl 0
loc 4
c 0
b 0
f 0
ccs 0
cts 2
cp 0
rs 10
cc 1
nc 1
nop 1
crap 2
1
<?php
2
3
/**
4
 * TechDivision\Import\Subjects\AbstractSubject
5
 *
6
 * NOTICE OF LICENSE
7
 *
8
 * This source file is subject to the Open Software License (OSL 3.0)
9
 * that is available through the world-wide-web at this URL:
10
 * http://opensource.org/licenses/osl-3.0.php
11
 *
12
 * PHP version 5
13
 *
14
 * @author    Tim Wagner <[email protected]>
15
 * @copyright 2016 TechDivision GmbH <[email protected]>
16
 * @license   http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0)
17
 * @link      https://github.com/techdivision/import
18
 * @link      http://www.techdivision.com
19
 */
20
21
namespace TechDivision\Import\Subjects;
22
23
use 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
     * Mapping for the virtual entity type code to the real Magento 2 EAV entity type code.
245
     *
246
     * @var array
247
     */
248
    protected $entityTypeCodeMappings = array(
249
        EntityTypeCodes::EAV_ATTRIBUTE                 => EntityTypeCodes::CATALOG_PRODUCT,
250
        EntityTypeCodes::EAV_ATTRIBUTE_SET             => EntityTypeCodes::CATALOG_PRODUCT,
251
        EntityTypeCodes::CATALOG_PRODUCT_PRICE         => EntityTypeCodes::CATALOG_PRODUCT,
252
        EntityTypeCodes::CATALOG_PRODUCT_INVENTORY     => EntityTypeCodes::CATALOG_PRODUCT,
253
        EntityTypeCodes::CATALOG_PRODUCT_INVENTORY_MSI => EntityTypeCodes::CATALOG_PRODUCT,
254
        EntityTypeCodes::CATALOG_PRODUCT_TIER_PRICE    => EntityTypeCodes::CATALOG_PRODUCT
255
    );
256
257
    /**
258
     * Initialize the subject instance.
259
     *
260
     * @param \TechDivision\Import\Services\RegistryProcessorInterface $registryProcessor          The registry processor instance
261
     * @param \TechDivision\Import\Utils\Generators\GeneratorInterface $coreConfigDataUidGenerator The UID generator for the core config data
262
     * @param \Doctrine\Common\Collections\Collection                  $systemLoggers              The array with the system loggers instances
263
     * @param \League\Event\EmitterInterface                           $emitter                    The event emitter instance
264
     */
265 82
    public function __construct(
266
        RegistryProcessorInterface $registryProcessor,
267
        GeneratorInterface $coreConfigDataUidGenerator,
268
        Collection $systemLoggers,
269
        EmitterInterface $emitter
270
    ) {
271 82
        $this->emitter = $emitter;
272 82
        $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...
273 82
        $this->registryProcessor = $registryProcessor;
274 82
        $this->coreConfigDataUidGenerator = $coreConfigDataUidGenerator;
275 82
    }
276
277
    /**
278
     * Return's the event emitter instance.
279
     *
280
     * @return \League\Event\EmitterInterface The event emitter instance
281
     */
282
    public function getEmitter()
283
    {
284
        return $this->emitter;
285
    }
286
287
    /**
288
     * Set's the name of the file to import
289
     *
290
     * @param string $filename The filename
291
     *
292
     * @return void
293
     */
294
    public function setFilename($filename)
295
    {
296
        $this->filename = $filename;
297
    }
298
299
    /**
300
     * Return's the name of the file to import.
301
     *
302
     * @return string The filename
303
     */
304
    public function getFilename()
305
    {
306
        return $this->filename;
307
    }
308
309
    /**
310
     * Set's the actual line number.
311
     *
312
     * @param integer $lineNumber The line number
313
     *
314
     * @return void
315
     */
316
    public function setLineNumber($lineNumber)
317
    {
318
        $this->lineNumber = $lineNumber;
319
    }
320
321
    /**
322
     * Return's the actual line number.
323
     *
324
     * @return integer The line number
325
     */
326
    public function getLineNumber()
327
    {
328
        return $this->lineNumber;
329
    }
330
331
    /**
332
     * Return's the default callback mappings.
333
     *
334
     * @return array The default callback mappings
335
     */
336
    public function getDefaultCallbackMappings()
337
    {
338
        return array();
339
    }
340
341
    /**
342
     * Load the default header mappings from the configuration.
343
     *
344
     * @return array
345
     */
346 82
    public function getDefaultHeaderMappings()
347
    {
348
349
        // initialize the array for the default header mappings
350 82
        $defaultHeaderMappings = array();
351
352
        // load the Magento edition and the entity type from the execution context
353 82
        $entityTypeCode = $this->getExecutionContext()->getEntityTypeCode();
354
355
        // load the header mappings from the configuration
356
        $headerMappings = $this->getConfiguration()->getHeaderMappings();
357
358
        // query whether or not header mappings for the entity type are available
359
        if (isset($headerMappings[$entityTypeCode])) {
360
            $defaultHeaderMappings = $headerMappings[$entityTypeCode];
361
        }
362
363
        // return the default header mappings
364
        return $defaultHeaderMappings;
365
    }
366
367
    /**
368
     * Tries to format the passed value to a valid date with format 'Y-m-d H:i:s'.
369
     * If the passed value is NOT a valid date, NULL will be returned.
370
     *
371
     * @param string $value The value to format
372
     *
373
     * @return string|null The formatted date or NULL if the date is not valid
374
     */
375
    public function formatDate($value)
376
    {
377
        return $this->getDateConverter()->convert($value);
378
    }
379
380
    /**
381
     * Extracts the elements of the passed value by exploding them
382
     * with the also passed delimiter.
383
     *
384
     * @param string      $value     The value to extract
385
     * @param string|null $delimiter The delimiter used to extrace the elements
386
     *
387
     * @return array The exploded values
388
     */
389
    public function explode($value, $delimiter = null)
390
    {
391
        return $this->getImportAdapter()->explode($value, $delimiter);
392
    }
393
394
    /**
395
     * Queries whether or not debug mode is enabled or not, default is TRUE.
396
     *
397
     * @return boolean TRUE if debug mode is enabled, else FALSE
398
     */
399
    public function isDebugMode()
400
    {
401
        return $this->getConfiguration()->isDebugMode();
402
    }
403
404
    /**
405
     * Return's the subject's execution context configuration.
406
     *
407
     * @return \TechDivision\Import\ExecutionContextInterface The execution context configuration to use
408
     */
409 5
    public function getExecutionContext()
410
    {
411 5
        return $this->getConfiguration()->getPluginConfiguration()->getExecutionContext();
0 ignored issues
show
Bug introduced by
The method getExecutionContext() does not seem to exist on object<TechDivision\Impo...ConfigurationInterface>.

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

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

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