Completed
Push — master ( 688f04...82dfd5 )
by Song
10:39 queued 08:23
created

Filter::scopeSeparator()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 1
nc 1
nop 0
dl 0
loc 4
rs 10
c 0
b 0
f 0
1
<?php
2
3
namespace Encore\Admin\Grid;
4
5
use Encore\Admin\Grid\Filter\AbstractFilter;
6
use Encore\Admin\Grid\Filter\Layout\Layout;
7
use Encore\Admin\Grid\Filter\Scope;
8
use Illuminate\Contracts\Support\Arrayable;
9
use Illuminate\Contracts\Support\Renderable;
10
use Illuminate\Support\Arr;
11
use Illuminate\Support\Collection;
12
use Illuminate\Support\Str;
13
14
/**
15
 * Class Filter.
16
 *
17
 * @method AbstractFilter equal($column, $label = '')
18
 * @method AbstractFilter notEqual($column, $label = '')
19
 * @method AbstractFilter leftLike($column, $label = '')
20
 * @method AbstractFilter like($column, $label = '')
21
 * @method AbstractFilter contains($column, $label = '')
22
 * @method AbstractFilter startsWith($column, $label = '')
23
 * @method AbstractFilter endsWith($column, $label = '')
24
 * @method AbstractFilter ilike($column, $label = '')
25
 * @method AbstractFilter gt($column, $label = '')
26
 * @method AbstractFilter lt($column, $label = '')
27
 * @method AbstractFilter between($column, $label = '')
28
 * @method AbstractFilter in($column, $label = '')
29
 * @method AbstractFilter notIn($column, $label = '')
30
 * @method AbstractFilter where($callback, $label = '', $column = null)
31
 * @method AbstractFilter date($column, $label = '')
32
 * @method AbstractFilter day($column, $label = '')
33
 * @method AbstractFilter month($column, $label = '')
34
 * @method AbstractFilter year($column, $label = '')
35
 * @method AbstractFilter hidden($name, $value)
36
 * @method AbstractFilter group($column, $label = '', $builder = null)
37
 */
