Passed
Pull Request — master (#70)
by Matthew
06:37
created

Mapper::removeUnrelated()   A

Complexity

Conditions 3
Paths 4

Size

Total Lines 15
Code Lines 9

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 1 Features 0
Metric Value
eloc 9
c 1
b 1
f 0
dl 0
loc 15
rs 9.9666
cc 3
nc 4
nop 3
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
        if ($file !== null) {
79
            $this->file = $file;
80
            $this->resetProductStream();
81
            $this->resetAssetStream();
82
        }
83
    }
84
85
    /**
86
     * Generates the current hash for the mapping
87
     *
88
     * @return string
89
     */
90
    private function getMappingHash()
91
    {
92
        return md5(serialize($this->config()->get('mapping')));
93
    }
94
95
    /**
96
     * Updates the mapping hash for the mapper
97
     * @param DataObject|SalsifyIDExtension $object
98
     * @param bool $relations
99
     */
100
    private function updateMappingHash($object, $relations)
101
    {
102
        $filter = [
103
            'MapperService' => $this->importerKey,
104
            'ForRelations' => $relations,
105
        ];
106
        /** @var MapperHash $mapperHash */
107
        $mapperHash = $object->MapperHashes()->filter($filter)->first();
108
        if ($mapperHash == null) {
109
            $mapperHash = MapperHash::create();
110
            $mapperHash->MappedObjectID = $object->ID;
1 ignored issue
show
Bug introduced by
The property ID does not seem to exist on Dynamic\Salsify\ORM\SalsifyIDExtension.
Loading history...
111
            $mapperHash->MappedObjectClass = $object->ClassName;
1 ignored issue
show
Bug introduced by
The property ClassName does not seem to exist on Dynamic\Salsify\ORM\SalsifyIDExtension.
Loading history...
112
            $mapperHash->MapperService = $this->importerKey;
113
            $mapperHash->ForRelations = $relations;
114
        }
115
116
        $mapperHash->MapperHash = $this->getMappingHash();
117
        $mapperHash->write();
118
    }
119
120
    /**
121
     *
122
     */
123
    public function resetProductStream()
124
    {
125
        $this->productStream = JsonMachine::fromFile($this->file, '/4/products');
126
    }
127
128
    /**
129
     *
130
     */
131
    public function resetAssetStream()
132
    {
133
        $this->assetStream = JsonMachine::fromFile($this->file, '/3/digital_assets');
134
    }
135
136
    /**
137
     * Maps the data
138
     * @throws \Exception
139
     */
140
    public function map()
141
    {
142
        $this->extend('onBeforeMap', $this->file, Mapper::$MULTIPLE);
143
144
        foreach ($this->yieldKeyVal($this->productStream) as $name => $data) {
145
            foreach ($this->yieldKeyVal($this->config()->get('mapping')) as $class => $mappings) {
146
                $this->mapToObject($class, $mappings, $data);
147
                $this->currentUniqueFields = [];
148
            }
149
        }
150
151
        if ($this->mappingHasSalsifyRelation()) {
152
            ImportTask::output("----------------");
153
            ImportTask::output("Setting up salsify relations");
154
            ImportTask::output("----------------");
155
            $this->resetProductStream();
156
157
            foreach ($this->yieldKeyVal($this->productStream) as $name => $data) {
158
                foreach ($this->yieldKeyVal($this->config()->get('mapping')) as $class => $mappings) {
159
                    $this->mapToObject($class, $mappings, $data, null, true);
160
                    $this->currentUniqueFields = [];
161
                }
162
            }
163
        }
164
165
        ImportTask::output("Imported and updated $this->importCount products.");
166
        $this->extend('onAfterMap', $this->file, Mapper::$MULTIPLE);
167
    }
168
169
    /**
170
     * @param string|DataObject $class
171
     * @param array $mappings The mapping for a specific class
172
     * @param array $data
173
     * @param DataObject|null $object
174
     * @param bool $salsifyRelations
175
     * @param bool $forceUpdate
176
     *
177
     * @return DataObject|null
178
     * @throws \Exception
179
     */
