ModuleRepository   F
last analyzed

Complexity

Total Complexity 139

Size/Duplication

Total Lines 940
Duplicated Lines 0 %

Test Coverage

Coverage 64.04%

Importance

Changes 3
Bugs 0 Features 1
Metric Value
eloc 309
c 3
b 0
f 1
dl 0
loc 940
ccs 228
cts 356
cp 0.6404
rs 2
wmc 139

50 Methods

Rating   Name   Duplication   Size   Complexity  
A listAll() 0 19 5
A updateOrCreate() 0 9 2
A firstOrCreate() 0 3 1
A update() 0 15 1
A duplicate() 0 23 3
A delete() 0 14 4
A create() 0 19 1
A getById() 0 3 1
A getCountForTrash() 0 4 1
A createForPreview() 0 7 1
A setNewOrder() 0 5 1
A cmsSearch() 0 17 3
A updateBasic() 0 40 5
A getCountForDraft() 0 4 1
B getCountByStatusSlug() 0 22 7
A getCountForAll() 0 4 1
A get() 0 16 4
A getCountForPublished() 0 4 1
A afterRestore() 0 4 2
C cleanupFields() 0 34 14
A bulkDelete() 0 17 3
A shouldIgnoreFieldBeforeSave() 0 3 1
A bulkForceDelete() 0 19 2
A addIgnoreFieldsBeforeSave() 0 5 2
A bulkRestore() 0 19 2
A prepareFieldsBeforeCreate() 0 9 2
A getLikeOperator() 0 7 2
A isUniqueFeature() 0 3 1
A __call() 0 3 1
A updateOneToMany() 0 14 5
A getFormFields() 0 9 2
A hasBehavior() 0 9 3
A afterUpdateBasic() 0 4 2
A beforeSave() 0 4 2
A updateMultiSelect() 0 3 1
A isTranslatable() 0 3 1
A addLikeFilterScope() 0 5 3
A forceDelete() 0 11 2
A getReservedFields() 0 7 1
A afterDelete() 0 4 2
A prepareFieldsBeforeSave() 0 9 2
B getModelRepository() 0 36 10
A afterSave() 0 4 2
A traitsMethods() 0 14 1
A addRelationFilterScope() 0 8 2
C filter() 0 32 12
A restore() 0 11 2
A searchIn() 0 8 4
A hydrate() 0 7 2
A order() 0 11 3

How to fix   Complexity   

Complex Class

Complex classes like ModuleRepository often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use ModuleRepository, and based on these observations, apply Extract Interface, too.

