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

Fields::hasUploadFields()   B
last analyzed

Complexity

Conditions 7
Paths 2

Size

Total Lines 17
Code Lines 8

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 7
eloc 8
c 0
b 0
f 0
nc 2
nop 0
dl 0
loc 17
rs 8.8333
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
use Illuminate\Support\Str;
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 with name processed to be usable in HTML.
20
     *
21
     * @return array
22
     */
23
    public function fields()
24
    {
25
        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

25
        return $this->overwriteFieldNamesFromDotNotationToArray($this->/** @scrutinizer ignore-call */ getOperationSetting('fields') ?? []);
Loading history...
26
    }
27
28
    /**
29
     * Returns the fields as they are stored inside operation setting, not running the
30
     * presentation callbacks like converting the `dot.names` into `dot[names]` for html for example.
31
     */
32
    public function getCleanStateFields()
33
    {
34
        return $this->getOperationSetting('fields') ?? [];
35
    }
36
37
    /**
38
     * The only REALLY MANDATORY attribute when defining a field is the 'name'.
39
     * Everything else Backpack can probably guess. This method makes sure  the
40
     * field definition array is complete, by guessing missing attributes.
41
     *
42
     * @param  string|array  $field  The definition of a field (string or array).
43
     * @return array The correct definition of that field.
44
     */
45
    public function makeSureFieldHasNecessaryAttributes($field)
46
    {
47
        $field = $this->makeSureFieldHasName($field);
48
        $field = $this->makeSureFieldHasEntity($field);
49
        $field = $this->makeSureFieldHasLabel($field);
50
51
        if (isset($field['entity']) && $field['entity'] !== false) {
52
            $field = $this->makeSureFieldHasRelationshipAttributes($field);
53
        }
54
55
        $field = $this->makeSureFieldHasType($field);
56
        $field = $this->makeSureSubfieldsHaveNecessaryAttributes($field);
57
        $field = $this->makeSureMorphSubfieldsAreDefined($field);
0 ignored issues
show
Bug introduced by
The method makeSureMorphSubfieldsAreDefined() does not exist on Backpack\CRUD\app\Library\CrudPanel\Traits\Fields. Did you maybe mean fields()? ( Ignorable by Annotation )

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

57
        /** @scrutinizer ignore-call */ 
58
        $field = $this->makeSureMorphSubfieldsAreDefined($field);

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...
58
59
        $this->setupFieldValidation($field, $field['parentFieldName'] ?? false);
0 ignored issues
show
Bug introduced by
It seems like setupFieldValidation() 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

59
        $this->/** @scrutinizer ignore-call */ 
60
               setupFieldValidation($field, $field['parentFieldName'] ?? false);
Loading history...
60
61
        return $field;
62
    }
63
64
    /**
65
     * When field is a relationship, Backpack will try to guess some basic attributes from the relation.
66
     *
67
     * @param  array  $field
68
     * @return array
69
     */
70
    public function makeSureFieldHasRelationshipAttributes($field)
71
    {
72
        $field = $this->makeSureFieldHasRelationType($field);
73
        $field = $this->makeSureFieldHasModel($field);
74
        $field = $this->makeSureFieldHasAttribute($field);
75
        $field = $this->makeSureFieldHasMultiple($field);
76
        $field = $this->makeSureFieldHasPivot($field);
77
        $field = $this->makeSureFieldHasType($field);
78
79
        return $field;
80
    }
81
82
    /**
83
     * Register all Eloquent Model events that are defined on fields.
84
     * Eg. saving, saved, creating, created, updating, updated.
85
     *
86
     * @see https://laravel.com/docs/master/eloquent#events
87
     *
88
     * @return void
89
     */
90
    public function registerFieldEvents()
91
    {
92
        foreach ($this->getCleanStateFields() as $key => $field) {
93
            if (isset($field['events'])) {
94
                foreach ($field['events'] as $event => $closure) {
95
                    $this->model->{$event}($closure);
96
                }
97
            }
98
        }
99
    }
100
101
    /**
102
     * Add a field to the create/update form or both.
103
     *
104
     * @param  string|array  $field  The new field.
105
     * @return self
106
     */
107
    public function addField($field)
108
    {
109
        $field = $this->makeSureFieldHasNecessaryAttributes($field);
110
111
        $this->enableTabsIfFieldUsesThem($field);
112
        $this->addFieldToOperationSettings($field);
113
        (new CrudField($field['name']))->callRegisteredAttributeMacros();
114
115
        return $this;
116
    }
117
118
    /**
119
     * Add multiple fields to the create/update form or both.
120
     *
121
     * @param  array  $fields  The new fields.
122
     */
