Issues (368)

src/Admin/Grid.php (1 issue)

1
<?php
2
3
namespace Arbory\Base\Admin;
4
5
use Closure;
6
use Illuminate\Support\Arr;
7
use Arbory\Base\Admin\Grid\Row;
8
use Arbory\Base\Admin\Grid\Column;
9
use Arbory\Base\Admin\Grid\Filter;
10
use Illuminate\Support\Collection;
11
use Arbory\Base\Html\Elements\Content;
12
use Illuminate\Database\Eloquent\Model;
13
use Arbory\Base\Admin\Traits\Renderable;
14
use Arbory\Base\Admin\Filter\FilterManager;
15
use Arbory\Base\Admin\Grid\FilterInterface;
16
use Illuminate\Pagination\LengthAwarePaginator;
17
use Illuminate\Contracts\Support\Renderable as RenderableInterface;
18
19
/**
20
 * Class Grid.
21
 */
22
class Grid
23
{
24
    use ModuleComponent;
25
    use Renderable;
26
27
    const FOOTER_SIDE_PRIMARY = 'primary';
28
    const FOOTER_SIDE_SECONDARY = 'secondary';
29
30
    /**
31
     * @var Model
32
     */
33
    protected $model;
34
35
    /**
36
     * @var Collection
37
     */
38
    protected $columns;
39
40
    /**
41
     * @var Collection
42
     */
43
    protected $rows;
44
45
    /**
46
     * @var array
47
     */
48
    protected $enabledDefaultTools = ['create', 'search'];
49
50
    /**
51
     * @var array
52
     */
53
    protected $tools = [];
54
55
    /**
56
     * @var Collection|null
57
     */
58
    protected $items;
59
60
    /**
61
     * @var bool
62
     */
63
    protected $paginated = true;
64
65
    /**
66
     * @var Filter
67
     */
68
    protected $filter;
69
70
    /**
71
     * @var callable
72
     */
73
    protected $rowUrlCallback;
74
75
    /**
76
     * @var callable
77
     */
78
    protected $orderUrlCallback;
79
80
    /**
81
     * @var FilterManager
82
     */
83
    protected $filterManager;
84
85
    /**
86
     * @var bool
87
     */
88
    protected $isExportEnabled = false;
89
90
    /**
91
     * @var bool
92
     */
93
    protected $rememberFilters = false;
94
95
    /**
96
     * @var bool
97
     */
98
    protected $hasToolbox = true;
99
100
    /**
101
     * @param  Model  $model
102
     */
103
    public function __construct(Model $model)
104
    {
105
        $this->model = $model;
106
        $this->columns = new Collection();
107
        $this->rows = new Collection();
108
    }
109
110
    /**
111
     * @return string
112
     */
113
    public function __toString()
114
    {
115
        return (string) $this->render();
116
    }
117
118
    /**
119
     * @param  Closure  $constructor
120
     * @return $this
121
     */
122
    public function setColumns(Closure $constructor): self
123
    {
124
        $constructor($this);
125
126
        return $this;
127
    }
128
129
    /**
130
     * @return void
131
     */
132
    public function setupFilter()
133
    {
134
        $filter = new Filter($this->model);
135
        $filter->setFilterManager($this->getFilterManager());
136
137
        $this->setFilter($filter);
138
    }
139
140
    /**
141
     * @param  FilterInterface  $filter
142
     * @return Grid
143
     */
144
    public function setFilter(FilterInterface $filter)
145
    {
146
        $this->filter = $filter;
147
148
        return $this;
149
    }
150
151
    /**
152
     * @return FilterInterface
153
     */
154
    public function getFilter()
155
    {
156
        return $this->filter;
157
    }
158
159
    /**
160
     * @return Model
161
     */
162
    public function getModel(): Model
163
    {
164
        return $this->model;
165
    }
166
167
    /**
168
     * @param  RenderableInterface  $tool
169
     * @param  string|null  $side
170
     * @return void
171
     */
172
    public function addTool(RenderableInterface $tool, string $side = null)
173
    {
174
        $this->tools[] = [$tool, $side ?: self::FOOTER_SIDE_SECONDARY];
175
    }
176
177
    /**
178
     * @return \Arbory\Base\Admin\Grid
179
     */
180
    public function showToolbox(): self
181
    {
182
        $this->hasToolbox = true;
183
184
        return $this;
185
    }
186
187
    /**
188
     * @return \Arbory\Base\Admin\Grid
189
     */
190
    public function hideToolbox(): self
191
    {
192
        $this->hasToolbox = false;
193
194
        return $this;
195
    }
196
197
    /**
198
     * @return bool
199
     */
200
    public function isToolboxEnable(): bool
201
    {
202
        return $this->hasToolbox;
203
    }
204
205
    /**
206
     * @return array
207
     */
208
    public function getTools()
209
    {
210
        return $this->tools;
211
    }
212
213
    /**
214
     * @param  string[]  $tools
215
     * @return Grid
216
     */
217
    public function tools(array $tools)
218
    {
219
        $this->enabledDefaultTools = $tools;
220
221
        return $this;
222
    }
223
224
    /**
225
     * @param  array|Collection  $items
226
     * @return Grid
227
     */
228
    public function items($items)
229
    {
230
        if (is_array($items)) {
231
            $items = new Collection($items);
232
        }
233
234
        $this->items = $items;
235
236
        return $this;
237
    }
238
239
    /**
240
     * @return LengthAwarePaginator|Collection|null
241
     */
242
    public function getItems()
243
    {
244
        if ($this->items === null) {
245
            $this->items = $this->fetchData();
246
        }
247
248
        return $this->items;
249
    }
250
251
    /**
252
     * @param  bool  $paginate
253
     * @return Grid
254
     */
255
    public function paginate(bool $paginate = true)
256
    {
257
        $this->paginated = $paginate;
258
259
        return $this;
260
    }
261
262
    /**
263
     * @return Collection|Column[]
264
     */
265
    public function getColumns()
266
    {
267
        return $this->columns;
268
    }
269
270
    /**
271
     * @return Collection
272
     */
273
    public function getRows()
274
    {
275
        return $this->rows;
276
    }
277
278
    /**
279
     * @param  string|null  $name
280
     * @param  string|null  $label
281
     * @return Column
282
     */
283
    public function column($name = null, $label = null): Column
284
    {
285
        return $this->appendColumn($name, $label);
286
    }
287
288
    /**
289
     * @param  string|null  $name
290
     * @param  string|null  $label
291
     * @return Column
292
     */
293
    public function appendColumn($name = null, $label = null): Column
294
    {
295
        $column = $this->createColumn($name, $label);
296
        $this->columns->push($column);
297
298
        $this->setColumnRelation($column, $name);
299
300
        return $column;
301
    }
302
303
    /**
304
     * @param  string|null  $name
305
     * @param  string|null  $label
306
     * @return Column
307
     */
308
    public function prependColumn($name = null, $label = null): Column
309
    {
310
        $column = $this->createColumn($name, $label);
311
        $this->columns->prepend($column);
312
313
        $this->setColumnRelation($column, $name);
314
315
        return $column;
316
    }
317
318
    /**
319
     * @param  string  $column
320
     * @param  string  $name
321
     * @return mixed
322
     */
323
    protected function setColumnRelation($column, $name): Column
324
    {
325
        if (strpos($name, '.') !== false) {
326
            [$relationName, $relationColumn] = explode('.', $name);
327
328
            $this->filter->withRelation($relationName);
329
            $column->setRelation($relationName, $relationColumn);
330
        }
331
332
        return $column;
333
    }
334
335
    /**
336
     * @param  string|null  $name
337
     * @param  string|null  $label
338
     * @return Column
339
     */
340
    protected function createColumn($name = null, $label = null): Column
341
    {
342
        $column = new Column($name, $label);
343
        $column->setGrid($this);
344
345
        return $column;
346
    }
347
348
    /**
349
     * @param  Collection|LengthAwarePaginator  $items
350
     */
351
    protected function buildRows($items)
352
    {
353
        if ($items instanceof LengthAwarePaginator) {
354
            $items = new Collection($items->items());
355
        }
356
357
        $this->rows = $items->map(function ($model) {
358
            return new Row($this, $model);
359
        });
360
    }
361
362
    /**
363
     * @param  Closure  $callback
364
     */
365
    public function filter(Closure $callback)
366
    {
367
        call_user_func($callback, $this->filter);
368
    }
369
370
    /**
371
     * @return LengthAwarePaginator|Collection
372
     */
373
    protected function fetchData()
374
    {
375
        if (method_exists($this->filter, 'setPaginated')) {
376
            $this->filter->setPaginated($this->paginated);
377
        }
378
379
        return $this->filter->execute($this->getColumns())->loadItems();
0 ignored issues
show
Bug Best Practice introduced by
The expression return $this->filter->ex...Columns())->loadItems() also could return the type Illuminate\Database\Eloq...base\Eloquent\Builder[] which is incompatible with the documented return type Illuminate\Pagination\Le...nate\Support\Collection.
Loading history...
380
    }
