Passed
Push — master ( 4eca88...b628f7 )
by Matthew
02:14
created

Mapper::isMapperHashUpToDate()   A

Complexity

Conditions 3
Paths 3

Size

Total Lines 16
Code Lines 9

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
eloc 9
c 0
b 0
f 0
dl 0
loc 16
rs 9.9666
cc 3
nc 3
nop 2
1
<?php
2
3
namespace Dynamic\Salsify\Model;
4
5
use Dynamic\Salsify\ORM\SalsifyIDExtension;
6
use Dynamic\Salsify\Task\ImportTask;
7
use Exception;
8
use JsonMachine\JsonMachine;
9
use SilverStripe\Core\Injector\Injector;
10
use SilverStripe\ORM\DataList;
11
use SilverStripe\ORM\DataObject;
12
use SilverStripe\ORM\HasManyList;
13
use SilverStripe\ORM\ManyManyList;
14
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...
15
16
/**
17
 * Class Mapper
18
 * @package Dynamic\Salsify\Model
19
 */
20
class Mapper extends Service
21
{
22
23
    /**
24
     * @var bool
25
     */
26
    public static $SINGLE = false;
27
28
    /**
29
     * @var bool
30
     */
31
    public static $MULTIPLE = true;
32
33
    /**
34
     * @var
35
     */
36
    private $file = null;
37
38
    /**
39
     * @var JsonMachine
40
     */
41
    private $productStream;
42
43
    /**
44
     * @var JsonMachine
45
     */
46
    private $assetStream;
47
48
    /**
49
     * @var array
50
     */
51
    private $currentUniqueFields = [];
52
53
    /**
54
     * @var int
55
     */
56
    private $importCount = 0;
57
58
    /**
59
     * @var bool
60
     */
61
    public $skipSilently = false;
62
63
    /**
64
     * Mapper constructor.
65
     * @param string $importerKey
66
     * @param $file
67
     * @throws \Exception
68
     */
69
    public function __construct($importerKey, $file = null)
70
    {
71
        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

71
        parent::__construct(/** @scrutinizer ignore-type */ $importerKey);
Loading history...
72
        if (!$this->config()->get('mapping')) {
73
            throw  new Exception('A Mapper needs a mapping');
74
        }
75
76
        if ($file !== null) {
77
            $this->file = $file;
78
            $this->resetProductStream();
79
            $this->resetAssetStream();
80
        }
81
    }
82
83
    /**
84
     * Generates the current hash for the mapping
85
     *
86
     * @return string
87
     */
88
    private function getMappingHash()
89
    {
90
        return md5(serialize($this->config()->get('mapping')));
91
    }
92
93
    /**
94
     * Updates the mapping hash for the mapper
95
     * @param DataObject|SalsifyIDExtension $object
96
     * @param bool $relations
97
     */
98
    private function updateMappingHash($object, $relations)
99
    {
100
        $filter = [
101
            'MapperService' => $this->importerKey,
102
            'ForRelations' => $relations,
103
        ];
104
        /** @var MapperHash $mapperHash */
105
        $mapperHash = $object->MapperHashes()->filter($filter)->first();
106
        if ($mapperHash == null) {
107
            $mapperHash = MapperHash::create();
108
            $mapperHash->MappedObjectID = $object->ID;
109
            $mapperHash->MappedObjectClass = $object->ClassName;
110
            $mapperHash->MapperService = $this->importerKey;
111
            $mapperHash->ForRelations = $relations;
112
        }
113
114
        $mapperHash->MapperHash = $this->getMappingHash();
115
        $mapperHash->write();
116
    }
117
118
    /**
119
     *
120
     */
121
    public function resetProductStream()
122
    {
123
        $this->productStream = JsonMachine::fromFile($this->file, '/4/products');
124
    }
125
126
    /**
127
     *
128
     */
129
    public function resetAssetStream()
130
    {
131
        $this->assetStream = JsonMachine::fromFile($this->file, '/3/digital_assets');
132
    }
133
134
    /**
135
     * Maps the data
136
     * @throws \Exception
137
     */
138
    public function map()
139
    {
140
        $this->extend('onBeforeMap', $this->file, Mapper::$MULTIPLE);
141
142
        foreach ($this->yieldKeyVal($this->productStream) as $name => $data) {
143
            foreach ($this->yieldKeyVal($this->config()->get('mapping')) as $class => $mappings) {
144
                $this->mapToObject($class, $mappings, $data);
145
                $this->currentUniqueFields = [];
146
            }
147
        }
148
149
        if ($this->mappingHasSalsifyRelation()) {
150
            ImportTask::output("----------------");
151
            ImportTask::output("Setting up salsify relations");
152
            ImportTask::output("----------------");
153
            $this->resetProductStream();
154
155
            foreach ($this->yieldKeyVal($this->productStream) as $name => $data) {
156
                foreach ($this->yieldKeyVal($this->config()->get('mapping')) as $class => $mappings) {
157
                    $this->mapToObject($class, $mappings, $data, null, true);
158
                    $this->currentUniqueFields = [];
159
                }
160
            }
161
        }
162
163
        ImportTask::output("Imported and updated $this->importCount products.");
164
        $this->extend('onAfterMap', $this->file, Mapper::$MULTIPLE);
165
    }
166
167
    /**
168
     * @param string|DataObject $class
169
     * @param array $mappings The mapping for a specific class
170
     * @param array $data
171
     * @param DataObject|null $object
172
     * @param bool $salsifyRelations
173
     * @param bool $forceUpdate
174
     *
175
     * @return DataObject|null
176
     * @throws \Exception
177
     */
178
    public function mapToObject(
179
        $class,
180
        $mappings,
181
        $data,
182
        $object = null,
183
        $salsifyRelations = false,
184
        $forceUpdate = false
185
    ) {
186
        if ($salsifyRelations) {
187
            if (!$this->classConfigHasSalsifyRelation($mappings)) {
188
                return null;
189
            }
190
        }
191
192
        // if object was not passed
193
        if ($object === null) {
194
            $object = $this->findObjectByUnique($class, $mappings, $data);
195
196
            $filter = $this->getUniqueFilter($class, $mappings, $data);
197
            if (count(array_filter($filter)) == 0) {
198
                return null;
199
            }
200
201
            // if no existing object was found but a unique filter is valid (not empty)
202
            if (!$object) {
0 ignored issues
show
introduced by
$object is of type SilverStripe\ORM\DataObject, thus it always evaluated to true.
Loading history...
203
                $object = $class::create();
204
            }
205
        }
206
207
        $wasPublished = $object->hasExtension(Versioned::class) ? $object->isPublished() : false;
208
        $wasWritten = $object->isInDB();
209
210
        $firstUniqueKey = array_keys($this->uniqueFields($class, $mappings))[0];
211
        if (array_key_exists($mappings[$firstUniqueKey]['salsifyField'], $data)) {
212
            $firstUniqueValue = $data[$mappings[$firstUniqueKey]['salsifyField']];
213
        } else {
214
            $firstUniqueValue = 'NULL';
215
        }
216
        ImportTask::output("Updating $class $firstUniqueKey $firstUniqueValue");
217
218
        if (
219
            !$forceUpdate &&
220
            $this->objectUpToDate($object, $data, $firstUniqueKey, $firstUniqueValue, $salsifyRelations)
221
        ) {
222
            return $object;
223
        }
224
225
        foreach ($this->yieldKeyVal($mappings) as $dbField => $salsifyField) {
226
            $field = $this->getField($salsifyField, $data);
227
            if ($field === false) {
228
                $this->clearValue($object, $dbField, $salsifyField);
229
                continue;
230
            }
231
232
            $type = $this->getFieldType($salsifyField);
233
            // skip all but salsify relations types if not doing relations
234
            if ($salsifyRelations && !$this->typeRequiresSalsifyObjects($type)) {
235
                continue;
236
            }
237
238
            // skip salsify relations types if not doing relations
239
            if (!$salsifyRelations && $this->typeRequiresSalsifyObjects($type)) {
240
                continue;
241
            }
242
243
            $value = null;
244
            $objectData = $data;
0 ignored issues
show
Unused Code introduced by
The assignment to $objectData is dead and can be removed.
Loading history...
245
246
            if ($this->handleShouldSkip($class, $dbField, $salsifyField, $data)) {
247
                if (!$this->skipSilently) {
248
                    ImportTask::output("Skipping $class $firstUniqueKey $firstUniqueValue");
249
                    $this->skipSilently = false;
250
                }
251
                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...
252
            };
253
254
            $objectData = $this->handleModification($type, $class, $dbField, $salsifyField, $data);
255
            $sortColumn = $this->getSortColumn($salsifyField);
256
257
            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...
258
                continue;
259
            }
260
261
            $value = $this->handleType($type, $class, $objectData, $field, $salsifyField, $dbField);
262
            $this->writeValue($object, $type, $dbField, $value, $sortColumn);
263
        }
264
265
        if (!$this->isMapperHashUpToDate($object, $salsifyRelations)) {
266
            $this->updateMappingHash($object, $salsifyRelations);
267
        }
268
269
        $this->extend('beforeObjectWrite', $object);
270
        if ($object->isChanged()) {
271
            $object->write();
272
            $this->importCount++;
273
            $this->extend('afterObjectWrite', $object, $wasWritten, $wasPublished);
274
        } else {
275
            ImportTask::output("$class $firstUniqueKey $firstUniqueValue was not changed.");
276
        }
277
        return $object;
278
    }
