Test Failed
Push — master ( 1fd3d4...509deb )
by Terzi
03:45
created

Saver::isMediaFile()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 3
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 2

Importance

Changes 0
Metric Value
cc 1
eloc 1
nc 1
nop 1
dl 0
loc 3
ccs 0
cts 0
cp 0
crap 2
rs 10
c 0
b 0
f 0
1
<?php
2
3
namespace Terranet\Administrator\Services;
4
5
use Illuminate\Database\Eloquent\Model;
0 ignored issues
show
Bug introduced by
The type Illuminate\Database\Eloquent\Model 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...
6
use Spatie\MediaLibrary\HasMedia\HasMedia;
0 ignored issues
show
Bug introduced by
The type Spatie\MediaLibrary\HasMedia\HasMedia 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...
7
use Terranet\Administrator\Contracts\Services\Saver as SaverContract;
8
use Terranet\Administrator\Field\BelongsTo;
9
use Terranet\Administrator\Field\BelongsToMany;
10
use Terranet\Administrator\Field\Boolean;
11
use Terranet\Administrator\Field\File;
12
use Terranet\Administrator\Field\HasMany;
13
use Terranet\Administrator\Field\HasOne;
14
use Terranet\Administrator\Field\Id;
15
use Terranet\Administrator\Field\Image;
16
use Terranet\Administrator\Field\Media;
17
use Terranet\Administrator\Field\Traits\HandlesRelation;
18
use Terranet\Administrator\Requests\UpdateRequest;
19
use Terranet\Translatable\Translatable;
20
use function admin\db\scheme;
21
22
class Saver implements SaverContract
23
{
24
    use HandlesRelation;
0 ignored issues
show
introduced by
The trait Terranet\Administrator\F...\Traits\HandlesRelation requires some properties which are not provided by Terranet\Administrator\Services\Saver: $model, $id
Loading history...
25
26
    /**
27
     * Data collected during saving process.
28
     *
29
     * @var array
30
     */
31
    protected $data = [];
32
33
    /**
34
     * List of relations queued for saving.
35
     *
36
     * @var array
37
     */
38
    protected $relations = [];
39
40
    /**
41
     * Main module repository.
42
     *
43
     * @var Model
44
     */
45
    protected $repository;
46
47
    /**
48
     * @var UpdateRequest
49
     */
50
    protected $request;
51
52
    /**
53
     * Saver constructor.
54
     *
55
     * @param               $eloquent
56
     * @param  UpdateRequest  $request
57
     */
58
    public function __construct($eloquent, UpdateRequest $request)
59
    {
60
        $this->repository = $eloquent;
61
        $this->request = $request;
62
    }
63
64
    /**
65
     * Process request and persist data.
66
     *
67
     * @return mixed
68
     */
69
    public function sync()
70
    {
71
        $this->connection()->transaction(function () {
72
            foreach ($this->editable() as $field) {
73
                // get original HTML input
74
                $name = $field->id();
75
76
                if ($this->isKey($field) || $this->isMediaFile($field) || $this->isTranslatable($field)) {
77
                    continue;
78
                }
79
80
                if ($this->isRelation($field)) {
81
                    $this->relations[$name] = $field;
82
                }
83
84
                $value = $this->isFile($field) ? $this->request->file($name) : $this->request->get($name);
85
86
                $value = $this->isBoolean($field) ? (bool) $value : $value;
87
88
                $value = $this->handleJsonType($name, $value);
89
90
                $this->data[$name] = $value;
91
            }
92
93
            $this->cleanData();
94
95
            $this->collectTranslatable();
96
97
            $this->appendTranslationsToRelations();
98
99
            Model::unguard();
100
101
            $this->process();
102
103
            Model::reguard();
104
        });
105
106
        return $this->repository;
107
    }
108
109
    /**
110
     * Fetch editable fields.
111
     *
112
     * @return mixed
113
     */
114
    protected function editable()
115
    {
116
        return app('scaffold.module')->form();
0 ignored issues
show
Bug introduced by
The function app was not found. Maybe you did not declare it correctly or list all dependencies? ( Ignorable by Annotation )

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

116
        return /** @scrutinizer ignore-call */ app('scaffold.module')->form();
Loading history...
117
    }
118
119
    /**
120
     * @param $field
121
     * @return bool
122
     */
123
    protected function isKey($field)
124
    {
125
        return $field instanceof Id;
126
    }
127
128
    /**
129
     * @param $field
130
     * @return bool
131
     */
132
    protected function isFile($field)
133
    {
134
        return $field instanceof File || $field instanceof Image;
135
    }
136
137
    /**
138
     * Protect request data against external data.
139
     */
140
    protected function cleanData()
141
    {
142
        $this->data = array_except($this->data, [
0 ignored issues
show
Bug introduced by
The function array_except was not found. Maybe you did not declare it correctly or list all dependencies? ( Ignorable by Annotation )

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

142
        $this->data = /** @scrutinizer ignore-call */ array_except($this->data, [
Loading history...
143
            '_token',
144
            'save',
145
            'save_create',
146
            'save_return',
147
            $this->repository->getKeyName(),
148
        ]);
149
150
        // clean from relationable fields.
151
        $this->data = array_diff_key($this->data, $this->relations);
152
    }
153
154
    /**
155
     * Persist main eloquent model including relations & media.
156
     */
157
    protected function process()
158
    {
159
        $this->nullifyEmptyNullables($this->repository->getTable());
160
161
        \DB::transaction(function () {
162
            $this->repository->fill(
163
                $this->protectAgainstNullPassword()
164
            )->save();
165
166
            $this->saveRelations();
167
168
            $this->saveMedia();
169
        });
170
    }
171
172
    /**
173
     * Save relations.
174
     */
175
    protected function saveRelations()
176
    {
177
        foreach ($this->relations as $name => $field) {
178
            $relation = \call_user_func([$this->repository, $name]);
179
180
            switch (\get_class($field)) {
181
                case BelongsTo::class:
182
                    // @var \Illuminate\Database\Eloquent\Relations\BelongsTo $relation
183
                    $relation->associate(
184
                        $this->request->get($this->getForeignKey($relation))
185
                    );
186
187
                    break;
188
                case HasOne::class:
189
                    /** @var \Illuminate\Database\Eloquent\Relations\HasOne $relation */
190
                    $related = $relation->getResults();
191
192
                    $related && $related->exists
193
                        ? $related->update($this->request->get($name))
194
                        : $this->repository->{$name}()->create($this->request->get($name));
195
196
                    break;
197
                case BelongsToMany::class:
198
                    $values = array_map('intval', $this->request->get($name, []));
199
                    $relation->sync($values);
200
201
                    break;
202
                default:
203
                    break;
204
            }
205
        }
206
    }
207
208
    /**
209
     * Process Media.
210
     */
211
    protected function saveMedia()
212
    {
213
        if ($this->repository instanceof HasMedia) {
214
            $media = (array) $this->request['_media_'];
215
216
            if (!empty($trash = array_get($media, '_trash_', []))) {
0 ignored issues
show
Bug introduced by
The function array_get was not found. Maybe you did not declare it correctly or list all dependencies? ( Ignorable by Annotation )

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

216
            if (!empty($trash = /** @scrutinizer ignore-call */ array_get($media, '_trash_', []))) {
Loading history...
217
                $this->repository->media()->whereIn(
218
                    'id',
219
                    $trash
220
                )->delete();
221
            }
222
223
            foreach (array_except($media, '_trash_') as $collection => $objects) {
0 ignored issues
show
Bug introduced by
The function array_except was not found. Maybe you did not declare it correctly or list all dependencies? ( Ignorable by Annotation )

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

223
            foreach (/** @scrutinizer ignore-call */ array_except($media, '_trash_') as $collection => $objects) {
Loading history...
224
                foreach ($objects as $uploadedFile) {
225
                    $this->repository->addMedia($uploadedFile)->toMediaCollection($collection);
226
                }
227
            }
228
        }
229
    }
230
231
    /**
232
     * Remove null values from data.
233
     *
234
     * @param $relation
235
     * @param $values
236
     * @return array
237
     */
238
    protected function forgetNullValues($relation, $values)
239
    {
240
        $keys = explode('.', $this->getQualifiedRelatedKeyName($relation));
0 ignored issues
show
Bug introduced by
The method getQualifiedRelatedKeyName() does not exist on Terranet\Administrator\Services\Saver. ( Ignorable by Annotation )

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

240
        $keys = explode('.', $this->/** @scrutinizer ignore-call */ getQualifiedRelatedKeyName($relation));

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...
241
        $key = array_pop($keys);
242
243
        return array_filter((array) $values[$key], function ($value) {
244
            return null !== $value;
245
        });
246
    }
247
248
    /**
249
     * Collect relations for saving.
250
     *
251
     * @param $field
252
     */
253
    protected function isRelation($field)
254
    {
255
        return ($field instanceof BelongsTo)
256
            || ($field instanceof HasOne)
257
            || ($field instanceof HasMany)
258
            || ($field instanceof BelongsToMany);
259
    }
260
261
    /**
262
     * @param $this
263
     */
264
    protected function appendTranslationsToRelations()
265
    {
266
        if (!empty($this->relations)) {
267
            foreach (array_keys((array) $this->relations) as $relation) {
268
                if ($translations = $this->input("{$relation}.translatable")) {
269
                    $this->relations[$relation] += $translations;
270
                }
271
            }
272
        }
273
    }
274
275
    /**
276
     * @param $name
277
     * @param $value
278
     * @return mixed
279
     */
280
    protected function handleJsonType($name, $value)
281
    {
282
        if ($cast = array_get($this->repository->getCasts(), $name)) {
0 ignored issues
show
Bug introduced by
The function array_get was not found. Maybe you did not declare it correctly or list all dependencies? ( Ignorable by Annotation )

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

282
        if ($cast = /** @scrutinizer ignore-call */ array_get($this->repository->getCasts(), $name)) {
Loading history...
283
            if (\in_array($cast, ['array', 'json'], true)) {
284
                $value = json_decode($value);
285
            }
286
        }
287
288
        return $value;
289
    }
290
291
    /**
292
     * Collect translations.
293
     */
294
    protected function collectTranslatable()
295
    {
296
        foreach ($this->request->get('translatable', []) as $key => $value) {
297
            $this->data[$key] = $value;
298
        }
299
    }
300
301
    /**
302
     * Get database connection.
303
     *
304
     * @return \Illuminate\Foundation\Application|mixed
0 ignored issues
show
Bug introduced by
The type Illuminate\Foundation\Application 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...
305
     */
306
    protected function connection()
307
    {
308
        return app('db');
0 ignored issues
show
Bug introduced by
The function app was not found. Maybe you did not declare it correctly or list all dependencies? ( Ignorable by Annotation )

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

308
        return /** @scrutinizer ignore-call */ app('db');
Loading history...
309
    }
310
311
    /**
312
     * Retrieve request input value.
313
     *
314
     * @param $key
315
     * @param  null  $default
0 ignored issues
show
Documentation Bug introduced by
Are you sure the doc-type for parameter $default is correct as it would always require null to be passed?
Loading history...
316
     * @return mixed
317
     */
318
    protected function input($key, $default = null)
319
    {
320
        return app('request')->input($key, $default);
0 ignored issues
show
Bug introduced by
The function app was not found. Maybe you did not declare it correctly or list all dependencies? ( Ignorable by Annotation )

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

320
        return /** @scrutinizer ignore-call */ app('request')->input($key, $default);
Loading history...
321
    }
322
323
    /**
324
     * Set empty "nullable" values to null.
325
     *
326
     * @param $table
327
     */
328
    protected function nullifyEmptyNullables($table)
329
    {
330
        $columns = scheme()->columns($table);
331
332
        foreach ($this->data as $key => &$value) {
333
            if (!array_key_exists($key, $columns)) {
334
                continue;
335
            }
336
337
            if (!$columns[$key]->getNotnull() && empty($value)) {
338
                $value = null;
339
            }
340
        }
341
    }
342
343
    /**
344
     * Ignore empty password from being saved.
345
     *
346
     * @return array
347
     */
348
    protected function protectAgainstNullPassword(): array
349
    {
350
        if (array_has($this->data, 'password') && empty($this->data['password'])) {
0 ignored issues
show
Bug introduced by
The function array_has was not found. Maybe you did not declare it correctly or list all dependencies? ( Ignorable by Annotation )

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

350
        if (/** @scrutinizer ignore-call */ array_has($this->data, 'password') && empty($this->data['password'])) {
Loading history...
351
            unset($this->data['password']);
352
        }
353
354
        return $this->data;
355
    }
356
357
    /**
358
     * @param $field
359
     * @return bool
360
     */
361
    protected function isBoolean($field)
362
    {
363
        return $field instanceof Boolean;
364
    }
365
366
    /**
367
     * @param $field
368
     * @return bool
369
     */
370
    protected function isMediaFile($field)
371
    {
372
        return $field instanceof Media;
373
    }
374
375
    /**
376
     * @param $field
377
     * @return bool
378
     */
379
    protected function isTranslatable($field)
380
    {
381
        return $field instanceof Translatable;
382
    }
383
}
384