381
382
    /**
383
     * @return Content
384
     */
385
    public function render()
386
    {
387
        $this->buildRows($this->getItems());
388
389
        return $this->renderer->render();
390
    }
391
392
    /**
393
     * @return string[]
394
     */
395
    public function getEnabledDefaultTools(): array
396
    {
397
        return $this->enabledDefaultTools;
398
    }
399
400
    /**
401
     * @return bool
402
     */
403
    public function isPaginated(): bool
404
    {
405
        return $this->paginated;
406
    }
407
408
    /**
409
     * @return bool
410
     */
411
    public function hasTools(): bool
412
    {
413
        return ! empty($this->enabledDefaultTools);
414
    }
415
416
    /**
417
     * @param  string  $tool
418
     * @return bool
419
     */
420
    public function hasTool(string $tool): bool
421
    {
422
        return in_array($tool, $this->enabledDefaultTools, false);
423
    }
424
425
    /**
426
     * @param  Model  $model
427
     * @return string|null
428
     */
429
    public function getRowUrl(Model $model): ?string
430
    {
431
        $filterParameters = $this->getFilterParameters();
432
        $params = [];
433
434
        if ($customUrlOpener = $this->getRowUrlCallback()) {
435
            return $customUrlOpener($model, $this, $filterParameters);
436
        }
437
438
        if ($this->rememberFilters()) {
439
            $params = [
440
                Form::INPUT_RETURN_URL => $this->getModule()->url('index', $filterParameters),
441
            ];
442
        }
443
444
        if ($this->hasTool('create')) {
445
            return $this->getModule()->url('edit', [$model->getKey()] + $params);
446
        }
447
448
        return null;
449
    }