1
<?php
2
3
namespace A17\Twill\Repositories;
4
5
use A17\Twill\Models\Behaviors\Sortable;
6
use A17\Twill\Models\Model;
7
use A17\Twill\Repositories\Behaviors\HandleBrowsers;
8
use A17\Twill\Repositories\Behaviors\HandleDates;
9
use A17\Twill\Repositories\Behaviors\HandleFieldsGroups;
10
use A17\Twill\Repositories\Behaviors\HandleRelatedBrowsers;
11
use A17\Twill\Repositories\Behaviors\HandleRepeaters;
12
use A17\Twill\Services\Capsules\HasCapsules;
13
use Illuminate\Database\Eloquent\Relations\Relation;
14
use Illuminate\Support\Arr;
15
use Illuminate\Support\Collection;
16
use Illuminate\Support\Facades\App;
17
use Illuminate\Support\Facades\Config;
18
use Illuminate\Support\Facades\DB;
19
use Illuminate\Support\Facades\Log;
20
use Illuminate\Support\Str;
21
use PDO;
22
23
abstract class ModuleRepository
24
{
25
    use HandleDates, HandleBrowsers, HandleRelatedBrowsers, HandleRepeaters, HandleFieldsGroups, HasCapsules;
0 ignored issues
show
Bug introduced by
The trait A17\Twill\Repositories\Behaviors\HandleRepeaters requires the property $id which is not provided by A17\Twill\Repositories\ModuleRepository.
Loading history...
introduced by
The trait A17\Twill\Repositories\Behaviors\HandleBrowsers requires some properties which are not provided by A17\Twill\Repositories\ModuleRepository: $id, $titleInBrowser, $adminEditUrl
Loading history...
introduced by
The trait A17\Twill\Services\Capsules\HasCapsules requires some properties which are not provided by A17\Twill\Repositories\ModuleRepository: $app, $command
Loading history...
26
27
    /**
28
     * @var \A17\Twill\Models\Model
29
     */
30
    protected $model;
31
32
    /**
33
     * @var string[]
34
     */
35
    protected $ignoreFieldsBeforeSave = [];
36
37
    /**
38
     * @var array
39
     */
40
    protected $countScope = [];
41
42
    /**
43
     * @var array
44
     */
45
    protected $fieldsGroups = [];
46
47
    /**
48
     * @var bool
49
     */
50
    public $fieldsGroupsFormFieldNamesAutoPrefix = false;
51
52
    /**
53
     * @var string|null
54 12
     */
55
    public $fieldsGroupsFormFieldNameSeparator = '_';
56 12
57
    /**
58 12
     * @param array $with
59 12
     * @param array $scopes
60
     * @param array $orders
61 12
     * @param int $perPage
62 3
     * @param bool $forcePagination
63
     * @return \Illuminate\Database\Eloquent\Collection
64
     */
65 10
    public function get($with = [], $scopes = [], $orders = [], $perPage = 20, $forcePagination = false)
66
    {
67
        $query = $this->model->with($with);
68
69 10
        $query = $this->filter($query, $scopes);
0 ignored issues
show
Bug introduced by
$query of type Illuminate\Database\Eloquent\Builder is incompatible with the type Illuminate\Database\Query\Builder expected by parameter $query of A17\Twill\Repositories\ModuleRepository::filter(). ( Ignorable by Annotation )

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

69
        $query = $this->filter(/** @scrutinizer ignore-type */ $query, $scopes);
Loading history...
70
        $query = $this->order($query, $orders);
71
72
        if (!$forcePagination && $this->model instanceof Sortable) {
73
            return $query->ordered()->get();
74
        }
75
76
        if ($perPage == -1) {
77 33
            return $query->get();
78
        }
79 33
80
        return $query->paginate($perPage);
81 33
    }
82 33
83 32
    /**
84 6
     * @param string $slug
85 6
     * @param array $scope
86 6
     * @return int
87 6
     */
88 6
    public function getCountByStatusSlug($slug, $scope = [])
89 6
    {
90
        $this->countScope = $scope;
91
92 5
        switch ($slug) {
93 5
            case 'all':
94 5
                return $this->getCountForAll();
95
            case 'published':
96
                return $this->getCountForPublished();
97
            case 'draft':
98
                return $this->getCountForDraft();
99
            case 'trash':
100
                return $this->getCountForTrash();
101
        }
102
103
        foreach ($this->traitsMethods(__FUNCTION__) as $method) {
104 32
            if (($count = $this->$method($slug)) !== false) {
105
                return $count;
106 32
            }
107 32
        }
108
109
        return 0;
110
    }
111
112
    /**
113 5
     * @return int
114
     */
115 5
    public function getCountForAll()
116 5
    {
117
        $query = $this->model->newQuery();
118
        return $this->filter($query, $this->countScope)->count();
0 ignored issues
show
Bug introduced by
$query of type Illuminate\Database\Eloquent\Builder is incompatible with the type Illuminate\Database\Query\Builder expected by parameter $query of A17\Twill\Repositories\ModuleRepository::filter(). ( Ignorable by Annotation )

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

118
        return $this->filter(/** @scrutinizer ignore-type */ $query, $this->countScope)->count();
Loading history...
119
    }
120
121
    /**
122 5
     * @return int
123
     */
124 5
    public function getCountForPublished()
125 5
    {
126
        $query = $this->model->newQuery();
127
        return $this->filter($query, $this->countScope)->published()->count();
0 ignored issues
show
Bug introduced by
$query of type Illuminate\Database\Eloquent\Builder is incompatible with the type Illuminate\Database\Query\Builder expected by parameter $query of A17\Twill\Repositories\ModuleRepository::filter(). ( Ignorable by Annotation )

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

127
        return $this->filter(/** @scrutinizer ignore-type */ $query, $this->countScope)->published()->count();
Loading history...
128
    }
129
130
    /**
131 5
     * @return int
132
     */
133 5
    public function getCountForDraft()
134 5
    {
135
        $query = $this->model->newQuery();
136
        return $this->filter($query, $this->countScope)->draft()->count();
0 ignored issues
show
Bug introduced by
$query of type Illuminate\Database\Eloquent\Builder is incompatible with the type Illuminate\Database\Query\Builder expected by parameter $query of A17\Twill\Repositories\ModuleRepository::filter(). ( Ignorable by Annotation )

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

136
        return $this->filter(/** @scrutinizer ignore-type */ $query, $this->countScope)->draft()->count();
Loading history...
137
    }
138
139
    /**
140
     * @return int
141
     */
142
    public function getCountForTrash()
143
    {
144 18
        $query = $this->model->newQuery();
145
        return $this->filter($query, $this->countScope)->onlyTrashed()->count();
0 ignored issues
show
Bug introduced by
$query of type Illuminate\Database\Eloquent\Builder is incompatible with the type Illuminate\Database\Query\Builder expected by parameter $query of A17\Twill\Repositories\ModuleRepository::filter(). ( Ignorable by Annotation )

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

145
        return $this->filter(/** @scrutinizer ignore-type */ $query, $this->countScope)->onlyTrashed()->count();
Loading history...
146 18
    }
147
148
    /**
149
     * @param $id
150
     * @param array $with
151
     * @param array $withCount
152
     * @return \A17\Twill\Models\Model
153
     * @throws \Illuminate\Database\Eloquent\ModelNotFoundException
154
     */
155
    public function getById($id, $with = [], $withCount = [])
156
    {
157
        return $this->model->with($with)->withCount($withCount)->findOrFail($id);
158
    }
159
160
    /**
161
     * @param string $column
162
     * @param array $orders
163
     * @param null $exceptId
0 ignored issues
show
Documentation Bug introduced by
Are you sure the doc-type for parameter $exceptId is correct as it would always require null to be passed?
Loading history...
164
     * @return \Illuminate\Support\Collection
165
     */
166
    public function listAll($column = 'title', $orders = [], $exceptId = null)
167
    {
168
        $query = $this->model->newQuery();
169
170
        if ($exceptId) {
0 ignored issues
show
introduced by
$exceptId is of type null, thus it always evaluated to false.
Loading history...
171
            $query = $query->where($this->model->getTable() . '.id', '<>', $exceptId);
172
        }
173
174
        if ($this->model instanceof Sortable) {
175
            $query = $query->ordered();
176
        } elseif (!empty($orders)) {
177
            $query = $this->order($query, $orders);
0 ignored issues
show
Bug introduced by
$query of type Illuminate\Database\Eloquent\Builder is incompatible with the type Illuminate\Database\Query\Builder expected by parameter $query of A17\Twill\Repositories\ModuleRepository::order(). ( Ignorable by Annotation )

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

177
            $query = $this->order(/** @scrutinizer ignore-type */ $query, $orders);
Loading history...
178
        }
179
180
        if ($this->model->isTranslatable()) {
181 1
            $query = $query->withTranslation();
182
        }
183 1
184
        return $query->get()->pluck($column, 'id');
185 1
    }
186
187 1
    /**
188 1
     * @param $search
189 1
     * @param array $fields
190 1
     * @return \Illuminate\Database\Eloquent\Collection
191 1
     */
192
    public function cmsSearch($search, $fields = [])
193
    {
194
        $query = $this->model->latest();
195
196
        $translatedAttributes = $this->model->translatedAttributes ?? [];
0 ignored issues
show
Bug introduced by
The property translatedAttributes does not seem to exist on A17\Twill\Models\Model. Are you sure there is no database migration missing?

Checks if undeclared accessed properties appear in database migrations and if the creating migration is correct.

Loading history...
197 1
198
        foreach ($fields as $field) {
199
            if (in_array($field, $translatedAttributes)) {
200
                $query->orWhereHas('translations', function ($q) use ($field, $search) {
201
                    $q->where($field, $this->getLikeOperator(), "%{$search}%");
202
                });
203
            } else {
204
                $query->orWhere($field, $this->getLikeOperator(), "%{$search}%");
205
            }
206
        }
207
208
        return $query->get();
209
    }
210
211
    /**
212
     * @param $attributes
213
     * @param $fields
214 31
     * @return \A17\Twill\Models\Model
215
     */
216 31
    public function firstOrCreate($attributes, $fields = [])
217 31
    {
218
        return $this->model->where($attributes)->first() ?? $this->create($attributes + $fields);
219 31
    }
220
221 31
    /**
222
     * @param string[] $fields
223 31
     * @return \A17\Twill\Models\Model
224
     */
225 31
    public function create($fields)
226
    {
227 31
        return DB::transaction(function () use ($fields) {
228
            $original_fields = $fields;
229 31
230
            $fields = $this->prepareFieldsBeforeCreate($fields);
231 31
232 31
            $object = $this->model->create(Arr::except($fields, $this->getReservedFields()));
233
234
            $this->beforeSave($object, $original_fields);
235
236
            $fields = $this->prepareFieldsBeforeSave($object, $fields);
237
238
            $object->save();
239 2
240
            $this->afterSave($object, $fields);
241 2
242
            return $object;
243 2
        }, 3);
244
    }
245 2
246
    /**
247
     * @param array $fields
248
     * @return \A17\Twill\Models\Model
249
     */
250
    public function createForPreview($fields)
251
    {
252
        $fields = $this->prepareFieldsBeforeCreate($fields);
253
254
        $object = $this->model->newInstance(Arr::except($fields, $this->getReservedFields()));
255
256
        return $this->hydrate($object, $fields);
257
    }
258
259
    /**
260
     * @param array $attributes
261
     * @param array $fields
262
     * @return \A17\Twill\Models\Model
263
     */
264
    public function updateOrCreate($attributes, $fields)
265
    {
266
        $object = $this->model->where($attributes)->first();
267
268
        if (!$object) {
269 12
            return $this->create($fields);
270
        }
271 12
272 12
        $this->update($object->id, $fields);
273
    }
274 12
275
    /**
276 12
     * @param mixed $id
277
     * @param array $fields
278 12
     * @return void
279
     */
280 12
    public function update($id, $fields)
281
    {
282 12
        DB::transaction(function () use ($id, $fields) {
283 12
            $object = $this->model->findOrFail($id);
284 12
285
            $this->beforeSave($object, $fields);
286
287
            $fields = $this->prepareFieldsBeforeSave($object, $fields);
288
289
            $object->fill(Arr::except($fields, $this->getReservedFields()));
290
291
            $object->save();
292 4
293
            $this->afterSave($object, $fields);
294 4
        }, 3);
295
    }
296 4
297 1
    /**
298
     * @param mixed $id
299 1
     * @param array $values
300
     * @param array $scopes
301
     * @return mixed
302
     */
303 1
    public function updateBasic($id, $values, $scopes = [])
304
    {
305 1
        return DB::transaction(function () use ($id, $values, $scopes) {
306
            // apply scopes if no id provided
307 1
            if (is_null($id)) {
308
                $query = $this->model->query();
309 1
310
                foreach ($scopes as $column => $value) {
311
                    $query->where($column, $value);
312
                }
313 3
314
                $query->update($values);
315
316
                $query->get()->each(function ($object) use ($values) {
317
                    $this->afterUpdateBasic($object, $values);
318
                });
319
320
                return true;
321
            }
322
323
            // apply to all ids if array of ids provided
324 3
            if (is_array($id)) {
325 2
                $query = $this->model->whereIn('id', $id);
326 2
                $query->update($values);
327 2
328
                $query->get()->each(function ($object) use ($values) {
329
                    $this->afterUpdateBasic($object, $values);
330 1
                });
331 4
332
                return true;
333
            }
334
335
            if (($object = $this->model->find($id)) != null) {
336
                $object->update($values);
337
                $this->afterUpdateBasic($object, $values);
338 2
                return true;
339
            }
340 2
341 2
            return false;
342 2
        }, 3);
343 1
    }
344
345
    /**
346
     * @param array $ids
347
     * @return void
348
     */
349
    public function setNewOrder($ids)
350
    {
351
        DB::transaction(function () use ($ids) {
352
            $this->model->setNewOrder($ids);
353
        }, 3);
354
    }
355
356
    /**
357
     * @param mixed $id
358
     * @return mixed
359
     */
360
    public function duplicate($id, $titleColumnKey = 'title')
361
    {
362
363
        if (($object = $this->model->find($id)) === null) {
364
            return false;
365
        }
366
367
        if (($revision = $object->revisions()->orderBy('created_at', 'desc')->first()) === null) {
368
            return false;
369
        }
370
371
        $revisionInput = json_decode($revision->payload, true);
372
        $baseInput = collect($revisionInput)->only([
373
            $titleColumnKey,
374
            'slug',
375
            'languages',
376
        ])->filter()->toArray();
377
378 2
        $newObject = $this->create($baseInput);
379
380 2
        $this->update($newObject->id, $revisionInput);
381 2
382
        return $newObject;
383
    }
384
385 2
    /**
386 2
     * @param mixed $id
387 2
     * @return mixed
388 2
     */
389
    public function delete($id)
390
    {
391 2
        return DB::transaction(function () use ($id) {
392
            if (($object = $this->model->find($id)) === null) {
393
                return false;
394
            }
395
396
            if (!method_exists($object, 'canDeleteSafely') || $object->canDeleteSafely()) {
397
                $object->delete();
398 21
                $this->afterDelete($object);
399
                return true;
400 21
            }
401
            return false;
402 21
        }, 3);
403
    }
404 21
405
    /**
406
     * @param array $ids
407
     * @return mixed
408
     */
409
    public function bulkDelete($ids)
410 21
    {
411 21
        return DB::transaction(function () use ($ids) {
412
            try {
413
                Collection::make($ids)->each(function ($id) {
414
                    $this->delete($id);
415
                });
416
            } catch (\Exception $e) {
417
                Log::error($e);
418
                if (config('app.debug')) {
419
                    throw $e;
420
                }
421
                return false;
422
            }
423
424
            return true;
425
        }, 3);
426
    }
427
428
    /**
429
     * @param mixed $id
430
     * @return mixed
431
     */
432
    public function forceDelete($id)
433
    {
434
        return DB::transaction(function () use ($id) {
435
            if (($object = $this->model->onlyTrashed()->find($id)) === null) {
436
                return false;
437
            } else {
438
                $object->forceDelete();
439
                $this->afterDelete($object);
440
                return true;
441
            }
442
        }, 3);
443
    }
444
445
    /**
446
     * @param mixed $id
447
     * @return mixed
448
     */
449
    public function bulkForceDelete($ids)
450
    {
451
        return DB::transaction(function () use ($ids) {
452
            try {
453
                $query = $this->model->onlyTrashed()->whereIn('id', $ids);
454
                $objects = $query->get();
455
456
                $query->forceDelete();
457
458
                $objects->each(function ($object) {
459
                    $this->afterDelete($object);
460 2
                });
461
            } catch (\Exception $e) {
462 2
                Log::error($e);
463 2
                return false;
464 1
            }
465 1
466 1
            return true;
467
        }, 3);
468
    }
469 1
470 2
    /**
471
     * @param mixed $id
472
     * @return mixed
473
     */
474
    public function restore($id)
475
    {
476
        return DB::transaction(function () use ($id) {
477
            if (($object = $this->model->withTrashed()->find($id)) != null) {
478
                $object->restore();
479
                $this->afterRestore($object);
480
                return true;
481
            }
482
483
            return false;
484
        }, 3);
485
    }
486
487
    /**
488
     * @param array $ids
489
     * @return mixed
490
     */
491
    public function bulkRestore($ids)
492
    {
493
        return DB::transaction(function () use ($ids) {
494
            try {
495
                $query = $this->model->withTrashed()->whereIn('id', $ids);
496
                $objects = $query->get();
497
498
                $query->restore();
499
500
                $objects->each(function ($object) {
501
                    $this->afterRestore($object);
502
                });
503 32
            } catch (\Exception $e) {
504
                Log::error($e);
505 32
                return false;
506 25
            }
507 25
508 25
            return true;
509 5
        }, 3);
510
    }
511 25
512
    /**
513
     * @param \A17\Twill\Models\Model $object
514
     * @param array $fields
515
     * @return array
516
     */
517 32
    public function cleanupFields($object, $fields)
518
    {
519
        if (property_exists($this->model, 'checkboxes')) {
520
            foreach ($this->model->checkboxes as $field) {
0 ignored issues
show
Bug introduced by
The property checkboxes does not seem to exist on A17\Twill\Models\Model. Are you sure there is no database migration missing?

Checks if undeclared accessed properties appear in database migrations and if the creating migration is correct.

Loading history...
521
                if (!$this->shouldIgnoreFieldBeforeSave($field)) {
522
                    if (!isset($fields[$field])) {
523
                        $fields[$field] = false;
524
                    } else {
525 32
                        $fields[$field] = !empty($fields[$field]);
526 32
                    }
527 32
                }
528 8
            }
529
        }
530 32
531
        if (property_exists($this->model, 'nullable')) {
532
            foreach ($this->model->nullable as $field) {
0 ignored issues
show
Bug introduced by
The property nullable does not seem to exist on A17\Twill\Models\Model. Are you sure there is no database migration missing?

Checks if undeclared accessed properties appear in database migrations and if the creating migration is correct.

Loading history...
533
                if (!isset($fields[$field]) && !$this->shouldIgnoreFieldBeforeSave($field)) {
534
                    $fields[$field] = null;
535
                }
536 32
            }
537
        }
538
539
        foreach ($fields as $key => $value) {
540
            if (!$this->shouldIgnoreFieldBeforeSave($key)) {
541
                if (is_array($value) && empty($value)) {
542
                    $fields[$key] = null;
543 26
                }
544
                if ($value === '') {
545 26
                    $fields[$key] = null;
546
                }
547 26
            }
548 26
        }
549
550
        return $fields;
551 26
    }
552
553
    /**
554
     * @param array $fields
555
     * @return array
556
     */
557
    public function prepareFieldsBeforeCreate($fields)
558
    {
559 31
        $fields = $this->cleanupFields(null, $fields);
560
561 31
        foreach ($this->traitsMethods(__FUNCTION__) as $method) {
562
            $fields = $this->$method($fields);
563 31
        }
564 31
565
        return $fields;
566
    }
567 31
568
    /**
569
     * @param \A17\Twill\Models\Model $object
570
     * @param array $fields
571
     * @return string[]
572
     */
573
    public function prepareFieldsBeforeSave($object, $fields)
574
    {
575 2
        $fields = $this->cleanupFields($object, $fields);
576
577 2
        foreach ($this->traitsMethods(__FUNCTION__) as $method) {
578
            $fields = $this->$method($object, $fields);
579
        }
580 2
581
        return $fields;
582
    }
583
584
    /**
585
     * @param \A17\Twill\Models\Model $object
586
     * @param array $fields
587 31
     * @return void
588
     */
589 31
    public function afterUpdateBasic($object, $fields)
590 21
    {
591
        foreach ($this->traitsMethods(__FUNCTION__) as $method) {
592 31
            $this->$method($object, $fields);
593
        }
594
    }
595
596
    /**
597
     * @param \A17\Twill\Models\Model $object
598
     * @param array $fields
599 31
     * @return void
600
     */
601 31
    public function beforeSave($object, $fields)
602 31
    {
603
        foreach ($this->traitsMethods(__FUNCTION__) as $method) {
604 31
            $this->$method($object, $fields);
605
        }
606
    }
607
608
    /**
609
     * @param \A17\Twill\Models\Model $object
610 2
     * @param array $fields
611
     * @return void
612 2
     */
613 2
    public function afterSave($object, $fields)
614
    {
615 2
        foreach ($this->traitsMethods(__FUNCTION__) as $method) {
616
            $this->$method($object, $fields);
617
        }
618
    }
619
620
    /**
621 1
     * @param \A17\Twill\Models\Model $object
622
     * @return void
623 1
     */
624 1
    public function afterDelete($object)
625
    {
626 1
        foreach ($this->traitsMethods(__FUNCTION__) as $method) {
627
            $this->$method($object);
628
        }
629
    }
630
631
    /**
632
     * @param \A17\Twill\Models\Model $object
633 3
     * @return void
634
     */
635 3
    public function afterRestore($object)
636 3
    {
637
        foreach ($this->traitsMethods(__FUNCTION__) as $method) {
638
            $this->$method($object);
639 3
        }
640
    }
641
642
    /**
643
     * @param \A17\Twill\Models\Model $object
644
     * @param array $fields
645
     * @return \A17\Twill\Models\Model
646 6
     */
647
    public function hydrate($object, $fields)
648 6
    {
649
        foreach ($this->traitsMethods(__FUNCTION__) as $method) {
650 6
            $object = $this->$method($object, $fields);
651 6
        }
652
653
        return $object;
654 6
    }
655
656
    /**
657
     * @param \A17\Twill\Models\Model $object
658
     * @return array
659
     */
660
    public function getFormFields($object)
661
    {
662 37
        $fields = $object->attributesToArray();
663
664 37
        foreach ($this->traitsMethods(__FUNCTION__) as $method) {
665
            $fields = $this->$method($object, $fields);
666 37
        }
667 36
668
        return $fields;
669
    }
670 37
671
    /**
672 37
     * @param \Illuminate\Database\Query\Builder $query
673 2
     * @param array $scopes
674 2
     * @return \Illuminate\Database\Query\Builder
675
     */
676
    public function filter($query, array $scopes = [])
677 37
    {
678 5
        $likeOperator = $this->getLikeOperator();
679 1
680
        foreach ($this->traitsMethods(__FUNCTION__) as $method) {
681 4
            $this->$method($query, $scopes);
682 2
        }
683 2
684
        unset($scopes['search']);
685 2
686
        if (isset($scopes['exceptIds'])) {
687 2
            $query->whereNotIn($this->model->getTable() . '.id', $scopes['exceptIds']);
688 2
            unset($scopes['exceptIds']);
689
        }
690
691
        foreach ($scopes as $column => $value) {
692
            if (method_exists($this->model, 'scope' . ucfirst($column))) {
693 37
                $query->$column();
694
            } else {
695
                if (is_array($value)) {
696
                    $query->whereIn($column, $value);
697
                } elseif ($column[0] == '%') {
698
                    $value && ($value[0] == '!') ? $query->where(substr($column, 1), "not $likeOperator", '%' . substr($value, 1) . '%') : $query->where(substr($column, 1), $likeOperator, '%' . $value . '%');
699
                } elseif (isset($value[0]) && $value[0] == '!') {
700
                    $query->where($column, '<>', substr($value, 1));
701 12
                } elseif ($value !== '') {
702
                    $query->where($column, $value);
703 12
                }
704 7
            }
705
        }
706
707 12
        return $query;
708 5
    }
709
710
    /**
711 12
     * @param \Illuminate\Database\Query\Builder $query
712
     * @param array $orders
713
     * @return \Illuminate\Database\Query\Builder
714
     */
715
    public function order($query, array $orders = [])
716
    {
717
        foreach ($this->traitsMethods(__FUNCTION__) as $method) {
718
            $this->$method($query, $orders);
719
        }
720
721
        foreach ($orders as $column => $direction) {
722
            $query->orderBy($column, $direction);
723
        }
724
725
        return $query;
726
    }
727
728
    /**
729
     * @param \A17\Twill\Models\Model $object
730
     * @param array $fields
731
     * @param string $relationship
732
     * @param string $formField
733
     * @param string $attribute
734
     * @return void
735
     */
736
    public function updateOneToMany($object, $fields, $relationship, $formField, $attribute)
737
    {
738
        if (isset($fields[$formField])) {
739
            foreach ($fields[$formField] as $id) {
740
                $object->$relationship()->updateOrCreate([$attribute => $id]);
741
            }
742
743
            foreach ($object->$relationship as $relationshipObject) {
744
                if (!in_array($relationshipObject->$attribute, $fields[$formField])) {
745
                    $relationshipObject->delete();
746
                }
747
            }
748
        } else {
749
            $object->$relationship()->delete();
750
        }
751
    }
752
753
    /**
754
     * @param \A17\Twill\Models\Model $object
755
     * @param array $fields
756
     * @param string $relationship
757 36
     * @return void
758
     */
759 36
    public function updateMultiSelect($object, $fields, $relationship)
760
    {
761
        $object->$relationship()->sync($fields[$relationship] ?? []);
762
    }
763
764
    /**
765
     * @param \Illuminate\Database\Query\Builder $query
766 36
     * @param array $scopes
767
     * @param string $scopeField
768
     * @param string $scopeRelation
769
     * @return void
770
     */
771
    public function addRelationFilterScope($query, &$scopes, $scopeField, $scopeRelation)
772
    {
773
        if (isset($scopes[$scopeField])) {
774
            $id = $scopes[$scopeField];
775
            $query->whereHas($scopeRelation, function ($query) use ($id, $scopeField) {
776
                $query->where($scopeField, $id);
777
            });
778
            unset($scopes[$scopeField]);
779
        }
780
    }
781
782
    /**
783
     * @param \Illuminate\Database\Query\Builder $query
784
     * @param array $scopes
785
     * @param string $scopeField
786
     * @return void
787
     */
788 5
    public function addLikeFilterScope($query, &$scopes, $scopeField)
789
    {
790
        if (isset($scopes[$scopeField]) && is_string($scopes[$scopeField])) {
791 5
            $query->where($scopeField, $this->getLikeOperator(), '%' . $scopes[$scopeField] . '%');
792 2
            unset($scopes[$scopeField]);
793 2
        }
794 2
    }
795 2
796
    /**
797 2
     * @param \Illuminate\Database\Query\Builder $query
798
     * @param array $scopes
799 5
     * @param string $scopeField
800
     * @param string[] $orFields
801
     */
802
    public function searchIn($query, &$scopes, $scopeField, $orFields = [])
803
    {
804 2
805
        if (isset($scopes[$scopeField]) && is_string($scopes[$scopeField])) {
806 2
            $query->where(function ($query) use (&$scopes, $scopeField, $orFields) {
807
                foreach ($orFields as $field) {
808
                    $query->orWhere($field, $this->getLikeOperator(), '%' . $scopes[$scopeField] . '%');
809
                    unset($scopes[$field]);
810
                }
811
            });
812
        }
813
    }
814
815
    /**
816
     * @return bool
817
     */
818
    public function isUniqueFeature()
819
    {
820
        return false;
821
    }
822
823
    /**
824 32
     * @param array $ignore
825
     * @return void
826 32
     */
827
    public function addIgnoreFieldsBeforeSave($ignore = [])
828
    {
829
        $this->ignoreFieldsBeforeSave = is_array($ignore)
0 ignored issues
show
introduced by
The condition is_array($ignore) is always true.
Loading history...
830
        ? array_merge($this->ignoreFieldsBeforeSave, $ignore)
831
        : array_merge($this->ignoreFieldsBeforeSave, [$ignore]);
832 32
    }
833
834
    /**
835 32
     * @param string $ignore
836
     * @return bool
837
     */
838
    public function shouldIgnoreFieldBeforeSave($ignore)
839
    {
840
        return in_array($ignore, $this->ignoreFieldsBeforeSave);
841
    }
842
843
    /**
844
     * @return string[]
845
     */
846
    public function getReservedFields()
847
    {
848
        return [
849
            'medias',
850
            'browsers',
851
            'repeaters',
852
            'blocks',
853
        ];
854
    }
855
856
    /**
857
     * @param string $relation
858
     * @param \A17\Twill\Models\Model|\A17\Twill\Repositories\ModuleRepository|null $modelOrRepository
859
     * @return mixed
860
     */
861
    protected function getModelRepository($relation, $modelOrRepository = null)
862
    {
863
        if (!$modelOrRepository) {
864
            if (class_exists($relation) && (new $relation) instanceof Model) {
865
                $modelOrRepository = Str::afterLast($relation, '\\');
866
            } else {
867
                $morphedModel = Relation::getMorphedModel($relation);
868
                if (class_exists($morphedModel) && (new $morphedModel) instanceof Model) {
869 46
                    $modelOrRepository = (new \ReflectionClass($morphedModel))->getShortName();
870
                } else {
871 46
                    $modelOrRepository = ucfirst(Str::singular($relation));
872
                }
873 46
            }
874
        }
875 46
876
        $repository = class_exists($modelOrRepository)
0 ignored issues
show
Bug introduced by
It seems like $modelOrRepository can also be of type A17\Twill\Repositories\ModuleRepository and null; however, parameter $class of class_exists() does only seem to accept string, maybe add an additional type check? ( Ignorable by Annotation )

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

876
        $repository = class_exists(/** @scrutinizer ignore-type */ $modelOrRepository)
Loading history...
877 46
        ? App::make($modelOrRepository)
878 46
        : $modelOrRepository;
879 46
880
        if ($repository instanceof ModuleRepository) {
881 46
            return $repository;
882 46
        } else {
883 46
            $class = Config::get('twill.namespace') . "\\Repositories\\" . ucfirst($modelOrRepository) . "Repository";
0 ignored issues
show
Bug introduced by
It seems like $modelOrRepository can also be of type A17\Twill\Repositories\ModuleRepository and null; however, parameter $string of ucfirst() does only seem to accept string, maybe add an additional type check? ( Ignorable by Annotation )

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

883
            $class = Config::get('twill.namespace') . "\\Repositories\\" . ucfirst(/** @scrutinizer ignore-type */ $modelOrRepository) . "Repository";
Loading history...
884
        }
885
886
        if (class_exists($class)) {
887
            return App::make($class);
888
        }
889 37
890
        $capsule = $this->getCapsuleByModel($modelOrRepository);
891 37
892
        if (blank($capsule)) {
893
            throw new \Exception("Repository class not found for model '{$modelOrRepository}'");
894
        }
895 37
896
        return App::make($capsule['repository']);
897
    }
898
899
    /**
900
     * @param string|null $method
901
     * @return array
902
     */
903 31
    protected function traitsMethods(string $method = null)
904
    {
905 31
        $method = $method ?? debug_backtrace()[1]['function'];
906
907
        $traits = array_values(class_uses_recursive(get_called_class()));
908
909
        $uniqueTraits = array_unique(array_map('class_basename', $traits));
910
911
        $methods = array_map(function (string $trait) use ($method) {
912 35
            return $method . $trait;
913
        }, $uniqueTraits);
914 35
915
        return array_filter($methods, function (string $method) {
916 35
            return method_exists(get_called_class(), $method);
917 32
        });
918
    }
919
920 35
    /**
921
     * @return string
922
     */
923
    protected function getLikeOperator()
924
    {
925
        if (DB::connection()->getPDO()->getAttribute(PDO::ATTR_DRIVER_NAME) === 'pgsql') {
926 11
            return 'ILIKE';
927
        }
928 11
929
        return 'LIKE';
930
    }
931
932
    /**
933
     * @param string $method
934
     * @param array $parameters
935
     * @return mixed
936
     */
937
    public function __call($method, $parameters)
938
    {
939
        return $this->model->$method(...$parameters);
940
    }
941
942
    /**
943
     * @param string $behavior
944
     * @return boolean
945
     */
946
    public function hasBehavior($behavior)
947
    {
948
        $hasBehavior = classHasTrait($this, 'A17\Twill\Repositories\Behaviors\Handle' . ucfirst($behavior));
949
950
        if (Str::startsWith($behavior, 'translation')) {
951
            $hasBehavior = $hasBehavior && $this->model->isTranslatable();
952
        }
953
954
        return $hasBehavior;
955
    }
956
957
    /**
958
     * @return boolean
959
     */
960
    public function isTranslatable($column)
961
    {
962
        return $this->model->isTranslatable($column);
963
    }
964
}
965