Completed
Push — master ( 5bd17e...325776 )
by Song
02:07
created

Column::setGrid()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 6

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 1
nc 1
nop 1
dl 0
loc 6
rs 10
c 0
b 0
f 0
1
<?php
2
3
namespace Encore\Admin\Grid;
4
5
use Carbon\Carbon;
6
use Closure;
7
use Encore\Admin\Actions\RowAction;
8
use Encore\Admin\Grid;
9
use Encore\Admin\Grid\Displayers\AbstractDisplayer;
10
use Illuminate\Contracts\Support\Arrayable;
11
use Illuminate\Database\Eloquent\Model as BaseModel;
12
use Illuminate\Support\Arr;
13
use Illuminate\Support\Collection;
14
use Illuminate\Support\Str;
15
16
/**
17
 * Class Column.
18
 *
19
 * @method $this editable()
20
 * @method $this switch ($states = [])
21
 * @method $this switchGroup($columns = [], $states = [])
22
 * @method $this select($options = [])
23
 * @method $this image($server = '', $width = 200, $height = 200)
24
 * @method $this label($style = 'success')
25
 * @method $this button($style = null)
26
 * @method $this link($href = '', $target = '_blank')
27
 * @method $this badge($style = 'red')
28
 * @method $this progress($style = 'primary', $size = 'sm', $max = 100)
29
 * @method $this radio($options = [])
30
 * @method $this checkbox($options = [])
31
 * @method $this orderable($column, $label = '')
32
 * @method $this table($titles = [])
33
 * @method $this expand($callback = null)
34
 * @method $this modal($callback = null)
35
 * @method $this carousel(int $width = 300, int $height = 200, $server = '')
36
 * @method $this downloadable($server = '')
37
 * @method $this copyable()
38
 * @method $this qrcode($formatter = null, $width = 150, $height = 150)
39
 * @method $this prefix($prefix)
40
 * @method $this suffix($suffix)
41
 */
