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 Failed
Pull Request — master (#3826)
by Cristian
15:46
created

CrudPanel::modelMethodHasParameters()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 9
Code Lines 4

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 2
eloc 4
c 0
b 0
f 0
nc 2
nop 2
dl 0
loc 9
rs 10
1
<?php
2
3
namespace Backpack\CRUD\app\Library\CrudPanel;
4
5
use Backpack\CRUD\app\Library\CrudPanel\Traits\Access;
6
use Backpack\CRUD\app\Library\CrudPanel\Traits\AutoFocus;
7
use Backpack\CRUD\app\Library\CrudPanel\Traits\AutoSet;
8
use Backpack\CRUD\app\Library\CrudPanel\Traits\Buttons;
9
use Backpack\CRUD\app\Library\CrudPanel\Traits\Columns;
10
use Backpack\CRUD\app\Library\CrudPanel\Traits\Create;
11
use Backpack\CRUD\app\Library\CrudPanel\Traits\Delete;
12
use Backpack\CRUD\app\Library\CrudPanel\Traits\Errors;
13
use Backpack\CRUD\app\Library\CrudPanel\Traits\FakeColumns;
14
use Backpack\CRUD\app\Library\CrudPanel\Traits\FakeFields;
15
use Backpack\CRUD\app\Library\CrudPanel\Traits\Fields;
16
use Backpack\CRUD\app\Library\CrudPanel\Traits\Filters;
17
use Backpack\CRUD\app\Library\CrudPanel\Traits\HeadingsAndTitles;
18
use Backpack\CRUD\app\Library\CrudPanel\Traits\Macroable;
19
use Backpack\CRUD\app\Library\CrudPanel\Traits\Operations;
20
use Backpack\CRUD\app\Library\CrudPanel\Traits\Query;
21
use Backpack\CRUD\app\Library\CrudPanel\Traits\Read;
22
use Backpack\CRUD\app\Library\CrudPanel\Traits\Relationships;
23
use Backpack\CRUD\app\Library\CrudPanel\Traits\Reorder;
24
use Backpack\CRUD\app\Library\CrudPanel\Traits\SaveActions;
25
use Backpack\CRUD\app\Library\CrudPanel\Traits\Search;
26
use Backpack\CRUD\app\Library\CrudPanel\Traits\Settings;
27
use Backpack\CRUD\app\Library\CrudPanel\Traits\Tabs;
28
use Backpack\CRUD\app\Library\CrudPanel\Traits\Update;
29
use Backpack\CRUD\app\Library\CrudPanel\Traits\Validation;
30
use Backpack\CRUD\app\Library\CrudPanel\Traits\Views;
31
use Exception;
32
use Illuminate\Database\Eloquent\Collection;
33
use Illuminate\Database\Eloquent\Model;
34
use Illuminate\Database\Eloquent\Relations\Relation;
35
use Illuminate\Support\Arr;
36
37
class CrudPanel
38
{
39
    // load all the default CrudPanel features
40
    use Create, Read, Search, Update, Delete, Errors, Reorder, Access, Columns, Fields, Query, Buttons, AutoSet, FakeFields, FakeColumns, AutoFocus, Filters, Tabs, Views, Validation, HeadingsAndTitles, Operations, SaveActions, Settings, Relationships;
0 ignored issues
show
introduced by
The trait Backpack\CRUD\app\Library\CrudPanel\Traits\Buttons requires some properties which are not provided by Backpack\CRUD\app\Library\CrudPanel\CrudPanel: $stack, $name
Loading history...
Bug introduced by
The trait Backpack\CRUD\app\Librar...Panel\Traits\Operations requires the property $action which is not provided by Backpack\CRUD\app\Library\CrudPanel\CrudPanel.
Loading history...
introduced by
The trait Backpack\CRUD\app\Library\CrudPanel\Traits\Filters requires some properties which are not provided by Backpack\CRUD\app\Library\CrudPanel\CrudPanel: $values, $logic, $fallbackLogic, $name
Loading history...
Bug introduced by
The trait Backpack\CRUD\app\Library\CrudPanel\Traits\Query requires the property $joins which is not provided by Backpack\CRUD\app\Library\CrudPanel\CrudPanel.
Loading history...
41
    // allow developers to add their own closures to this object
42
    use Macroable;
0 ignored issues
show
Bug introduced by
The trait Backpack\CRUD\app\Librar...dPanel\Traits\Macroable requires the property $name which is not provided by Backpack\CRUD\app\Library\CrudPanel\CrudPanel.
Loading history...
43
44
    // --------------
45
    // CRUD variables
46
    // --------------
47
    // These variables are passed to the CRUD views, inside the $crud variable.
48
    // All variables are public, so they can be modified from your EntityCrudController.
49
    // All functions and methods are also public, so they can be used in your EntityCrudController to modify these variables.
50
51
    public $model = "\App\Models\Entity"; // what's the namespace for your entity's model
52
    public $route; // what route have you defined for your entity? used for links.
53
    public $entity_name = 'entry'; // what name will show up on the buttons, in singural (ex: Add entity)
54
    public $entity_name_plural = 'entries'; // what name will show up on the buttons, in plural (ex: Delete 5 entities)
55
56
    public $entry;
57
58
    protected $request;
59
60
    // The following methods are used in CrudController or your EntityCrudController to manipulate the variables above.
61
62
    public function __construct()
63
    {
64
        $this->setRequest();
65
66
        if ($this->getCurrentOperation()) {
67
            $this->setOperation($this->getCurrentOperation());
68
        }
69
    }
70
71
    /**
72
     * Set the request instance for this CRUD.
73
     *
74
     * @param  \Illuminate\Http\Request  $request
75
     */
76
    public function setRequest($request = null)
77
    {
78
        $this->request = $request ?? \Request::instance();
79
    }
80
81
    /**
82
     * [getRequest description].
83
     *
84
     * @return [type] [description]
0 ignored issues
show
Documentation Bug introduced by
The doc comment [type] at position 0 could not be parsed: Unknown type name '[' at position 0 in [type].
Loading history...
85
     */
86
    public function getRequest()
87
    {
88
        return $this->request;
89
    }
90
91
    // ------------------------------------------------------
92
    // BASICS - model, route, entity_name, entity_name_plural
93
    // ------------------------------------------------------
94
95
    /**
96
     * This function binds the CRUD to its corresponding Model (which extends Eloquent).
97
     * All Create-Read-Update-Delete operations are done using that Eloquent Collection.
98
     *
99
     * @param  string  $model_namespace  Full model namespace. Ex: App\Models\Article
100
     *
101
     * @throws \Exception in case the model does not exist
102
     */
103
    public function setModel($model_namespace)
104
    {
105
        if (! class_exists($model_namespace)) {
106
            throw new \Exception('The model does not exist.', 500);
107
        }
108
109
        if (! method_exists($model_namespace, 'hasCrudTrait')) {
110
            throw new \Exception('Please use CrudTrait on the model.', 500);
111
        }
112
113
        $this->model = new $model_namespace();
114
        $this->query = $this->model->select('*');
115
        $this->entry = null;
116
    }
117
118
    /**
119
     * Get the corresponding Eloquent Model for the CrudController, as defined with the setModel() function.
120
     *
121
     * @return string|\Illuminate\Database\Eloquent\Model
122
     */
123
    public function getModel()
124
    {
125
        return $this->model;
126
    }
127
128
    /**
129
     * Get the database connection, as specified in the .env file or overwritten by the property on the model.
130
     *
131
     * @return \Illuminate\Database\Schema\Builder
132
     */
133
    private function getSchema()
134
    {
135
        return $this->getModel()->getConnection()->getSchemaBuilder();
136
    }
137
138
    /**
139
     * Check if the database connection driver is using mongodb.
140
     *
141
     * DEPRECATION NOTICE: This method is no longer used and will be removed in future versions of Backpack
142
     *
143
     * @deprecated
144
     *
145
     * @return bool
146
     */
147
    private function driverIsMongoDb()
148
    {
149
        return $this->getSchema()->getConnection()->getConfig()['driver'] === 'mongodb';
150
    }
151
152
    /**
153
     * Check if the database connection is any sql driver.
154
     *
155
     * @return bool
156
     */
157
    private function driverIsSql()
158
    {
159
        $driver = $this->getSchema()->getConnection()->getConfig('driver');
160
161
        return in_array($driver, $this->getSqlDriverList());
162
    }
163
164
    /**
165
     * Get SQL driver list.
166
     *
167
     * @return array
168
     */
169
    public function getSqlDriverList()
170
    {
171
        return ['mysql', 'sqlsrv', 'sqlite', 'pgsql'];
172
    }
173
174
    /**
175
     * Set the route for this CRUD.
176
     * Ex: admin/article.
177
     *
178
     * @param  string  $route  Route name.
179
     */
180
    public function setRoute($route)
181
    {
182
        $this->route = $route;
183
    }
184
185
    /**
186
     * Set the route for this CRUD using the route name.
187
     * Ex: admin.article.
188
     *
189
     * @param  string  $route  Route name.
190
     * @param  array  $parameters  Parameters.
191
     *
192
     * @throws \Exception
193
     */
194
    public function setRouteName($route, $parameters = [])
195
    {
196
        $complete_route = $route.'.index';
197
198
        if (! \Route::has($complete_route)) {
199
            throw new \Exception('There are no routes for this route name.', 404);
200
        }
201
202
        $this->route = route($complete_route, $parameters);
203
    }
204
205
    /**
206
     * Get the current CrudController route.
207
     *
208
     * Can be defined in the CrudController with:
209
     * - $this->crud->setRoute(config('backpack.base.route_prefix').'/article')
210
     * - $this->crud->setRouteName(config('backpack.base.route_prefix').'.article')
211
     * - $this->crud->route = config('backpack.base.route_prefix')."/article"
212
     *
213
     * @return string
214
     */
215
    public function getRoute()
216
    {
217
        return $this->route;
218
    }
219
220
    /**
221
     * Set the entity name in singular and plural.
222
     * Used all over the CRUD interface (header, add button, reorder button, breadcrumbs).
223
     *
224
     * @param  string  $singular  Entity name, in singular. Ex: article
225
     * @param  string  $plural  Entity name, in plural. Ex: articles
226
     */
227
    public function setEntityNameStrings($singular, $plural)
228
    {
229
        $this->entity_name = $singular;
230
        $this->entity_name_plural = $plural;
231
    }
232
233
    // -----------------------------------------------
234
    // ACTIONS - the current operation being processed
235
    // -----------------------------------------------
236
237
    /**
238
     * Get the action being performed by the controller,
239
     * including middleware names, route name, method name,
240
     * namespace, prefix, etc.
241
     *
242
     * @return string The EntityCrudController route action array.
243
     */
244
    public function getAction()
245
    {
246
        return $this->getRequest()->route()->getAction();
0 ignored issues
show
Bug Best Practice introduced by
The expression return $this->getRequest()->route()->getAction() also could return the type array which is incompatible with the documented return type string.
Loading history...
247
    }
248
249
    /**
250
     * Get the full name of the controller method
251
     * currently being called (including namespace).
252
     *
253
     * @return string The EntityCrudController full method name with namespace.
254
     */
255
    public function getActionName()
256
    {
257
        return $this->getRequest()->route()->getActionName();
258
    }
259
260
    /**
261
     * Get the name of the controller method
262
     * currently being called.
263
     *
264
     * @return string The EntityCrudController method name.
265
     */
266
    public function getActionMethod()
267
    {
268
        return $this->getRequest()->route()->getActionMethod();
269
    }
270
271
    /**
272
     * Check if the controller method being called
273
     * matches a given string.
274
     *
275
     * @param  string  $methodName  Name of the method (ex: index, create, update)
276
     * @return bool Whether the condition is met or not.
277
     */
278
    public function actionIs($methodName)
279
    {
280
        return $methodName === $this->getActionMethod();
281
    }
282
283
    // ----------------------------------
284
    // Miscellaneous functions or methods
285
    // ----------------------------------
286
287
    /**
288
     * Return the first element in an array that has the given 'type' attribute.
289
     *
290
     * @param  string  $type
291
     * @param  array  $array
292
     * @return array
293
     */
294
    public function getFirstOfItsTypeInArray($type, $array)
295
    {
296
        return Arr::first($array, function ($item) use ($type) {
297
            return $item['type'] == $type;
298
        });
299
    }
300
301
    // ------------
302
    // TONE FUNCTIONS - UNDOCUMENTED, UNTESTED, SOME MAY BE USED IN THIS FILE
303
    // ------------
304
    //
305
    // TODO:
306
    // - figure out if they are really needed
307
    // - comments inside the function to explain how they work
308
    // - write docblock for them
309
    // - place in the correct section above (CREATE, READ, UPDATE, DELETE, ACCESS, MANIPULATION)
310
311
    public function sync($type, $fields, $attributes)
312
    {
313
        if (! empty($this->{$type})) {
314
            $this->{$type} = array_map(function ($field) use ($fields, $attributes) {
315
                if (in_array($field['name'], (array) $fields)) {
316
                    $field = array_merge($field, $attributes);
317
                }
318
319
                return $field;
320
            }, $this->{$type});
321
        }
322
    }
323
324
    /**
325
     * Get the Eloquent Model name from the given relation definition string.
326
     *
327
     * @example For a given string 'company' and a relation between App/Models/User and App/Models/Company, defined by a
328
     *          company() method on the user model, the 'App/Models/Company' string will be returned.
329
     * @example For a given string 'company.address' and a relation between App/Models/User, App/Models/Company and
330
     *          App/Models/Address defined by a company() method on the user model and an address() method on the
331
     *          company model, the 'App/Models/Address' string will be returned.
332
     *
333
     * @param  string  $relationString  Relation string. A dot notation can be used to chain multiple relations.
334
     * @param  int  $length  Optionally specify the number of relations to omit from the start of the relation string. If
335
     *                       the provided length is negative, then that many relations will be omitted from the end of the relation
336
     *                       string.
337
     * @param  \Illuminate\Database\Eloquent\Model  $model  Optionally specify a different model than the one in the crud object.
338
     * @return string Relation model name.
339
     */
340
    public function getRelationModel($relationString, $length = null, $model = null)
341
    {
342
        $relationArray = explode('.', $relationString);
343
344
        if (! isset($length)) {
345
            $length = count($relationArray);
346
        }
347
348
        if (! isset($model)) {
349
            $model = $this->model;
350
        }
351
352
        $result = array_reduce(array_splice($relationArray, 0, $length), function ($obj, $method) {
353
            try {
354
                $result = $obj->$method();
355
356
                return $result->getRelated();
357
            } catch (Exception $e) {
358
                return $obj;
359
            }
360
        }, $model);
361
362
        return get_class($result);
363
    }
364
365
    /**
366
     * Get the given attribute from a model or models resulting from the specified relation string (eg: the list of streets from
367
     * the many addresses of the company of a given user).
368
     *
369
     * @param  \Illuminate\Database\Eloquent\Model  $model  Model (eg: user).
370
     * @param  string  $relationString  Model relation. Can be a string representing the name of a relation method in the given
371
     *                                  Model or one from a different Model through multiple relations. A dot notation can be used to specify
372
     *                                  multiple relations (eg: user.company.address).
373
     * @param  string  $attribute  The attribute from the relation model (eg: the street attribute from the address model).
374
     * @return array An array containing a list of attributes from the resulting model.
375
     */
376
    public function getRelatedEntriesAttributes($model, $relationString, $attribute)
377
    {
378
        $endModels = $this->getRelatedEntries($model, $relationString);
379
        $attributes = [];
380
        foreach ($endModels as $model => $entries) {
0 ignored issues
show
introduced by
$model is overwriting one of the parameters of this function.
Loading history...
381
            $model_instance = new $model();
382
            $modelKey = $model_instance->getKeyName();
383
384
            if (is_array($entries)) {
385
                //if attribute does not exist in main array we have more than one entry OR the attribute
386
                //is an acessor that is not in $appends property of model.
387
                if (! isset($entries[$attribute])) {
388
                    //we first check if we don't have the attribute because it's and acessor that is not in appends.
389
                    if ($model_instance->hasGetMutator($attribute) && isset($entries[$modelKey])) {
390
                        $entry_in_database = $model_instance->find($entries[$modelKey]);
391
                        $attributes[$entry_in_database->{$modelKey}] = $this->parseTranslatableAttributes($model_instance, $attribute, $entry_in_database->{$attribute});
392
                    } else {
393
                        //we have multiple entries
394
                        //for each entry we check if $attribute exists in array or try to check if it's an acessor.
395
                        foreach ($entries as $entry) {
396
                            if (isset($entry[$attribute])) {
397
                                $attributes[$entry[$modelKey]] = $this->parseTranslatableAttributes($model_instance, $attribute, $entry[$attribute]);
398
                            } else {
399
                                if ($model_instance->hasGetMutator($attribute)) {
400
                                    $entry_in_database = $model_instance->find($entry[$modelKey]);
401
                                    $attributes[$entry_in_database->{$modelKey}] = $this->parseTranslatableAttributes($model_instance, $attribute, $entry_in_database->{$attribute});
402
                                }
403
                            }
404
                        }
405
                    }
406
                } else {
407
                    //if we have the attribute we just return it, does not matter if it is direct attribute or an acessor added in $appends.
408
                    $attributes[$entries[$modelKey]] = $this->parseTranslatableAttributes($model_instance, $attribute, $entries[$attribute]);
409
                }
410
            }
411
        }
412
413
        return $attributes;
414
    }
415
416
    /**
417
     * Parse translatable attributes from a model or models resulting from the specified relation string.
418
     *
419
     * @param  \Illuminate\Database\Eloquent\Model  $model  Model (eg: user).
420
     * @param  string  $attribute  The attribute from the relation model (eg: the street attribute from the address model).
421
     * @param  string  $value  Attribute value translatable or not
422
     * @return string A string containing the translated attributed based on app()->getLocale()
423
     */
424
    public function parseTranslatableAttributes($model, $attribute, $value)
425
    {
426
        if (! method_exists($model, 'isTranslatableAttribute')) {
427
            return $value;
428
        }
429
430
        if (! $model->isTranslatableAttribute($attribute)) {
431
            return $value;
432
        }
433
434
        if (! is_array($value)) {
0 ignored issues
show
introduced by
The condition is_array($value) is always false.
Loading history...
435
            $decodedAttribute = json_decode($value, true);
436
        } else {
437
            $decodedAttribute = $value;
438
        }
439
440
        if (is_array($decodedAttribute) && ! empty($decodedAttribute)) {
441
            if (isset($decodedAttribute[app()->getLocale()])) {
0 ignored issues
show
introduced by
The method getLocale() does not exist on Illuminate\Container\Container. Are you sure you never get this type here, but always one of the subclasses? ( Ignorable by Annotation )

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

441
            if (isset($decodedAttribute[app()->/** @scrutinizer ignore-call */ getLocale()])) {
Loading history...
442
                return $decodedAttribute[app()->getLocale()];
443
            } else {
444
                return Arr::first($decodedAttribute);
445
            }
446
        }
447
448
        return $value;
449
    }
450
451
    /**
452
     * Traverse the tree of relations for the given model, defined by the given relation string, and return the ending
453
     * associated model instance or instances.
454
     *
455
     * @param  \Illuminate\Database\Eloquent\Model  $model  The CRUD model.
456
     * @param  string  $relationString  Relation string. A dot notation can be used to chain multiple relations.
457
     * @return array An array of the associated model instances defined by the relation string.
458
     */
459
    private function getRelatedEntries($model, $relationString)
460
    {
461
        $relationArray = explode('.', $relationString);
462
        $firstRelationName = Arr::first($relationArray);
463
        $relation = $model->{$firstRelationName};
464
465
        $results = [];
466
        if (! is_null($relation)) {
467
            if ($relation instanceof Collection) {
468
                $currentResults = $relation->all();
469
            } elseif (is_array($relation)) {
470
                $currentResults = $relation;
471
            } elseif ($relation instanceof Model) {
472
                $currentResults = [$relation];
473
            } else {
474
                $currentResults = [];
475
            }
476
477
            array_shift($relationArray);
478
479
            if (! empty($relationArray)) {
480
                foreach ($currentResults as $currentResult) {
481
                    $results = array_merge_recursive($results, $this->getRelatedEntries($currentResult, implode('.', $relationArray)));
482
                }
483
            } else {
484
                $relatedClass = get_class($model->{$firstRelationName}()->getRelated());
485
                $results[$relatedClass] = $currentResults;
486
            }
487
        }
488
489
        return $results;
490
    }
491
492
    /**
493
     * Check if a the method has any parameters in the model.
494
     *
495
     * @param  object  $model
496
     * @param  string  $method
497
     * @return bool
498
     */
499
    private function modelMethodHasParameters($model, $method)
500
    {
501
        $reflectClassMethod = new \ReflectionMethod(get_class($model), $method);
502
503
        if ($reflectClassMethod->getNumberOfParameters() > 0) {
504
            return true;
505
        }
506
507
        return false;
508
    }
509
}
510