123
    public function addFields($fields)
124
    {
125
        if (count($fields)) {
126
            foreach ($fields as $field) {
127
                $this->addField($field);
128
            }
129
        }
130
    }
131
132
    /**
133
     * Move the most recently added field after the given target field.
134
     *
135
     * @param  string  $targetFieldName  The target field name.
136
     */
137
    public function afterField($targetFieldName)
138
    {
139
        $this->transformFields(function ($fields) use ($targetFieldName) {
140
            return $this->moveField($fields, $targetFieldName, false);
141
        });
142
    }
143
144
    /**
145
     * Move the most recently added field before the given target field.
146
     *
147
     * @param  string  $targetFieldName  The target field name.
148
     */
149
    public function beforeField($targetFieldName)
150
    {
151
        $this->transformFields(function ($fields) use ($targetFieldName) {
152
            return $this->moveField($fields, $targetFieldName, true);
153
        });
154
    }
155
156
    /**
157
     * Move this field to be first in the fields list.
158
     *
159
     * @return bool|null
160
     */
161
    public function makeFirstField()
162
    {
163
        if (! $this->fields()) {
164
            return false;
165
        }
166
167
        $firstField = array_keys(array_slice($this->getCleanStateFields(), 0, 1))[0];
168
        $this->beforeField($firstField);
169
    }
170
171
    /**
172
     * Remove a certain field from the create/update/both forms by its name.
173
     *
174
     * @param  string  $name  Field name (as defined with the addField() procedure)
175
     */
176
    public function removeField($name)
177
    {
178
        $this->transformFields(function ($fields) use ($name) {
179
            Arr::forget($fields, $name);
180
181
            return $fields;
182
        });
183
    }
184
185
    /**
186
     * Remove many fields from the create/update/both forms by their name.
187
     *
188
     * @param  array  $array_of_names  A simple array of the names of the fields to be removed.
189
     */
190
    public function removeFields($array_of_names)
191
    {
192
        if (! empty($array_of_names)) {
193
            foreach ($array_of_names as $name) {
194
                $this->removeField($name);
195
            }
196
        }
197
    }
198
199
    /**
200
     * Remove all fields from the create/update/both forms.
201
     */
202
    public function removeAllFields()
203
    {
204
        $current_fields = $this->getCleanStateFields();
205
        if (! empty($current_fields)) {
206
            foreach ($current_fields as $field) {
207
                $this->removeField($field['name']);
208
            }
209
        }
210
    }
211
212
    /**
213
     * Remove an attribute from one field's definition array.
214
     *
215
     * @param  string  $field  The name of the field.
216
     * @param  string  $attribute  The name of the attribute being removed.
217
     */
218
    public function removeFieldAttribute($field, $attribute)
219
    {
220
        $fields = $this->getCleanStateFields();
221
222
        unset($fields[$field][$attribute]);
223
224
        $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

224
        $this->/** @scrutinizer ignore-call */ 
225
               setOperationSetting('fields', $fields);
Loading history...
225
    }
226
227
    /**
228
     * Update value of a given key for a current field.
229
     *
230
     * @param  string  $fieldName  The field name
231
     * @param  array  $modifications  An array of changes to be made.
232
     */
233
    public function modifyField($fieldName, $modifications)
234
    {
235
        $fieldsArray = $this->getCleanStateFields();
236
        $field = $this->firstFieldWhere('name', $fieldName);
237
238
        foreach ($modifications as $attributeName => $attributeValue) {
239
            $fieldsArray[$field['name']][$attributeName] = $attributeValue;
240
        }
241
242
        $this->enableTabsIfFieldUsesThem($modifications);
243
244
        $this->setOperationSetting('fields', $fieldsArray);
245
    }
246
247
    /**
248
     * Set label for a specific field.
249
     *
250
     * @param  string  $field
251
     * @param  string  $label
252
     */
253
    public function setFieldLabel($field, $label)
254
    {
255
        $this->modifyField($field, ['label' => $label]);
256
    }
257
258
    /**
259
     * Check if field is the first of its type in the given fields array.
260
     * It's used in each field_type.blade.php to determine whether 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).
261
     *
262
     * @param  array  $field  The current field being tested if it's the first of its type.
263
     * @return bool true/false
264
     */
265
    public function checkIfFieldIsFirstOfItsType($field)
266
    {
267
        $fields_array = $this->getCleanStateFields();
268
        $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

268
        /** @scrutinizer ignore-call */ 
269
        $first_field = $this->getFirstOfItsTypeInArray($field['type'], $fields_array);
Loading history...
269
270
        if ($first_field && $field['name'] == $first_field['name']) {
271
            return true;
272
        }
273
274
        return false;
275
    }