42
class Column
43
{
44
    use Column\HasHeader;
45
46
    const SELECT_COLUMN_NAME = '__row_selector__';
47
48
    const ACTION_COLUMN_NAME = '__actions__';
49
50
    /**
51
     * @var Grid
52
     */
53
    protected $grid;
54
55
    /**
56
     * Name of column.
57
     *
58
     * @var string
59
     */
60
    protected $name;
61
62
    /**
63
     * Label of column.
64
     *
65
     * @var string
66
     */
67
    protected $label;
68
69
    /**
70
     * Original value of column.
71
     *
72
     * @var mixed
73
     */
74
    protected $original;
75
76
    /**
77
     * Attributes of column.
78
     *
79
     * @var array
80
     */
81
    protected $attributes = [];
82
83
    /**
84
     * Relation name.
85
     *
86
     * @var bool
87
     */
88
    protected $relation = false;
89
90
    /**
91
     * Relation column.
92
     *
93
     * @var string
94
     */
95
    protected $relationColumn;
96
97
    /**
98
     * Original grid data.
99
     *
100
     * @var Collection
101
     */
102
    protected static $originalGridModels;
103
104
    /**
105
     * @var []Closure
106
     */
107
    protected $displayCallbacks = [];
108
109
    /**
110
     * Displayers for grid column.
111
     *
112
     * @var array
113
     */
114
    public static $displayers = [
115
        'editable'    => Displayers\Editable::class,
116
        'switch'      => Displayers\SwitchDisplay::class,
117
        'switchGroup' => Displayers\SwitchGroup::class,
118
        'select'      => Displayers\Select::class,
119
        'image'       => Displayers\Image::class,
120
        'label'       => Displayers\Label::class,
121
        'button'      => Displayers\Button::class,
122
        'link'        => Displayers\Link::class,
123
        'badge'       => Displayers\Badge::class,
124
        'progressBar' => Displayers\ProgressBar::class,
125
        'progress'    => Displayers\ProgressBar::class,
126
        'radio'       => Displayers\Radio::class,
127
        'checkbox'    => Displayers\Checkbox::class,
128
        'orderable'   => Displayers\Orderable::class,
129
        'table'       => Displayers\Table::class,
130
        'expand'      => Displayers\Expand::class,
131
        'modal'       => Displayers\Modal::class,
132
        'carousel'    => Displayers\Carousel::class,
133
        'downloadable'=> Displayers\Downloadable::class,
134
        'copyable'    => Displayers\Copyable::class,
135
        'qrcode'      => Displayers\QRCode::class,
136
        'prefix'      => Displayers\Prefix::class,
137
        'suffix'      => Displayers\Suffix::class,
138
    ];
139
140
    /**
141
     * Defined columns.
142
     *
143
     * @var array
144
     */
145
    public static $defined = [];
146
147
    /**
148
     * @var array
149
     */
150
    protected static $htmlAttributes = [];
151
152
    /**
153
     * @var array
154
     */
155
    protected static $rowAttributes = [];
156
157
    /**
158
     * @var Model
159
     */
160
    protected static $model;
161
162
    /**
163
     * @param string $name
164
     * @param string $label
165
     */
166
    public function __construct($name, $label)
167
    {
168
        $this->name = $name;
169
170
        $this->label = $this->formatLabel($label);
171
172
        $this->initAttributes();
173
    }
174
175
    /**
176
     * Initialize column attributes.
177
     */
178
    protected function initAttributes()
179
    {
180
        $name = str_replace('.', '-', $this->name);
181
182
        $this->setAttributes(['class' => "column-{$name}"]);
183
    }
184
185
    /**
186
     * Extend column displayer.
187
     *
188
     * @param $name
189
     * @param $displayer
190
     */
191
    public static function extend($name, $displayer)
192
    {
193
        static::$displayers[$name] = $displayer;
194
    }
195
196
    /**
197
     * Define a column globally.
198
     *
199
     * @param string $name
200
     * @param mixed  $definition
201
     */
202
    public static function define($name, $definition)
203
    {
204
        static::$defined[$name] = $definition;
205
    }
206
207
    /**
208
     * Set grid instance for column.
209
     *
210
     * @param Grid $grid
211
     */
212
    public function setGrid(Grid $grid)
213
    {
214
        $this->grid = $grid;
215
216
        $this->setModel($grid->model()->eloquent());
217
    }
218
219
    /**
220
     * Set model for column.
221
     *
222
     * @param $model
223
     */
224
    public function setModel($model)
225
    {
226
        if (is_null(static::$model) && ($model instanceof BaseModel)) {
227
            static::$model = $model->newInstance();
228
        }
229
    }
230
231
    /**
232
     * Set original data for column.
233
     *
234
     * @param Collection $collection
235
     */
236
    public static function setOriginalGridModels(Collection $collection)
237
    {
238
        static::$originalGridModels = $collection;
239
    }
240
241
    /**
242
     * Set column attributes.
243
     *
244
     * @param array $attributes
245
     *
246
     * @return $this
247
     */
248
    public function setAttributes($attributes = [], $key = null)
249
    {
250
        if ($key) {
251
            static::$rowAttributes[$this->name][$key] = array_merge(
252
                Arr::get(static::$rowAttributes, "{$this->name}.{$key}", []),
253
                $attributes
254
            );
255
256
            return $this;
257
        }
258
259
        static::$htmlAttributes[$this->name] = array_merge(
260
            Arr::get(static::$htmlAttributes, $this->name, []),
261
            $attributes
262
        );
263
264
        return $this;
265
    }
266
267
    /**
268
     * Get column attributes.
269
     *
270
     * @param string $name
271
     *
272
     * @return mixed
273
     */
274
    public static function getAttributes($name, $key = null)
275
    {
276
        $rowAttributes = [];
277
278
        if ($key && Arr::has(static::$rowAttributes, "{$name}.{$key}")) {
279
            $rowAttributes = Arr::get(static::$rowAttributes, "{$name}.{$key}", []);
280
        }
281
282
        $columnAttributes = Arr::get(static::$htmlAttributes, $name, []);
283
284
        return array_merge($rowAttributes, $columnAttributes);
285
    }
286
287
    /**
288
     * Format attributes to html.
289
     *
290
     * @return string
291
     */
292
    public function formatHtmlAttributes()
293
    {
294
        $attrArr = [];
295
        foreach (static::getAttributes($this->name) as $name => $val) {
296
            $attrArr[] = "$name=\"$val\"";
297
        }
298
299
        return implode(' ', $attrArr);
300
    }
301
302
    /**
303
     * Set style of this column.
304
     *
305
     * @param string $style
306
     *
307
     * @return $this
308
     */
309
    public function style($style)
310
    {
311
        return $this->setAttributes(compact('style'));
312
    }
313
314
    /**
315
     * Set the width of column.
316
     *
317
     * @param int $width
318
     *
319
     * @return $this
320
     */
321
    public function width(int $width)
322
    {
323
        return $this->style("width: {$width}px;");
324
    }
325
326
    /**
327
     * Set the color of column.
328
     *
329
     * @param string $color
330
     *
331
     * @return $this
332
     */
333
    public function color($color)
334
    {
335
        return $this->style("color:$color;");
336
    }
337
338
    /**
339
     * Get original column value.
340
     *
341
     * @return mixed
342
     */
343
    public function getOriginal()
344
    {
345
        return $this->original;
346
    }
347
348
    /**
349
     * Get name of this column.
350
     *
351
     * @return mixed
352
     */
353
    public function getName()
354
    {
355
        return $this->name;
356
    }
357
358
    /**
359
     * @return string
360
     */
361
    public function getClassName()
362
    {
363
        $name = str_replace('.', '-', $this->getName());
364
365
        return "column-{$name}";
366
    }
367
368
    /**
369
     * Format label.
370
     *
371
     * @param $label
372
     *
373
     * @return mixed
374
     */
375
    protected function formatLabel($label)
376
    {
377
        if ($label) {
378
            return $label;
379
        }
380
381
        $label = ucfirst($this->name);
382
383
        return __(str_replace(['.', '_'], ' ', $label));
384
    }
385
386
    /**
387
     * Get label of the column.
388
     *
389
     * @return mixed
390
     */
391
    public function getLabel()
392
    {
393
        return $this->label;
394
    }
395
396
    /**
397
     * Set relation.
398
     *
399
     * @param string $relation
400
     * @param string $relationColumn
401
     *
402
     * @return $this
403
     */
404
    public function setRelation($relation, $relationColumn = null)
405
    {
406
        $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...
407
        $this->relationColumn = $relationColumn;
408
409
        return $this;
410
    }
411
412
    /**
413
     * If this column is relation column.
414
     *
415
     * @return bool
416
     */
417
    protected function isRelation()
418
    {
419
        return (bool) $this->relation;
420
    }
421
422
    /**
423
     * Mark this column as sortable.
424
     *
425
     * @param null|string $cast
426
     *
427
     * @return Column|string
428
     */
429
    public function sortable($cast = null)
430
    {
431
        return $this->addSorter($cast);
432
    }
433
434
    /**
435
     * Set cast name for sortable.
436
     *
437
     * @return $this
438
     *
439
     * @deprecated Use `$column->sortable($cast)` instead.
440
     */
441
    public function cast($cast)
442
    {
443
        $this->cast = $cast;
0 ignored issues
show
Bug introduced by
The property cast does not exist. Did you maybe forget to declare it?

In PHP it is possible to write to properties without declaring them. For example, the following is perfectly valid PHP code:

class MyClass { }

$x = new MyClass();
$x->foo = true;

Generally, it is a good practice to explictly declare properties to avoid accidental typos and provide IDE auto-completion:

class MyClass {
    public $foo;
}

$x = new MyClass();
$x->foo = true;
Loading history...
444
445
        return $this;
446
    }
447
448
    /**
449
     * Set help message for column.
450
     *
451
     * @param string $help
452
     *
453
     * @return $this|string
454
     */
455
    public function help($help = '')
456
    {
457
        return $this->addHelp($help);
458
    }
459
460
    /**
461
     * Set column filter.
462
     *
463
     * @param null $builder
464
     *
465
     * @return $this
466
     */
467
    public function filter($builder = null)
0 ignored issues
show
Unused Code introduced by
The parameter $builder 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...
468
    {
469
        return $this->addFilter(...func_get_args());
470
    }
471
472
    /**
473
     * Add a display callback.
474
     *
475
     * @param Closure $callback
476
     *
477
     * @return $this
478
     */
479
    public function display(Closure $callback)
480
    {
481
        $this->displayCallbacks[] = $callback;
482
483
        return $this;
484
    }
485
486
    /**
487
     * Display using display abstract.
488
     *
489
     * @param string $abstract
490
     * @param array  $arguments
491
     *
492
     * @return $this
493
     */
494
    public function displayUsing($abstract, $arguments = [])
495
    {
496
        $grid = $this->grid;
497
498
        $column = $this;
499
500 View Code Duplication
        return $this->display(function ($value) use ($grid, $column, $abstract, $arguments) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across 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...
501
            /** @var AbstractDisplayer $displayer */
502
            $displayer = new $abstract($value, $grid, $column, $this);
503
504
            return $displayer->display(...$arguments);
0 ignored issues
show
Unused Code introduced by
The call to AbstractDisplayer::display() has too many arguments starting with $arguments.

This check compares calls to functions or methods with their respective definitions. If the call has more arguments than are defined, it raises an issue.

If a function is defined several times with a different number of parameters, the check may pick up the wrong definition and report false positives. One codebase where this has been known to happen is Wordpress.

In this case you can add the @ignore PhpDoc annotation to the duplicate definition and it will be ignored.

Loading history...
505
        });
506
    }
