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 (#4161)
by
unknown
15:22
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
        $field = $this->makeSureSubfieldsHaveNecessaryAttributes($field);
60
61
        return $field;
62
    }
63
64
    /**
65
     * Register all Eloquent Model events that are defined on fields.
66
     * Eg. saving, saved, creating, created, updating, updated.
67
     *
68
     * @see https://laravel.com/docs/master/eloquent#events
69
     *
70
     * @return void
71
     */
72
    public function registerFieldEvents()
73
    {
74
        foreach ($this->getCleanStateFields() as $key => $field) {
75
            if (isset($field['events'])) {
76
                foreach ($field['events'] as $event => $closure) {
77
                    $this->model->{$event}($closure);
78
                }
79
            }
80
        }
81
    }
82
83
    /**
84
     * Add a field to the create/update form or both.
85
     *
86
     * @param  string|array  $field  The new field.
87
     * @return self
88
     */
89
    public function addField($field)
90
    {
91
        $field = $this->makeSureFieldHasNecessaryAttributes($field);
92
93
        $this->enableTabsIfFieldUsesThem($field);
94
        $this->addFieldToOperationSettings($field);
95
96
        return $this;
97
    }
98
99
    /**
100
     * Add multiple fields to the create/update form or both.
101
     *
102
     * @param  array  $fields  The new fields.
103
     */
104
    public function addFields($fields)
105
    {
106
        if (count($fields)) {
107
            foreach ($fields as $field) {
108
                $this->addField($field);
109
            }
110
        }
111
    }
112
113
    /**
114
     * Move the most recently added field after the given target field.
115
     *
116
     * @param  string  $targetFieldName  The target field name.
117
     */
118
    public function afterField($targetFieldName)
119
    {
120
        $this->transformFields(function ($fields) use ($targetFieldName) {
121
            return $this->moveField($fields, $targetFieldName, false);
122
        });
123
    }
124
125
    /**
126
     * Move the most recently added field before the given target field.
127
     *
128
     * @param  string  $targetFieldName  The target field name.
129
     */
130
    public function beforeField($targetFieldName)
131
    {
132
        $this->transformFields(function ($fields) use ($targetFieldName) {
133
            return $this->moveField($fields, $targetFieldName, true);
134
        });
135
    }
136
137
    /**
138
     * Move this field to be first in the fields list.
139
     *
140
     * @return bool|null
141
     */
142
    public function makeFirstField()
143
    {
144
        if (! $this->fields()) {
145
            return false;
146
        }
147
148
        $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

148
        $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...
149
        $this->beforeField($firstField);
150
    }
151
152
    /**
153
     * Remove a certain field from the create/update/both forms by its name.
154
     *
155
     * @param  string  $name  Field name (as defined with the addField() procedure)
156
     */
157
    public function removeField($name)
158
    {
159
        $this->transformFields(function ($fields) use ($name) {
160
            Arr::forget($fields, $name);
161
162
            return $fields;
163
        });
164
    }
165
166
    /**
167
     * Remove many fields from the create/update/both forms by their name.
168
     *
169
     * @param  array  $array_of_names  A simple array of the names of the fields to be removed.
170
     */
171
    public function removeFields($array_of_names)
172
    {
173
        if (! empty($array_of_names)) {
174
            foreach ($array_of_names as $name) {
175
                $this->removeField($name);
176
            }
177
        }
178
    }
179
180
    /**
181
     * Remove all fields from the create/update/both forms.
182
     */
183
    public function removeAllFields()
184
    {
185
        $current_fields = $this->getCleanStateFields();
186
        if (! empty($current_fields)) {
187
            foreach ($current_fields as $field) {
188
                $this->removeField($field['name']);
189
            }
190
        }
191
    }
192
193
    /**
194
     * Remove an attribute from one field's definition array.
195
     *
196
     * @param  string  $field  The name of the field.
197
     * @param  string  $attribute  The name of the attribute being removed.
198
     */
199
    public function removeFieldAttribute($field, $attribute)
200
    {
201
        $fields = $this->getCleanStateFields();
202
203
        unset($fields[$field][$attribute]);
204
205
        $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

205
        $this->/** @scrutinizer ignore-call */ 
206
               setOperationSetting('fields', $fields);
Loading history...
206
    }
207
208
    /**
209
     * Update value of a given key for a current field.
210
     *
211
     * @param  string  $fieldName  The field name
212
     * @param  array  $modifications  An array of changes to be made.
213
     */
214
    public function modifyField($fieldName, $modifications)
215
    {
216
        $fieldsArray = $this->getCleanStateFields();
217
        $field = $this->firstFieldWhere('name', $fieldName);
218
        $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

218
        $fieldKey = $this->getFieldKey(/** @scrutinizer ignore-type */ $field);
Loading history...
219
220
        foreach ($modifications as $attributeName => $attributeValue) {
221
            $fieldsArray[$fieldKey][$attributeName] = $attributeValue;
222
        }
223
224
        $this->enableTabsIfFieldUsesThem($modifications);
225
226
        $this->setOperationSetting('fields', $fieldsArray);
227
    }
228
229
    /**
230
     * Set label for a specific field.
231
     *
232
     * @param  string  $field
233
     * @param  string  $label
234
     */
235
    public function setFieldLabel($field, $label)
236
    {
237
        $this->modifyField($field, ['label' => $label]);
238
    }
239
240
    /**
241
     * Check if field is the first of its type in the given fields array.
242
     * 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).
243
     *
244
     * @param  array  $field  The current field being tested if it's the first of its type.
245
     * @return bool true/false
246
     */
247
    public function checkIfFieldIsFirstOfItsType($field)
248
    {
249
        $fields_array = $this->getCleanStateFields();
250
        $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

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