38
class Filter implements Renderable
39
{
40
    /**
41
     * @var Model
42
     */
43
    protected $model;
44
45
    /**
46
     * @var array
47
     */
48
    protected $filters = [];
49
50
    /**
51
     * @var array
52
     */
53
    protected static $supports = [
54
        'equal'      => Filter\Equal::class,
55
        'notEqual'   => Filter\NotEqual::class,
56
        'ilike'      => Filter\Ilike::class,
57
        'like'       => Filter\Like::class,
58
        'gt'         => Filter\Gt::class,
59
        'lt'         => Filter\Lt::class,
60
        'between'    => Filter\Between::class,
61
        'group'      => Filter\Group::class,
62
        'where'      => Filter\Where::class,
63
        'in'         => Filter\In::class,
64
        'notIn'      => Filter\NotIn::class,
65
        'date'       => Filter\Date::class,
66
        'day'        => Filter\Day::class,
67
        'month'      => Filter\Month::class,
68
        'year'       => Filter\Year::class,
69
        'hidden'     => Filter\Hidden::class,
70
        'contains'   => Filter\Like::class,
71
        'startsWith' => Filter\StartsWith::class,
72
        'endsWith'   => Filter\EndsWith::class,
73
    ];
74
75
    /**
76
     * If use id filter.
77
     *
78
     * @var bool
79
     */
80
    protected $useIdFilter = true;
81
82
    /**
83
     * Id filter was removed.
84
     *
85
     * @var bool
86
     */
87
    protected $idFilterRemoved = false;
88
89
    /**
90
     * Action of search form.
91
     *
92
     * @var string
93
     */
94
    protected $action;
95
96
    /**
97
     * @var string
98
     */
99
    protected $view = 'admin::filter.container';
100
101
    /**
102
     * @var string
103
     */
104
    protected $filterID = 'filter-box';
105
106
    /**
107
     * @var string
108
     */
109
    protected $name = '';
110
111
    /**
112
     * @var bool
113
     */
114
    public $expand = false;
115
116
    /**
117
     * @var Collection
118
     */
119
    protected $scopes;
120
121
    /**
122
     * @var Layout
123
     */
124
    protected $layout;
125
126
    /**
127
     * Set this filter only in the layout.
128
     *
129
     * @var bool
130
     */
131
    protected $thisFilterLayoutOnly = false;
132
133
    /**
134
     * Columns of filter that are layout-only.
135
     *
136
     * @var array
137
     */
138
    protected $layoutOnlyFilterColumns = [];
139
140
    /**
141
     * Primary key of giving model.
142
     *
143
     * @var mixed
144
     */
145
    protected $primaryKey;
146
147
    /**
148
     * Create a new filter instance.
149
     *
150
     * @param Model $model
151
     */
152
    public function __construct(Model $model)
153
    {
154
        $this->model = $model;
155
156
        $this->primaryKey = $this->model->eloquent()->getKeyName();
157
158
        $this->initLayout();
159
160
        $this->equal($this->primaryKey, strtoupper($this->primaryKey));
161
        $this->scopes = new Collection();
162
    }
163
164
    /**
165
     * Initialize filter layout.
166
     */
167
    protected function initLayout()
168
    {
169
        $this->layout = new Filter\Layout\Layout($this);
170
    }
171
172
    /**
173
     * Set action of search form.
174
     *
175
     * @param string $action
176
     *
177
     * @return $this
178
     */
179
    public function setAction($action)
180
    {
181
        $this->action = $action;
182
183
        return $this;
184
    }
185
186
    /**
187
     * Get grid model.
188
     *
189
     * @return Model
190
     */
191
    public function getModel()
192
    {
193
        $conditions = array_merge(
194
            $this->conditions(),
195
            $this->scopeConditions()
196
        );
197
198
        return $this->model->addConditions($conditions);
199
    }
200
201
    /**
202
     * Set ID of search form.
203
     *
204
     * @param string $filterID
205
     *
206
     * @return $this
207
     */
208
    public function setFilterID($filterID)
209
    {
210
        $this->filterID = $filterID;
211
212
        return $this;
213
    }
214
215
    /**
216
     * Get filter ID.
217
     *
218
     * @return string
219
     */
220
    public function getFilterID()
221
    {
222
        return $this->filterID;
223
    }
224
225
    /**
226
     * @param $name
227
     *
228
     * @return $this
229
     */
230
    public function setName($name)
231
    {
232
        $this->name = $name;
233
234
        $this->setFilterID("{$this->name}-{$this->filterID}");
235
236
        return $this;
237
    }
238
239
    /**
240
     * @return string
241
     */
242
    public function getName()
243
    {
244
        return $this->name;
245
    }
246
247
    /**
248
     * Disable Id filter.
249
     *
250
     * @return $this
251
     */
252
    public function disableIdFilter(bool $disable = true)
253
    {
254
        $this->useIdFilter = !$disable;
255
256
        return $this;
257
    }
258
259
    /**
260
     * Remove ID filter if needed.
261
     */
262
    public function removeIDFilterIfNeeded()
263
    {
264
        if (!$this->useIdFilter && !$this->idFilterRemoved) {
265
            $this->removeDefaultIDFilter();
266
267
            $this->layout->removeDefaultIDFilter();
268
269
            $this->idFilterRemoved = true;
270
        }
271
    }
272
273
    /**
274
     * Remove the default ID filter.
275
     */
276
    protected function removeDefaultIDFilter()
277
    {
278
        array_shift($this->filters);
279
    }
280
281
    /**
282
     * Remove filter by filter id.
283
     *
284
     * @param mixed $id
285
     */
286
    public function removeFilterByID($id)
287
    {
288
        $this->filters = array_filter($this->filters, function (AbstractFilter $filter) use ($id) {
289
            return $filter->getId() != $id;
290
        });
291
    }
292
293
    /**
294
     * Get all conditions of the filters.
295
     *
296
     * @return array
297
     */
298
    public function conditions()
299
    {
300
        $inputs = Arr::dot(request()->all());
301
302
        $inputs = array_filter($inputs, function ($input) {
303
            return $input !== '' && !is_null($input);
304
        });
305
306
        $this->sanitizeInputs($inputs);
307
308
        if (empty($inputs)) {
309
            return [];
310
        }
311
312
        $params = [];
313
314
        foreach ($inputs as $key => $value) {
315
            Arr::set($params, $key, $value);
316
        }
317
318
        $conditions = [];
319
320
        $this->removeIDFilterIfNeeded();
321
322
        foreach ($this->filters() as $filter) {
323
            if (in_array($column = $filter->getColumn(), $this->layoutOnlyFilterColumns)) {
324
                $filter->default(Arr::get($params, $column));
325
            } else {
326
                $conditions[] = $filter->condition($params);
327
            }
328
        }
329
330
        return tap(array_filter($conditions), function ($conditions) {
331
            if (!empty($conditions)) {
332
                $this->expand();
333
            }
334
        });
335
    }
336
337
    /**
338
     * @param $inputs
339
     *
340
     * @return array
341
     */
342
    protected function sanitizeInputs(&$inputs)
343
    {
344
        if (!$this->name) {
345
            return $inputs;
346
        }
347
348
        $inputs = collect($inputs)->filter(function ($input, $key) {
349
            return Str::startsWith($key, "{$this->name}_");
350
        })->mapWithKeys(function ($val, $key) {
351
            $key = str_replace("{$this->name}_", '', $key);
352
353
            return [$key => $val];
354
        })->toArray();
355
    }
356
357
    /**
358
     * Set this filter layout only.
359
     *
360
     * @return $this
361
     */
362
    public function layoutOnly()
363
    {
364
        $this->thisFilterLayoutOnly = true;
365
366
        return $this;
367
    }
368
369
    /**
370
     * Add a filter to grid.
371
     *
372
     * @param AbstractFilter $filter
373
     *
374
     * @return AbstractFilter
375
     */
376
    protected function addFilter(AbstractFilter $filter)
377
    {
378
        $this->layout->addFilter($filter);
379
380
        $filter->setParent($this);
381
382
        if ($this->thisFilterLayoutOnly) {
383
            $this->thisFilterLayoutOnly = false;
384
            $this->layoutOnlyFilterColumns[] = $filter->getColumn();
385
        }
386
387
        return $this->filters[] = $filter;
388
    }
389
390
    /**
391
     * Use a custom filter.
392
     *
393
     * @param AbstractFilter $filter
394
     *
395
     * @return AbstractFilter
396
     */
397
    public function use(AbstractFilter $filter)
398
    {
399
        return $this->addFilter($filter);
400
    }
401
402
    /**
403
     * Get all filters.
404
     *
405
     * @return AbstractFilter[]
406
     */
407
    public function filters()
408
    {
409
        return $this->filters;
410
    }
411
412
    /**
413
     * @param string $key
414
     * @param string $label
415
     *
416
     * @return mixed
417
     */
418
    public function scope($key, $label = '')
419
    {
420
        return tap(new Scope($key, $label), function (Scope $scope) {
421
            return $this->scopes->push($scope);
422
        });
423
    }
424
425
    /**
426
     * Add separator in filter scope.
427
     *
428
     * @return mixed
429
     */
430
    public function scopeSeparator()
431
    {
432
        return $this->scope(Scope::SEPARATOR);
433
    }
434
435
    /**
436
     * Get all filter scopes.
437
     *
438
     * @return Collection
439
     */
440
    public function getScopes()
441
    {
442
        return $this->scopes;
443
    }
444
445
    /**
446
     * Get current scope.
447
     *
448
     * @return Scope|null
449
     */
450
    public function getCurrentScope()
451
    {
452
        $key = request(Scope::QUERY_NAME);
453
454
        return $this->scopes->first(function ($scope) use ($key) {
455
            return $scope->key == $key;
456
        });
457
    }
458
459
    /**
460
     * Get scope conditions.
461
     *
462
     * @return array
463
     */
464
    protected function scopeConditions()
465
    {
466
        if ($scope = $this->getCurrentScope()) {
467
            return $scope->condition();
468
        }
469
470
        return [];
471
    }
472
473
    /**
474
     * Add a new layout column.
475
     *
476
     * @param int      $width
477
     * @param \Closure $closure
478
     *
479
     * @return $this
480
     */
481 View Code Duplication
    public function column($width, \Closure $closure)
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
482
    {
483
        $width = $width < 1 ? round(12 * $width) : $width;
484
485
        $this->layout->column($width, $closure);
486
487
        return $this;
488
    }
489
490
    /**
491
     * Expand filter container.
492
     *
493
     * @return $this
494
     */
495
    public function expand()
496
    {
497
        $this->expand = true;
498
499
        return $this;
500
    }
501
502
    /**
503
     * Execute the filter with conditions.
504
     *
505
     * @param bool $toArray
506
     *
507
     * @return array|Collection|mixed
508
     */
509
    public function execute($toArray = true)
510
    {
511
        if (method_exists($this->model->eloquent(), 'paginate')) {
512
            $this->model->usePaginate(true);
513
514
            return $this->model->buildData($toArray);
515
        }
516
        $conditions = array_merge(
517
            $this->conditions(),
518
            $this->scopeConditions()
519
        );
520
521
        return $this->model->addConditions($conditions)->buildData($toArray);
522
    }
523
524
    /**
525
     * @param callable $callback
526
     * @param int      $count
527
     *
528
     * @return bool
529
     */
530
    public function chunk(callable $callback, $count = 100)
531
    {
532
        $conditions = array_merge(
533
            $this->conditions(),
534
            $this->scopeConditions()
535
        );
536
537
        return $this->model->addConditions($conditions)->chunk($callback, $count);
538
    }
539
540
    /**
541
     * Get the string contents of the filter view.
542
     *
543
     * @return \Illuminate\View\View|string
544
     */
545
    public function render()
546
    {
547
        $this->removeIDFilterIfNeeded();
548
549
        if (empty($this->filters)) {
550
            return '';
551
        }
552
553
        return view($this->view)->with([
0 ignored issues
show
Bug introduced by
The method with does only exist in Illuminate\View\View, but not in Illuminate\Contracts\View\Factory.

It seems like the method you are trying to call exists only in some of the possible types.

Let’s take a look at an example:

class A
{
    public function foo() { }
}

class B extends A
{
    public function bar() { }
}

/**
 * @param A|B $x
 */
function someFunction($x)
{
    $x->foo(); // This call is fine as the method exists in A and B.
    $x->bar(); // This method only exists in B and might cause an error.
}

Available Fixes

  1. Add an additional type-check:

    /**
     * @param A|B $x
     */
    function someFunction($x)
    {
        $x->foo();
    
        if ($x instanceof B) {
            $x->bar();
        }
    }
    
  2. Only allow a single type to be passed if the variable comes from a parameter:

    function someFunction(B $x) { /** ... */ }
    
Loading history...
554
            'action'   => $this->action ?: $this->urlWithoutFilters(),
555
            'layout'   => $this->layout,
556
            'filterID' => $this->filterID,
557
            'expand'   => $this->expand,
558
        ])->render();
559
    }
560
561
    /**
562
     * Get url without filter queryString.
563
     *
564
     * @return string
565
     */
566
    public function urlWithoutFilters()
567
    {
568
        /** @var Collection $columns */
569
        $columns = collect($this->filters)->map->getColumn()->flatten();
570
571
        $pageKey = 'page';
572
573
        if ($gridName = $this->model->getGrid()->getName()) {
574
            $pageKey = "{$gridName}_{$pageKey}";
575
        }
576
577
        $columns->push($pageKey);
578
579
        $groupNames = collect($this->filters)->filter(function ($filter) {
580
            return $filter instanceof Filter\Group;
581
        })->map(function (AbstractFilter $filter) {
582
            return "{$filter->getId()}_group";
583
        });
584
585
        return $this->fullUrlWithoutQuery(
586
            $columns->merge($groupNames)
587
        );
588
    }
589
590
    /**
591
     * Get url without scope queryString.
592
     *
593
     * @return string
594
     */
595
    public function urlWithoutScopes()
596
    {
597
        return $this->fullUrlWithoutQuery(Scope::QUERY_NAME);
598
    }
599
600
    /**
601
     * Get full url without query strings.
602
     *
603
     * @param Arrayable|array|string $keys
604
     *
605
     * @return string
606
     */
607
    protected function fullUrlWithoutQuery($keys)
608
    {
609
        if ($keys instanceof Arrayable) {
610
            $keys = $keys->toArray();
611
        }
612
613
        $keys = (array) $keys;
614
615
        $request = request();
616
617
        $query = $request->query();
618
        Arr::forget($query, $keys);
619
620
        $question = $request->getBaseUrl().$request->getPathInfo() == '/' ? '/?' : '?';
621
622
        return count($request->query()) > 0
623
            ? $request->url().$question.http_build_query($query)
624
            : $request->fullUrl();
625
    }
626
627
    /**
628
     * @param string $name
629
     * @param string $filterClass
630
     */
631
    public static function extend($name, $filterClass)
632
    {
633
        if (!is_subclass_of($filterClass, AbstractFilter::class)) {
0 ignored issues
show
Bug introduced by
Due to PHP Bug #53727, is_subclass_of might return inconsistent results on some PHP versions if \Encore\Admin\Grid\Filter\AbstractFilter::class can be an interface. If so, you could instead use ReflectionClass::implementsInterface.
Loading history...
634
            throw new \InvalidArgumentException("The class [$filterClass] must be a type of ".AbstractFilter::class.'.');
635
        }
636
637
        static::$supports[$name] = $filterClass;
638
    }
639
640
    /**
641
     * @param string $abstract
642
     * @param array  $arguments
643
     *
644
     * @return AbstractFilter
645
     */
646
    public function resolveFilter($abstract, $arguments)
647
    {
648
        if (isset(static::$supports[$abstract])) {
649
            return new static::$supports[$abstract](...$arguments);
650
        }
651
    }
652
653
    /**
654
     * Generate a filter object and add to grid.
655
     *
656
     * @param string $method
657
     * @param array  $arguments
658
     *
659
     * @return AbstractFilter|$this
660
     */
661
    public function __call($method, $arguments)
662
    {
663
        if ($filter = $this->resolveFilter($method, $arguments)) {
664
            return $this->addFilter($filter);
665
        }
666
667
        return $this;
668
    }
669
}
670