507
508
    /**
509
     * Display column using array value map.
510
     *
511
     * @param array $values
512
     * @param null  $default
513
     *
514
     * @return $this
515
     */
516 View Code Duplication
    public function using(array $values, $default = null)
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...
517
    {
518
        return $this->display(function ($value) use ($values, $default) {
519
            if (is_null($value)) {
520
                return $default;
521
            }
522
523
            return Arr::get($values, $value, $default);
524
        });
525
    }
526
527
    /**
528
     * Replace output value with giving map.
529
     *
530
     * @param array $replacements
531
     *
532
     * @return $this
533
     */
534
    public function replace(array $replacements)
535
    {
536
        return $this->display(function ($value) use ($replacements) {
537
            if (isset($replacements[$value])) {
538
                return $replacements[$value];
539
            }
540
541
            return $value;
542
        });
543
    }
544
545
    /**
546
     * Render this column with the given view.
547
     *
548
     * @param string $view
549
     *
550
     * @return $this
551
     */
552
    public function view($view)
553
    {
554
        return $this->display(function ($value) use ($view) {
555
            $model = $this;
556
557
            return view($view, compact('model', 'value'))->render();
0 ignored issues
show
Bug introduced by
The method render 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...
558
        });
559
    }
560
561
    /**
562
     * Hide this column by default.
563
     *
564
     * @return $this
565
     */