450
451
    /**
452
     * @return callable|null
453
     */
454
    public function getRowUrlCallback(): ?callable
455
    {
456
        return $this->rowUrlCallback;
457
    }
458
459
    /**
460
     * @param  callable  $rowUrlCallback
461
     * @return Grid
462
     */
463
    public function setRowUrlCallback(callable $rowUrlCallback): self
464
    {
465
        $this->rowUrlCallback = $rowUrlCallback;
466
467
        return $this;
468
    }
469
470
    /**
471
     * @param  Column  $column
472
     * @return string|null
473
     */
474
    public function getColumnOrderUrl(Column $column): ?string
475
    {
476
        $params = $this->getFilterParameters();
477
        $params['_order_by'] = $column->getName();
478
        $params['_order'] = Arr::get($params, '_order') === 'ASC' ? 'DESC' : 'ASC';
479
480
        if ($callback = $this->getOrderUrlCallback()) {
481
            return $callback($column, $this, $params);
482
        }
483
484
        return $this->getModule()->url('index', $params);
485
    }
486
487
    /**
488
     * @return callable|null
489
     */
490
    public function getOrderUrlCallback(): ?callable
491
    {
492
        return $this->orderUrlCallback;
493
    }
494
495
    /**
496
     * @param  callable  $orderUrlCallback
497
     * @return Grid
498
     */
499
    public function setOrderUrlCallback(callable $orderUrlCallback): self
500
    {
501
        $this->orderUrlCallback = $orderUrlCallback;
502
    }
503
504
    /**
505
     * @return bool
506
     */
507
    public function isExportEnabled(): bool
508
    {
509
        return $this->isExportEnabled;
510
    }
511
512
    /**
513
     * @return $this
514
     */
515
    public function exportEnabled(): self
516
    {
517
        $this->isExportEnabled = true;
518
519
        return $this;
520
    }
521
522
    /**
523
     * @return $this
524
     */
525
    public function exportDisabled(): self
526
    {
527
        $this->isExportEnabled = false;
528
529
        return $this;
530
    }
531
532
    /**
533
     * @return array
534
     */
535
    public function toArray(): array
536
    {
537
        $items = $this->fetchData();
538
539
        $this->buildRows($items);
540
541
        $columns = $this->columns->map(function (Column $column) {
542
            return (string) $column;
543
        })->toArray();
544
545
        return $this->rows->map(function (Row $row) use ($columns) {
546
            return array_combine($columns, $row->toArray());
547
        })->toArray();
548
    }
549
550
    /**
551
     * @return FilterManager
552
     */
553
    public function getFilterManager(): FilterManager
554
    {
555
        return $this->filterManager;
556
    }
557
558
    /**
559
     * @param  FilterManager  $filterManager
560
     * @return Grid
561
     */
562
    public function setFilterManager(FilterManager $filterManager): self
563
    {
564
        $filterManager->setModule($this->getModule());
565
        $this->filterManager = $filterManager;
566
567
        return $this;
568
    }
569
570
    /**
571
     * @param  bool  $rememberFilters
572
     * @return Grid
573
     */
574
    public function setRememberFilters(bool $rememberFilters): self
575
    {
576
        $this->rememberFilters = $rememberFilters;
577
578
        return $this;
579
    }
580
581
    /**
582
     * @return bool
583
     */
584
    public function rememberFilters(): bool
585
    {
586
        return $this->rememberFilters;
587
    }
588
589
    /**
590
     * @return array|null
591
     */
592
    protected function getFilterParameters(): ?array
593
    {
594
        $filterParameters = $this->getFilterManager()->getParameters();
595
        $params = request()->only(['search', '_order', '_order_by']);
596
597
        if (! $filterParameters->isEmpty()) {
598
            $params[$filterParameters->getNamespace()] = $filterParameters->toArray();
599
        }
600
601
        return $params;
602
    }
603
}
604