279
280
    /**
281
     * @param DataObject $object
282
     * @param array $data
283
     * @param string $firstUniqueKey
284
     * @param string $firstUniqueValue
285
     * @param bool $salsifyRelations
286
     * @return bool
287
     */
288
    private function objectUpToDate($object, $data, $firstUniqueKey, $firstUniqueValue, $salsifyRelations = false)
289
    {
290
        if ($this->config()->get('skipUpToDate') == false) {
291
            return false;
292
        }
293
294
        if (!$this->isMapperHashUpToDate($object, $salsifyRelations)) {
295
            ImportTask::output("Forcing update for $firstUniqueKey $firstUniqueValue. Mappings changed.");
296
            return false;
297
        }
298
299
        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...
300
            if ($this->objectDataUpToDate($object, $data, $firstUniqueKey, $firstUniqueValue)) {
301
                ImportTask::output("Skipping $firstUniqueKey $firstUniqueValue. It is up to Date.");
302
                return true;
303
            }
304
        } else {
305
            if ($this->objectRelationsUpToDate($object, $data, $firstUniqueKey, $firstUniqueValue)) {
306
                ImportTask::output("Skipping $firstUniqueKey $firstUniqueValue relations. It is up to Date.");
307
                return true;
308
            }
309
        }
310
311
        return false;