566
    public function hide()
567
    {
568
        $this->grid->hideColumns($this->getName());
569
570
        return $this;
571
    }
572
573
    /**
574
     * Add column to total-row.
575
     *
576
     * @param null $display
577
     *
578
     * @return $this
579
     */
580
    public function totalRow($display = null)
581
    {
582
        $this->grid->addTotalRow($this->name, $display);
0 ignored issues
show
Documentation introduced by
$display is of type null, but the function expects a object<Closure>.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
583
584
        return $this;
585
    }
586
587
    /**
588
     * Convert file size to a human readable format like `100mb`.
589
     *
590
     * @return $this
591
     */
592
    public function filesize()
593
    {
594
        return $this->display(function ($value) {
595
            return file_size($value);
596
        });
597
    }
598
599
    /**
600
     * Display the fields in the email format as gavatar.
601
     *
602
     * @param int $size
603
     *
604
     * @return $this
605
     */
606
    public function gravatar($size = 30)
607
    {
608
        return $this->display(function ($value) use ($size) {
609
            $src = sprintf(
610
                'https://www.gravatar.com/avatar/%s?s=%d',
611
                md5(strtolower($value)),
612
                $size
613
            );
614
615
            return "<img src='$src' class='img img-circle'/>";
616
        });
617
    }
