Issues (7)

Security Analysis    no request data  

This project does not seem to handle request data directly as such no vulnerable execution paths were found.

  Cross-Site Scripting
Cross-Site Scripting enables an attacker to inject code into the response of a web-request that is viewed by other users. It can for example be used to bypass access controls, or even to take over other users' accounts.
  File Exposure
File Exposure allows an attacker to gain access to local files that he should not be able to access. These files can for example include database credentials, or other configuration files.
  File Manipulation
File Manipulation enables an attacker to write custom data to files. This potentially leads to injection of arbitrary code on the server.
  Object Injection
Object Injection enables an attacker to inject an object into PHP code, and can lead to arbitrary code execution, file exposure, or file manipulation attacks.
  Code Injection
Code Injection enables an attacker to execute arbitrary code on the server.
  Response Splitting
Response Splitting can be used to send arbitrary responses.
  File Inclusion
File Inclusion enables an attacker to inject custom files into PHP's file loading mechanism, either explicitly passed to include, or for example via PHP's auto-loading mechanism.
  Command Injection
Command Injection enables an attacker to inject a shell command that is execute with the privileges of the web-server. This can be used to expose sensitive data, or gain access of your server.
  SQL Injection
SQL Injection enables an attacker to execute arbitrary SQL code on your database server gaining access to user data, or manipulating user data.
  XPath Injection
XPath Injection enables an attacker to modify the parts of XML document that are read. If that XML document is for example used for authentication, this can lead to further vulnerabilities similar to SQL Injection.
  LDAP Injection
LDAP Injection enables an attacker to inject LDAP statements potentially granting permission to run unauthorized queries, or modify content inside the LDAP tree.
  Header Injection
  Other Vulnerability
This category comprises other attack vectors such as manipulating the PHP runtime, loading custom extensions, freezing the runtime, or similar.
  Regex Injection
Regex Injection enables an attacker to execute arbitrary code in your PHP process.
  XML Injection
XML Injection enables an attacker to read files on your local filesystem including configuration files, or can be abused to freeze your web-server process.
  Variable Injection
Variable Injection enables an attacker to overwrite program variables with custom data, and can lead to further vulnerabilities.
Unfortunately, the security analysis is currently not available for your project. If you are a non-commercial open-source project, please contact support to gain access.

src/Searchzy.php (7 issues)

Upgrade to new PHP Analysis Engine

These results are based on our legacy PHP analysis, consider migrating to our new PHP analysis engine instead. Learn more

