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 (#2951)
by Cristian
22:13
created

Fields::fieldGroup()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 3
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Importance

Changes 2
Bugs 1 Features 0
Metric Value
cc 1
eloc 1
c 2
b 1
f 0
nc 1
nop 1
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 Backpack\CRUD\app\Library\CrudPanel\FieldGroup;
7
use Illuminate\Support\Arr;
8
9
trait Fields
10
{
11
    use FieldsProtectedMethods;
12
    use FieldsPrivateMethods;
13
14
    // ------------
15
    // FIELDS
16
    // ------------
17
18
    /**
19
     * Get the CRUD fields for the current operation.
20
     *
21
     * @return array
22
     */
23
    public function fields()
24
    {
25
        return $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

25
        return $this->/** @scrutinizer ignore-call */ getOperationSetting('fields') ?? [];
Loading history...
26
    }
27
28
    /**
29
     * The only REALLY MANDATORY attribute when defining a field is the 'name'.
30
     * Everything else Backpack can probably guess. This method makes sure  the
31
     * field definition array is complete, by guessing missing attributes.
32
     *
33
     * @param  string|array $field  The definition of a field (string or array).
34
     * @return array                The correct definition of that field.
35
     */
36
    public function makeSureFieldHasNecessaryAttributes($field)
37
    {
38
        $field = $this->makeSureFieldHasName($field);
39
        $field = $this->makeSureFieldHasEntity($field);
40
        $field = $this->makeSureFieldHasLabel($field);
41
42
        if (isset($field['entity'])) {
43
            $field = $this->makeSureFieldHasRelationType($field);
44
            $field = $this->makeSureFieldHasModel($field);
45
            $field = $this->overwriteFieldNameFromEntity($field);
46
            $field = $this->makeSureFieldHasAttribute($field);
47
            $field = $this->makeSureFieldHasMultiple($field);
48
            $field = $this->makeSureFieldHasPivot($field);
49
        }
50
51
        $field = $this->makeSureFieldHasType($field);
52
        $field = $this->overwriteFieldNameFromDotNotationToArray($field);
53
54
        return $field;
55
    }
56
57
    /**
58
     * Add a field to the create/update form or both.
59
     *
60
     * @param string|array $field The new field.
61
     *
62
     * @return self
63
     */
64
    public function addField($field)
65
    {
66
        $field = $this->makeSureFieldHasNecessaryAttributes($field);
67
68
        $this->enableTabsIfFieldUsesThem($field);
69
        $this->addFieldToOperationSettings($field);
70
71
        return $this;
72
    }
73
74
    /**
75
     * Add multiple fields to the create/update form or both.
76
     *
77
     * @param array  $fields The new fields.
78
     */
79
    public function addFields($fields)
80
    {
81
        if (count($fields)) {
82
            foreach ($fields as $field) {
83
                $this->addField($field);
84
            }
85
        }
86
    }
87
88
    /**
89
     * Move the most recently added field after the given target field.
90
     *
91
     * @param string $targetFieldName The target field name.
92
     */
93
    public function afterField($targetFieldName)
94
    {
95
        $this->transformFields(function ($fields) use ($targetFieldName) {
96
            return $this->moveField($fields, $targetFieldName, false);
97
        });
98
    }
99
100
    /**
101
     * Move the most recently added field before the given target field.
102
     *
103
     * @param string $targetFieldName The target field name.
104
     */
105
    public function beforeField($targetFieldName)
106
    {
107
        $this->transformFields(function ($fields) use ($targetFieldName) {
108
            return $this->moveField($fields, $targetFieldName, true);
109
        });
110
    }
111
112
    /**
113
     * Move this field to be first in the fields list.
114
     *
115
     * @return bool|null
116
     */
117
    public function makeFirstField()
118
    {
119
        if (! $this->fields()) {
120
            return false;
121
        }
122
123
        $firstField = array_keys(array_slice($this->fields(), 0, 1))[0];
124
        $this->beforeField($firstField);
125
    }
126
127
    /**
128
     * Remove a certain field from the create/update/both forms by its name.
129
     *
130
     * @param string $name Field name (as defined with the addField() procedure)
131
     */
132
    public function removeField($name)
133
    {
134
        $this->transformFields(function ($fields) use ($name) {
135
            Arr::forget($fields, $name);
136
137
            return $fields;
138
        });
139
    }
140
141
    /**
142
     * Remove many fields from the create/update/both forms by their name.
143
     *
144
     * @param array $array_of_names A simple array of the names of the fields to be removed.
145
     */
146
    public function removeFields($array_of_names)
147
    {
148
        if (! empty($array_of_names)) {
149
            foreach ($array_of_names as $name) {
150
                $this->removeField($name);
151
            }
152
        }
153
    }
154
155
    /**
156
     * Remove all fields from the create/update/both forms.
157
     */
158
    public function removeAllFields()
159
    {
160
        $current_fields = $this->getCurrentFields();
161
        if (! empty($current_fields)) {
162
            foreach ($current_fields as $field) {
163
                $this->removeField($field['name']);
164
            }
165
        }
166
    }
167
168
    /**
169
     * Remove an attribute from one field's definition array.
170
     * @param  string $field     The name of the field.
171
     * @param  string $attribute The name of the attribute being removed.
172
     */
173
    public function removeFieldAttribute($field, $attribute)
174
    {
175
        $fields = $this->fields();
176
177
        unset($fields[$field][$attribute]);
178
179
        $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

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

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

225
        /** @scrutinizer ignore-call */ 
226
        $first_field = $this->getFirstOfItsTypeInArray($field['type'], $fields_array);
Loading history...
226
227
        if ($first_field && $field['name'] == $first_field['name']) {
228
            return true;
229
        }
230
231
        return false;
232
    }
233
234
    /**
235
     * Decode attributes that are casted as array/object/json in the model.
236
     * So that they are not json_encoded twice before they are stored in the db
237
     * (once by Backpack in front-end, once by Laravel Attribute Casting).
238
     */
239
    public function decodeJsonCastedAttributes($data)
240
    {
241
        $fields = $this->getFields();
242
        $casted_attributes = $this->model->getCastedAttributes();
243
244
        foreach ($fields as $field) {
245
246
            // Test the field is castable
247
            if (isset($field['name']) && is_string($field['name']) && array_key_exists($field['name'], $casted_attributes)) {
248
249
                // Handle JSON field types
250
                $jsonCastables = ['array', 'object', 'json'];
251
                $fieldCasting = $casted_attributes[$field['name']];
252
253
                if (in_array($fieldCasting, $jsonCastables) && isset($data[$field['name']]) && ! empty($data[$field['name']]) && ! is_array($data[$field['name']])) {
254
                    try {
255
                        $data[$field['name']] = json_decode($data[$field['name']]);
256
                    } catch (\Exception $e) {
257
                        $data[$field['name']] = [];
258
                    }
259
                }
260
            }
261
        }
262
263
        return $data;
264
    }
265
266
    /**
267
     * @return array
268
     */
269
    public function getCurrentFields()
270
    {
271
        return $this->fields();
272
    }
273
274
    /**
275
     * Order the CRUD fields. If certain fields are missing from the given order array, they will be
276
     * pushed to the new fields array in the original order.
277
     *
278
     * @param array $order An array of field names in the desired order.
279
     */
280
    public function orderFields($order)
281
    {
282
        $this->transformFields(function ($fields) use ($order) {
283
            return $this->applyOrderToFields($fields, $order);
284
        });
285
    }
286
287
    /**
288
     * Get the fields for the create or update forms.
289
     *
290
     * @return array all the fields that need to be shown and their information
291
     */
292
    public function getFields()
293
    {
294
        return $this->fields();
295
    }
296
297
    /**
298
     * Check if the create/update form has upload fields.
299
     * Upload fields are the ones that have "upload" => true defined on them.
300
     *
301
     * @param string   $form create/update/both - defaults to 'both'
302
     * @param bool|int $id   id of the entity - defaults to false
303
     *
304
     * @return bool
305
     */
306
    public function hasUploadFields()
307
    {
308
        $fields = $this->getFields();
309
        $upload_fields = Arr::where($fields, function ($value, $key) {
310
            return isset($value['upload']) && $value['upload'] == true;
311
        });
312
313
        return count($upload_fields) ? true : false;
314
    }
315
316
    // ----------------------
317
    // FIELD ASSET MANAGEMENT
318
    // ----------------------
319
320
    /**
321
     * Get all the field types whose resources (JS and CSS) have already been loaded on page.
322
     *
323
     * @return array Array with the names of the field types.
324
     */
325
    public function getLoadedFieldTypes()
326
    {
327
        return $this->getOperationSetting('loadedFieldTypes') ?? [];
328
    }
329
330
    /**
331
     * Set an array of field type names as already loaded for the current operation.
332
     *
333
     * @param array $fieldTypes
334
     */
335
    public function setLoadedFieldTypes($fieldTypes)
336
    {
337
        $this->setOperationSetting('loadedFieldTypes', $fieldTypes);
338
    }
339
340
    /**
341
     * Get a namespaced version of the field type name.
342
     * Appends the 'view_namespace' attribute of the field to the `type', using dot notation.
343
     *
344
     * @param  mixed $field
345
     * @return string Namespaced version of the field type name. Ex: 'text', 'custom.view.path.text'
346
     */
347
    public function getFieldTypeWithNamespace($field)
348
    {
349
        if (is_array($field)) {
350
            $fieldType = $field['type'];
351
            if (isset($field['view_namespace'])) {
352
                $fieldType = implode('.', [$field['view_namespace'], $field['type']]);
353
            }
354
        } else {
355
            $fieldType = $field;
356
        }
357
358
        return $fieldType;
359
    }
360
361
    /**
362
     * Add a new field type to the loadedFieldTypes array.
363
     *
364
     * @param string $field Field array
365
     * @return  bool Successful operation true/false.
366
     */
367
    public function addLoadedFieldType($field)
368
    {
369
        $alreadyLoaded = $this->getLoadedFieldTypes();
370
        $type = $this->getFieldTypeWithNamespace($field);
371
372
        if (! in_array($type, $this->getLoadedFieldTypes(), true)) {
373
            $alreadyLoaded[] = $type;
374
            $this->setLoadedFieldTypes($alreadyLoaded);
375
376
            return true;
377
        }
378
379
        return false;
380
    }
381
382
    /**
383
     * Alias of the addLoadedFieldType() method.
384
     * Adds a new field type to the loadedFieldTypes array.
385
     *
386
     * @param string $field Field array
387
     * @return  bool Successful operation true/false.
388
     */
389
    public function markFieldTypeAsLoaded($field)
390
    {
391
        return $this->addLoadedFieldType($field);
392
    }
393
394
    /**
395
     * Check if a field type's reasources (CSS and JS) have already been loaded.
396
     *
397
     * @param string $field Field array
398
     * @return  bool Whether the field type has been marked as loaded.
399
     */
400
    public function fieldTypeLoaded($field)
401
    {
402
        return in_array($this->getFieldTypeWithNamespace($field), $this->getLoadedFieldTypes());
403
    }
404
405
    /**
406
     * Check if a field type's reasources (CSS and JS) have NOT been loaded.
407
     *
408
     * @param string $field Field array
409
     * @return  bool Whether the field type has NOT been marked as loaded.
410
     */
411
    public function fieldTypeNotLoaded($field)
412
    {
413
        return ! in_array($this->getFieldTypeWithNamespace($field), $this->getLoadedFieldTypes());
414
    }
415
416
    /**
417
     * Get a list of all field names for the current operation.
418
     *
419
     * @return array
420
     */
421
    public function getAllFieldNames()
422
    {
423
        //we need to parse field names in relation fields so they get posted/stored correctly
424
        $fields = $this->parseRelationFieldNamesFromHtml($this->getCurrentFields());
0 ignored issues
show
Bug introduced by
It seems like parseRelationFieldNamesFromHtml() 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

424
        /** @scrutinizer ignore-call */ 
425
        $fields = $this->parseRelationFieldNamesFromHtml($this->getCurrentFields());
Loading history...
425
426
        return Arr::flatten(Arr::pluck($fields, 'name'));
427
    }
428
429
    /**
430
     * Returns the request without anything that might have been maliciously inserted.
431
     * Only specific field names that have been introduced with addField() are kept in the request.
432
     */
433
    public function getStrippedSaveRequest()
434
    {
435
        $setting = $this->getOperationSetting('saveAllInputsExcept');
436
        if ($setting == false || $setting == null) {
437
            return $this->getRequest()->only($this->getAllFieldNames());
0 ignored issues
show
Bug introduced by
It seems like getRequest() 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

437
            return $this->/** @scrutinizer ignore-call */ getRequest()->only($this->getAllFieldNames());
Loading history...
438
        }
439
440
        if (is_array($setting)) {
441
            return $this->getRequest()->except($this->getOperationSetting('saveAllInputsExcept'));
442
        }
443
444
        return $this->getRequest()->only($this->getAllFieldNames());
445
    }
446
447
    /**
448
     * Check if a field exists, by any given attribute.
449
     *
450
     * @param  string  $attribute   Attribute name on that field definition array.
451
     * @param  string  $value       Value of that attribute on that field definition array.
452
     * @return bool
453
     */
454
    public function hasFieldWhere($attribute, $value)
455
    {
456
        $match = Arr::first($this->fields(), function ($field, $fieldKey) use ($attribute, $value) {
457
            return isset($field[$attribute]) && $field[$attribute] == $value;
458
        });
459
460
        return (bool) $match;
461
    }
462
463
    /**
464
     * Get the first field where a given attribute has the given value.
465
     *
466
     * @param  string  $attribute   Attribute name on that field definition array.
467
     * @param  string  $value       Value of that attribute on that field definition array.
468
     * @return bool
469
     */
470
    public function firstFieldWhere($attribute, $value)
471
    {
472
        return Arr::first($this->fields(), function ($field, $fieldKey) use ($attribute, $value) {
473
            return isset($field[$attribute]) && $field[$attribute] == $value;
474
        });
475
    }
476
477
    /**
478
     * Create and return a CrudField object for that field name.
479
     *
480
     * Enables developers to use a fluent syntax to declare their fields,
481
     * in addition to the existing options:
482
     * - CRUD::addField(['name' => 'price', 'type' => 'number']);
483
     * - CRUD::field('price')->type('number');
484
     *
485
     * And if the developer uses the CrudField object as Field in his CrudController:
486
     * - Field::name('price')->type('number');
487
     *
488
     * @param  string $name The name of the column in the db, or model attribute.
489
     * @return CrudField
490
     */
491
    public function field($name)
492
    {
493
        return new CrudField($name);
494
    }
495
496
    /**
497
     * Allow to add an attribute to multiple fields at same time.
498
     *
499
     * Using the fluent syntax allow the developer to add attributes to multiple fields at the same time. Eg:
500
     *
501
     * - CRUD::fieldGroup(CRUD::field('price')->type('number'), CRUD::field('title')->type('text'))->tab('both_on_same_tab');
502
     *
503
     * @param  mixed fluent syntax fields.
0 ignored issues
show
Bug introduced by
The type Backpack\CRUD\app\Library\CrudPanel\Traits\fluent was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
504
     * @return FieldGroup
505
     */
506
    public function fieldGroup(...$fields)
507
    {
508
        return new FieldGroup(...$fields);
509
    }
510
}
511