Passed
Pull Request — master (#11)
by Matthew
05:18 queued 25s
created

Mapper::createFile()   A

Complexity

Conditions 4
Paths 3

Size

Total Lines 16
Code Lines 9

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 4
eloc 9
nc 3
nop 1
dl 0
loc 16
rs 9.9666
c 0
b 0
f 0
1
<?php
2
3
namespace Dynamic\Salsify\Model;
4
5
use Dynamic\Salsify\Task\ImportTask;
6
use JsonMachine\JsonMachine;
7
use SilverStripe\Assets\File;
8
use SilverStripe\Assets\Image;
9
use SilverStripe\Core\Config\Configurable;
10
use SilverStripe\Core\Extensible;
11
use SilverStripe\Core\Injector\Injectable;
12
use SilverStripe\ORM\DataObject;
13
14
/**
15
 * Class Mapper
16
 * @package Dynamic\Salsify\Model
17
 */
18
class Mapper
19
{
20
    use Configurable;
21
    use Extensible;
22
    use Injectable;
23
24
    /**
25
     * @var array
26
     */
27
    private static $field_types = [
28
        'RAW' => '0',
29
        'FILE' => '1',
30
        'IMAGE' => '2',
31
    ];
32
33
    /**
34
     * @var
35
     */
36
    private $file;
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
     * Mapper constructor.
60
     * @param $file
61
     */
62
    public function __construct($file)
63
    {
64
        $this->file = $file;
65
        $this->productStream = JsonMachine::fromFile($file, '/4/products');
66
        $this->resetAssetStream();
67
    }
68
69
    /**
70
     * Maps the data
71
     */
72
    public function map()
73
    {
74
        foreach ($this->productStream as $name => $data) {
75
            foreach ($this->config()->get('mapping') as $class => $mappings) {
76
                $this->mapToObject($class, $mappings, $data);
77
                $this->currentUniqueFields = [];
78
            }
79
        }
80
        ImportTask::echo("Imported and updated $this->importCount products.");
81
    }
82
83
    /**
84
     * @param string $class
85
     * @param array $mappings
86
     * @param array $data
87
     */
88
    private function mapToObject($class, $mappings, $data)
89
    {
90
        $object = $this->findObjectByUnique($class, $mappings, $data);
91
        if (!$object) {
0 ignored issues
show
introduced by
$object is of type SilverStripe\ORM\DataObject, thus it always evaluated to true.
Loading history...
92
            $object = $class::create();
93
        }
94
95
        $fieldTypes = $this->config()->get('field_types');
96
97
        $firstUniqueKey = array_keys($this->uniqueFields($class, $mappings))[0];
98
        $firstUniqueValue = $data[$mappings[$firstUniqueKey]['salsifyField']];
99
        ImportTask::echo("Updating $firstUniqueKey $firstUniqueValue");
100
101
        foreach ($mappings as $dbField => $salsifyField) {
102
            $type = $this->config()->get('field_types')['RAW'];
103
            if (is_array($salsifyField)) {
104
                if (!array_key_exists('salsifyField', $salsifyField)) {
105
                    continue;
106
                }
107
108
                if (array_key_exists('type', $salsifyField)) {
109
                    if (array_key_exists($salsifyField['type'], $fieldTypes)) {
110
                        $type = $fieldTypes[$salsifyField['type']];
111
                    }
112
                }
113
114
                $salsifyField = $salsifyField['salsifyField'];
115
            }
116
117
            if (!array_key_exists($salsifyField, $data)) {
118
                ImportTask::echo("Skipping mapping for field $salsifyField for $firstUniqueKey $firstUniqueValue");
119
                continue;
120
            }
121
122
            $object->$dbField = $this->handleType($type, $data[$salsifyField], $dbField);
123
        }
124
125
        if ($object->isChanged()) {
126
            $object->write();
127
            $this->importCount++;
128
        } else {
129
            ImportTask::echo("$firstUniqueKey $firstUniqueValue was not changed.");
130
        }
131
    }
132
133
    /**
134
     * @param string $class
135
     * @param array $mappings
136
     * @param array $data
137
     *
138
     * @return \SilverStripe\ORM\DataObject
139
     */
140
    private function findObjectByUnique($class, $mappings, $data)
141
    {
142
        $uniqueFields = $this->uniqueFields($class, $mappings);
143
        $filter = [];
144
        foreach ($uniqueFields as $dbField => $salsifyField) {
145
            $filter[$dbField] = $data[$salsifyField];
146
        }
147
148
        return DataObject::get($class)->filter($filter)->first();
149
    }
150
151
    /**
152
     * @param string $class
153
     * @param array $mappings
154
     * @return array
155
     */
156
    private function uniqueFields($class, $mappings)
0 ignored issues
show
Unused Code introduced by
The parameter $class is not used and could be removed. ( Ignorable by Annotation )

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

156
    private function uniqueFields(/** @scrutinizer ignore-unused */ $class, $mappings)

This check looks for parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
157
    {
158
        if (!empty($this->currentUniqueFields)) {
159
            return $this->currentUniqueFields;
160
        }
161
162
        $uniqueFields = [];
163
        foreach ($mappings as $dbField => $salsifyField) {
164
            if (!is_array($salsifyField)) {
165
                continue;
166
            }
167
168
            if (!array_key_exists('unique', $salsifyField) ||
169
                !array_key_exists('salsifyField', $salsifyField)) {
170
                continue;
171
            }
172
173
            if (!$salsifyField['unique'] == true) {
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...
174
                continue;
175
            }
176
177
            $uniqueFields[$dbField] = $salsifyField['salsifyField'];
178
        }
179
180
        $this->currentUniqueFields = $uniqueFields;
181
        return $uniqueFields;
182
    }
183
184
    /**
185
     * @param int $type
186
     * @param string|int $value
187
     * @param string $dbField
188
     * @return mixed
189
     */
190
    private function handleType($type, $value, $dbField)
191
    {
192
        $fieldTypes = $this->config()->get('field_types');
193
        switch ($type) {
194
            case $fieldTypes['RAW']:
195
                return $value;
196
197
            case $fieldTypes['FILE']:
198
                if ($asset = $this->createFile($this->getAssetBySalsifyID($value))) {
0 ignored issues
show
Coding Style Comprehensibility introduced by
Consider adding a comment if this fall-through is intended.
Loading history...
199
                    return preg_match('/ID$/', $dbField) ? $asset->ID : $asset;
200
                }
201
202
            case $fieldTypes['IMAGE']:
203
                $asset = $this->getAssetBySalsifyID($value);
204
205
                $file = $this->findOrCreateFile($asset['salsify:id'], Image::class);
206
                if ($file->SalsifyUpdatedAt && $file->SalsifyUpdatedAt == $asset['salsify:updated_at']) {
207
                    return preg_match('/ID$/', $dbField) ? $file->ID : $file;
0 ignored issues
show
Bug introduced by
The property ID does not seem to exist on Dyanmic\Salsify\ORM\FileExtension.
Loading history...
208
                }
209
210
                $url = $asset['salsify:url'];
211
                $name = $asset['salsify:name'];
212
                $supportedImageExtensions = Image::get_category_extensions(
213
                    Image::singleton()->File->getAllowedCategories()
214
                );
215
216
                if (!in_array(pathinfo($asset['salsify:url'])['extension'], $supportedImageExtensions)) {
217
                    $url = str_replace(
218
                        '.' . pathinfo($asset['salsify:url'])['extension'],
219
                        '.png',
220
                        $asset['salsify:url']
221
                    );
222
                }
223
224
                if (!in_array(pathinfo($asset['salsify:name'])['extension'], $supportedImageExtensions)) {
225
                    $name = str_replace(
226
                        '.' . pathinfo($asset['salsify:name'])['extension'],
227
                        '.png',
228
                        $asset['salsify:name']
229
                    );
230
                }
231
232
                $file->SalsifyUpdatedAt = $asset['salsify:updated_at'];
233
                $file->setFromStream(fopen($url, 'r'), $name);
0 ignored issues
show
Bug introduced by
The method setFromStream() does not exist on Dyanmic\Salsify\ORM\FileExtension. ( Ignorable by Annotation )

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

233
                $file->/** @scrutinizer ignore-call */ 
234
                       setFromStream(fopen($url, 'r'), $name);

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

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

Loading history...
234
                $file->write();
0 ignored issues
show
Bug introduced by
The method write() does not exist on Dyanmic\Salsify\ORM\FileExtension. ( Ignorable by Annotation )

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

234
                $file->/** @scrutinizer ignore-call */ 
235
                       write();

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

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

Loading history...
235
                return preg_match('/ID$/', $dbField) ? $file->ID : $file;
236
        }
237
        return '';
238
    }
239
240
    /**
241
     * @param $id
242
     * @return array|bool
243
     */
244
    private function getAssetBySalsifyID($id)
245
    {
246
        if (is_array($id)) {
247
            $id = $id[count($id) - 1];
248
        }
249
250
        $asset = false;
251
        foreach ($this->assetStream as $name => $data) {
252
            if ($data['salsify:id'] == $id) {
253
                $asset = $data;
254
            }
255
        }
256
        $this->resetAssetStream();
257
        return $asset;
258
    }
259
260
    /**
261
     * @param array|bool $assetData
262
     * @param string $class
263
     * @return File|bool
264
     */
265
    private function createFile($assetData)
266
    {
267
        if (!$assetData) {
268
            return false;
269
        }
270
271
        $file = $this->findOrCreateFile($assetData['salsify:id']);
272
        if ($file->SalsifyUpdatedAt && $file->SalsifyUpdatedAt == $assetData['salsify:updated_at']) {
273
            return $file;
0 ignored issues
show
Bug Best Practice introduced by
The expression return $file also could return the type Dyanmic\Salsify\ORM\FileExtension which is incompatible with the documented return type SilverStripe\Assets\File|boolean.
Loading history...
274
        }
275
276
        $file->SalsifyUpdatedAt = $assetData['salsify:updated_at'];
277
        $file->setFromStream(fopen($assetData['salsify:url'], 'r'), $assetData['salsify:name']);
278
279
        $file->write();
280
        return $file;
0 ignored issues
show
Bug Best Practice introduced by
The expression return $file also could return the type Dyanmic\Salsify\ORM\FileExtension which is incompatible with the documented return type SilverStripe\Assets\File|boolean.
Loading history...
281
    }
282
283
    /**
284
     * @param string $id
285
     * @param string $class
286
     * @return File|\Dyanmic\Salsify\ORM\FileExtension
287
     */
288
    private function findOrCreateFile($id, $class = File::class)
289
    {
290
        /** @var File|\Dyanmic\Salsify\ORM\FileExtension $file */
291
        if ($file = $class::get()->find('SalisfyID', $id)) {
292
            return $file;
293
        }
294
295
        $file = $class::create();
296
        $file->SalisfyID = $id;
297
        return $file;
298
    }
299
300
    /**
301
     *
302
     */
303
    private function resetAssetStream()
304
    {
305
        $this->assetStream = JsonMachine::fromFile($this->file, '/3/digital_assets');
306
    }
307
}
308