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 (#2950)
by Bogdan
08:58
created

Fields::tab()   A

Complexity

Conditions 5
Paths 9

Size

Total Lines 18
Code Lines 11

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 5
eloc 11
c 1
b 0
f 0
nc 9
nop 2
dl 0
loc 18
rs 9.6111
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.
19
     *
20
     * @return array
21
     */
22
    public function fields()
23
    {
24
        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

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

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

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

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

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

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

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

506
                $field->/** @scrutinizer ignore-call */ 
507
                        tab($name);
Loading history...
507
            }
508
509
            if (is_array($field)) {
510
                $field['tab'] = $name;
511
                $this->addField($field);
512
            }
513
514
            if (is_string($field)) {
515
                $field = [
516
                    'name' => $field,
517
                    'tab' => $name,
518
                ];
519
                $this->addField($field);
520
            }
521
        }
522
    }
523
}
524