Completed
Push — master ( 68b94f...3dffbe )
by Song
03:57
created

Column::setOriginalGridData()   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 1
dl 0
loc 4
rs 10
c 0
b 0
f 0
1
<?php
2
3
namespace Encore\Admin\Grid;
4
5
use Closure;
6
use Encore\Admin\Grid;
7
use Encore\Admin\Grid\Displayers\AbstractDisplayer;
8
use Illuminate\Contracts\Support\Arrayable;
9
use Illuminate\Database\Eloquent\Model;
0 ignored issues
show
Bug introduced by
This use statement conflicts with another class in this namespace, Encore\Admin\Grid\Model.

Let’s assume that you have a directory layout like this:

.
|-- OtherDir
|   |-- Bar.php
|   `-- Foo.php
`-- SomeDir
    `-- Foo.php

and let’s assume the following content of Bar.php:

// Bar.php
namespace OtherDir;

use SomeDir\Foo; // This now conflicts the class OtherDir\Foo

If both files OtherDir/Foo.php and SomeDir/Foo.php are loaded in the same runtime, you will see a PHP error such as the following:

PHP Fatal error:  Cannot use SomeDir\Foo as Foo because the name is already in use in OtherDir/Foo.php

However, as OtherDir/Foo.php does not necessarily have to be loaded and the error is only triggered if it is loaded before OtherDir/Bar.php, this problem might go unnoticed for a while. In order to prevent this error from surfacing, you must import the namespace with a different alias:

// Bar.php
namespace OtherDir;

use SomeDir\Foo as SomeDirFoo; // There is no conflict anymore.
Loading history...
10
use Illuminate\Support\Facades\URL;
11
use Illuminate\Support\Str;
12
13
class Column
14
{
15
    /**
16
     * @var Grid
17
     */
18
    protected $grid;
19
20
    /**
21
     * Name of column.
22
     *
23
     * @var string
24
     */
25
    protected $name;
26
27
    /**
28
     * Label of column.
29
     *
30
     * @var string
31
     */
32
    protected $label;
33
34
    /**
35
     * Original value of column.
36
     *
37
     * @var mixed
38
     */
39
    protected $original;
40
41
    /**
42
     * Is column sortable.
43
     *
44
     * @var bool
45
     */
46
    protected $sortable = false;
47
48
    /**
49
     * Sort arguments.
50
     *
51
     * @var array
52
     */
53
    protected $sort;
54
55
    /**
56
     * Attributes of column.
57
     *
58
     * @var array
59
     */
60
    protected $attributes = [];
61
62
    /**
63
     * Relation name.
64
     *
65
     * @var bool
66
     */
67
    protected $relation = false;
68
69
    /**
70
     * Relation column.
71
     *
72
     * @var string
73
     */
74
    protected $relationColumn;
75
76
    /**
77
     * Original grid data.
78
     *
79
     * @var array
80
     */
81
    protected static $originalGridData = [];
82
83
    /**
84
     * @var []Closure
85
     */
86
    protected $displayCallbacks = [];
87
88
    /**
89
     * Displayers for grid column.
90
     *
91
     * @var array
92
     */
93
    public static $displayers = [];
94
95
    /**
96
     * Defined columns.
97
     *
98
     * @var array
99
     */
100
    public static $defined = [];
101
102
    /**
103
     * @var array
104
     */
105
    protected static $htmlAttributes = [];
106
107
    /**
108
     * @var
109
     */
110
    protected static $model;
111
112
    const SELECT_COLUMN_NAME = '__row_selector__';
113
114
    /**
115
     * @param string $name
116
     * @param string $label
117
     */
118
    public function __construct($name, $label)
119
    {
120
        $this->name = $name;
121
122
        $this->label = $this->formatLabel($label);
123
    }
124
125
    /**
126
     * Extend column displayer.
127
     *
128
     * @param $name
129
     * @param $displayer
130
     */
131
    public static function extend($name, $displayer)
132
    {
133
        static::$displayers[$name] = $displayer;
134
    }
135
136
    /**
137
     * Define a column globally.
138
     *
139
     * @param string $name
140
     * @param mixed  $definition
141
     */
142
    public static function define($name, $definition)
143
    {
144
        static::$defined[$name] = $definition;
145
    }
146
147
    /**
148
     * Set grid instance for column.
149
     *
150
     * @param Grid $grid
151
     */
152
    public function setGrid(Grid $grid)
153
    {
154
        $this->grid = $grid;
155
156
        $this->setModel($grid->model()->eloquent());
157
    }
158
159
    /**
160
     * Set model for column.
161
     *
162
     * @param $model
163
     */
164
    public function setModel($model)
165
    {
166
        if (is_null(static::$model) && ($model instanceof Model)) {
167
            static::$model = $model->newInstance();
168
        }
169
    }
170
171
    /**
172
     * Set original data for column.
173
     *
174
     * @param array $input
175
     */
176
    public static function setOriginalGridData(array $input)
177
    {
178
        static::$originalGridData = $input;
179
    }
180
181
    /**
182
     * Set column attributes.
183
     *
184
     * @param array $attributes
185
     *
186
     * @return $this
187
     */
188
    public function setAttributes($attributes = [])
189
    {
190
        static::$htmlAttributes[$this->name] = $attributes;
191
192
        return $this;
193
    }
194
195
    /**
196
     * Get column attributes.
197
     *
198
     * @param string $name
199
     *
200
     * @return mixed
201
     */
202
    public static function getAttributes($name)
203
    {
204
        return array_get(static::$htmlAttributes, $name, '');
205
    }
206
207
    /**
208
     * Set style of this column.
209
     *
210
     * @param string $style
211
     *
212
     * @return Column
213
     */
214
    public function style($style)
215
    {
216
        return $this->setAttributes(compact('style'));
217
    }
218
219
    /**
220
     * Get name of this column.
221
     *
222
     * @return mixed
223
     */
224
    public function getName()
225
    {
226
        return $this->name;
227
    }
228
229
    /**
230
     * Format label.
231
     *
232
     * @param $label
233
     *
234
     * @return mixed
235
     */
236
    protected function formatLabel($label)
237
    {
238
        $label = $label ?: ucfirst($this->name);
239
240
        return str_replace(['.', '_'], ' ', $label);
241
    }
242
243
    /**
244
     * Get label of the column.
245
     *
246
     * @return mixed
247
     */
248
    public function getLabel()
249
    {
250
        return $this->label;
251
    }
252
253
    /**
254
     * Set relation.
255
     *
256
     * @param string $relation
257
     * @param string $relationColumn
258
     *
259
     * @return $this
260
     */
261
    public function setRelation($relation, $relationColumn = null)
262
    {
263
        $this->relation = $relation;
0 ignored issues
show
Documentation Bug introduced by
The property $relation was declared of type boolean, but $relation is of type string. Maybe add a type cast?

This check looks for assignments to scalar types that may be of the wrong type.

To ensure the code behaves as expected, it may be a good idea to add an explicit type cast.

$answer = 42;

$correct = false;

$correct = (bool) $answer;
Loading history...
264
        $this->relationColumn = $relationColumn;
265
266
        return $this;
267
    }
268
269
    /**
270
     * If this column is relation column.
271
     *
272
     * @return bool
273
     */
274
    protected function isRelation()
275
    {
276
        return (bool) $this->relation;
277
    }
278
279
    /**
280
     * Mark this column as sortable.
281
     *
282
     * @return Column
283
     */
284
    public function sortable()
285
    {
286
        $this->sortable = true;
287
288
        return $this;
289
    }
290
291
    /**
292
     * Add a display callback.
293
     *
294
     * @param Closure $callback
295
     *
296
     * @return $this
297
     */
298
    public function display(Closure $callback)
299
    {
300
        $this->displayCallbacks[] = $callback;
301
302
        return $this;
303
    }
304
305
    /**
306
     * Display column using array value map.
307
     *
308
     * @param array $values
309
     * @param null  $default
310
     *
311
     * @return $this
312
     */
313
    public function using(array $values, $default = null)
314
    {
315
        return $this->display(function ($value) use ($values, $default) {
316
            return array_get($values, $value, $default);
317
        });
318
    }
319
320
    /**
321
     * If has display callbacks.
322
     *
323
     * @return bool
324
     */
325
    protected function hasDisplayCallbacks()
326
    {
327
        return !empty($this->displayCallbacks);
328
    }
329
330
    /**
331
     * Call all of the "display" callbacks column.
332
     *
333
     * @param mixed $value
334
     * @param int   $key
335
     *
336
     * @return mixed
337
     */
338
    protected function callDisplayCallbacks($value, $key)
339
    {
340
        foreach ($this->displayCallbacks as $callback) {
341
            $callback = $this->bindOriginalRow($callback, $key);
342
            $value = call_user_func($callback, $value);
343
        }
344
345
        return $value;
346
    }
347
348
    /**
349
     * Set original grid data to column.
350
     *
351
     * @param Closure $callback
352
     * @param int     $key
353
     *
354
     * @return Closure
355
     */
356
    protected function bindOriginalRow(Closure $callback, $key)
357
    {
358
        $originalRow = static::$originalGridData[$key];
359
360
        return $callback->bindTo(static::$model->newFromBuilder($originalRow));
361
    }
362
363
    /**
364
     * Fill all data to every column.
365
     *
366
     * @param array $data
367
     *
368
     * @return mixed
369
     */
370
    public function fill(array $data)
371
    {
372
        foreach ($data as $key => &$row) {
373
            $this->original = $value = array_get($row, $this->name);
374
375
            $value = $this->htmlEntityEncode($value);
376
377
            array_set($row, $this->name, $value);
378
379
            if ($this->isDefinedColumn()) {
380
                $this->useDefinedColumn();
381
            }
382
383
            if ($this->hasDisplayCallbacks()) {
384
                $value = $this->callDisplayCallbacks($this->original, $key);
385
                array_set($row, $this->name, $value);
386
            }
387
        }
388
389
        return $data;
390
    }
391
392
    /**
393
     * If current column is a defined column.
394
     *
395
     * @return bool
396
     */
397
    protected function isDefinedColumn()
398
    {
399
        return array_key_exists($this->name, static::$defined);
400
    }
401
402
    /**
403
     * Use a defined column.
404
     *
405
     * @throws \Exception
406
     */
407
    protected function useDefinedColumn()
408
    {
409
        // clear all display callbacks.
410
        $this->displayCallbacks = [];
411
412
        $class = static::$defined[$this->name];
413
414
        if ($class instanceof Closure) {
415
            $this->display($class);
416
417
            return;
418
        }
419
420
        if (!class_exists($class) || !is_subclass_of($class, AbstractDisplayer::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\Displ...bstractDisplayer::class can be an interface. If so, you could instead use ReflectionClass::implementsInterface.
Loading history...
421
            throw new \Exception("Invalid column definition [$class]");
422
        }
423
424
        $grid = $this->grid;
425
        $column = $this;
426
427
        $this->display(function ($value) use ($grid, $column, $class) {
428
            $definition = new $class($value, $grid, $column, $this);
429
430
            return $definition->display();
431
        });
432
    }
433
434
    /**
435
     * Convert characters to HTML entities recursively.
436
     *
437
     * @param array|string $item
438
     *
439
     * @return mixed
440
     */
441
    protected function htmlEntityEncode($item)
442
    {
443
        if (is_array($item)) {
444
            array_walk_recursive($item, function (&$value) {
445
                $value = htmlentities($value);
446
            });
447
        } else {
448
            $item = htmlentities($item);
449
        }
450
451
        return $item;
452
    }
453
454
    /**
455
     * Create the column sorter.
456
     *
457
     * @return string|void
458
     */
459
    public function sorter()
460
    {
461
        if (!$this->sortable) {
462
            return;
463
        }
464
465
        $icon = 'fa-sort';
466
        $type = 'desc';
467
468
        if ($this->isSorted()) {
469
            $type = $this->sort['type'] == 'desc' ? 'asc' : 'desc';
470
            $icon .= "-amount-{$this->sort['type']}";
471
        }
472
473
        $query = app('request')->all();
474
        $query = array_merge($query, [$this->grid->model()->getSortName() => ['column' => $this->name, 'type' => $type]]);
475
476
        $url = URL::current().'?'.http_build_query($query);
477
478
        return "<a class=\"fa fa-fw $icon\" href=\"$url\"></a>";
479
    }
480
481
    /**
482
     * Determine if this column is currently sorted.
483
     *
484
     * @return bool
485
     */
486
    protected function isSorted()
487
    {
488
        $this->sort = app('request')->get($this->grid->model()->getSortName());
489
490
        if (empty($this->sort)) {
491
            return false;
492
        }
493
494
        return isset($this->sort['column']) && $this->sort['column'] == $this->name;
495
    }
496
497
    /**
498
     * Find a displayer to display column.
499
     *
500
     * @param string $abstract
501
     * @param array  $arguments
502
     *
503
     * @return Column
504
     */
505
    protected function resolveDisplayer($abstract, $arguments)
506
    {
507
        if (array_key_exists($abstract, static::$displayers)) {
508
            return $this->callBuiltinDisplayer(static::$displayers[$abstract], $arguments);
509
        }
510
511
        return $this->callSupportDisplayer($abstract, $arguments);
512
    }
513
514
    /**
515
     * Call Illuminate/Support displayer.
516
     *
517
     * @param string $abstract
518
     * @param array  $arguments
519
     *
520
     * @return Column
521
     */
522
    protected function callSupportDisplayer($abstract, $arguments)
523
    {
524
        return $this->display(function ($value) use ($abstract, $arguments) {
525
            if (is_array($value) || $value instanceof Arrayable) {
526
                return call_user_func_array([collect($value), $abstract], $arguments);
527
            }
528
529
            if (is_string($value)) {
530
                return call_user_func_array([Str::class, $abstract], array_merge([$value], $arguments));
531
            }
532
533
            return $value;
534
        });
535
    }
536
537
    /**
538
     * Call Builtin displayer.
539
     *
540
     * @param string $abstract
541
     * @param array  $arguments
542
     *
543
     * @return Column
544
     */
545
    protected function callBuiltinDisplayer($abstract, $arguments)
546
    {
547
        if ($abstract instanceof Closure) {
548
            return $this->display(function ($value) use ($abstract, $arguments) {
549
                return $abstract->call($this, ...array_merge([$value], $arguments));
550
            });
551
        }
552
553
        if (class_exists($abstract) && is_subclass_of($abstract, AbstractDisplayer::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\Displ...bstractDisplayer::class can be an interface. If so, you could instead use ReflectionClass::implementsInterface.
Loading history...
554
            $grid = $this->grid;
555
            $column = $this;
556
557
            return $this->display(function ($value) use ($abstract, $grid, $column, $arguments) {
558
                $displayer = new $abstract($value, $grid, $column, $this);
559
560
                return call_user_func_array([$displayer, 'display'], $arguments);
561
            });
562
        }
563
564
        return $this;
565
    }
566
567
    /**
568
     * Passes through all unknown calls to builtin displayer or supported displayer.
569
     *
570
     * Allow fluent calls on the Column object.
571
     *
572
     * @param string $method
573
     * @param array  $arguments
574
     *
575
     * @return $this
576
     */
577
    public function __call($method, $arguments)
578
    {
579
        if ($this->isRelation() && !$this->relationColumn) {
580
            $this->name = "{$this->relation}.$method";
581
            $this->label = isset($arguments[0]) ? $arguments[0] : ucfirst($method);
582
583
            $this->relationColumn = $method;
584
585
            return $this;
586
        }
587
588
        return $this->resolveDisplayer($method, $arguments);
589
    }
590
}
591