618
619
    /**
620
     * Display field as a loading icon.
621
     *
622
     * @param array $values
623
     * @param array $others
624
     *
625
     * @return $this
626
     */
627
    public function loading($values = [], $others = [])
628
    {
629
        return $this->display(function ($value) use ($values, $others) {
630
            $values = (array) $values;
0 ignored issues
show
Bug introduced by
Consider using a different name than the imported variable $values, or did you forget to import by reference?

It seems like you are assigning to a variable which was imported through a use statement which was not imported by reference.

For clarity, we suggest to use a different name or import by reference depending on whether you would like to have the change visibile in outer-scope.

Change not visible in outer-scope

$x = 1;
$callable = function() use ($x) {
    $x = 2; // Not visible in outer scope. If you would like this, how
            // about using a different variable name than $x?
};

$callable();
var_dump($x); // integer(1)

Change visible in outer-scope

$x = 1;
$callable = function() use (&$x) {
    $x = 2;
};

$callable();
var_dump($x); // integer(2)
Loading history...
631
632
            if (in_array($value, $values)) {
633
                return '<i class="fa fa-refresh fa-spin text-primary"></i>';
634
            }
635
636
            return Arr::get($others, $value, $value);
637
        });
638
    }
639
640
    /**
641
     * Display column as an font-awesome icon based on it's value.
642
     *
643
     * @param array  $setting
644
     * @param string $default
645
     *
646
     * @return $this
647
     */
648
    public function icon(array $setting, $default = '')
649
    {
650
        return $this->display(function ($value) use ($setting, $default) {
651
            $fa = '';
652
653
            if (isset($setting[$value])) {
654
                $fa = $setting[$value];
655
            } elseif ($default) {
656
                $fa = $default;
657
            }
658
659
            return "<i class=\"fa fa-{$fa}\"></i>";
660
        });
661
    }
662
663
    /**
664
     * Return a human readable format time.
665
     *
666
     * @param null $locale
667
     *
668
     * @return $this
669
     */
670
    public function diffForHumans($locale = null)
671
    {
672
        if ($locale) {
673
            Carbon::setLocale($locale);
674
        }
675
676
        return $this->display(function ($value) {
677
            return Carbon::parse($value)->diffForHumans();
0 ignored issues
show
Bug introduced by
The method diffForHumans does only exist in Carbon\CarbonInterface, but not in Carbon\Traits\Creator.

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...
678
        });
679
    }
680
681
    /**
682
     * Returns a string formatted according to the given format string.
683
     *
684
     * @param string $format
685
     *
686
     * @return $this
687
     */
688
    public function date($format)
689
    {
690
        return $this->display(function ($value) use ($format) {
691
            return date($format, strtotime($value));
692
        });
693
    }
694
695
    /**
696
     * Display column as boolean , `✓` for true, and `✗` for false.
697
     *
698
     * @param array $map
699
     * @param bool  $default
700
     *
701
     * @return $this
702
     */
703
    public function bool(array $map = [], $default = false)
704
    {
705
        return $this->display(function ($value) use ($map, $default) {
706
            $bool = empty($map) ? boolval($value) : Arr::get($map, $value, $default);
707
708
            return $bool ? '<i class="fa fa-check text-green"></i>' : '<i class="fa fa-close text-red"></i>';
709
        });
710
    }
711
712
    /**
713
     * Display column using a grid row action.
714
     *
715
     * @param string $action
716
     *
717
     * @return Column
718
     */
719
    public function action($action)
