Passed
Pull Request — master (#70)
by Matthew
02:01
created

Mapper::objectUpToDate()   A

Complexity

Conditions 5
Paths 5

Size

Total Lines 19
Code Lines 11

Duplication

Lines 0
Ratio 0 %

Importance

Changes 3
Bugs 0 Features 0
Metric Value
eloc 11
c 3
b 0
f 0
dl 0
loc 19
rs 9.6111
cc 5
nc 5
nop 5
1
<?php
2
3
namespace Dynamic\Salsify\Model;
4
5
use Dynamic\Salsify\ORM\SalsifyIDExtension;
6
use Dynamic\Salsify\ORM\SiteConfigExtension;
7
use Dynamic\Salsify\Task\ImportTask;
8
use Exception;
9
use JsonMachine\JsonMachine;
10
use SilverStripe\Core\Injector\Injector;
11
use SilverStripe\ORM\DataList;
12
use SilverStripe\ORM\DataObject;
13
use SilverStripe\ORM\HasManyList;
14
use SilverStripe\ORM\ManyManyList;
15
use SilverStripe\SiteConfig\SiteConfig;
0 ignored issues
show
Bug introduced by
The type SilverStripe\SiteConfig\SiteConfig was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
16
use SilverStripe\Versioned\Versioned;
0 ignored issues
show
Bug introduced by
The type SilverStripe\Versioned\Versioned was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
17
18
/**
19
 * Class Mapper
20
 * @package Dynamic\Salsify\Model
21
 */
22
class Mapper extends Service
23
{
24
25
    /**
26
     * @var bool
27
     */
28
    public static $SINGLE = false;
29
30
    /**
31
     * @var bool
32
     */
33
    public static $MULTIPLE = true;
34
35
    /**
36
     * @var
37
     */
38
    private $file = null;
39
40
    /**
41
     * @var JsonMachine
42
     */
43
    private $productStream;
44
45
    /**
46
     * @var JsonMachine
47
     */
48
    private $assetStream;
49
50
    /**
51
     * @var array
52
     */
53
    private $currentUniqueFields = [];
54
55
    /**
56
     * @var int
57
     */
58
    private $importCount = 0;
59
60
    /**
61
     * @var bool
62
     */
63
    public $skipSilently = false;
64
65
    /**
66
     * Mapper constructor.
67
     * @param string $importerKey
68
     * @param $file
69
     * @throws \Exception
70
     */
71
    public function __construct($importerKey, $file = null)
72
    {
73
        parent::__construct($importerKey);
0 ignored issues
show
Bug introduced by
$importerKey of type string is incompatible with the type Dynamic\Salsify\Model\stirng expected by parameter $importerKey of Dynamic\Salsify\Model\Service::__construct(). ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

73
        parent::__construct(/** @scrutinizer ignore-type */ $importerKey);
Loading history...
74
        if (!$this->config()->get('mapping')) {
75
            throw  new Exception('A Mapper needs a mapping');
76
        }
77
78
        /** @var SiteConfig|SiteConfigExtension $config */
79
        $config = SiteConfig::current_site_config();
80
        $mappingHash = $this->getMappingHash();
81
82
        /** @var MapperHash $hash */
83
        if ($hash = $config->MapperHashes()->find('MapperService', $importerKey)) {
84
            if ($hash->MapperHash != $mappingHash) {
85
                $this->updateMappingHash($mappingHash, $hash);
86
            }
87
        } else {
88
            $this->updateMappingHash($mappingHash);
89
        }
90
91
        if ($file !== null) {
92
            $this->file = $file;
93
            $this->resetProductStream();
94
            $this->resetAssetStream();
95
        }
96
    }
97
98
    /**
99
     * Generates the current hash for the mapping
100
     *
101
     * @return string
102
     */
103
    private function getMappingHash()
104
    {
105
        return md5(serialize($this->config()->get('mapping')));
106
    }
107
108
    /**
109
     * Updates the mapping hash for the mapper
110
     * @param string $hash
111
     * @param MapperHash|null $mapperHash
112
     */
113
    private function updateMappingHash($hash, $mapperHash = null)
114
    {
115
        if ($mapperHash == null) {
116
            $mapperHash = MapperHash::create();
117
            $mapperHash->SiteConfigID = SiteConfig::current_site_config()->ID;
118
            $mapperHash->MapperService = $this->importerKey;
119
        }
120
121
        $mapperHash->MapperHash = $hash;
122
        $mapperHash->write();
123
124
        $this->config()->set('skipUpToDate', false);
125
        ImportTask::output("mappings have updated, treating all objects as out of date");
126
        ImportTask::output("----------------");
127
    }
128
129
    /**
130
     *
131
     */
132
    public function resetProductStream()
133
    {
134
        $this->productStream = JsonMachine::fromFile($this->file, '/4/products');
135
    }
136
137
    /**
138
     *
139
     */
140
    public function resetAssetStream()
141
    {
142
        $this->assetStream = JsonMachine::fromFile($this->file, '/3/digital_assets');
143
    }
144
145
    /**
146
     * Maps the data
147
     * @throws \Exception
148
     */
149
    public function map()
150
    {
151
        $this->extend('onBeforeMap', $this->file, Mapper::$MULTIPLE);
152
153
        foreach ($this->yieldKeyVal($this->productStream) as $name => $data) {
154
            foreach ($this->yieldKeyVal($this->config()->get('mapping')) as $class => $mappings) {
155
                $this->mapToObject($class, $mappings, $data);
156
                $this->currentUniqueFields = [];
157
            }
158
        }
159
160
        if ($this->mappingHasSalsifyRelation()) {
161
            ImportTask::output("----------------");
162
            ImportTask::output("Setting up salsify relations");
163
            ImportTask::output("----------------");
164
            $this->resetProductStream();
165
166
            foreach ($this->yieldKeyVal($this->productStream) as $name => $data) {
167
                foreach ($this->yieldKeyVal($this->config()->get('mapping')) as $class => $mappings) {
168
                    $this->mapToObject($class, $mappings, $data, null, true);
169
                    $this->currentUniqueFields = [];
170
                }
171
            }
172
        }
173
174
        ImportTask::output("Imported and updated $this->importCount products.");
175
        $this->extend('onAfterMap', $this->file, Mapper::$MULTIPLE);
176
    }
177
178
    /**
179
     * @param string|DataObject $class
180
     * @param array $mappings The mapping for a specific class
181
     * @param array $data
182
     * @param DataObject|null $object
183
     * @param bool $salsifyRelations
184
     * @param bool $forceUpdate
185
     *
186
     * @return DataObject|null
187
     * @throws \Exception
188
     */
189
    public function mapToObject(
190
        $class,
191
        $mappings,
192
        $data,
193
        $object = null,
194
        $salsifyRelations = false,
195
        $forceUpdate = false
196
    )
197
    {
198
        if ($salsifyRelations) {
199
            if (!$this->classConfigHasSalsifyRelation($mappings)) {
200
                return null;
201
            }
202
        }
203
204
        // if object was not passed
205
        if ($object === null) {
206
            $object = $this->findObjectByUnique($class, $mappings, $data);
207
208
            $filter = $this->getUniqueFilter($class, $mappings, $data);
209
            if (count(array_filter($filter)) == 0) {
210
                return null;
211
            }
212
213
            // if no existing object was found but a unique filter is valid (not empty)
214
            if (!$object) {
0 ignored issues
show
introduced by
$object is of type SilverStripe\ORM\DataObject, thus it always evaluated to true.
Loading history...
215
                $object = $class::create();
216
            }
217
        }
218
219
        $wasPublished = $object->hasExtension(Versioned::class) ? $object->isPublished() : false;
220
        $wasWritten = $object->isInDB();
221
222
        $firstUniqueKey = array_keys($this->uniqueFields($class, $mappings))[0];
223
        if (array_key_exists($mappings[$firstUniqueKey]['salsifyField'], $data)) {
224
            $firstUniqueValue = $data[$mappings[$firstUniqueKey]['salsifyField']];
225
        } else {
226
            $firstUniqueValue = 'NULL';
227
        }
228
        ImportTask::output("Updating $class $firstUniqueKey $firstUniqueValue");
229
230
        if (
231
            !$forceUpdate &&
232
            $this->objectUpToDate($object, $data, $firstUniqueKey, $firstUniqueValue, $salsifyRelations)
233
        ) {
234
            return $object;
235
        }
236
237
        foreach ($this->yieldKeyVal($mappings) as $dbField => $salsifyField) {
238
            $field = $this->getField($salsifyField, $data);
239
            if ($field === false) {
240
                $this->clearValue($object, $dbField, $salsifyField);
241
                continue;
242
            }
243
244
            $type = $this->getFieldType($salsifyField);
245
            // skip all but salsify relations types if not doing relations
246
            if ($salsifyRelations && !$this->typeRequiresSalsifyObjects($type)) {
247
                continue;
248
            }
249
250
            // skip salsify relations types if not doing relations
251
            if (!$salsifyRelations && $this->typeRequiresSalsifyObjects($type)) {
252
                continue;
253
            }
254
255
            $value = null;
256
            $objectData = $data;
0 ignored issues
show
Unused Code introduced by
The assignment to $objectData is dead and can be removed.
Loading history...
257
258
            if ($this->handleShouldSkip($class, $dbField, $salsifyField, $data)) {
259
                if (!$this->skipSilently) {
260
                    ImportTask::output("Skipping $class $firstUniqueKey $firstUniqueValue");
261
                    $this->skipSilently = false;
262
                }
263
                return false;
0 ignored issues
show
Bug Best Practice introduced by
The expression return false returns the type false which is incompatible with the documented return type SilverStripe\ORM\DataObject|null.
Loading history...
264
            };
265
266
            $objectData = $this->handleModification($type, $class, $dbField, $salsifyField, $data);
267
            $sortColumn = $this->getSortColumn($salsifyField);
268
269
            if ($salsifyRelations == false && !array_key_exists($field, $objectData)) {
0 ignored issues
show
Coding Style Best Practice introduced by
It seems like you are loosely comparing two booleans. Considering using the strict comparison === instead.

When comparing two booleans, it is generally considered safer to use the strict comparison operator.

Loading history...
270
                continue;
271
            }
272
273
            $value = $this->handleType($type, $class, $objectData, $field, $salsifyField, $dbField);
274
            $this->writeValue($object, $type, $dbField, $value, $sortColumn);
275
        }
276
277
        if ($object->isChanged()) {
278
            $object->write();
279
            $this->importCount++;
280
            $this->extend('afterObjectWrite', $object, $wasWritten, $wasPublished);
281
        } else {
282
            ImportTask::output("$class $firstUniqueKey $firstUniqueValue was not changed.");
283
        }
284
        return $object;
285
    }
286
287
    /**
288
     * @param DataObject $object
289
     * @param array $data
290
     * @param string $firstUniqueKey
291
     * @param string $firstUniqueValue
292
     * @param bool $salsifyRelations
293
     * @return bool
294
     */
295
    private function objectUpToDate($object, $data, $firstUniqueKey, $firstUniqueValue, $salsifyRelations = false)
296
    {
297
        if ($this->config()->get('skipUpToDate') == false) {
298
            return false;
299
        }
300
301
        if ($salsifyRelations == false) {
0 ignored issues
show
Coding Style Best Practice introduced by
It seems like you are loosely comparing two booleans. Considering using the strict comparison === instead.

When comparing two booleans, it is generally considered safer to use the strict comparison operator.

Loading history...
302
            if ($this->objectDataUpToDate($object, $data, $firstUniqueKey, $firstUniqueValue)) {
303
                ImportTask::output("Skipping $firstUniqueKey $firstUniqueValue. It is up to Date.");
304
                return true;
305
            }
306
        } else {
307
            if ($this->objectRelationsUpToDate($object, $data, $firstUniqueKey, $firstUniqueValue)) {
308
                ImportTask::output("Skipping $firstUniqueKey $firstUniqueValue relations. It is up to Date.");
309
                return true;
310
            }
311
        }
312
313
        return false;
314
    }
315
316
    /**
317
     * @param DataObject $object
318
     * @param array $data
319
     * @param string $firstUniqueKey
320
     * @param string $firstUniqueValue
321
     * @return bool
322
     */
323
    public function objectDataUpToDate($object, $data, $firstUniqueKey, $firstUniqueValue)
324
    {
325
        // assume not up to date if field does not exist on object
326
        if (!$object->hasField('SalsifyUpdatedAt')) {
327
            return false;
328
        }
329
330
        if ($data['salsify:updated_at'] != $object->getField('SalsifyUpdatedAt')) {
331
            return false;
332
        }
333
334
        return true;
335
    }
336
337
    /**
338
     * @param DataObject $object
339
     * @param array $data
340
     * @param string $firstUniqueKey
341
     * @param string $firstUniqueValue
342
     * @return bool
343
     */
344
    public function objectRelationsUpToDate($object, $data, $firstUniqueKey, $firstUniqueValue)
345
    {
346
        // assume not up to date if field does not exist on object
347
        if (!$object->hasField('SalsifyRelationsUpdatedAt')) {
348
            return false;
349
        }
350
351
        // relations were never updated, so its up to date
352
        if (!isset($data['salsify:relations_updated_at'])) {
353
            return true;
354
        }
355
356
        if ($data['salsify:relations_updated_at'] != $object->getField('SalsifyRelationsUpdatedAt')) {
357
            return false;
358
        }
359
360
        return true;
361
    }
362
363
    /**
364
     * @param array $salsifyField
365
     * @param array $data
366
     *
367
     * @return string|false
368
     */
369
    private function getField($salsifyField, $data)
370
    {
371
        if (!is_array($salsifyField)) {
0 ignored issues
show
introduced by
The condition is_array($salsifyField) is always true.
Loading history...
372
            return array_key_exists($salsifyField, $data) ? $salsifyField : false;
373
        }
374
375
        $hasSalsifyField = array_key_exists('salsifyField', $salsifyField);
376
        $isLiteralField = (
377
            $this->getFieldType($salsifyField) === 'Literal' &&
0 ignored issues
show
introduced by
The condition $this->getFieldType($salsifyField) === 'Literal' is always false.
Loading history...
378
            array_key_exists('value', $salsifyField)
379
        );
380
        $isSalsifyRelationField = (
381
            $this->getFieldType($salsifyField) === 'SalsifyRelation' &&
0 ignored issues
show
introduced by
The condition $this->getFieldType($sal...) === 'SalsifyRelation' is always false.
Loading history...
382
            $hasSalsifyField
383
        );
384
385
        if ($isLiteralField) {
0 ignored issues
show
introduced by
The condition $isLiteralField is always false.
Loading history...
386
            return $salsifyField['value'];
387
        }
388
389
        if ($isSalsifyRelationField) {
0 ignored issues
show
introduced by
The condition $isSalsifyRelationField is always false.
Loading history...
390
            return $salsifyField['salsifyField'];
391
        }
392
393
        if (!$hasSalsifyField) {
394
            return false;
395
        }
396
397
        if (array_key_exists($salsifyField['salsifyField'], $data)) {
398
            return $salsifyField['salsifyField'];
399
        } elseif (array_key_exists('fallback', $salsifyField)) {
400
            // make fallback an array
401
            if (!is_array($salsifyField['fallback'])) {
402
                $salsifyField['fallback'] = [$salsifyField['fallback']];
403
            }
404
405
            foreach ($this->yieldSingle($salsifyField['fallback']) as $fallback) {
406
                if (array_key_exists($fallback, $data)) {
407
                    return $fallback;
408
                }
409
            }
410
        } elseif (array_key_exists('modification', $salsifyField)) {
411
            return $salsifyField['salsifyField'];
412
        }
413
414
        return false;
415
    }
416
417
    /**
418
     * @param string $class
419
     * @param array $mappings
420
     * @param array $data
421
     *
422
     * @return array
423
     */
424
    private function getUniqueFilter($class, $mappings, $data)
425
    {
426
        $uniqueFields = $this->uniqueFields($class, $mappings);
427
        // creates a filter
428
        $filter = [];
429
        foreach ($this->yieldKeyVal($uniqueFields) as $dbField => $salsifyField) {
430
            $modifiedData = $data;
431
            $fieldMapping = $mappings[$dbField];
432
            $fieldType = $this->getFieldType($salsifyField);
433
            $modifiedData = $this->handleModification($fieldType, $class, $dbField, $fieldMapping, $modifiedData);
434
435
            // adds unique fields to filter
436
            if (array_key_exists($salsifyField, $modifiedData)) {
437
                $filter[$dbField] = $modifiedData[$salsifyField];
438
            }
439
        }
440
441
        return $filter;
442
    }
443
444
    /**
445
     * @param string $class
446
     * @param array $mappings
447
     * @param array $data
448
     *
449
     * @return \SilverStripe\ORM\DataObject
450
     */
451
    private function findObjectByUnique($class, $mappings, $data)
452
    {
453
        if ($obj = $this->findBySalsifyID($class, $mappings, $data)) {
454
            return $obj;
455
        }
456
457
        $filter = $this->getUniqueFilter($class, $mappings, $data);
458
        return DataObject::get($class)->filter($filter)->first();
459
    }
460
461
    /**
462
     * @param string $class
463
     * @param array $mappings
464
     * @param array $data
465
     *
466
     * @return \SilverStripe\ORM\DataObject|bool
467
     */
468
    private function findBySalsifyID($class, $mappings, $data)
469
    {
470
        /** @var DataObject $genericObject */
471
        $genericObject = Injector::inst()->get($class);
472
        if (
473
            !$genericObject->hasExtension(SalsifyIDExtension::class) &&
474
            !$genericObject->hasField('SalsifyID')
475
        ) {
476
            return false;
477
        }
478
479
        $obj = DataObject::get($class)->filter([
480
            'SalsifyID' => $data['salsify:id'],
481
        ])->first();
482
        if ($obj) {
0 ignored issues
show
introduced by
$obj is of type SilverStripe\ORM\DataObject, thus it always evaluated to true.
Loading history...
483
            return $obj;
484
        }
485
486
        return false;
487
    }
488
489
    /**
490
     * Gets a list of all the unique field keys
491
     *
492
     * @param string class
0 ignored issues
show
Bug introduced by
The type Dynamic\Salsify\Model\class was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
493
     * @param array $mappings
494
     * @return array
495
     */
496
    private function uniqueFields($class, $mappings)
497
    {
498
        // cached after first map
499
        if (array_key_exists($class, $this->currentUniqueFields) && !empty($this->currentUniqueFields[$class])) {
500
            return $this->currentUniqueFields[$class];
501
        }
502
503
        $uniqueFields = [];
504
        foreach ($this->yieldKeyVal($mappings) as $dbField => $salsifyField) {
505
            if (!is_array($salsifyField)) {
506
                continue;
507
            }
508
509
            if (
510
                !array_key_exists('unique', $salsifyField) ||
511
                !array_key_exists('salsifyField', $salsifyField)
512
            ) {
513
                continue;
514
            }
515
516
            if ($salsifyField['unique'] !== true) {
517
                continue;
518
            }
519
520
            $uniqueFields[$dbField] = $salsifyField['salsifyField'];
521
        }
522
523
        $this->currentUniqueFields[$class] = $uniqueFields;
524
        return $uniqueFields;
525
    }
526
527
    /**
528
     * @param array|string $salsifyField
529
     * @return bool|mixed
530
     */
531
    private function getSortColumn($salsifyField)
532
    {
533
        if (!is_array($salsifyField)) {
534
            return false;
535
        }
536
537
        if (array_key_exists('sortColumn', $salsifyField)) {
538
            return $salsifyField['sortColumn'];
539
        }
540
541
        return false;
542
    }
543
544
    /**
545
     * @return bool
546
     */
547
    private function mappingHasSalsifyRelation()
548
    {
549
        foreach ($this->yieldKeyVal($this->config()->get('mapping')) as $class => $mappings) {
550
            if ($this->classConfigHasSalsifyRelation($mappings)) {
551
                return true;
552
            }
553
        }
554
        return false;
555
    }
556
557
    /**
558
     * @param array $classConfig
559
     * @return bool
560
     */
561
    private function classConfigHasSalsifyRelation($classConfig)
562
    {
563
        foreach ($this->yieldKeyVal($classConfig) as $field => $config) {
564
            if (!is_array($config)) {
565
                continue;
566
            }
567
568
            if (!array_key_exists('salsifyField', $config)) {
569
                continue;
570
            }
571
572
            if (!array_key_exists('type', $config)) {
573
                continue;
574
            }
575
576
            if (in_array($config['type'], $this->getFieldsRequiringSalsifyObjects())) {
577
                return true;
578
            }
579
        }
580
        return false;
581
    }
582
583
    /**
584
     * @return array
585
     */
586
    private function getFieldsRequiringSalsifyObjects()
587
    {
588
        $fieldTypes = $this->config()->get('field_types');
0 ignored issues
show
Unused Code introduced by
The assignment to $fieldTypes is dead and can be removed.
Loading history...
589
        $types = [];
590
        foreach ($this->yieldKeyVal($this->config()->get('field_types')) as $field => $config) {
591
            $type = [
592
                'type' => $field,
593
                'config' => $config,
594
            ];
595
            if ($this->typeRequiresSalsifyObjects($type)) {
596
                $types[] = $field;
597
            }
598
        }
599
600
        return $types;
601
    }
602
603
    /**
604
     * @param array $type
605
     * @return bool
606
     */
607
    private function typeRequiresWrite($type)
608
    {
609
        $config = $type['config'];
610
611
        if (array_key_exists('requiresWrite', $config)) {
612
            return $config['requiresWrite'];
613
        }
614
615
        return false;
616
    }
617
618
    /**
619
     * @param array $type
620
     * @return bool
621
     */
622
    private function typeRequiresSalsifyObjects($type)
623
    {
624
        $config = $type['config'];
625
626
        if (array_key_exists('requiresSalsifyObjects', $config)) {
627
            return $config['requiresSalsifyObjects'];
628
        }
629
630
        return false;
631
    }
632
633
    /**
634
     * @param array $type
635
     * @return string|bool
636
     */
637
    private function typeFallback($type)
638
    {
639
        $config = $type['config'];
640
641
        if (array_key_exists('fallback', $config)) {
642
            return $config['fallback'];
643
        }
644
645
        return false;
646
    }
647
648
    /**
649
     * @param array $type
650
     * @return bool
651
     */
652
    private function canModifyType($type)
653
    {
654
        $config = $type['config'];
655
656
        if (array_key_exists('allowsModification', $config)) {
657
            return $config['allowsModification'];
658
        }
659
660
        return true;
661
    }
662
663
    /**
664
     * @param array $type
665
     * @param string $class
666
     * @param string $dbField
667
     * @param array $config
668
     * @param array $data
669
     * @return array
670
     */
671
    private function handleModification($type, $class, $dbField, $config, $data)
672
    {
673
        if (!is_array($config)) {
0 ignored issues
show
introduced by
The condition is_array($config) is always true.
Loading history...
674
            return $data;
675
        }
676
677
        if (!$this->canModifyType($type)) {
678
            return $data;
679
        }
680
681
        if (array_key_exists('modification', $config)) {
682
            $mod = $config['modification'];
683
            if ($this->hasMethod($mod)) {
684
                return $this->{$mod}($class, $dbField, $config, $data);
685
            }
686
            ImportTask::output("{$mod} is not a valid field modifier. skipping modification for field {$dbField}.");
687
        }
688
        return $data;
689
    }
690
691
    /**
692
     * @param string $class
693
     * @param string $dbField
694
     * @param array $config
695
     * @param array $data
696
     * @return boolean
697
     */
698
    public function handleShouldSkip($class, $dbField, $config, $data)
699
    {
700
        if (!is_array($config)) {
0 ignored issues
show
introduced by
The condition is_array($config) is always true.
Loading history...
701
            return false;
702
        }
703
704
        if (array_key_exists('shouldSkip', $config)) {
705
            $skipMethod = $config['shouldSkip'];
706
            if ($this->hasMethod($skipMethod)) {
707
                return $this->{$skipMethod}($class, $dbField, $config, $data);
708
            }
709
            ImportTask::output(
710
                "{$skipMethod} is not a valid skip test method. Skipping skip test for field {$dbField}."
711
            );
712
        }
713
        return false;
714
    }
715
716
    /**
717
     * @param string|array $field
718
     * @return array
719
     */
720
    public function getFieldType($field)
721
    {
722
        $fieldTypes = $this->config()->get('field_types');
723
        if (is_array($field) && array_key_exists('type', $field)) {
724
            if (array_key_exists($field['type'], $fieldTypes)) {
725
                return [
726
                    'type' => $field['type'],
727
                    'config' => $fieldTypes[$field['type']],
728
                ];
729
            }
730
        }
731
        // default to raw
732
        return [
733
            'type' => 'Raw',
734
            'config' => $fieldTypes['Raw'],
735
        ];
736
    }
737
738
    /**
739
     * @param array $type
740
     * @param string|DataObject $class
741
     * @param array $salsifyData
742
     * @param string $salsifyField
743
     * @param array $dbFieldConfig
744
     * @param string $dbField
745
     *
746
     * @return mixed
747
     */
748
    private function handleType($type, $class, $salsifyData, $salsifyField, $dbFieldConfig, $dbField)
749
    {
750
        $typeName = $type['type'];
751
        $typeConfig = $type['config'];
752
        if ($this->hasMethod("handle{$typeName}Type")) {
753
            return $this->{"handle{$typeName}Type"}($class, $salsifyData, $salsifyField, $dbFieldConfig, $dbField);
754
        }
755
756
        if (array_key_exists('fallback', $typeConfig)) {
757
            $fallback = $typeConfig['fallback'];
758
            if ($this->hasMethod("handle{$fallback}Type")) {
759
                return $this->{"handle{$fallback}Type"}($class, $salsifyData, $salsifyField, $dbFieldConfig, $dbField);
760
            }
761
        }
762
763
        ImportTask::output("{$typeName} is not a valid type. skipping field {$dbField}.");
764
        return '';
765
    }
766
767
    /**
768
     * @param DataObject $object
769
     * @param array $type
770
     * @param string $dbField
771
     * @param mixed $value
772
     * @param string|bool $sortColumn
773
     *
774
     * @throws \Exception
775
     */
776
    private function writeValue($object, $type, $dbField, $value, $sortColumn)
777
    {
778
        $isManyRelation = array_key_exists($dbField, $object->config()->get('has_many')) ||
779
            array_key_exists($dbField, $object->config()->get('many_many')) ||
780
            array_key_exists($dbField, $object->config()->get('belongs_many_many'));
781
782
        $isSingleRelation = array_key_exists(rtrim($dbField, 'ID'), $object->config()->get('has_one'));
783
784
        // write the object so relations can be written
785
        if ($this->typeRequiresWrite($type) && !$object->exists()) {
786
            $object->write();
787
        }
788
789
        if (!$isManyRelation) {
790
            if (!$isSingleRelation || ($isSingleRelation && $value !== false)) {
791
                $object->$dbField = $value;
792
            }
793
            return;
794
        }
795
796
        // change to an array and filter out empty values
797
        if (!is_array($value)) {
798
            $value = [$value];
799
        }
800
        $value = array_filter($value);
801
802
        // don't try to write an empty set
803
        if (!count($value)) {
804
            return;
805
        }
806
807
        $this->removeUnrelated($object, $dbField, $value);
808
        $this->writeManyRelation($object, $dbField, $value, $sortColumn);
809
    }
810
811
    /**
812
     * @param DataObject $object
813
     * @param string $dbField
814
     * @param array $value
815
     * @param string|bool $sortColumn
816
     *
817
     * @throws \Exception
818
     */
819
    private function writeManyRelation($object, $dbField, $value, $sortColumn)
820
    {
821
        /** @var DataList|HasManyList|ManyManyList $relation */
822
        $relation = $object->{$dbField}();
823
824
        if ($sortColumn && $relation instanceof ManyManyList) {
825
            for ($i = 0; $i < count($value); $i++) {
0 ignored issues
show
Performance Best Practice introduced by
It seems like you are calling the size function count() as part of the test condition. You might want to compute the size beforehand, and not on each iteration.

If the size of the collection does not change during the iteration, it is generally a good practice to compute it beforehand, and not on each iteration:

for ($i=0; $i<count($array); $i++) { // calls count() on each iteration
}

// Better
for ($i=0, $c=count($array); $i<$c; $i++) { // calls count() just once
}
Loading history...
826
                $relation->add($value[$i], [$sortColumn => $i]);
827
            }
828
            return;
829
        }
830
831
        // HasManyList, so it exists on the value
832
        if ($sortColumn) {
833
            for ($i = 0; $i < count($value); $i++) {
0 ignored issues
show
Performance Best Practice introduced by
It seems like you are calling the size function count() as part of the test condition. You might want to compute the size beforehand, and not on each iteration.

If the size of the collection does not change during the iteration, it is generally a good practice to compute it beforehand, and not on each iteration:

for ($i=0; $i<count($array); $i++) { // calls count() on each iteration
}

// Better
for ($i=0, $c=count($array); $i<$c; $i++) { // calls count() just once
}
Loading history...
834
                $value[$i]->{$sortColumn} = $i;
835
                $relation->add($value[$i]);
836
            }
837
            return;
838
        }
839
840
        $relation->addMany($value);
841
    }
842
843
    /**
844
     * Removes unrelated objects in the relation that were previously related
845
     * @param DataObject $object
846
     * @param string $dbField
847
     * @param array $value
848
     */
849
    private function removeUnrelated($object, $dbField, $value)
850
    {
851
        $ids = [];
852
        foreach ($value as $v) {
853
            $ids[] = $v->ID;
854
        }
855
856
        /** @var DataList $relation */
857
        $relation = $object->{$dbField}();
858
        // remove all unrelated - removeAll had an odd side effect (relations only got added back half the time)
859
        if (!empty($ids)) {
860
            $relation->removeMany(
861
                $relation->exclude([
862
                    'ID' => $ids,
863
                ])->column('ID')
864
            );
865
        }
866
    }
867
868
    /**
869
     * @param string|array $salsifyField
870
     * @throws Exception
871
     */
872
    private function clearValue($object, $dbField, $salsifyField)
873
    {
874
        if (
875
            is_array($salsifyField) &&
876
            array_key_exists('keepExistingValue', $salsifyField) &&
877
            $salsifyField['keepExistingValue']
878
        ) {
879
            return;
880
        }
881
882
        $type = [
883
            'type' => 'null',
884
            'config' => [],
885
        ];
886
887
        // clear any existing value
888
        $this->writeValue($object, $type, $dbField, null, null);
889
    }
890
891
    /**
892
     * @return \JsonMachine\JsonMachine
893
     */
894
    public function getAssetStream()
895
    {
896
        return $this->assetStream;
897
    }
898
899
    /**
900
     * @return bool
901
     */
902
    public function hasFile()
903
    {
904
        return $this->file !== null;
905
    }
906
}
907