276
277
    /**
278
     * @return array
279
     */
280
    public function getCurrentFields()
281
    {
282
        return $this->fields();
283
    }
284
285
    /**
286
     * Order the CRUD fields. If certain fields are missing from the given order array, they will be
287
     * pushed to the new fields array in the original order.
288
     *
289
     * @param  array  $order  An array of field names in the desired order.
290
     */
291
    public function orderFields($order)
292
    {
293
        $this->transformFields(function ($fields) use ($order) {
294
            return $this->applyOrderToFields($fields, $order);
295
        });
296
    }
297
298
    /**
299
     * Get the fields for the create or update forms.
300
     *
301
     * @return array all the fields that need to be shown and their information
302
     */
303
    public function getFields()
304
    {
305
        return $this->fields();
306
    }
307
308
    /**
309
     * Check if the create/update form has upload fields.
310
     * Upload fields are the ones that have "upload" => true defined on them.
311
     *
312
     * @return bool
313
     */
314
    public function hasUploadFields()
315
    {
316
        $fields = $this->getCleanStateFields();
317
        $upload_fields = Arr::where($fields, function ($value, $key) {
318
            // check if any subfields have uploads
319
            if (isset($value['subfields'])) {
320
                foreach ($value['subfields'] as $subfield) {
321
                    if (isset($subfield['upload']) && $subfield['upload'] === true) {
322
                        return true;
323
                    }
324
                }
325
            }
326
327
            return isset($value['upload']) && $value['upload'] == true;
328
        });
329
330
        return count($upload_fields) ? true : false;
331
    }
332
333
    // ----------------------
334
    // FIELD ASSET MANAGEMENT
335
    // ----------------------
336
337
    /**
338
     * Get all the field types whose resources (JS and CSS) have already been loaded on page.
339
     *
340
     * @return array Array with the names of the field types.
341
     */
342
    public function getLoadedFieldTypes()
343
    {
344
        return $this->getOperationSetting('loadedFieldTypes') ?? [];
345
    }
346
347
    /**
348
     * Set an array of field type names as already loaded for the current operation.
349
     *
350
     * @param  array  $fieldTypes
351
     */
352
    public function setLoadedFieldTypes($fieldTypes)
353
    {
354
        $this->setOperationSetting('loadedFieldTypes', $fieldTypes);
355
    }
356
357
    /**
358
     * Get a namespaced version of the field type name.
359
     * Appends the 'view_namespace' attribute of the field to the `type', using dot notation.
360
     *
361
     * @param  mixed  $field
362
     * @return string Namespaced version of the field type name. Ex: 'text', 'custom.view.path.text'
363
     */
364
    public function getFieldTypeWithNamespace($field)
365
    {
366
        if (is_array($field)) {
367
            $fieldType = $field['type'];
368
            if (isset($field['view_namespace'])) {
369
                $fieldType = implode('.', [$field['view_namespace'], $field['type']]);
370
            }
371
        } else {
372
            $fieldType = $field;
373
        }
374
375
        return $fieldType;
376
    }
377
378
    /**
379
     * Add a new field type to the loadedFieldTypes array.
380
     *
381
     * @param  string  $field  Field array
382
     * @return bool Successful operation true/false.
383
     */
384
    public function addLoadedFieldType($field)
385
    {
386
        $alreadyLoaded = $this->getLoadedFieldTypes();
387
        $type = $this->getFieldTypeWithNamespace($field);
388
389
        if (! in_array($type, $this->getLoadedFieldTypes(), true)) {
390
            $alreadyLoaded[] = $type;
391
            $this->setLoadedFieldTypes($alreadyLoaded);
392
393
            return true;
394
        }
395
396
        return false;
397
    }
398
399
    /**
400
     * Alias of the addLoadedFieldType() method.
401
     * Adds a new field type to the loadedFieldTypes array.
402
     *
403
     * @param  string  $field  Field array
404
     * @return bool Successful operation true/false.
405
     */
406
    public function markFieldTypeAsLoaded($field)
407
    {
408
        return $this->addLoadedFieldType($field);
409
    }
410
411
    /**
412
     * Check if a field type's reasources (CSS and JS) have already been loaded.
413
     *
414
     * @param  string  $field  Field array
415
     * @return bool Whether the field type has been marked as loaded.
416
     */
417
    public function fieldTypeLoaded($field)
418
    {
419
        return in_array($this->getFieldTypeWithNamespace($field), $this->getLoadedFieldTypes());
420
    }