312
    }
313
314
    /**
315
     * @param DataObject $object
316
     * @param array $data
317
     * @param string $firstUniqueKey
318
     * @param string $firstUniqueValue
319
     * @return bool
320
     */
321
    public function objectDataUpToDate($object, $data, $firstUniqueKey, $firstUniqueValue)
322
    {
323
        // assume not up to date if field does not exist on object
324
        if (!$object->hasField('SalsifyUpdatedAt')) {
325
            return false;
326
        }
327
328
        if ($data['salsify:updated_at'] != $object->getField('SalsifyUpdatedAt')) {
329
            return false;
330
        }
331
332
        return true;
333
    }
334
335
    /**
336
     * @param DataObject $object
337
     * @param array $data
338
     * @param string $firstUniqueKey
339
     * @param string $firstUniqueValue
340
     * @return bool
341
     */
342
    public function objectRelationsUpToDate($object, $data, $firstUniqueKey, $firstUniqueValue)
343
    {
344
        // assume not up to date if field does not exist on object
345
        if (!$object->hasField('SalsifyRelationsUpdatedAt')) {
346
            return false;
347
        }
348
349
        // relations were never updated, so its up to date
350
        if (!isset($data['salsify:relations_updated_at'])) {
351
            return true;
352
        }
353
354
        if ($data['salsify:relations_updated_at'] != $object->getField('SalsifyRelationsUpdatedAt')) {
355
            return false;
356
        }
357
358
        return true;
359
    }
360
361
    /**
362
     * @param array $salsifyField
363
     * @param array $data
364
     *
365
     * @return string|false
366
     */
367
    private function getField($salsifyField, $data)
