Scrutinizer GitHub App not installed

We could not synchronize checks via GitHub's checks API since Scrutinizer's GitHub App is not installed for this repository.

Install GitHub App

Passed
Pull Request — main (#4988)
by Pedro
38:52 queued 24:06
created

Fields::fieldTypeLoaded()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 3
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 1
eloc 1
nc 1
nop 1
dl 0
loc 3
rs 10
c 0
b 0
f 0
1
<?php
2
3
namespace Backpack\CRUD\app\Library\CrudPanel\Traits;
4
5
use Backpack\CRUD\app\Library\CrudPanel\CrudField;
6
use Illuminate\Support\Arr;
7
8
trait Fields
9
{
10
    use FieldsProtectedMethods;
11
    use FieldsPrivateMethods;
12
13
    // ------------
14
    // FIELDS
15
    // ------------
16
17
    /**
18
     * Get the CRUD fields for the current operation with name processed to be usable in HTML.
19
     *
20
     * @return array
21
     */
22
    public function fields()
23
    {
24
        return $this->overwriteFieldNamesFromDotNotationToArray($this->getOperationSetting('fields') ?? []);
0 ignored issues
show
Bug introduced by
It seems like getOperationSetting() must be provided by classes using this trait. How about adding it as abstract method to this trait? ( Ignorable by Annotation )

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

24
        return $this->overwriteFieldNamesFromDotNotationToArray($this->/** @scrutinizer ignore-call */ getOperationSetting('fields') ?? []);
Loading history...
25
    }
26
27
    /**
28
     * Returns the fields as they are stored inside operation setting, not running the
29
     * presentation callbacks like converting the `dot.names` into `dot[names]` for html for example.
30
     */
31
    public function getCleanStateFields()
32
    {
33
        return $this->getOperationSetting('fields') ?? [];
34
    }
35
36
    /**
37
     * The only REALLY MANDATORY attribute when defining a field is the 'name'.
38
     * Everything else Backpack can probably guess. This method makes sure  the
39
     * field definition array is complete, by guessing missing attributes.
40
     *
41
     * @param  string|array  $field  The definition of a field (string or array).
42
     * @return array The correct definition of that field.
43
     */
44
    public function makeSureFieldHasNecessaryAttributes($field)
45
    {
46
        $field = $this->makeSureFieldHasName($field);
47
        $field = $this->makeSureFieldHasEntity($field);
48
        $field = $this->makeSureFieldHasLabel($field);
49
50
        if (isset($field['entity']) && $field['entity'] !== false) {
51
            $field = $this->makeSureFieldHasRelationshipAttributes($field);
52
        }
53
54
        $field = $this->makeSureFieldHasType($field);
55
        $field = $this->makeSureSubfieldsHaveNecessaryAttributes($field);
56
        $field = $this->makeSureMorphSubfieldsAreDefined($field);
0 ignored issues
show
Bug introduced by
The method makeSureMorphSubfieldsAreDefined() does not exist on Backpack\CRUD\app\Library\CrudPanel\Traits\Fields. Did you maybe mean fields()? ( Ignorable by Annotation )

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

56
        /** @scrutinizer ignore-call */ 
57
        $field = $this->makeSureMorphSubfieldsAreDefined($field);

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...
57
58
        $this->setupFieldValidation($field, $field['parentFieldName'] ?? false);
0 ignored issues
show
Bug introduced by
It seems like setupFieldValidation() must be provided by classes using this trait. How about adding it as abstract method to this trait? ( Ignorable by Annotation )

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

58
        $this->/** @scrutinizer ignore-call */ 
59
               setupFieldValidation($field, $field['parentFieldName'] ?? false);
Loading history...
59
60
        return $field;
61
    }
62
63
    /**
64
     * When field is a relationship, Backpack will try to guess some basic attributes from the relation.
65
     *
66
     * @param  array  $field
67
     * @return array
68
     */
69
    public function makeSureFieldHasRelationshipAttributes($field)
70
    {
71
        $field = $this->makeSureFieldHasRelationType($field);
72
        $field = $this->makeSureFieldHasModel($field);
73
        $field = $this->makeSureFieldHasAttribute($field);
74
        $field = $this->makeSureFieldHasMultiple($field);
75
        $field = $this->makeSureFieldHasPivot($field);
76
        $field = $this->makeSureFieldHasType($field);
77
78
        return $field;
79
    }
80
81
    /**
82
     * Register all Eloquent Model events that are defined on fields.
83
     * Eg. saving, saved, creating, created, updating, updated.
84
     *
85
     * @see https://laravel.com/docs/master/eloquent#events
86
     *
87
     * @return void
88
     */
89
    public function registerFieldEvents()
90
    {
91
        foreach ($this->getCleanStateFields() as $key => $field) {
92
            if (isset($field['events'])) {
93
                foreach ($field['events'] as $event => $closure) {
94
                    $this->model->{$event}($closure);
95
                }
96
            }
97
        }
98
    }
99
100
    /**
101
     * Add a field to the create/update form or both.
102
     *
103
     * @param  string|array  $field  The new field.
104
     * @return self
105
     */
106
    public function addField($field)
107
    {
108
        $field = $this->makeSureFieldHasNecessaryAttributes($field);
109
110
        $this->enableTabsIfFieldUsesThem($field);
111
        $this->addFieldToOperationSettings($field);
112
113
        $crudFieldObject = (new CrudField($field['name']));
114
115
        $this->callRegisteredAttributeMacros($crudFieldObject);
0 ignored issues
show
Bug introduced by
It seems like callRegisteredAttributeMacros() must be provided by classes using this trait. How about adding it as abstract method to this trait? ( Ignorable by Annotation )

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

115
        $this->/** @scrutinizer ignore-call */ 
116
               callRegisteredAttributeMacros($crudFieldObject);
Loading history...
116
117
        return $this;
118
    }
119
120
    /**
121
     * Add multiple fields to the create/update form or both.
122
     *
123
     * @param  array  $fields  The new fields.
124
     */
125
    public function addFields($fields)
126
    {
127
        if (count($fields)) {
128
            foreach ($fields as $field) {
129
                $this->addField($field);
130
            }
131
        }
132
    }
133
134
    /**
135
     * Move the most recently added field after the given target field.
136
     *
137
     * @param  string  $targetFieldName  The target field name.
138
     */
139
    public function afterField($targetFieldName)
140
    {
141
        $this->transformFields(function ($fields) use ($targetFieldName) {
142
            return $this->moveField($fields, $targetFieldName, false);
143
        });
144
    }
145
146
    /**
147
     * Move the most recently added field before the given target field.
148
     *
149
     * @param  string  $targetFieldName  The target field name.
150
     */
151
    public function beforeField($targetFieldName)
152
    {
153
        $this->transformFields(function ($fields) use ($targetFieldName) {
154
            return $this->moveField($fields, $targetFieldName, true);
155
        });
156
    }
157
158
    /**
159
     * Move this field to be first in the fields list.
160
     *
161
     * @return bool|null
162
     */
163
    public function makeFirstField()
164
    {
165
        if (! $this->fields()) {
166
            return false;
167
        }
168
169
        $firstField = array_keys(array_slice($this->getCleanStateFields(), 0, 1))[0];
170
        $this->beforeField($firstField);
171
    }
172
173
    /**
174
     * Remove a certain field from the create/update/both forms by its name.
175
     *
176
     * @param  string  $name  Field name (as defined with the addField() procedure)
177
     */
178
    public function removeField($name)
179
    {
180
        $this->transformFields(function ($fields) use ($name) {
181
            Arr::forget($fields, $name);
182
183
            return $fields;
184
        });
185
    }
186
187
    /**
188
     * Remove many fields from the create/update/both forms by their name.
189
     *
190
     * @param  array  $array_of_names  A simple array of the names of the fields to be removed.
191
     */
192
    public function removeFields($array_of_names)
193
    {
194
        if (! empty($array_of_names)) {
195
            foreach ($array_of_names as $name) {
196
                $this->removeField($name);
197
            }
198
        }
199
    }
200
201
    /**
202
     * Remove all fields from the create/update/both forms.
203
     */
204
    public function removeAllFields()
205
    {
206
        $current_fields = $this->getCleanStateFields();
207
        if (! empty($current_fields)) {
208
            foreach ($current_fields as $field) {
209
                $this->removeField($field['name']);
210
            }
211
        }
212
    }
213
214
    /**
215
     * Remove an attribute from one field's definition array.
216
     *
217
     * @param  string  $field  The name of the field.
218
     * @param  string  $attribute  The name of the attribute being removed.
219
     */
220
    public function removeFieldAttribute($field, $attribute)
221
    {
222
        $fields = $this->getCleanStateFields();
223
224
        unset($fields[$field][$attribute]);
225
226
        $this->setOperationSetting('fields', $fields);
0 ignored issues
show
Bug introduced by
It seems like setOperationSetting() must be provided by classes using this trait. How about adding it as abstract method to this trait? ( Ignorable by Annotation )

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

226
        $this->/** @scrutinizer ignore-call */ 
227
               setOperationSetting('fields', $fields);
Loading history...
227
    }
228
229
    /**
230
     * Update value of a given key for a current field.
231
     *
232
     * @param  string  $fieldName  The field name
233
     * @param  array  $modifications  An array of changes to be made.
234
     */
235
    public function modifyField($fieldName, $modifications)
236
    {
237
        $fieldsArray = $this->getCleanStateFields();
238
        $field = $this->firstFieldWhere('name', $fieldName);
239
        $fieldKey = $this->getFieldKey($field);
0 ignored issues
show
Bug introduced by
$field of type boolean is incompatible with the type array expected by parameter $field of Backpack\CRUD\app\Librar...s\Fields::getFieldKey(). ( Ignorable by Annotation )

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

239
        $fieldKey = $this->getFieldKey(/** @scrutinizer ignore-type */ $field);
Loading history...
240
241
        foreach ($modifications as $attributeName => $attributeValue) {
242
            $fieldsArray[$fieldKey][$attributeName] = $attributeValue;
243
        }
244
245
        $this->enableTabsIfFieldUsesThem($modifications);
246
247
        $this->setOperationSetting('fields', $fieldsArray);
248
    }
249
250
    /**
251
     * Set label for a specific field.
252
     *
253
     * @param  string  $field
254
     * @param  string  $label
255
     */
256
    public function setFieldLabel($field, $label)
257
    {
258
        $this->modifyField($field, ['label' => $label]);
259
    }
260
261
    /**
262
     * Check if field is the first of its type in the given fields array.
263
     * It's used in each field_type.blade.php to determine wether to push the css and js content or not (we only need to push the js and css for a field the first time it's loaded in the form, not any subsequent times).
264
     *
265
     * @param  array  $field  The current field being tested if it's the first of its type.
266
     * @return bool true/false
267
     */
268
    public function checkIfFieldIsFirstOfItsType($field)
269
    {
270
        $fields_array = $this->getCleanStateFields();
271
        $first_field = $this->getFirstOfItsTypeInArray($field['type'], $fields_array);
0 ignored issues
show
Bug introduced by
It seems like getFirstOfItsTypeInArray() must be provided by classes using this trait. How about adding it as abstract method to this trait? ( Ignorable by Annotation )

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

271
        /** @scrutinizer ignore-call */ 
272
        $first_field = $this->getFirstOfItsTypeInArray($field['type'], $fields_array);
Loading history...
272
273
        if ($first_field && $field['name'] == $first_field['name']) {
274
            return true;
275
        }
276
277
        return false;
278
    }
279
280
    /**
281
     * Decode attributes that are casted as array/object/json in the model.
282
     * So that they are not json_encoded twice before they are stored in the db
283
     * (once by Backpack in front-end, once by Laravel Attribute Casting).
284
     *
285
     * @param  array  $input
286
     * @param  mixed  $model
287
     * @return array
288
     */
289
    public function decodeJsonCastedAttributes($input, $model = false)
290
    {
291
        $model = $model ? $model : $this->model;
292
        $fields = $this->getCleanStateFields();
293
        $casted_attributes = $model->getCastedAttributes();
294
295
        foreach ($fields as $field) {
296
            // Test the field is castable
297
            if (isset($field['name']) && is_string($field['name']) && array_key_exists($field['name'], $casted_attributes)) {
298
                // Handle JSON field types
299
                $jsonCastables = ['array', 'object', 'json'];
300
                $fieldCasting = $casted_attributes[$field['name']];
301
302
                if (in_array($fieldCasting, $jsonCastables) && isset($input[$field['name']]) && ! empty($input[$field['name']]) && ! is_array($input[$field['name']])) {
303
                    try {
304
                        $input[$field['name']] = json_decode($input[$field['name']]);
305
                    } catch (\Exception $e) {
306
                        $input[$field['name']] = [];
307
                    }
308
                }
309
            }
310
        }
311
312
        return $input;
313
    }
314
315
    /**
316
     * @return array
317
     */
318
    public function getCurrentFields()
319
    {
320
        return $this->fields();
321
    }
322
323
    /**
324
     * Order the CRUD fields. If certain fields are missing from the given order array, they will be
325
     * pushed to the new fields array in the original order.
326
     *
327
     * @param  array  $order  An array of field names in the desired order.
328
     */
329
    public function orderFields($order)
330
    {
331
        $this->transformFields(function ($fields) use ($order) {
332
            return $this->applyOrderToFields($fields, $order);
333
        });
334
    }
335
336
    /**
337
     * Get the fields for the create or update forms.
338
     *
339
     * @return array all the fields that need to be shown and their information
340
     */
341
    public function getFields()
342
    {
343
        return $this->fields();
344
    }
345
346
    /**
347
     * Check if the create/update form has upload fields.
348
     * Upload fields are the ones that have "upload" => true defined on them.
349
     *
350
     * @param  string  $form  create/update/both - defaults to 'both'
351
     * @param  bool|int  $id  id of the entity - defaults to false
352
     * @return bool
353
     */
354
    public function hasUploadFields()
355
    {
356
        $fields = $this->getCleanStateFields();
357
        $upload_fields = Arr::where($fields, function ($value, $key) {
358
            // check if any subfields have uploads
359
            if (isset($value['subfields'])) {
360
                foreach ($value['subfields'] as $subfield) {
361
                    if (isset($subfield['upload']) && $subfield['upload'] === true) {
362
                        return true;
363
                    }
364
                }
365
            }
366
367
            return isset($value['upload']) && $value['upload'] == true;
368
        });
369
370
        return count($upload_fields) ? true : false;
371
    }
372
373
    // ----------------------
374
    // FIELD ASSET MANAGEMENT
375
    // ----------------------
376
377
    /**
378
     * Get all the field types whose resources (JS and CSS) have already been loaded on page.
379
     *
380
     * @return array Array with the names of the field types.
381
     */
382
    public function getLoadedFieldTypes()
383
    {
384
        return $this->getOperationSetting('loadedFieldTypes') ?? [];
385
    }
386
387
    /**
388
     * Set an array of field type names as already loaded for the current operation.
389
     *
390
     * @param  array  $fieldTypes
391
     */
392
    public function setLoadedFieldTypes($fieldTypes)
393
    {
394
        $this->setOperationSetting('loadedFieldTypes', $fieldTypes);
395
    }
396
397
    /**
398
     * Get a namespaced version of the field type name.
399
     * Appends the 'view_namespace' attribute of the field to the `type', using dot notation.
400
     *
401
     * @param  mixed  $field
402
     * @return string Namespaced version of the field type name. Ex: 'text', 'custom.view.path.text'
403
     */
404
    public function getFieldTypeWithNamespace($field)
405
    {
406
        if (is_array($field)) {
407
            $fieldType = $field['type'];
408
            if (isset($field['view_namespace'])) {
409
                $fieldType = implode('.', [$field['view_namespace'], $field['type']]);
410
            }
411
        } else {
412
            $fieldType = $field;
413
        }
414
415
        return $fieldType;
416
    }
417
418
    /**
419
     * Add a new field type to the loadedFieldTypes array.
420
     *
421
     * @param  string  $field  Field array
422
     * @return bool Successful operation true/false.
423
     */
424
    public function addLoadedFieldType($field)
425
    {
426
        $alreadyLoaded = $this->getLoadedFieldTypes();
427
        $type = $this->getFieldTypeWithNamespace($field);
428
429
        if (! in_array($type, $this->getLoadedFieldTypes(), true)) {
430
            $alreadyLoaded[] = $type;
431
            $this->setLoadedFieldTypes($alreadyLoaded);
432
433
            return true;
434
        }
435
436
        return false;
437
    }
438
439
    /**
440
     * Alias of the addLoadedFieldType() method.
441
     * Adds a new field type to the loadedFieldTypes array.
442
     *
443
     * @param  string  $field  Field array
444
     * @return bool Successful operation true/false.
445
     */
446
    public function markFieldTypeAsLoaded($field)
447
    {
448
        return $this->addLoadedFieldType($field);
449
    }
450
451
    /**
452
     * Check if a field type's reasources (CSS and JS) have already been loaded.
453
     *
454
     * @param  string  $field  Field array
455
     * @return bool Whether the field type has been marked as loaded.
456
     */
457
    public function fieldTypeLoaded($field)
458
    {
459
        return in_array($this->getFieldTypeWithNamespace($field), $this->getLoadedFieldTypes());
460
    }
461
462
    /**
463
     * Check if a field type's reasources (CSS and JS) have NOT been loaded.
464
     *
465
     * @param  string  $field  Field array
466
     * @return bool Whether the field type has NOT been marked as loaded.
467
     */
468
    public function fieldTypeNotLoaded($field)
469
    {
470
        return ! in_array($this->getFieldTypeWithNamespace($field), $this->getLoadedFieldTypes());
471
    }
472
473
    /**
474
     * Get a list of all field names for the current operation.
475
     *
476
     * @return array
477
     */
478
    public function getAllFieldNames()
479
    {
480
        return Arr::flatten(Arr::pluck($this->getCleanStateFields(), 'name'));
481
    }
482
483
    /**
484
     * Returns the request without anything that might have been maliciously inserted.
485
     * Only specific field names that have been introduced with addField() are kept in the request.
486
     *
487
     * @param  \Illuminate\Http\Request  $request
488
     * @return array
489
     */
490
    public function getStrippedSaveRequest($request)
491
    {
492
        $setting = $this->getOperationSetting('strippedRequest');
493
494
        // if a closure was passed
495
        if (is_callable($setting)) {
496
            return $setting($request);
497
        }
498
499
        // if an invokable class was passed
500
        // eg. \App\Http\Requests\BackpackStrippedRequest
501
        if (is_string($setting) && class_exists($setting)) {
502
            $setting = new $setting();
503
504
            return is_callable($setting) ? $setting($request) : abort(500, get_class($setting).' is not invokable.');
0 ignored issues
show
Bug introduced by
Are you sure the usage of abort(500, get_class($se.... ' is not invokable.') is correct as it seems to always return null.

This check looks for function or method calls that always return null and whose return value is used.

class A
{
    function getObject()
    {
        return null;
    }

}

$a = new A();
if ($a->getObject()) {

The method getObject() can return nothing but null, so it makes no sense to use the return value.

The reason is most likely that a function or method is imcomplete or has been reduced for debug purposes.

Loading history...
505
        }
506
507
        return $request->only($this->getAllFieldNames());
508
    }
509
510
    /**
511
     * Check if a field exists, by any given attribute.
512
     *
513
     * @param  string  $attribute  Attribute name on that field definition array.
514
     * @param  string  $value  Value of that attribute on that field definition array.
515
     * @return bool
516
     */
517
    public function hasFieldWhere($attribute, $value)
518
    {
519
        $match = Arr::first($this->getCleanStateFields(), function ($field, $fieldKey) use ($attribute, $value) {
520
            return isset($field[$attribute]) && $field[$attribute] == $value;
521
        });
522
523
        return (bool) $match;
524
    }
525
526
    /**
527
     * Get the first field where a given attribute has the given value.
528
     *
529
     * @param  string  $attribute  Attribute name on that field definition array.
530
     * @param  string  $value  Value of that attribute on that field definition array.
531
     * @return bool
532
     */
533
    public function firstFieldWhere($attribute, $value)
534
    {
535
        return Arr::first($this->getCleanStateFields(), function ($field, $fieldKey) use ($attribute, $value) {
536
            return isset($field[$attribute]) && $field[$attribute] == $value;
537
        });
538
    }
539
540
    /**
541
     * Create and return a CrudField object for that field name.
542
     *
543
     * Enables developers to use a fluent syntax to declare their fields,
544
     * in addition to the existing options:
545
     * - CRUD::addField(['name' => 'price', 'type' => 'number']);
546
     * - CRUD::field('price')->type('number');
547
     *
548
     * And if the developer uses the CrudField object as Field in their CrudController:
549
     * - Field::name('price')->type('number');
550
     *
551
     * @param  string  $name  The name of the column in the db, or model attribute.
552
     * @return CrudField
553
     */
554
    public function field($name)
555
    {
556
        return new CrudField($name);
557
    }
558
}
559