1
<?php
2
3
namespace Jhormantasayco\LaravelSearchzy;
4
5
use Illuminate\Database\Eloquent\Builder;
6
use Illuminate\Support\Arr;
7
use Illuminate\Support\Str;
8
9
trait Searchzy
10
{
11
    /**
12
     * Agrupa todas los closures de las relaciones del Modelo, en forma de árbol.
13
     *
14
     * @var array
15
     */
16
    private $relationConstraints = [];
17
18
    /**
19
     * Define el array de las relaciones y el query de las relaciones. En el query
20
     * ya se aplicaron las closures de cada relación.
21
     *
22
     * @var array
23
     */
24
    private $eagerRelationConstraints = [];
25
26
    /**
27
     * Define el array con los valores searchable con la 'keyword' de searchzy.
28
     *
29
     * @var array
30
     */
31
    private $searchableInputsKeyword;
32
33
    /**
34
     * Define el array con todos los inputs searchable del Modelo.
35
     *
36
     * @var array
37
     */
38
    private $searchableInputs = [];
39
40
    /**
41
     * Define el array con todos los inputs filterable del Modelo.
42
     *
43
     * @var array
44
     */
45
    private $filterableInputs = [];
46
47
    /**
48
     * Define el array con todos los inputs adicionales del Modelo.
49
     *
50
     * @var array
51
     */
52
    private $aditionableInputs = [];
53
54
    /**
55
     * Define el valor de la 'keyword' de searchzy.
56
     *
57
     * @var array
58
     */
59
    private $searchableKeyword;
60
61
    /**
62
     * Define el request usado por searchzy.
63
     *
64
     * @var Request
65
     */
66
    private $currentRequest;
67
68
    /**
69
     * Scope que realiza una búsqueda searchzy.
70
     *
71
     * @param  Illuminate\Database\Eloquent\Builder $query
72
     * @return Illuminate\Database\Eloquent\Builder
73
     */
74
    public function scopeSearchzy($query, $keyword = null, $request = null): Builder
75
    {
76
        $keyword = $keyword ?: config('searchzy.keyword');
77
78
        $this->currentRequest = $request ?: request();
79
80
        $this->searchableKeyword = $this->currentRequest->get($keyword, null);
81
82
        $this->searchableInputsKeyword = $this->getInputsKeyword();
83
84
        $this->searchableInputs = $this->getInputsFromRequest('searchable', 'searchableInputs');
85
86
        $this->filterableInputs = $this->getInputsFromRequest('filterable', 'filterableInputs');
87
88
        $query = $this->parseInputsKeywordConstraints($query);
89
90
        $query = $this->parseRelationConstraints($query);
91
92
        $query = $this->loadRelationContraints($query);
93
94
        return $query;
95
    }
96
97
    /**
98
     * Agrupa las relaciones del Modelo. Retorna un array 'arbol' de las relaciones y sus columnas.
99
     *
100
     * @param  array $arrInputs
101
     * @return array
102
     */
103
    private function parseRelationInputs($arrInputs): array
104
    {
105
        $relationInputs = [];
106
107
        foreach (array_keys($arrInputs) as $attribute) {
108
109
            if (Str::contains($attribute, ':')) {
110
111
                [$relation, $column] = explode(':', $attribute);
0 ignored issues
show
The variable $relation does not exist. Did you mean $relationInputs?

This check looks for variables that are accessed but have not been defined. It raises an issue if it finds another variable that has a similar name.

The variable may have been renamed without also renaming all references.

Loading history...
The variable $column does not exist. Did you forget to declare it?

This check marks access to variables or properties that have not been declared yet. While PHP has no explicit notion of declaring a variable, accessing it before a value is assigned to it is most likely a bug.

Loading history...
112
113
                $relationInputs[$relation][] = $column;
0 ignored issues
show
The variable $relation does not exist. Did you mean $relationInputs?

This check looks for variables that are accessed but have not been defined. It raises an issue if it finds another variable that has a similar name.

The variable may have been renamed without also renaming all references.

Loading history...
114
            }
115
        }
116
117
        return $relationInputs;
118
    }
119
120
    /**
121
     * Agrupas las columnas propias del Modelo.
122
     *
123
     * @param  array $arrInputs
124
     * @return array
125
     */
126
    private function parseModelInputs($arrInputs): array
127
    {
128
        $modelInputs = [];
129
130
        foreach (array_keys($arrInputs) as $attribute) {
131
132
            if (!Str::contains($attribute, ':')) {
133
134
                $modelInputs[] = $attribute;
135
            }
136
        }
137
138
        return $modelInputs;
139
    }
140
141
    /**
142
     * Aplica los 'wheres' que serán aplicado en la tabla del Modelo
143
     *
144
     * @param  Builder $query
145
     * @return Builder
146
     */
147
    private function applySearchableConstraints($query): Builder
148
    {
149
        $searchableModelInputs = $this->parseModelInputs($this->searchableInputsKeyword);
150
151
        if (count($searchableModelInputs)) {
152
153
            $query = $query->where(function ($query) use ($searchableModelInputs) {
154
155
                foreach ($searchableModelInputs as $attribute) {
156
157
                    $value = Arr::get($this->searchableInputsKeyword, $attribute, $this->searchableKeyword);
158
159
                    if (filter_nullables($value)) {
160
161
                        $query->orWhere($attribute, 'LIKE', "%{$value}")
162
                            ->orWhere($attribute, 'LIKE', "{$value}%")
163
                            ->orWhere($attribute, 'LIKE', "%{$value}%");
164
                    }
165
                }
166
            });
167
        }
168
169
        return $query;
170
    }
171
172
    /**
173
     * Aplica los 'wheres' que serán aplicado en la relaciones del Modelo.
174
     *
175
     * @param  Builder $query
176
     * @return Builder
177
     */
178
    private function applySearchableRelationConstraints($query): Builder
179
    {
180
        if (filter_nullables($this->searchableKeyword)) {
181
182
            $searchableRelationInputs = $this->parseRelationInputs($this->searchableInputsKeyword);
183
184
            $searchableRelation = $this->parseRelationInputs($this->searchableInputs);
185
186
            foreach ($searchableRelationInputs as $attribute => $columns) {
187
188
                if (!in_array($attribute, array_keys($searchableRelation))) {
189
190
                    $query->orWhereHas($attribute, function ($query) use ($attribute, $searchableRelationInputs) {
191
192
                        $query = $query->where(function ($query) use ($attribute, $searchableRelationInputs) {
0 ignored issues
show
$query is not used, you could remove the assignment.

This check looks for variable assignements that are either overwritten by other assignments or where the variable is not used subsequently.

$myVar = 'Value';
$higher = false;

if (rand(1, 6) > 3) {
    $higher = true;
} else {
    $higher = false;
}

Both the $myVar assignment in line 1 and the $higher assignment in line 2 are dead. The first because $myVar is never used and the second because $higher is always overwritten for every possible time line.

Loading history...
193
194
                            $columns = $searchableRelationInputs[$attribute] ?? [];
195
196
                            foreach ($columns as $column) {
197
198
                                $value = $this->searchableInputsKeyword["{$attribute}:{$column}"] ?? $this->searchableKeyword;
199
200
                                if (filter_nullables($value)) {
201
202
                                    $query->orWhere($column, 'LIKE', "%{$value}")
203
                                        ->orWhere($column, 'LIKE', "{$value}%")
204
                                        ->orWhere($column, 'LIKE', "%{$value}%");
205
                                }
206
                            }
207
                        });
208
                    });
209
                }
210
            }
211
        }
212
213
        return $query;
214
    }
215
216
    /**
217
     * Obtiene el operador del where usado en el armado de la consulta de filterable.
218
     *
219
     * @param  mixed $value
220
     * @return array
221
     */
222
    private function getOperatorFilterable($value): array
223
    {
224
        $operator = is_array($value) ? 'whereIn' : 'where';
225
226
        $value = is_array($value) ? array_filter($value, 'filter_nullables') : str_trimmer($value);
227
228
        return [$operator, $value];
229
    }
230
231
    /**
232
     * Aplicación del los where's de los atributos filterable propios del Modelo.
233
     *
234
     * @param  Builder $query
235
     * @return Builder
236
     */
237
    private function applyFilterableConstraints($query): Builder
238
    {
239
        $filterableModelInputs = $this->parseModelInputs($this->filterableInputs);
240
241
        $filterableModelInputs = Arr::only($this->filterableInputs, $filterableModelInputs);
242
243
        foreach ($filterableModelInputs as $column => $value) {
244
245
            list($operator, $value) = $this->getOperatorFilterable($value);
246
247
            if (is_array($value) and !count($value)) {
248
                break;
249
            }
250
251
            $query->{$operator}($column, $value);
252
        }
253
254
        return $query;
255
    }
256
257
    /**
258
     * Añade la relación y su callback en un array para despues ser llamada via whereHas.
259
     *
260
     * @param  Builder $query
261
     * @return Builder
262
     */
263
    private function addRelationSearchableFromConstraint($query): void
0 ignored issues
show
The parameter $query is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
264
    {
265
        $searchableRelationInputs = $this->parseRelationInputs($this->searchableInputs);
266
267
        if (count($searchableRelationInputs)) {
268
269
            foreach ($searchableRelationInputs as $attribute => $columns) {
270
271
                $this->addRelationConstraints([$attribute => function ($query) use ($attribute, $searchableRelationInputs) {
272
273
                    $columns = $searchableRelationInputs[$attribute] ?? [];
274
275
                    foreach ($columns as $column) {
276
277
                        $value = Arr::get($this->searchableInputs, "{$attribute}:{$column}");
278
279
                        if (filter_nullables($value)) {
280
281
                            $query->where(function ($query) use ($column, $value) {
282
283
                                $query->orWhere($column, 'LIKE', "%{$value}")
284
                                    ->orWhere($column, 'LIKE', "{$value}%")
285
                                    ->orWhere($column, 'LIKE', "%{$value}%");
286
                            });
287
                        }
288
                    }
289
                }]);
290
            }
291
        }
292
    }
293
294
    /**
295
     * Añade la relación y su callback en un array para despues ser llamada via whereHas.
296
     *
297
     * @param  Builder $query
298
     * @return Builder
299
     */
300
    private function addRelationFilterableFromConstraint($query): void
0 ignored issues
show
The parameter $query is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
301
    {
302
        $filterableRelationInputs = $this->parseRelationInputs($this->filterableInputs);
303
304
        if (count($filterableRelationInputs)) {
305
306
            foreach ($filterableRelationInputs as $attribute => $columns) {
307
308
                $this->addRelationConstraints([$attribute => function ($query) use ($attribute, $filterableRelationInputs) {
309
310
                    $columns = $filterableRelationInputs[$attribute] ?? [];
311
312
                    foreach ($columns as $column) {
313
314
                        $value = Arr::get($this->filterableInputs, "{$attribute}:{$column}");
315
316
                        list($operator, $value) = $this->getOperatorFilterable($value);
317
318
                        if (is_array($value) and !count($value)) {
319
                            break;
320
                        }
321
322
                        $query->{$operator}($column, $value);
323
                    }
324
                }]);
325
            }
326
        }
327
    }
328
329
    /**
330
     * Parsea los inputs de búsqueda del Modelo.
331
     *
332
     * @param  Builder $query
333
     * @return Builder
334
     */
335
    private function parseInputsKeywordConstraints($query): Builder
336
    {
337
        $query = $query->where(function ($query) {
338
            $this->applySearchableConstraints($query);
339
            $this->applySearchableRelationConstraints($query);
340
        });
341
342
        $query = $this->applyFilterableConstraints($query);
343
344
        /** Añade los closures a un array para ser luego usado cuando se cargan su relaciones */
345
346
        $this->addRelationSearchableFromConstraint($query);
347
348
        $this->addRelationFilterableFromConstraint($query);
349
350
        return $query;
351
    }
352
353
    /**
354
     * Agrupa las closures por cada relación en {relationConstraints}.
355
     *
356
     * @param  array  $relations
357
     * @return void
358
     */
359
    private function addRelationConstraints(array $relations): void
360
    {
361
        foreach ($relations as $name => $closure) {
362
363
            $this->relationConstraints[$name][] = $closure;
364
        }
365
    }
366
367
    /**
368
     * Sí hay closures en las relaciones, aplica al query y agrupalas
369
     * por cada la relación en {eagerRelationConstraints}.
370
     *
371
     * @param  Builder $query
372
     * @return Builder
373
     */
374
    private function parseRelationConstraints($query): Builder
375
    {
376
        if ($this->relationConstraints) {
377
378
            foreach ($this->relationConstraints as $relation => $constraints) {
379
380
                $this->eagerRelationConstraints[$relation] = function ($query) use ($constraints) {
381
382
                    foreach ($constraints as $constraint) {
383
384
                        $constraint($query);
385
                    }
386
                };
387
            }
388
        }
389
390
        return $query;
391
    }
392
393
    /**
394
     * Aplica los 'closures' que estan en {eagerRelationConstraints} por cada relación vía whereHas.
395
     *
396
     * @param  Builder $query
397
     * @return Builder
398
     */
399
    private function loadRelationContraints($query): Builder
400
    {
401
        if ($this->eagerRelationConstraints) {
402
403
            foreach ($this->eagerRelationConstraints as $relation => $closure) {
404
405
                $query->whereHas($relation, $closure);
406
            }
407
        }
408
409
        return $query;
410
    }
411
412
    /**
413
     * Retorna un array con los inputs 'searchables' cuyo valor será el ingresado en la 'keyword'.
414
     *
415
     * @return array
416
     */
417
    private function getInputsKeyword(): array
418
    {
419
        $arrInputs  = [];
420
421
        $searchableInputsFromModel = $this->getInputsFromModel('searchable', 'searchableInputs');
422
423
        if (count($searchableInputsFromModel)) {
424
425
            foreach ($searchableInputsFromModel as $column) {
426
427
                $arrInputs[$column] = $this->searchableKeyword ?: $this->currentRequest->get($column, null);
428
            }
429
430
            $arrInputs = array_keys_replace($arrInputs, $this->getInputsFromModel('searchable', 'searchableInputs', true));
431
        }
432
433
        return $arrInputs;
434
    }
435
436
    /**
437
     * Obtiene los inputs definidos en el Modelo y que se encuentran en el Request.
438
     *
439
     * @param  string $property
440
     * @param  string $method
441
     * @return array
442
     */
443
    private function getInputsFromRequest($property, $method): array
444
    {
445
        $inputsFromModel = $this->getInputsFromModel($property, $method);
446
447
        $filledInputs = array_filter_empty($this->currentRequest->only($inputsFromModel));
448
449
        $filledInputs = array_keys_replace($filledInputs, $this->getInputsFromModel($property, $method, true));
450
451
        $filledInputs = array_filter_recursive($filledInputs, 'filter_nullables');
452
453
        return $filledInputs;
454
    }
455
456
    /**
457
     * Obtiene los inputs definidos en el Model, tanto en la propiedad como método.
458
     *
459
     * @param  string $property
460
     * @param  string $method
461
     * @return array
462
     */
463
    private function getInputsFromModel($property, $method, $associative = false): array
464
    {
465
        $inputs = [];
466
467
        $inputs = property_exists($this, $property) ? Arr::wrap($this->{$property}) : $inputs;
468
469
        $inputs = method_exists($this, $method) ? Arr::wrap($this->{$method}()) : $inputs;
470
471
        if ($associative) {
472
            return $inputs;
473
        }
474
475
        $inputs = Arr::isAssoc($inputs) ? array_keys($inputs) : $inputs;
476
477
        return $inputs;
478
    }
479
480
    /**
481
     * Obtiene los inputs de searchzy (keyword, searchzy, extra) cuyo valor será el de Request o el definido por defecto.
482
     *
483
     * @link    (https://timacdonald.me/query-scopes-meet-action-scopes/)
484
     * @param   Builder $query
485
     * @param   array   $extra
486
     * @param   string  $default
487
     * @return  array
488
     */
489
    public function scopeSearchzyInputs($query, $extra = [], $default = '', $request = null): array
0 ignored issues
show
The parameter $query is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
490
    {
491
        $this->currentRequest = $request ?: request();
492
493
        $searchable  = $this->getInputsFromModel('searchable', 'searchableInputs');
494
495
        $filterable  = $this->getInputsFromModel('filterable', 'filterableInputs');
496
497
        $aditionable = $this->getInputsFromModel('aditionable', 'aditionableInputs');
498
499
        $extra   = Arr::wrap($extra);
500
501
        $keyword = Arr::wrap(config('searchzy.keyword'));
502
503
        $inputs = array_merge($searchable, $filterable, $aditionable, $keyword, $extra);
504
505
        $inputs = array_filler($this->currentRequest->all(), $inputs, $default);
506
507
        return $inputs;
508
    }
509
}
510