368
    {
369
        if (!is_array($salsifyField)) {
0 ignored issues
show
introduced by
The condition is_array($salsifyField) is always true.
Loading history...
370
            return array_key_exists($salsifyField, $data) ? $salsifyField : false;
371
        }
372
373
        $hasSalsifyField = array_key_exists('salsifyField', $salsifyField);
374
        $isLiteralField = (
375
            $this->getFieldType($salsifyField) === 'Literal' &&
0 ignored issues
show
introduced by
The condition $this->getFieldType($salsifyField) === 'Literal' is always false.
Loading history...
376
            array_key_exists('value', $salsifyField)
377
        );
378
        $isSalsifyRelationField = (
379
            $this->getFieldType($salsifyField) === 'SalsifyRelation' &&
0 ignored issues
show
introduced by
The condition $this->getFieldType($sal...) === 'SalsifyRelation' is always false.
Loading history...
380
            $hasSalsifyField
381
        );
382
383
        if ($isLiteralField) {
0 ignored issues
show
introduced by
The condition $isLiteralField is always false.
Loading history...
384
            return $salsifyField['value'];
385
        }
386
387
        if ($isSalsifyRelationField) {
0 ignored issues
show
introduced by
The condition $isSalsifyRelationField is always false.
Loading history...
388
            return $salsifyField['salsifyField'];
389
        }
390
391
        if (!$hasSalsifyField) {
392
            return false;
393
        }
394
395
        if (array_key_exists($salsifyField['salsifyField'], $data)) {
396
            return $salsifyField['salsifyField'];
397
        } elseif (array_key_exists('fallback', $salsifyField)) {
398
            // make fallback an array
399
            if (!is_array($salsifyField['fallback'])) {
400
                $salsifyField['fallback'] = [$salsifyField['fallback']];
401
            }
402
403
            foreach ($this->yieldSingle($salsifyField['fallback']) as $fallback) {
404
                if (array_key_exists($fallback, $data)) {
405
                    return $fallback;
406
                }
407
            }
408
        } elseif (array_key_exists('modification', $salsifyField)) {
409
            return $salsifyField['salsifyField'];
410
        }
411
412
        return false;
413
    }
414
415
    /**
416
     * @param string $class
417
     * @param array $mappings
418
     * @param array $data
419
     *
420
     * @return array
421
     */
422
    private function getUniqueFilter($class, $mappings, $data)
423
    {
424
        $uniqueFields = $this->uniqueFields($class, $mappings);
425
        // creates a filter
426
        $filter = [];
427
        foreach ($this->yieldKeyVal($uniqueFields) as $dbField => $salsifyField) {
428
            $modifiedData = $data;
429
            $fieldMapping = $mappings[$dbField];
430
            $fieldType = $this->getFieldType($salsifyField);
431
            $modifiedData = $this->handleModification($fieldType, $class, $dbField, $fieldMapping, $modifiedData);
432
433
            // adds unique fields to filter
434
            if (array_key_exists($salsifyField, $modifiedData)) {
435
                $filter[$dbField] = $modifiedData[$salsifyField];
436
            }
437
        }
438
439
        return $filter;
440
    }
441
442
    /**
443
     * @param string $class
444
     * @param array $mappings
445
     * @param array $data
446
     *
447
     * @return \SilverStripe\ORM\DataObject
448
     */
449
    private function findObjectByUnique($class, $mappings, $data)
450
    {
451
        if ($obj = $this->findBySalsifyID($class, $mappings, $data)) {
452
            return $obj;
453
        }
454
455
        $filter = $this->getUniqueFilter($class, $mappings, $data);
456
        return DataObject::get($class)->filter($filter)->first();
457
    }
458
459
    /**
460
     * @param string $class
461
     * @param array $mappings
462
     * @param array $data
463
     *
464
     * @return \SilverStripe\ORM\DataObject|bool
465
     */
466
    private function findBySalsifyID($class, $mappings, $data)
467
    {
468
        /** @var DataObject $genericObject */
469
        $genericObject = Injector::inst()->get($class);
470
        if (
471
            !$genericObject->hasExtension(SalsifyIDExtension::class) &&
472
            !$genericObject->hasField('SalsifyID')
473
        ) {
474
            return false;
475
        }
476
477
        $obj = DataObject::get($class)->filter([
478
            'SalsifyID' => $data['salsify:id'],
479
        ])->first();
480
        if ($obj) {
0 ignored issues
show
introduced by
$obj is of type SilverStripe\ORM\DataObject, thus it always evaluated to true.
Loading history...
481
            return $obj;
482
        }
483
484
        return false;
485
    }
