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 — master (#2951)
by Cristian
07:25
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