720
    {
721
        if (!is_subclass_of($action, RowAction::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\Actions\RowAction::class can be an interface. If so, you could instead use ReflectionClass::implementsInterface.
Loading history...
722
            throw new \InvalidArgumentException("Action class [$action] must be sub-class of [Encore\Admin\Actions\GridAction]");
723
        }
724
725
        $grid = $this->grid;
726
727
        return $this->display(function ($value, $column) use ($action, $grid) {
728
            /** @var RowAction $action */
729
            $action = new $action();
0 ignored issues
show
Bug introduced by
Consider using a different name than the imported variable $action, or did you forget to import by reference?

It seems like you are assigning to a variable which was imported through a use statement which was not imported by reference.

For clarity, we suggest to use a different name or import by reference depending on whether you would like to have the change visibile in outer-scope.

Change not visible in outer-scope

$x = 1;
$callable = function() use ($x) {
    $x = 2; // Not visible in outer scope. If you would like this, how
            // about using a different variable name than $x?
};

$callable();
var_dump($x); // integer(1)

Change visible in outer-scope

$x = 1;
$callable = function() use (&$x) {
    $x = 2;
};

$callable();
var_dump($x); // integer(2)
Loading history...
730
731
            return $action->setGrid($grid)
732
                ->setColumn($column)
733
                ->setRow($this);
0 ignored issues
show
Compatibility introduced by
$this of type object<Encore\Admin\Grid\Column> is not a sub-type of object<Illuminate\Database\Eloquent\Model>. It seems like you assume a child class of the class Encore\Admin\Grid\Column to be always present.

This check looks for parameters that are defined as one type in their type hint or doc comment but seem to be used as a narrower type, i.e an implementation of an interface or a subclass.

Consider changing the type of the parameter or doing an instanceof check before assuming your parameter is of the expected type.

Loading history...
734
        });
735
    }
736
737
    /**
738
     * If has display callbacks.
739
     *
740
     * @return bool
741
     */
742
    protected function hasDisplayCallbacks()
743
    {
744
        return !empty($this->displayCallbacks);
745
    }
746
747
    /**
748
     * Call all of the "display" callbacks column.
749
     *
750
     * @param mixed $value
751
     * @param int   $key
752
     *
753
     * @return mixed
754
     */
755
    protected function callDisplayCallbacks($value, $key)
756
    {
757
        foreach ($this->displayCallbacks as $callback) {
758
            $previous = $value;
759
760
            $callback = $this->bindOriginalRowModel($callback, $key);
761
            $value = call_user_func_array($callback, [$value, $this]);
762
763
            if (($value instanceof static) &&
764
                ($last = array_pop($this->displayCallbacks))
765
            ) {
766
                $last = $this->bindOriginalRowModel($last, $key);
767
                $value = call_user_func($last, $previous);
768
            }
769
        }
770
771
        return $value;
772
    }
773
774
    /**
775
     * Set original grid data to column.
776
     *
777
     * @param Closure $callback
778
     * @param int     $key
779
     *
780
     * @return Closure
781
     */
782
    protected function bindOriginalRowModel(Closure $callback, $key)
783
    {
784
        $rowModel = static::$originalGridModels[$key];
785
786
        return $callback->bindTo($rowModel);
787
    }
788
789
    /**
790
     * Fill all data to every column.
791
     *
792
     * @param array $data
793
     *
794
     * @return mixed
795
     */
796
    public function fill(array $data)
797
    {
798
        foreach ($data as $key => &$row) {
799
            $this->original = $value = Arr::get($row, $this->name);
800
801
            $value = $this->htmlEntityEncode($value);
802
803
            Arr::set($row, $this->name, $value);
804
805
            if ($this->isDefinedColumn()) {
806
                $this->useDefinedColumn();
807
            }
808
809
            if ($this->hasDisplayCallbacks()) {
810
                $value = $this->callDisplayCallbacks($this->original, $key);
811
                Arr::set($row, $this->name, $value);
812
            }
813
        }
814
815
        return $data;
816
    }
817
818
    /**
819
     * If current column is a defined column.
820
     *
821
     * @return bool
822
     */
823
    protected function isDefinedColumn()
824
    {
825
        return array_key_exists($this->name, static::$defined);
826
    }
827
828
    /**
829
     * Use a defined column.
830
     *
831
     * @throws \Exception
832
     */
833
    protected function useDefinedColumn()
834
    {
835
        // clear all display callbacks.
836
        $this->displayCallbacks = [];
837
838
        $class = static::$defined[$this->name];
839
840
        if ($class instanceof Closure) {
841
            $this->display($class);
842
843
            return;
844
        }
845
846
        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...
847
            throw new \Exception("Invalid column definition [$class]");
848
        }
849
850
        $grid = $this->grid;
851
        $column = $this;
852
853
        $this->display(function ($value) use ($grid, $column, $class) {
854
            /** @var AbstractDisplayer $definition */
855
            $definition = new $class($value, $grid, $column, $this);
856
857
            return $definition->display();
858
        });
859
    }