486
487
    /**
488
     * Gets a list of all the unique field keys
489
     *
490
     * @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...
491
     * @param array $mappings
492
     * @return array
493
     */
494
    private function uniqueFields($class, $mappings)
495
    {
496
        // cached after first map
497
        if (array_key_exists($class, $this->currentUniqueFields) && !empty($this->currentUniqueFields[$class])) {
498
            return $this->currentUniqueFields[$class];
499
        }
500
501
        $uniqueFields = [];
502
        foreach ($this->yieldKeyVal($mappings) as $dbField => $salsifyField) {
503
            if (!is_array($salsifyField)) {
504
                continue;
505
            }
506
507
            if (
508
                !array_key_exists('unique', $salsifyField) ||
509
                !array_key_exists('salsifyField', $salsifyField)
510
            ) {
511
                continue;
512
            }
513
514
            if ($salsifyField['unique'] !== true) {
515
                continue;
516
            }
517
518
            $uniqueFields[$dbField] = $salsifyField['salsifyField'];
519
        }
520
521
        $this->currentUniqueFields[$class] = $uniqueFields;
522
        return $uniqueFields;
523
    }
524
525
    /**
526
     * @param array|string $salsifyField
527
     * @return bool|mixed
528
     */
529
    private function getSortColumn($salsifyField)
530
    {
531
        if (!is_array($salsifyField)) {
532
            return false;
533
        }
534
535
        if (array_key_exists('sortColumn', $salsifyField)) {
536
            return $salsifyField['sortColumn'];
537
        }
538
539
        return false;
540
    }
541
542
    /**
543
     * @return bool
544
     */
545
    private function mappingHasSalsifyRelation()
546
    {
547
        foreach ($this->yieldKeyVal($this->config()->get('mapping')) as $class => $mappings) {
548
            if ($this->classConfigHasSalsifyRelation($mappings)) {
549
                return true;
550
            }
551
        }
552
        return false;
553
    }
554
555
    /**
556
     * @param array $classConfig
557
     * @return bool
558
     */
559
    private function classConfigHasSalsifyRelation($classConfig)
560
    {
561
        foreach ($this->yieldKeyVal($classConfig) as $field => $config) {
562
            if (!is_array($config)) {
563
                continue;
564
            }
565
566
            if (!array_key_exists('salsifyField', $config)) {
567
                continue;
568
            }
569
570
            if (!array_key_exists('type', $config)) {
571
                continue;
572
            }
573
574
            if (in_array($config['type'], $this->getFieldsRequiringSalsifyObjects())) {
575
                return true;
576
            }
577
        }
578
        return false;
579
    }
580
581
    /**
582
     * @return array
583
     */
584
    private function getFieldsRequiringSalsifyObjects()
585
    {
586
        $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...
587
        $types = [];
588
        foreach ($this->yieldKeyVal($this->config()->get('field_types')) as $field => $config) {
589
            $type = [
590
                'type' => $field,
591
                'config' => $config,
592
            ];
593
            if ($this->typeRequiresSalsifyObjects($type)) {
594
                $types[] = $field;
595
            }
596
        }
597
598
        return $types;
599
    }
600
601
    /**
602
     * @param array $type
603
     * @return bool
604
     */
605
    private function typeRequiresWrite($type)
606
    {
607
        $config = $type['config'];
608
609
        if (array_key_exists('requiresWrite', $config)) {
610
            return $config['requiresWrite'];
611
        }
612
613
        return false;
614
    }
615
616
    /**
617
     * @param array $type
618
     * @return bool
619
     */
620
    private function typeRequiresSalsifyObjects($type)
621
    {
622
        $config = $type['config'];
623
624
        if (array_key_exists('requiresSalsifyObjects', $config)) {
625
            return $config['requiresSalsifyObjects'];
626
        }
627
628
        return false;
629
    }
630
631
    /**
632
     * @param array $type
633
     * @return string|bool
634
     */
635
    private function typeFallback($type)
636
    {
637
        $config = $type['config'];
638
639
        if (array_key_exists('fallback', $config)) {
640
            return $config['fallback'];
641
        }
642
643
        return false;
644
    }
645
646
    /**
647
     * @param array $type
648
     * @return bool
649
     */