180
    public function mapToObject(
181
        $class,
182
        $mappings,
183
        $data,
184
        $object = null,
185
        $salsifyRelations = false,
186
        $forceUpdate = false
187
    ) {
188
        if ($salsifyRelations) {
189
            if (!$this->classConfigHasSalsifyRelation($mappings)) {
190
                return null;
191
            }
192
        }
193
194
        // if object was not passed
195
        if ($object === null) {
196
            $object = $this->findObjectByUnique($class, $mappings, $data);
197
198
            $filter = $this->getUniqueFilter($class, $mappings, $data);
199
            if (count(array_filter($filter)) == 0) {
200
                return null;
201
            }
202
203
            // if no existing object was found but a unique filter is valid (not empty)
204
            if (!$object) {
0 ignored issues
show
introduced by
$object is of type SilverStripe\ORM\DataObject, thus it always evaluated to true.
Loading history...
205
                $object = $class::create();
206
            }
207
        }
208
209
        $wasPublished = $object->hasExtension(Versioned::class) ? $object->isPublished() : false;
210
        $wasWritten = $object->isInDB();
211
212
        $firstUniqueKey = array_keys($this->uniqueFields($class, $mappings))[0];
213
        if (array_key_exists($mappings[$firstUniqueKey]['salsifyField'], $data)) {
214
            $firstUniqueValue = $data[$mappings[$firstUniqueKey]['salsifyField']];
215
        } else {
216
            $firstUniqueValue = 'NULL';
217
        }
218
        ImportTask::output("Updating $class $firstUniqueKey $firstUniqueValue");
219
220
        if (
221
            !$forceUpdate &&
222
            $this->objectUpToDate($object, $data, $firstUniqueKey, $firstUniqueValue, $salsifyRelations)
223
        ) {
224
            return $object;
225
        }
226
227
        foreach ($this->yieldKeyVal($mappings) as $dbField => $salsifyField) {
228
            $field = $this->getField($salsifyField, $data);
229
            if ($field === false) {
230
                $this->clearValue($object, $dbField, $salsifyField);
231
                continue;
232
            }
233
234
            $type = $this->getFieldType($salsifyField);
235
            // skip all but salsify relations types if not doing relations
236
            if ($salsifyRelations && !$this->typeRequiresSalsifyObjects($type)) {
237
                continue;
238
            }
239
240
            // skip salsify relations types if not doing relations
241
            if (!$salsifyRelations && $this->typeRequiresSalsifyObjects($type)) {
242
                continue;
243
            }
244
245
            $value = null;
246
            $objectData = $data;
0 ignored issues
show
Unused Code introduced by
The assignment to $objectData is dead and can be removed.
Loading history...
247
248
            if ($this->handleShouldSkip($class, $dbField, $salsifyField, $data)) {
249
                if (!$this->skipSilently) {
250
                    ImportTask::output("Skipping $class $firstUniqueKey $firstUniqueValue");
251
                    $this->skipSilently = false;
252
                }
253
                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...
254
            };
255
256
            $objectData = $this->handleModification($type, $class, $dbField, $salsifyField, $data);
257
            $sortColumn = $this->getSortColumn($salsifyField);
258
259
            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...
260
                continue;
261
            }
262
263
            $value = $this->handleType($type, $class, $objectData, $field, $salsifyField, $dbField);
264
            $this->writeValue($object, $type, $dbField, $value, $sortColumn);
265
        }
266
267
        if (!$this->isMapperHashUpToDate($object, $salsifyRelations)) {
268
            $this->updateMappingHash($object, $salsifyRelations);
269
        }
270
271
        $this->extend('beforeObjectWrite', $object);
272
        if ($object->isChanged()) {
273
            $object->write();
274
            $this->importCount++;
275
            $this->extend('afterObjectWrite', $object, $wasWritten, $wasPublished);
276
        } else {
277
            ImportTask::output("$class $firstUniqueKey $firstUniqueValue was not changed.");
278
        }
279
        return $object;
280
    }
281
282
    /**
283
     * @param DataObject $object
284
     * @param array $data
285
     * @param string $firstUniqueKey
286
     * @param string $firstUniqueValue
287
     * @param bool $salsifyRelations
288
     * @return bool
289
     */
290
    private function objectUpToDate($object, $data, $firstUniqueKey, $firstUniqueValue, $salsifyRelations = false)