860
861
    /**
862
     * Convert characters to HTML entities recursively.
863
     *
864
     * @param array|string $item
865
     *
866
     * @return mixed
867
     */
868
    protected function htmlEntityEncode($item)
869
    {
870
        if (is_array($item)) {
871
            array_walk_recursive($item, function (&$value) {
872
                $value = htmlentities($value);
873
            });
874
        } else {
875
            $item = htmlentities($item);
876
        }
877
878
        return $item;
879
    }
880
881
    /**
882
     * Find a displayer to display column.
883
     *
884
     * @param string $abstract
885
     * @param array  $arguments
886
     *
887
     * @return $this
888
     */
889
    protected function resolveDisplayer($abstract, $arguments)
890
    {
891
        if (array_key_exists($abstract, static::$displayers)) {
892
            return $this->callBuiltinDisplayer(static::$displayers[$abstract], $arguments);
893
        }
894
895
        return $this->callSupportDisplayer($abstract, $arguments);
896
    }
897
898
    /**
899
     * Call Illuminate/Support displayer.
900
     *
901
     * @param string $abstract
902
     * @param array  $arguments
903
     *
904
     * @return $this
905
     */
906
    protected function callSupportDisplayer($abstract, $arguments)
907
    {
908
        return $this->display(function ($value) use ($abstract, $arguments) {
909
            if (is_array($value) || $value instanceof Arrayable) {
910
                return call_user_func_array([collect($value), $abstract], $arguments);
911
            }
912
913
            if (is_string($value)) {
914
                return call_user_func_array([Str::class, $abstract], array_merge([$value], $arguments));
915
            }
916
917
            return $value;
918
        });
919
    }
920
921
    /**
922
     * Call Builtin displayer.
923
     *
924
     * @param string $abstract
925
     * @param array  $arguments
926
     *
927
     * @return $this
928
     */
929
    protected function callBuiltinDisplayer($abstract, $arguments)
930
    {
931
        if ($abstract instanceof Closure) {
932
            return $this->display(function ($value) use ($abstract, $arguments) {
933
                return $abstract->call($this, ...array_merge([$value], $arguments));
934
            });
935
        }
936
937
        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...
938
            $grid = $this->grid;
939
            $column = $this;
940
941 View Code Duplication
            return $this->display(function ($value) use ($abstract, $grid, $column, $arguments) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across 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...
942
                /** @var AbstractDisplayer $displayer */
943
                $displayer = new $abstract($value, $grid, $column, $this);
944
945
                return $displayer->display(...$arguments);
0 ignored issues
show
Unused Code introduced by
The call to AbstractDisplayer::display() has too many arguments starting with $arguments.

This check compares calls to functions or methods with their respective definitions. If the call has more arguments than are defined, it raises an issue.

If a function is defined several times with a different number of parameters, the check may pick up the wrong definition and report false positives. One codebase where this has been known to happen is Wordpress.

In this case you can add the @ignore PhpDoc annotation to the duplicate definition and it will be ignored.

Loading history...
946
            });
947
        }
948
949
        return $this;
950
    }
951
952
    /**
953
     * Passes through all unknown calls to builtin displayer or supported displayer.
954
     *
955
     * Allow fluent calls on the Column object.
956
     *
957
     * @param string $method
958
     * @param array  $arguments
959
     *
960
     * @return $this
961
     */
962
    public function __call($method, $arguments)
963
    {
964
        if ($this->isRelation() && !$this->relationColumn) {
965
            $this->name = "{$this->relation}.$method";
966
            $this->label = $this->formatLabel($arguments[0] ?? null);
967
968
            $this->relationColumn = $method;
969
970
            return $this;
971
        }
972
973
        return $this->resolveDisplayer($method, $arguments);
974
    }
975
}
976