650
    private function canModifyType($type)
651
    {
652
        $config = $type['config'];
653
654
        if (array_key_exists('allowsModification', $config)) {
655
            return $config['allowsModification'];
656
        }
657
658
        return true;
659
    }
660
661
    /**
662
     * @param array $type
663
     * @param string $class
664
     * @param string $dbField
665
     * @param array $config
666
     * @param array $data
667
     * @return array
668
     */
669
    private function handleModification($type, $class, $dbField, $config, $data)
670
    {
671
        if (!is_array($config)) {
0 ignored issues
show
introduced by
The condition is_array($config) is always true.
Loading history...
672
            return $data;
673
        }
674
675
        if (!$this->canModifyType($type)) {
676
            return $data;
677
        }
678
679
        if (array_key_exists('modification', $config)) {
680
            $mod = $config['modification'];
681
            if ($this->hasMethod($mod)) {
682
                return $this->{$mod}($class, $dbField, $config, $data);
683
            }
684
            ImportTask::output("{$mod} is not a valid field modifier. skipping modification for field {$dbField}.");
685
        }
686
        return $data;
687
    }
688
689
    /**
690
     * @param string $class
691
     * @param string $dbField
692
     * @param array $config
693
     * @param array $data
694
     * @return boolean
695
     */
696
    public function handleShouldSkip($class, $dbField, $config, $data)
697
    {
698
        if (!is_array($config)) {
0 ignored issues
show
introduced by
The condition is_array($config) is always true.
Loading history...
699
            return false;
700
        }
701
702
        if (array_key_exists('shouldSkip', $config)) {
703
            $skipMethod = $config['shouldSkip'];
704
            if ($this->hasMethod($skipMethod)) {
705
                return $this->{$skipMethod}($class, $dbField, $config, $data);
706
            }
707
            ImportTask::output(
708
                "{$skipMethod} is not a valid skip test method. Skipping skip test for field {$dbField}."
709
            );
710
        }
711
        return false;
712
    }
713
714
    /**
715
     * @param string|array $field
716
     * @return array
717
     */
718
    public function getFieldType($field)
719
    {
720
        $fieldTypes = $this->config()->get('field_types');
721
        if (is_array($field) && array_key_exists('type', $field)) {
722
            if (array_key_exists($field['type'], $fieldTypes)) {
723
                return [
724
                    'type' => $field['type'],
725
                    'config' => $fieldTypes[$field['type']],
726
                ];
727
            }
728
        }
729
        // default to raw
730
        return [
731
            'type' => 'Raw',
732
            'config' => $fieldTypes['Raw'],
733
        ];
734
    }
735
736
    /**
737
     * @param array $type
738
     * @param string|DataObject $class
739
     * @param array $salsifyData
740
     * @param string $salsifyField
741
     * @param array $dbFieldConfig
742
     * @param string $dbField
743
     *
744
     * @return mixed
745
     */
746
    private function handleType($type, $class, $salsifyData, $salsifyField, $dbFieldConfig, $dbField)
747
    {
748
        $typeName = $type['type'];
749
        $typeConfig = $type['config'];
750
        if ($this->hasMethod("handle{$typeName}Type")) {
751
            return $this->{"handle{$typeName}Type"}($class, $salsifyData, $salsifyField, $dbFieldConfig, $dbField);
752
        }
753
754
        if (array_key_exists('fallback', $typeConfig)) {
755
            $fallback = $typeConfig['fallback'];
756
            if ($this->hasMethod("handle{$fallback}Type")) {
757
                return $this->{"handle{$fallback}Type"}($class, $salsifyData, $salsifyField, $dbFieldConfig, $dbField);
758
            }
759
        }
760
761
        ImportTask::output("{$typeName} is not a valid type. skipping field {$dbField}.");
762
        return '';
763
    }
764
765
    /**
766
     * @param DataObject $object
767
     * @param array $type
768
     * @param string $dbField
769
     * @param mixed $value
770
     * @param string|bool $sortColumn
771
     *
772
     * @throws \Exception
773
     */
774
    private function writeValue($object, $type, $dbField, $value, $sortColumn)
