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

Test Setup Failed
Pull Request — master (#4002)
by
unknown
24:42 queued 09:36
created

Fields::getCleanStateFields()   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
c 0
b 0
f 0
nc 1
nop 0
dl 0
loc 3
rs 10
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->makeSureFieldHasRelationType($field);
52
            $field = $this->makeSureFieldHasModel($field);
53
            $field = $this->makeSureFieldHasAttribute($field);
54
            $field = $this->makeSureFieldHasMultiple($field);
55
            $field = $this->makeSureFieldHasPivot($field);
56
        }
57
58
        $field = $this->makeSureFieldHasType($field);
59
60
        return $field;
61
    }
62
63
    /**
64
     * Add a field to the create/update form or both.
65
     *
66
     * @param  string|array  $field  The new field.
67
     * @return self
68
     */
69
    public function addField($field)
70
    {
71
        $field = $this->makeSureFieldHasNecessaryAttributes($field);
72
73
        $this->enableTabsIfFieldUsesThem($field);
74
        $this->addFieldToOperationSettings($field);
75
76
        return $this;
77
    }
78
79
    /**
80
     * Add multiple fields to the create/update form or both.
81
     *
82
     * @param  array  $fields  The new fields.
83
     */
84
    public function addFields($fields)
85
    {
86
        if (count($fields)) {
87
            foreach ($fields as $field) {
88
                $this->addField($field);
89
            }
90
        }
91
    }
92
93
    /**
94
     * Move the most recently added field after the given target field.
95
     *
96
     * @param  string  $targetFieldName  The target field name.
97
     */
98
    public function afterField($targetFieldName)
99
    {
100
        $this->transformFields(function ($fields) use ($targetFieldName) {
101
            return $this->moveField($fields, $targetFieldName, false);
102
        });
103
    }
104
105
    /**
106
     * Move the most recently added field before the given target field.
107
     *
108
     * @param  string  $targetFieldName  The target field name.
109
     */
110
    public function beforeField($targetFieldName)
111
    {
112
        $this->transformFields(function ($fields) use ($targetFieldName) {
113
            return $this->moveField($fields, $targetFieldName, true);
114
        });
115
    }
116
117
    /**
118
     * Move this field to be first in the fields list.
119
     *
120
     * @return bool|null
121
     */
122
    public function makeFirstField()
123
    {
124
        if (! $this->fields()) {
125
            return false;
126
        }
127
128
        $firstField = array_keys(array_slice($this->getCleanFields(), 0, 1))[0];
0 ignored issues
show
Bug introduced by
The method getCleanFields() does not exist on Backpack\CRUD\app\Library\CrudPanel\Traits\Fields. Did you maybe mean getCleanStateFields()? ( Ignorable by Annotation )

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

128
        $firstField = array_keys(array_slice($this->/** @scrutinizer ignore-call */ getCleanFields(), 0, 1))[0];

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...
129
        $this->beforeField($firstField);
130
    }
131
132
    /**
133
     * Remove a certain field from the create/update/both forms by its name.
134
     *
135
     * @param  string  $name  Field name (as defined with the addField() procedure)
136
     */
137
    public function removeField($name)
138
    {
139
        $this->transformFields(function ($fields) use ($name) {
140
            Arr::forget($fields, $name);
141
142
            return $fields;
143
        });
144
    }
145
146
    /**
147
     * Remove many fields from the create/update/both forms by their name.
148
     *
149
     * @param  array  $array_of_names  A simple array of the names of the fields to be removed.
150
     */
151
    public function removeFields($array_of_names)
152
    {
153
        if (! empty($array_of_names)) {
154
            foreach ($array_of_names as $name) {
155
                $this->removeField($name);
156
            }
157
        }
158
    }
159
160
    /**
161
     * Remove all fields from the create/update/both forms.
162
     */
163
    public function removeAllFields()
164
    {
165
        $current_fields = $this->getCleanStateFields();
166
        if (! empty($current_fields)) {
167
            foreach ($current_fields as $field) {
168
                $this->removeField($field['name']);
169
            }
170
        }
171
    }
172
173
    /**
174
     * Remove an attribute from one field's definition array.
175
     *
176
     * @param  string  $field  The name of the field.
177
     * @param  string  $attribute  The name of the attribute being removed.
178
     */
179
    public function removeFieldAttribute($field, $attribute)
180
    {
181
        $fields = $this->getCleanStateFields();
182
183
        unset($fields[$field][$attribute]);
184
185
        $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

185
        $this->/** @scrutinizer ignore-call */ 
186
               setOperationSetting('fields', $fields);
Loading history...
186
    }
187
188
    /**
189
     * Update value of a given key for a current field.
190
     *
191
     * @param  string  $fieldName  The field name
192
     * @param  array  $modifications  An array of changes to be made.
193
     */
194
    public function modifyField($fieldName, $modifications)
195
    {
196
        $fieldsArray = $this->getCleanStateFields();
197
        $field = $this->firstFieldWhere('name', $fieldName);
198
        $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

198
        $fieldKey = $this->getFieldKey(/** @scrutinizer ignore-type */ $field);
Loading history...
199
200
        foreach ($modifications as $attributeName => $attributeValue) {
201
            $fieldsArray[$fieldKey][$attributeName] = $attributeValue;
202
        }
203
204
        $this->enableTabsIfFieldUsesThem($modifications);
205
206
        $this->setOperationSetting('fields', $fieldsArray);
207
    }
208
209
    /**
210
     * Set label for a specific field.
211
     *
212
     * @param  string  $field
213
     * @param  string  $label
214
     */
215
    public function setFieldLabel($field, $label)
216
    {
217
        $this->modifyField($field, ['label' => $label]);
218
    }
219
220
    /**
221
     * Check if field is the first of its type in the given fields array.
222
     * 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).
223
     *
224
     * @param  array  $field  The current field being tested if it's the first of its type.
225
     * @return bool true/false
226
     */
227
    public function checkIfFieldIsFirstOfItsType($field)
228
    {
229
        $fields_array = $this->getCleanStateFields();
230
        $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

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