421
422
    /**
423
     * Check if a field type's reasources (CSS and JS) have NOT been loaded.
424
     *
425
     * @param  string  $field  Field array
426
     * @return bool Whether the field type has NOT been marked as loaded.
427
     */
428
    public function fieldTypeNotLoaded($field)
429
    {
430
        return ! in_array($this->getFieldTypeWithNamespace($field), $this->getLoadedFieldTypes());
431
    }
432
433
    /**
434
     * Get a list of all field names for the current operation.
435
     *
436
     * @return array
437
     */
438
    public function getAllFieldNames()
439
    {
440
        $fieldNamesArray = array_column($this->getCleanStateFields(), 'name');
441
442
        return array_reduce($fieldNamesArray, function ($names, $item) {
443
            if (strpos($item, ',') === false) {
444
                $names[] = $item;
445
446
                return $names;
447
            }
448
449
            foreach (explode(',', $item) as $fieldName) {
450
                $names[] = $fieldName;
451
            }
452
453
            return $names;
454
        });
455
    }
456
457
    /**
458
     * Returns the request without anything that might have been maliciously inserted.
459
     * Only specific field names that have been introduced with addField() are kept in the request.
460
     *
461
     * @param  \Illuminate\Http\Request  $request
462
     * @return array
463
     */
464
    public function getStrippedSaveRequest($request)
465
    {
466
        $setting = $this->getOperationSetting('strippedRequest');
467
468
        // if a closure was passed
469
        if (is_callable($setting)) {
470
            return $setting($request);
471
        }
472
473
        // if an invokable class was passed
474
        // eg. \App\Http\Requests\BackpackStrippedRequest
475
        if (is_string($setting) && class_exists($setting)) {
476
            $setting = new $setting();
477
478
            return is_callable($setting) ? $setting($request) : abort(500, get_class($setting).' is not invokable.', ['developer-error-exception']);
0 ignored issues
show
Bug introduced by
Are you sure the usage of abort(500, get_class($se...oper-error-exception')) is correct as it seems to always return null.

This check looks for function or method calls that always return null and whose return value is used.

class A
{
    function getObject()
    {
        return null;
    }

}

$a = new A();
if ($a->getObject()) {

The method getObject() can return nothing but null, so it makes no sense to use the return value.

The reason is most likely that a function or method is imcomplete or has been reduced for debug purposes.

Loading history...
479
        }
480
481
        return $request->only($this->getAllFieldNames());
482
    }
483
484
    /**
485
     * Check if a field exists, by any given attribute.
486
     *
487
     * @param  string  $attribute  Attribute name on that field definition array.
488
     * @param  string  $value  Value of that attribute on that field definition array.
489
     * @return bool
490
     */
491
    public function hasFieldWhere($attribute, $value)
492
    {
493
        $match = Arr::first($this->getCleanStateFields(), function ($field, $fieldKey) use ($attribute, $value) {
494
            return isset($field[$attribute]) && $field[$attribute] == $value;
495
        });
496
497
        return (bool) $match;
498
    }
499
500
    /**
501
     * Get the first field where a given attribute has the given value.
502
     *
503
     * @param  string  $attribute  Attribute name on that field definition array.
504
     * @param  string  $value  Value of that attribute on that field definition array.
505
     * @return bool
506
     */
507
    public function firstFieldWhere($attribute, $value)
508
    {
509
        return Arr::first($this->getCleanStateFields(), function ($field, $fieldKey) use ($attribute, $value) {
510
            return isset($field[$attribute]) && $field[$attribute] == $value;
511
        });
512
    }
513
514
    /**
515
     * The field hold multiple inputs (one field represent multiple model attributes / relations)
516
     * eg: date range or checklist dependency.
517
     */
518
    public function holdsMultipleInputs(string $fieldName): bool
519
    {
520
        return Str::contains($fieldName, ',');
521
    }
522
523
    /**
524
     * Create and return a CrudField object for that field name.
525
     *
526
     * Enables developers to use a fluent syntax to declare their fields,
527
     * in addition to the existing options:
528
     * - CRUD::addField(['name' => 'price', 'type' => 'number']);
529
     * - CRUD::field('price')->type('number');
530
     * - CRUD::field(['name' => 'price', 'type' => 'number']);
531
     *
532
     * And if the developer uses the CrudField object as Field in their CrudController:
533
     * - Field::name('price')->type('number');
534
     *
535
     * @param  string|array  $nameOrDefinition  The name of the column in the db, or model attribute.
536
     * @return CrudField
537
     */
538
    public function field($nameOrDefinition)
539
    {
540
        return new CrudField($nameOrDefinition);
541
    }
542
}
543