775
    {
776
        $isManyRelation = array_key_exists($dbField, $object->config()->get('has_many')) ||
777
            array_key_exists($dbField, $object->config()->get('many_many')) ||
778
            array_key_exists($dbField, $object->config()->get('belongs_many_many'));
779
780
        $isSingleRelation = array_key_exists(rtrim($dbField, 'ID'), $object->config()->get('has_one'));
781
782
        // write the object so relations can be written
783
        if ($this->typeRequiresWrite($type) && !$object->exists()) {
784
            $this->extend('beforeObjectWrite', $object);
785
            $object->write();
786
        }
787
788
        if (!$isManyRelation) {
789
            if (!$isSingleRelation || ($isSingleRelation && $value !== false)) {
790
                $object->$dbField = $value;
791
            }
792
            return;
793
        }
794
795
        // change to an array and filter out empty values
796
        if (!is_array($value)) {
797
            $value = [$value];
798
        }
799
        $value = array_filter($value);
800
801
        // don't try to write an empty set
802
        if (!count($value)) {
803
            return;
804
        }
805
806
        $this->removeUnrelated($object, $dbField, $value);
807
        $this->writeManyRelation($object, $dbField, $value, $sortColumn);
808
    }
809
810
    /**
811
     * @param DataObject $object
812
     * @param string $dbField
813
     * @param array $value
814
     * @param string|bool $sortColumn
815
     *
816
     * @throws \Exception
817
     */
818
    private function writeManyRelation($object, $dbField, $value, $sortColumn)
819
    {
820
        /** @var DataList|HasManyList|ManyManyList $relation */
821
        $relation = $object->{$dbField}();
822
823
        if ($sortColumn && $relation instanceof ManyManyList) {
824
            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...
825
                $relation->add($value[$i], [$sortColumn => $i]);
826
            }
827
            return;
828
        }
829
830
        // HasManyList, so it exists on the value
831
        if ($sortColumn) {
832
            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...
833
                $value[$i]->{$sortColumn} = $i;
834
                $relation->add($value[$i]);
835
            }
836
            return;
837
        }
838
839
        $relation->addMany($value);
840
    }
841
842
    /**
843
     * Removes unrelated objects in the relation that were previously related
844
     * @param DataObject $object
845
     * @param string $dbField
846
     * @param array $value
847
     */
848
    private function removeUnrelated($object, $dbField, $value)
849
    {
850
        $ids = [];
851
        foreach ($value as $v) {
852
            $ids[] = $v->ID;
853
        }
854
855
        /** @var DataList $relation */
856
        $relation = $object->{$dbField}();
857
        // remove all unrelated - removeAll had an odd side effect (relations only got added back half the time)
858
        if (!empty($ids)) {
859
            $relation->removeMany(
860
                $relation->exclude([
861
                    'ID' => $ids,
862
                ])->column('ID')
863
            );
864
        }
865
    }
866
867
    /**
868
     * @param string|array $salsifyField
869
     * @throws Exception
870
     */
871
    private function clearValue($object, $dbField, $salsifyField)
872
    {
873
        if (
874
            is_array($salsifyField) &&
875
            array_key_exists('keepExistingValue', $salsifyField) &&
876
            $salsifyField['keepExistingValue']
877
        ) {
878
            return;
879
        }
880
881
        $type = [
882
            'type' => 'null',
883
            'config' => [],
884
        ];
885
886
        // clear any existing value
887
        $this->writeValue($object, $type, $dbField, null, null);
888
    }
889
890
    /**
891
     * @return \JsonMachine\JsonMachine
892
     */
893
    public function getAssetStream()
894
    {
895
        return $this->assetStream;
896
    }
897
898
    /**
899
     * @return bool
900
     */
901
    public function hasFile()
902
    {
903
        return $this->file !== null;
904
    }
905
906
    /**
907
     * @param DataObject|SalsifyIDExtension $object
908
     * @return bool
909
     */
910
    private function isMapperHashUpToDate($object, $relations)
911
    {
912
        $filter = [
913
            'MapperService' => $this->importerKey,
914
            'ForRelations' => $relations,
915
        ];
916
        /** @var MapperHash $hash */
917
        if ($hash = $object->MapperHashes()->filter($filter)->first()) {
918
            if ($hash->MapperHash != $this->getMappingHash()) {
919
                return false;
920
            }
921
        } else {
922
            return false;
923
        }
924
925
        return true;
926
    }
927
}
928