291
    {
292
        if ($this->config()->get('skipUpToDate') == false) {
293
            return false;
294
        }
295
296
        if (!$this->isMapperHashUpToDate($object, $salsifyRelations)) {
297
            ImportTask::output("Forcing update for $firstUniqueKey $firstUniqueValue. Mappings changed.");
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
            $this->extend('beforeObjectWrite', $object);
787
            $object->write();
788
        }
789
790
        if (!$isManyRelation) {
791
            if (!$isSingleRelation || ($isSingleRelation && $value !== false)) {
792
                $object->$dbField = $value;
793
            }
794
            return;
795
        }
796
797
        // change to an array and filter out empty values
798
        if (!is_array($value)) {
799
            $value = [$value];
800
        }
801
        $value = array_filter($value);
802
803
        // don't try to write an empty set
804
        if (!count($value)) {
805
            return;
806
        }
807
808
        $this->removeUnrelated($object, $dbField, $value);
809
        $this->writeManyRelation($object, $dbField, $value, $sortColumn);
810
    }
811
812
    /**
813
     * @param DataObject $object
814
     * @param string $dbField
815
     * @param array $value
816
     * @param string|bool $sortColumn
817
     *
818
     * @throws \Exception
819
     */
820
    private function writeManyRelation($object, $dbField, $value, $sortColumn)
821
    {
822
        /** @var DataList|HasManyList|ManyManyList $relation */
823
        $relation = $object->{$dbField}();
824
825
        if ($sortColumn && $relation instanceof ManyManyList) {
826
            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...
827
                $relation->add($value[$i], [$sortColumn => $i]);
828
            }
829
            return;
830
        }
831
832
        // HasManyList, so it exists on the value
833
        if ($sortColumn) {
834
            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...
835
                $value[$i]->{$sortColumn} = $i;
836
                $relation->add($value[$i]);
837
            }
838
            return;
839
        }
840
841
        $relation->addMany($value);
842
    }
843
844
    /**
845
     * Removes unrelated objects in the relation that were previously related
846
     * @param DataObject $object
847
     * @param string $dbField
848
     * @param array $value
849
     */
850
    private function removeUnrelated($object, $dbField, $value)
851
    {
852
        $ids = [];
853
        foreach ($value as $v) {
854
            $ids[] = $v->ID;
855
        }
856
857
        /** @var DataList $relation */
858
        $relation = $object->{$dbField}();
859
        // remove all unrelated - removeAll had an odd side effect (relations only got added back half the time)
860
        if (!empty($ids)) {
861
            $relation->removeMany(
862
                $relation->exclude([
863
                    'ID' => $ids,
864
                ])->column('ID')
865
            );
866
        }
867
    }
868
869
    /**
870
     * @param string|array $salsifyField
871
     * @throws Exception
872
     */
873
    private function clearValue($object, $dbField, $salsifyField)
874
    {
875
        if (
876
            is_array($salsifyField) &&
877
            array_key_exists('keepExistingValue', $salsifyField) &&
878
            $salsifyField['keepExistingValue']
879
        ) {
880
            return;
881
        }
882
883
        $type = [
884
            'type' => 'null',
885
            'config' => [],
886
        ];
887
888
        // clear any existing value
889
        $this->writeValue($object, $type, $dbField, null, null);
890
    }
891
892
    /**
893
     * @return \JsonMachine\JsonMachine
894
     */
895
    public function getAssetStream()
896
    {
897
        return $this->assetStream;
898
    }
899
900
    /**
901
     * @return bool
902
     */
903
    public function hasFile()
904
    {
905
        return $this->file !== null;
906
    }
907
908
    /**
909
     * @param DataObject|SalsifyIDExtension $object
910
     * @return bool
911
     */
912
    private function isMapperHashUpToDate($object, $relations)
913
    {
914
        $filter = [
915
            'MapperService' => $this->importerKey,
916
            'ForRelations' => $relations,
917
        ];
918
        /** @var MapperHash $hash */
919
        if ($hash = $object->MapperHashes()->filter($filter)->first()) {
920
            if ($hash->MapperHash != $this->getMappingHash()) {
921
                return false;
922
            }
923
        } else {
924
            return false;
925
        }
926
927
        return true;
928
    }
929
}
930